Skip to content

Commit

Permalink
Merge pull request #247 from specklesystems/gergo/pathingUnification
Browse files Browse the repository at this point in the history
feat(path-handling): rework path handling to match behavior of Core
  • Loading branch information
gjedlicska authored Jan 6, 2023
2 parents 0533aa0 + 85e7a72 commit 65c8294
Show file tree
Hide file tree
Showing 31 changed files with 768 additions and 608 deletions.
883 changes: 440 additions & 443 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/specklepy/api/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

from pydantic import BaseModel, Field # pylint: disable=no-name-in-module

from specklepy import paths
from specklepy.api.models import ServerInfo
from specklepy.core.helpers import speckle_path_provider
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.transports.sqlite import SQLiteTransport
Expand Down Expand Up @@ -64,7 +64,7 @@ def get_local_accounts(base_path: Optional[str] = None) -> List[Account]:
pass

json_acct_files = []
json_path = paths.accounts_path()
json_path = str(speckle_path_provider.accounts_folder_path())
try:
os.makedirs(json_path, exist_ok=True)
json_acct_files.extend(
Expand Down
6 changes: 6 additions & 0 deletions src/specklepy/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
This is the Core SDK module of `specklepy`.
This module should be kept in sync with the functionalities of our other SDKs especially
C# Core https://github.com/specklesystems/speckle-sharp/tree/main/Core/Core
"""
1 change: 1 addition & 0 deletions src/specklepy/core/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Common helpers module for Core."""
115 changes: 115 additions & 0 deletions src/specklepy/core/helpers/speckle_path_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""
Provides uniform and consistent path helpers for `specklepy`
"""
import os
import sys
from pathlib import Path
from typing import Optional

from specklepy.logging.exceptions import SpeckleException

_user_data_env_var = "SPECKLE_USERDATA_PATH"


def _path() -> Optional[Path]:
"""Read the user data path override setting."""
path_override = os.environ.get(_user_data_env_var)
if path_override:
return Path(path_override)
return None


_application_name = "Speckle"


def override_application_name(application_name: str) -> None:
"""Override the global Speckle application name."""
global _application_name
_application_name = application_name


def override_application_data_path(path: Optional[str]) -> None:
"""
Override the global Speckle application data path.
If the value of path is `None` the environment variable gets deleted.
"""
if path:
os.environ[_user_data_env_var] = path
else:
os.environ.pop(_user_data_env_var, None)


_blob_folder_name = "Blobs"


def override_blob_storage_folder(blob_folder_name: str) -> None:
"""Override the global Blob storage folder name."""
global _blob_folder_name
_blob_folder_name = blob_folder_name


_accounts_folder_name = "Accounts"


def override_accounts_folder_name(accounts_folder_name: str) -> None:
"""Override the global Accounts folder name."""
global _accounts_folder_name
_accounts_folder_name = accounts_folder_name


_objects_folder_name = "Objects"


def override_objects_folder_name(objects_folder_name: str) -> None:
"""Override global Objects folder name."""
global _objects_folder_name
_objects_folder_name = objects_folder_name


def _ensure_folder_exists(base_path: Path, folder_name: str) -> Path:
path = base_path.joinpath(folder_name)
path.mkdir(exist_ok=True, parents=True)
return path


def user_application_data_path() -> Path:
"""Get the platform specific user configuration folder path"""
path_override = _path()
if path_override:
return path_override

try:
if sys.platform.startswith("win"):
app_data_path = os.getenv("APPDATA")
if not app_data_path:
raise SpeckleException(
message="Cannot get appdata path from environment."
)
return Path(app_data_path)
else:
# try getting the standard XDG_DATA_HOME value
# as that is used as an override
app_data_path = os.getenv("XDG_DATA_HOME")
if app_data_path:
return Path(app_data_path)
else:
return _ensure_folder_exists(Path.home(), ".config")
except Exception as ex:
raise SpeckleException(
message="Failed to initialize user application data path.", exception=ex
)


def user_speckle_folder_path() -> Path:
"""Get the folder where the user's Speckle data should be stored."""
return _ensure_folder_exists(user_application_data_path(), _application_name)


def accounts_folder_path() -> Path:
"""Get the folder where the Speckle accounts data should be stored."""
return _ensure_folder_exists(user_speckle_folder_path(), _accounts_folder_name)


def blob_storage_path(path: Optional[Path] = None) -> Path:
return _ensure_folder_exists(path or user_speckle_folder_path(), _blob_folder_name)
28 changes: 0 additions & 28 deletions src/specklepy/paths.py

This file was deleted.

6 changes: 4 additions & 2 deletions src/specklepy/transports/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from contextlib import closing
from typing import Any, Dict, List, Optional, Tuple

from specklepy.core.helpers import speckle_path_provider
from specklepy.logging.exceptions import SpeckleException
from specklepy.paths import base_path
from specklepy.transports.abstract_transport import AbstractTransport


Expand Down Expand Up @@ -72,7 +72,9 @@ def get_base_path(app_name):
# path = os.path.expanduser("~/.config/")
# return os.path.join(path, app_name)

return str(base_path(app_name))
return str(
speckle_path_provider.user_application_data_path().joinpath(app_name)
)

def save_object_from_transport(
self, id: str, source_transport: AbstractTransport
Expand Down
107 changes: 0 additions & 107 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,115 +1,8 @@
import random
import uuid

import pytest
import requests

from specklepy.api.client import SpeckleClient
from specklepy.api.models import Stream
from specklepy.logging import metrics
from specklepy.objects.base import Base
from specklepy.objects.fakemesh import FakeDirection, FakeMesh
from specklepy.objects.geometry import Point

metrics.disable()


@pytest.fixture(scope="session")
def host():
return "localhost:3000"


def seed_user(host):
seed = uuid.uuid4().hex
user_dict = {
"email": f"{seed[0:7]}@spockle.com",
"password": "$uper$3cr3tP@ss",
"name": f"{seed[0:7]} Name",
"company": "test spockle",
}

r = requests.post(
url=f"http://{host}/auth/local/register?challenge=pyspeckletests",
data=user_dict,
# do not follow redirects here, they lead to the frontend, which might not be
# running in a test environment
# causing the response to not be OK in the end
allow_redirects=False,
)
if not r.ok:
raise Exception(f"Cannot seed user: {r.reason}")
access_code = r.text.split("access_code=")[1]

r_tokens = requests.post(
url=f"http://{host}/auth/token",
json={
"appSecret": "spklwebapp",
"appId": "spklwebapp",
"accessCode": access_code,
"challenge": "pyspeckletests",
},
)

user_dict.update(**r_tokens.json())

return user_dict


@pytest.fixture(scope="session")
def user_dict(host):
return seed_user(host)


@pytest.fixture(scope="session")
def second_user_dict(host):
return seed_user(host)


@pytest.fixture(scope="session")
def client(host, user_dict):
client = SpeckleClient(host=host, use_ssl=False)
client.authenticate_with_token(user_dict["token"])
return client


@pytest.fixture(scope="session")
def second_client(host, second_user_dict):
client = SpeckleClient(host=host, use_ssl=False)
client.authenticate_with_token(second_user_dict["token"])
return client


@pytest.fixture(scope="session")
def sample_stream(client):
stream = Stream(
name="a sample stream for testing",
description="a stream created for testing",
isPublic=True,
)
stream.id = client.stream.create(stream.name, stream.description, stream.isPublic)
return stream


@pytest.fixture(scope="session")
def mesh():
mesh = FakeMesh()
mesh.name = "my_mesh"
mesh.vertices = [random.uniform(0, 10) for _ in range(1, 210)]
mesh.faces = list(range(1, 210))
mesh["@(100)colours"] = [random.uniform(0, 10) for _ in range(1, 210)]
mesh["@()default_chunk"] = [random.uniform(0, 10) for _ in range(1, 210)]
mesh.cardinal_dir = FakeDirection.WEST
mesh.test_bases = [Base(name=f"test {i}") for i in range(1, 22)]
mesh.detach_this = Base(name="predefined detached base")
mesh["@detach"] = Base(name="detached base")
mesh["@detached_list"] = [
42,
"some text",
[1, 2, 3],
Base(name="detached within a list"),
]
mesh.origin = Point(x=4, y=2)
return mesh


@pytest.fixture(scope="session")
Expand Down
Empty file added tests/intergration/__init__.py
Empty file.
Loading

0 comments on commit 65c8294

Please sign in to comment.