diff --git a/example/many_children.py b/example/many_children.py index d4eac7c..64a1554 100644 --- a/example/many_children.py +++ b/example/many_children.py @@ -1,12 +1,13 @@ -from typing import List -from specklepy.objects import Base -from specklepy.api import operations -from specklepy.transports.sqlite import SQLiteTransport +import os +import random +import string import time from pathlib import Path -import os -import string -import random +from typing import List + +from specklepy.api import operations +from specklepy.objects import Base +from specklepy.transports.sqlite import SQLiteTransport class Sub(Base): diff --git a/example/stream_objects.py b/example/stream_objects.py index 6fd0375..cc4ecb8 100644 --- a/example/stream_objects.py +++ b/example/stream_objects.py @@ -1,9 +1,10 @@ +import random +import string from typing import List + +from specklepy.api import operations from specklepy.api.wrapper import StreamWrapper from specklepy.objects import Base -from specklepy.api import operations -import string -import random class Sub(Base): diff --git a/example/using_speckle_base.py b/example/using_speckle_base.py index a8e73d9..753fbed 100644 --- a/example/using_speckle_base.py +++ b/example/using_speckle_base.py @@ -1,10 +1,11 @@ """This is an example showcasing the usage of speckle `Base` class.""" # the speckle.objects module exposes all speckle provided classes -from specklepy.objects import Base -from specklepy.api import operations from devtools import debug +from specklepy.api import operations +from specklepy.objects import Base + class ExampleSub(Base): """ diff --git a/pyproject.toml b/pyproject.toml index 1689895..7545297 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,3 +54,6 @@ target-version = ["py37", "py38", "py39", "py310"] [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[tool.isort] +profile = "black" \ No newline at end of file diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index e81502a..d1b78ee 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -1,30 +1,28 @@ import re -from warnings import warn -from deprecated import deprecated -from specklepy.api.credentials import Account, get_account_from_token -from specklepy.logging import metrics -from specklepy.logging.exceptions import ( - SpeckleException, - SpeckleWarning, -) from typing import Dict +from warnings import warn -from specklepy.api import resources -from specklepy.api.resources import ( - branch, - commit, - stream, - object, - server, - user, - subscriptions, - other_user, - active_user -) +from deprecated import deprecated from gql import Client from gql.transport.requests import RequestsHTTPTransport from gql.transport.websockets import WebsocketsTransport +from specklepy.api import resources +from specklepy.api.credentials import Account, get_account_from_token +from specklepy.api.resources import ( + active_user, + branch, + commit, + object, + other_user, + server, + stream, + subscriptions, + user, +) +from specklepy.logging import metrics +from specklepy.logging.exceptions import SpeckleException, SpeckleWarning + class SpeckleClient: """ diff --git a/src/specklepy/api/credentials.py b/src/specklepy/api/credentials.py index 2a8ca87..689c5b9 100644 --- a/src/specklepy/api/credentials.py +++ b/src/specklepy/api/credentials.py @@ -1,11 +1,13 @@ import os -from pydantic import BaseModel, Field # pylint: disable=no-name-in-module from typing import List, Optional -from specklepy.logging import metrics -from specklepy.api.models import ServerInfo -from specklepy.transports.sqlite import SQLiteTransport -from specklepy.logging.exceptions import SpeckleException + +from pydantic import BaseModel, Field # pylint: disable=no-name-in-module + from specklepy import paths +from specklepy.api.models import ServerInfo +from specklepy.logging import metrics +from specklepy.logging.exceptions import SpeckleException +from specklepy.transports.sqlite import SQLiteTransport class UserInfo(BaseModel): @@ -64,7 +66,7 @@ def get_local_accounts(base_path: Optional[str] = None) -> List[Account]: json_acct_files.extend( file for file in os.listdir(json_path) if file.endswith(".json") ) - + except Exception: # cannot find or get the json account paths pass diff --git a/src/specklepy/api/host_applications.py b/src/specklepy/api/host_applications.py index 8851256..0a36283 100644 --- a/src/specklepy/api/host_applications.py +++ b/src/specklepy/api/host_applications.py @@ -1,5 +1,5 @@ -from enum import Enum from dataclasses import dataclass +from enum import Enum from unicodedata import name diff --git a/src/specklepy/api/models.py b/src/specklepy/api/models.py index 3f92b56..4729095 100644 --- a/src/specklepy/api/models.py +++ b/src/specklepy/api/models.py @@ -1,7 +1,6 @@ from datetime import datetime from typing import List, Optional - from pydantic import BaseModel, Field @@ -60,20 +59,20 @@ class Branches(BaseModel): class Stream(BaseModel): - id: Optional[str] = None + id: Optional[str] = None name: Optional[str] role: Optional[str] = None isPublic: Optional[bool] = None description: Optional[str] = None - createdAt: Optional[datetime] = None - updatedAt: Optional[datetime] = None + createdAt: Optional[datetime] = None + updatedAt: Optional[datetime] = None collaborators: List[Collaborator] = Field(default_factory=list) - branches: Optional[Branches] = None - commit: Optional[Commit] = None - object: Optional[Object] = None - commentCount: Optional[int] = None - favoritedDate: Optional[datetime] = None - favoritesCount: Optional[int] = None + branches: Optional[Branches] = None + commit: Optional[Commit] = None + object: Optional[Object] = None + commentCount: Optional[int] = None + favoritedDate: Optional[datetime] = None + favoritesCount: Optional[int] = None def __repr__(self): return f"Stream( id: {self.id}, name: {self.name}, description: {self.description}, isPublic: {self.isPublic})" diff --git a/src/specklepy/api/operations.py b/src/specklepy/api/operations.py index cb778ca..722cd40 100644 --- a/src/specklepy/api/operations.py +++ b/src/specklepy/api/operations.py @@ -1,10 +1,11 @@ from typing import List, Optional + from specklepy.logging import metrics -from specklepy.objects.base import Base -from specklepy.transports.sqlite import SQLiteTransport from specklepy.logging.exceptions import SpeckleException -from specklepy.transports.abstract_transport import AbstractTransport +from specklepy.objects.base import Base from specklepy.serialization.base_object_serializer import BaseObjectSerializer +from specklepy.transports.abstract_transport import AbstractTransport +from specklepy.transports.sqlite import SQLiteTransport def send( diff --git a/src/specklepy/api/resource.py b/src/specklepy/api/resource.py index 83af7e6..fd1519b 100644 --- a/src/specklepy/api/resource.py +++ b/src/specklepy/api/resource.py @@ -1,15 +1,17 @@ -from graphql import DocumentNode -from specklepy.api.credentials import Account -from specklepy.transports.sqlite import SQLiteTransport from typing import Any, Dict, List, Optional, Tuple, Type, Union + from gql.client import Client from gql.transport.exceptions import TransportQueryError +from graphql import DocumentNode + +from specklepy.api.credentials import Account from specklepy.logging.exceptions import ( GraphQLException, SpeckleException, UnsupportedException, ) from specklepy.serialization.base_object_serializer import BaseObjectSerializer +from specklepy.transports.sqlite import SQLiteTransport class ResourceBase(object): diff --git a/src/specklepy/api/resources/__init__.py b/src/specklepy/api/resources/__init__.py index acf781a..42512fa 100644 --- a/src/specklepy/api/resources/__init__.py +++ b/src/specklepy/api/resources/__init__.py @@ -1,9 +1,7 @@ -from pathlib import Path -import sys import pkgutil +import sys from importlib import import_module - for (_, name, _) in pkgutil.iter_modules(__path__): imported_module = import_module("." + name, package=__name__) diff --git a/src/specklepy/api/resources/active_user.py b/src/specklepy/api/resources/active_user.py index 0eb017f..46fb6b7 100644 --- a/src/specklepy/api/resources/active_user.py +++ b/src/specklepy/api/resources/active_user.py @@ -1,11 +1,12 @@ -from typing import List, Optional from datetime import datetime, timezone +from typing import List, Optional + from gql import gql + +from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User +from specklepy.api.resource import ResourceBase from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException -from specklepy.api.resource import ResourceBase -from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User - NAME = "active_user" diff --git a/src/specklepy/api/resources/branch.py b/src/specklepy/api/resources/branch.py index dccb7cd..5e8aa10 100644 --- a/src/specklepy/api/resources/branch.py +++ b/src/specklepy/api/resources/branch.py @@ -1,6 +1,7 @@ from gql import gql -from specklepy.api.resource import ResourceBase + from specklepy.api.models import Branch +from specklepy.api.resource import ResourceBase from specklepy.logging import metrics NAME = "branch" diff --git a/src/specklepy/api/resources/commit.py b/src/specklepy/api/resources/commit.py index 7312de4..b3eed8f 100644 --- a/src/specklepy/api/resources/commit.py +++ b/src/specklepy/api/resources/commit.py @@ -1,9 +1,10 @@ -from typing import Optional, List -from gql import gql -from specklepy.api.resource import ResourceBase -from specklepy.api.models import Commit -from specklepy.logging import metrics +from typing import List, Optional +from gql import gql + +from specklepy.api.models import Commit +from specklepy.api.resource import ResourceBase +from specklepy.logging import metrics NAME = "commit" diff --git a/src/specklepy/api/resources/object.py b/src/specklepy/api/resources/object.py index 36c0675..dd80238 100644 --- a/src/specklepy/api/resources/object.py +++ b/src/specklepy/api/resources/object.py @@ -1,5 +1,7 @@ from typing import Dict, List + from gql import gql + from specklepy.api.resource import ResourceBase from specklepy.objects.base import Base diff --git a/src/specklepy/api/resources/other_user.py b/src/specklepy/api/resources/other_user.py index 7d5f313..8496c79 100644 --- a/src/specklepy/api/resources/other_user.py +++ b/src/specklepy/api/resources/other_user.py @@ -1,13 +1,12 @@ -from typing import List, Optional, Union from datetime import datetime, timezone +from typing import List, Optional, Union + from gql import gql + +from specklepy.api.models import ActivityCollection, LimitedUser +from specklepy.api.resource import ResourceBase from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException -from specklepy.api.resource import ResourceBase -from specklepy.api.models import ( - ActivityCollection, - LimitedUser, -) NAME = "other_user" diff --git a/src/specklepy/api/resources/server.py b/src/specklepy/api/resources/server.py index c8b42e0..c2d4163 100644 --- a/src/specklepy/api/resources/server.py +++ b/src/specklepy/api/resources/server.py @@ -1,12 +1,13 @@ import re from typing import Any, Dict, List, Tuple + from gql import gql + from specklepy.api.models import ServerInfo from specklepy.api.resource import ResourceBase from specklepy.logging import metrics from specklepy.logging.exceptions import GraphQLException - NAME = "server" diff --git a/src/specklepy/api/resources/stream.py b/src/specklepy/api/resources/stream.py index 921a6ec..a1306cf 100644 --- a/src/specklepy/api/resources/stream.py +++ b/src/specklepy/api/resources/stream.py @@ -1,12 +1,13 @@ from datetime import datetime, timezone from typing import List, Optional + from deprecated import deprecated from gql import gql -from specklepy.logging import metrics + from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, Stream from specklepy.api.resource import ResourceBase -from specklepy.logging.exceptions import UnsupportedException, SpeckleException - +from specklepy.logging import metrics +from specklepy.logging.exceptions import SpeckleException, UnsupportedException NAME = "stream" diff --git a/src/specklepy/api/resources/subscriptions.py b/src/specklepy/api/resources/subscriptions.py index db1fe94..8bdf021 100644 --- a/src/specklepy/api/resources/subscriptions.py +++ b/src/specklepy/api/resources/subscriptions.py @@ -1,7 +1,9 @@ -from typing import Callable, Dict, List, Union from functools import wraps +from typing import Callable, Dict, List, Union + from gql import gql from graphql import DocumentNode + from specklepy.api.resource import ResourceBase from specklepy.api.resources.stream import Stream from specklepy.logging.exceptions import SpeckleException diff --git a/src/specklepy/api/resources/user.py b/src/specklepy/api/resources/user.py index fd718e6..4e23417 100644 --- a/src/specklepy/api/resources/user.py +++ b/src/specklepy/api/resources/user.py @@ -1,12 +1,13 @@ -from typing import List, Optional, Union from datetime import datetime, timezone +from typing import List, Optional, Union + +from deprecated import deprecated from gql import gql + +from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User +from specklepy.api.resource import ResourceBase from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException -from specklepy.api.resource import ResourceBase -from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User -from deprecated import deprecated - NAME = "user" diff --git a/src/specklepy/api/wrapper.py b/src/specklepy/api/wrapper.py index 558118e..d6e3b01 100644 --- a/src/specklepy/api/wrapper.py +++ b/src/specklepy/api/wrapper.py @@ -1,14 +1,15 @@ +from urllib.parse import unquote, urlparse from warnings import warn -from urllib.parse import urlparse, unquote + +from specklepy.api.client import SpeckleClient from specklepy.api.credentials import ( Account, get_account_from_token, get_local_accounts, ) from specklepy.logging import metrics -from specklepy.api.client import SpeckleClient -from specklepy.transports.server.server import ServerTransport from specklepy.logging.exceptions import SpeckleException, SpeckleWarning +from specklepy.transports.server.server import ServerTransport class StreamWrapper: diff --git a/src/specklepy/logging/metrics.py b/src/specklepy/logging/metrics.py index ee095e9..fc197aa 100644 --- a/src/specklepy/logging/metrics.py +++ b/src/specklepy/logging/metrics.py @@ -1,14 +1,14 @@ -import sys -import queue -import hashlib -import getpass -import logging -from typing import Optional -import requests -import threading -import platform import contextlib +import getpass +import hashlib +import logging +import platform +import queue +import sys +import threading +from typing import Optional +import requests """ Anonymous telemetry to help us understand how to make a better Speckle. diff --git a/src/specklepy/objects/__init__.py b/src/specklepy/objects/__init__.py index 34a6d83..fef8b55 100644 --- a/src/specklepy/objects/__init__.py +++ b/src/specklepy/objects/__init__.py @@ -1,5 +1,6 @@ """Builtin Speckle object kit.""" +from specklepy.objects import encoding, geometry, other, primitive, structural, units from specklepy.objects.base import Base -__all__ = ["Base"] +__all__ = ["Base", "encoding", "geometry", "other", "units", "structural", "primitive"] diff --git a/src/specklepy/objects/base.py b/src/specklepy/objects/base.py index 0507d0f..6e5ac0a 100644 --- a/src/specklepy/objects/base.py +++ b/src/specklepy/objects/base.py @@ -1,21 +1,22 @@ +import contextlib +from enum import Enum +from inspect import isclass from typing import ( Any, ClassVar, Dict, List, Optional, - Union, Set, + Tuple, Type, + Union, get_type_hints, ) - -import contextlib -from enum import EnumMeta from warnings import warn -from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException -from specklepy.objects.units import get_units_from_string, Units +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.units import Units, get_units_from_string from specklepy.transports.memory import MemoryTransport PRIMITIVES = (int, float, str, bool) @@ -90,7 +91,8 @@ class _RegisteringBase: """ speckle_type: ClassVar[str] - _type_registry: ClassVar[Dict[str, "Base"]] = {} + _speckle_type_override: ClassVar[Optional[str]] = None + _type_registry: ClassVar[Dict[str, Type["Base"]]] = {} _attr_types: ClassVar[Dict[str, Type]] = {} # dict of chunkable props and their max chunk size _chunkable: Dict[str, int] = {} @@ -98,22 +100,40 @@ class _RegisteringBase: _detachable: Set[str] = set() # list of defined detachable props _serialize_ignore: Set[str] = set() - class Config: - validate_assignment = True - @classmethod - def get_registered_type( - cls, speckle_type: str - ) -> Union["Base", Type["Base"], None]: + def get_registered_type(cls, speckle_type: str) -> Optional[Type["Base"]]: """Get the registered type from the protected mapping via the `speckle_type`""" return cls._type_registry.get(speckle_type, None) + @classmethod + def _determine_speckle_type(cls) -> str: + """ + This method brings the speckle_type construction in par with peckle-sharp/Core. + + The implementation differs, because in Core the basis of the speckle_type if + type.FullName, which includes the dotnet namespace name too. + Copying that behavior is hard in python, where the concept of namespaces + means something entirely different. + + So we enabled a speckle_type override mechanism, that enables + """ + base_name = "Base" + if cls.__name__ == base_name: + return base_name + + bases = [ + b._speckle_type_override if b._speckle_type_override else b.__name__ + for b in reversed(cls.mro()) + if issubclass(b, Base) and b.__name__ != base_name + ] + return ":".join(bases) + def __init_subclass__( cls, - speckle_type: str = None, - chunkable: Dict[str, int] = None, - detachable: Set[str] = None, - serialize_ignore: Set[str] = None, + speckle_type: Optional[str] = None, + chunkable: Optional[Dict[str, int]] = None, + detachable: Optional[Set[str]] = None, + serialize_ignore: Optional[Set[str]] = None, **kwargs: Dict[str, Any], ): """ @@ -123,13 +143,14 @@ class _RegisteringBase: initialization. This is reused to register each subclassing type into a class level dictionary. """ - if speckle_type in cls._type_registry: + cls._speckle_type_override = speckle_type + cls.speckle_type = cls._determine_speckle_type() + if cls.speckle_type in cls._type_registry: raise ValueError( f"The speckle_type: {speckle_type} is already registered for type: " - f"{cls._type_registry[speckle_type].__name__}. " + f"{cls._type_registry[cls.speckle_type].__name__}. " f"Please choose a different type name." ) - cls.speckle_type = speckle_type or cls.__name__ cls._type_registry[cls.speckle_type] = cls # type: ignore try: cls._attr_types = get_type_hints(cls) @@ -145,6 +166,102 @@ class _RegisteringBase: super().__init_subclass__(**kwargs) +# T = TypeVar("T") + +# how i wish the code below would be correct, but we're also parsing into floats +# and converting into strings if the original type is string, but the value isn't +# def _validate_type(t: type, value: T) -> Tuple[bool, T]: + + +def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]: + # this should be reworked. Its only ok to return null for Optionals... + # if t is None and value is None: + if value is None: + return True, value + + # after fixing the None t above, this should be + # if t is Any: + # if t is None: + + if t is None or t is Any: + return True, value + + if isclass(t) and issubclass(t, Enum): + if isinstance(value, t): + return True, value + if value in t._value2member_map_: + return True, t(value) + + if getattr(t, "__module__", None) == "typing": + origin = getattr(t, "__origin__") + # below is what in nicer for >= py38 + # origin = get_origin(t) + + # recursive validation for Unions on both types preferring the fist type + if origin is Union: + t_1, t_2 = t.__args__ # type: ignore + # below is what in nicer for >= py38 + # t_1, t_2 = get_args(t) + t_1_success, t_1_value = _validate_type(t_1, value) + if t_1_success: + return True, t_1_value + return _validate_type(t_2, value) + if origin is dict: + if not isinstance(value, dict): + return False, value + if value == {}: + return True, value + t_key, t_value = t.__args__ # type: ignore + # we're only checking the first item, but the for loop and return after + # evaluating the first item is the fastest way + for dict_key, dict_value in value.items(): + valid_key, _ = _validate_type(t_key, dict_key) + valid_value, _ = _validate_type(t_value, dict_value) + + if valid_key and valid_value: + return True, value + return False, value + + if origin is list: + if not isinstance(value, list): + return False, value + if value == []: + return True, value + t_items = t.__args__[0] # type: ignore + first_item_valid, _ = _validate_type(t_items, value[0]) + if first_item_valid: + return True, value + return False, value + + if origin is tuple: + if not isinstance(value, tuple): + return False, value + args = t.__args__ # type: ignore + # we're not checking for empty tuple, cause tuple lengths must match + if len(args) != len(value): + return False, value + values = [] + for t_item, v_item in zip(args, value): + item_valid, item_value = _validate_type(t_item, v_item) + if not item_valid: + return False, value + values.append(item_value) + return True, tuple(values) + + if isinstance(value, t): + return True, value + + with contextlib.suppress(ValueError): + if t is float and value is not None: + return True, float(value) + # TODO: dafuq, i had to add this not list check + # but it would also fail for objects and other complex values + if t is str and value and not isinstance(value, list): + return True, str(value) + + return False, value + + class Base(_RegisteringBase): id: Union[str, None] = None totalChildrenCount: Union[int, None] = None @@ -242,55 +359,27 @@ class Base(_RegisteringBase): "Invalid Name: Base member names cannot contain characters '.' or '/'", ) - def _type_check(self, name: str, value: Any): + def _type_check(self, name: str, value: Any) -> Any: """ Lightweight type checking of values before setting them - NOTE: Does not check subscripted types within generics as the performance hit of checking - each item within a given collection isn't worth it. Eg if you have a type Dict[str, float], + NOTE: Does not check subscripted types within generics as the performance hit + of checking each item within a given collection isn't worth it. + Eg if you have a type Dict[str, float], we will only check if the value you're trying to set is a dict. """ types = getattr(self, "_attr_types", {}) t = types.get(name, None) - if t is None or t is Any: - return value + valid, checked_value = _validate_type(t, value) - if value is None: - return None - - if isinstance(t, EnumMeta) and (value in t._value2member_map_): - return t(value) - - if t.__module__ == "typing": - origin = getattr(t, "__origin__") - t = ( - tuple(getattr(sub_t, "__origin__", sub_t) for sub_t in t.__args__) - if origin is Union - else origin - ) - - if not isinstance(t, (type, tuple)): - warn( - f"Unrecognised type '{t}' provided for attribute '{name}'. Type will not been validated." - ) - return value - if isinstance(value, t): - return value - - # to be friendly, we'll parse ints and strs into floats, but not the other way around - # (to avoid unexpected rounding) - if isinstance(t, tuple): - t = t[0] - - with contextlib.suppress(ValueError): - if t is float: - return float(value) - if t is str and value: - return str(value) + if valid: + return checked_value raise SpeckleException( - f"Cannot set '{self.__class__.__name__}.{name}': it expects type '{t.__name__}', but received type '{type(value).__name__}'" + f"Cannot set '{self.__class__.__name__}.{name}':" + f"it expects type '{str(t)}'," + f"but received type '{type(value).__name__}'" ) def add_chunkable_attrs(self, **kwargs: int) -> None: diff --git a/src/specklepy/objects/encoding.py b/src/specklepy/objects/encoding.py index dd4f0f1..eab2d87 100644 --- a/src/specklepy/objects/encoding.py +++ b/src/specklepy/objects/encoding.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Any, Callable, List, Type, Dict +from typing import Any, Callable, Dict, List, Optional, Type from specklepy.logging.exceptions import SpeckleException from specklepy.objects.base import Base @@ -43,7 +43,7 @@ def curve_from_list(args: List[float]): class ObjectArray: - def __init__(self, data: list = None) -> None: + def __init__(self, data: Optional[list] = None) -> None: self.data = data or [] @classmethod @@ -68,7 +68,7 @@ class ObjectArray: def decode_data( data: List[Any], decoder: Callable[[List[Any]], Base], **kwargs: Dict[str, Any] ) -> List[Base]: - bases = [] + bases: List[Base] = [] if not data: return bases index = 0 diff --git a/src/specklepy/objects/fakemesh.py b/src/specklepy/objects/fakemesh.py index 319a130..413d4b1 100644 --- a/src/specklepy/objects/fakemesh.py +++ b/src/specklepy/objects/fakemesh.py @@ -1,5 +1,6 @@ from enum import Enum -from typing import List +from typing import List, Optional + from specklepy.objects.geometry import Point from .base import Base @@ -16,8 +17,8 @@ DETACHABLE = {"detach_this", "origin", "detached_list"} class FakeGeo(Base, chunkable={"dots": 50}, detachable={"pointslist"}): - pointslist: List[Base] = None - dots: List[int] = None + pointslist: Optional[List[Base]] = None + dots: Optional[List[int]] = None class FakeDirection(Enum): @@ -28,15 +29,15 @@ class FakeDirection(Enum): class FakeMesh(FakeGeo, chunkable=CHUNKABLE_PROPS, detachable=DETACHABLE): - vertices: List[float] = None - faces: List[int] = None - colors: List[int] = None - textureCoordinates: List[float] = None - cardinal_dir: FakeDirection = None - test_bases: List[Base] = None - detach_this: Base = None - detached_list: List[Base] = None - _origin: Point = None + vertices: Optional[List[float]] = None + faces: Optional[List[int]] = None + colors: Optional[List[int]] = None + textureCoordinates: Optional[List[float]] = None + cardinal_dir: Optional[FakeDirection] = None + test_bases: Optional[List[Base]] = None + detach_this: Optional[Base] = None + detached_list: Optional[List[Base]] = None + _origin: Optional[Point] = None # def __init__(self, **kwargs) -> None: # super(FakeMesh, self).__init__(**kwargs) diff --git a/src/specklepy/objects/geometry.py b/src/specklepy/objects/geometry.py index 664ad17..78d9a34 100644 --- a/src/specklepy/objects/geometry.py +++ b/src/specklepy/objects/geometry.py @@ -1,28 +1,14 @@ -from enum import Enum +from enum import Enum, IntEnum, auto from typing import Any, List, Optional -from .base import Base -from .encoding import CurveArray, CurveTypeEncoding, ObjectArray -from .units import get_encoding_from_units, get_units_from_encoding +from specklepy.objects.base import Base +from specklepy.objects.encoding import CurveArray, CurveTypeEncoding, ObjectArray +from specklepy.objects.primitive import Interval +from specklepy.objects.units import get_encoding_from_units, get_units_from_encoding GEOMETRY = "Objects.Geometry." -class Interval(Base, speckle_type="Objects.Primitive.Interval"): - start: float = 0.0 - end: float = 0.0 - - def length(self): - return abs(self.start - self.end) - - @classmethod - def from_list(cls, args: List[Any]) -> "Interval": - return cls(start=args[0], end=args[1]) - - def to_list(self) -> List[Any]: - return [self.start, self.end] - - class Point(Base, speckle_type=GEOMETRY + "Point"): x: float = 0.0 y: float = 0.0 @@ -47,12 +33,46 @@ class Point(Base, speckle_type=GEOMETRY + "Point"): return pt -class Vector(Point, speckle_type=GEOMETRY + "Vector"): - pass +class Pointcloud(Base, speckle_type=GEOMETRY + "Pointcloud"): + points: Optional[List[float]] = None + colors: Optional[List[int]] = None + sizes: Optional[List[float]] = None + bbox: Optional["Box"] = None + + +class Vector(Base, speckle_type=GEOMETRY + "Vector"): + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + applicationId: Optional[str] = None + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__} " + "(x: {self.x}, y: {self.y}, z: {self.z}, id: {self.id}, " + "speckle_type: {self.speckle_type})" + ) + + @classmethod + def from_list(cls, args: List[float]) -> "Vector": + """ + Create from a list of three floats representing the x, y, and z coordinates. + """ + return cls(x=args[0], y=args[1], z=args[2]) + + def to_list(self) -> List[float]: + return [self.x, self.y, self.z] + + @classmethod + def from_coords(cls, x: float = 0.0, y: float = 0.0, z: float = 0.0) -> "Vector": + """Create a new Point from x, y, and z values""" + v = Vector() + v.x, v.y, v.z = x, y, z + return v class ControlPoint(Point, speckle_type=GEOMETRY + "ControlPoint"): - weight: float = None + weight: Optional[float] = None class Plane(Base, speckle_type=GEOMETRY + "Plane"): @@ -83,19 +103,19 @@ class Plane(Base, speckle_type=GEOMETRY + "Plane"): class Box(Base, speckle_type=GEOMETRY + "Box"): basePlane: Plane = Plane() + xSize: Interval = Interval() ySize: Interval = Interval() zSize: Interval = Interval() - xSize: Interval = Interval() - area: float = None - volume: float = None + area: Optional[float] = None + volume: Optional[float] = None class Line(Base, speckle_type=GEOMETRY + "Line"): start: Point = Point() - end: Point = None - domain: Interval = None - bbox: Box = None - length: float = None + end: Optional[Point] = None + domain: Optional[Interval] = None + bbox: Optional[Box] = None + length: Optional[float] = None @classmethod def from_list(cls, args: List[Any]) -> "Line": @@ -118,18 +138,18 @@ class Line(Base, speckle_type=GEOMETRY + "Line"): class Arc(Base, speckle_type=GEOMETRY + "Arc"): - radius: float = None - startAngle: float = None - endAngle: float = None - angleRadians: float = None - plane: Plane = None - domain: Interval = None - startPoint: Point = None - midPoint: Point = None - endPoint: Point = None - bbox: Box = None - area: float = None - length: float = None + radius: Optional[float] = None + startAngle: Optional[float] = None + endAngle: Optional[float] = None + angleRadians: Optional[float] = None + plane: Optional[Plane] = None + domain: Optional[Interval] = None + startPoint: Optional[Point] = None + midPoint: Optional[Point] = None + endPoint: Optional[Point] = None + bbox: Optional[Box] = None + area: Optional[float] = None + length: Optional[float] = None @classmethod def from_list(cls, args: List[Any]) -> "Arc": @@ -163,12 +183,12 @@ class Arc(Base, speckle_type=GEOMETRY + "Arc"): class Circle(Base, speckle_type=GEOMETRY + "Circle"): - radius: float = None - plane: Plane = None - domain: Interval = None - bbox: Box = None - area: float = None - length: float = None + radius: Optional[float] = None + plane: Optional[Plane] = None + domain: Optional[Interval] = None + bbox: Optional[Box] = None + area: Optional[float] = None + length: Optional[float] = None @classmethod def from_list(cls, args: List[Any]) -> "Circle": @@ -190,14 +210,14 @@ class Circle(Base, speckle_type=GEOMETRY + "Circle"): class Ellipse(Base, speckle_type=GEOMETRY + "Ellipse"): - firstRadius: float = None - secondRadius: float = None - plane: Plane = None - domain: Interval = None - trimDomain: Interval = None - bbox: Box = None - area: float = None - length: float = None + firstRadius: Optional[float] = None + secondRadius: Optional[float] = None + plane: Optional[Plane] = None + domain: Optional[Interval] = None + trimDomain: Optional[Interval] = None + bbox: Optional[Box] = None + area: Optional[float] = None + length: Optional[float] = None @classmethod def from_list(cls, args: List[Any]) -> "Ellipse": @@ -221,12 +241,12 @@ class Ellipse(Base, speckle_type=GEOMETRY + "Ellipse"): class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 20000}): - value: List[float] = None - closed: bool = None - domain: Interval = None - bbox: Box = None - area: float = None - length: float = None + value: Optional[List[float]] = None + closed: Optional[bool] = None + domain: Optional[Interval] = None + bbox: Optional[Box] = None + area: Optional[float] = None + length: Optional[float] = None @classmethod def from_points(cls, points: List[Point]): @@ -272,23 +292,50 @@ class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 200 ] +class SpiralType(Enum): + Biquadratic = (0,) + BiquadraticParabola = (1,) + Bloss = (2,) + Clothoid = (3,) + Cosine = (4,) + Cubic = (5,) + CubicParabola = (6,) + Radioid = (7,) + Sinusoid = (8,) + Unknown = 9 + + +class Spiral(Base, speckle_type=GEOMETRY + "Spiral", detachable={"displayValue"}): + startPoint: Optional[Point] = None + endPoint: Optional[Point] + plane: Optional[Plane] + turns: Optional[int] + pitchAxis: Optional[Vector] = Vector() + pitch: float = 0 + spiralType: Optional[SpiralType] = None + displayValue: Optional[Polyline] = None + bbox: Optional[Box] = None + length: Optional[float] = None + domain: Optional[Interval] = None + + class Curve( Base, speckle_type=GEOMETRY + "Curve", chunkable={"points": 20000, "weights": 20000, "knots": 20000}, ): - degree: int = None - periodic: bool = None - rational: bool = None - points: List[float] = None - weights: List[float] = None - knots: List[float] = None - domain: Interval = None - displayValue: Polyline = None - closed: bool = None - bbox: Box = None - area: float = None - length: float = None + degree: Optional[int] = None + periodic: Optional[bool] = None + rational: Optional[bool] = None + points: Optional[List[float]] = None + weights: Optional[List[float]] = None + knots: Optional[List[float]] = None + domain: Optional[Interval] = None + displayValue: Optional[Polyline] = None + closed: Optional[bool] = None + bbox: Optional[Box] = None + area: Optional[float] = None + length: Optional[float] = None def as_points(self) -> List[Point]: """Converts the `value` attribute to a list of Points""" @@ -345,12 +392,12 @@ class Curve( class Polycurve(Base, speckle_type=GEOMETRY + "Polycurve"): - segments: List[Base] = None - domain: Interval = None - closed: bool = None - bbox: Box = None - area: float = None - length: float = None + segments: Optional[List[Base]] = None + domain: Optional[Interval] = None + closed: Optional[bool] = None + bbox: Optional[Box] = None + area: Optional[float] = None + length: Optional[float] = None @classmethod def from_list(cls, args: List[Any]) -> "Polycurve": @@ -375,17 +422,17 @@ class Polycurve(Base, speckle_type=GEOMETRY + "Polycurve"): class Extrusion(Base, speckle_type=GEOMETRY + "Extrusion"): - capped: bool = None - profile: Base = None - pathStart: Point = None - pathEnd: Point = None - pathCurve: Base = None - pathTangent: Base = None - profiles: List[Base] = None - length: float = None - area: float = None - volume: float = None - bbox: Box = None + capped: Optional[bool] = None + profile: Optional[Base] = None + pathStart: Optional[Point] = None + pathEnd: Optional[Point] = None + pathCurve: Optional[Base] = None + pathTangent: Optional[Base] = None + profiles: Optional[List[Base]] = None + length: Optional[float] = None + area: Optional[float] = None + volume: Optional[float] = None + bbox: Optional[Box] = None class Mesh( @@ -398,21 +445,21 @@ class Mesh( "textureCoordinates": 2000, }, ): - vertices: List[float] = None - faces: List[int] = None - colors: List[int] = None - textureCoordinates: List[float] = None - bbox: Box = None - area: float = None - volume: float = None + vertices: Optional[List[float]] = None + faces: Optional[List[int]] = None + colors: Optional[List[int]] = None + textureCoordinates: Optional[List[float]] = None + bbox: Optional[Box] = None + area: Optional[float] = None + volume: Optional[float] = None @classmethod def create( cls, vertices: List[float], faces: List[int], - colors: List[int] = None, - texture_coordinates: List[float] = None, + colors: Optional[List[int]] = None, + texture_coordinates: Optional[List[float]] = None, ) -> "Mesh": """ Create a new Mesh from lists representing its vertices, faces, @@ -430,20 +477,20 @@ class Mesh( class Surface(Base, speckle_type=GEOMETRY + "Surface"): - degreeU: int = None - degreeV: int = None - rational: bool = None - area: float = None - pointData: List[float] = None - countU: int = None - countV: int = None - bbox: Box = None - closedU: bool = None - closedV: bool = None - domainU: Interval = None - domainV: Interval = None - knotsU: List[float] = None - knotsV: List[float] = None + degreeU: Optional[int] = None + degreeV: Optional[int] = None + rational: Optional[bool] = None + area: Optional[float] = None + pointData: Optional[List[float]] = None + countU: Optional[int] = None + countV: Optional[int] = None + bbox: Optional[Box] = None + closedU: Optional[bool] = None + closedV: Optional[bool] = None + domainU: Optional[Interval] = None + domainV: Optional[Interval] = None + knotsU: Optional[List[float]] = None + knotsV: Optional[List[float]] = None @classmethod def from_list(cls, args: List[Any]) -> "Surface": @@ -493,11 +540,11 @@ class Surface(Base, speckle_type=GEOMETRY + "Surface"): class BrepFace(Base, speckle_type=GEOMETRY + "BrepFace"): - _Brep: "Brep" = None - SurfaceIndex: int = None - OuterLoopIndex: int = None - OrientationReversed: bool = None - LoopIndices: List[int] = None + _Brep: Optional["Brep"] = None + SurfaceIndex: Optional[int] = None + OuterLoopIndex: Optional[int] = None + OrientationReversed: Optional[bool] = None + LoopIndices: Optional[List[int]] = None @property def _outer_loop(self): @@ -533,13 +580,13 @@ class BrepFace(Base, speckle_type=GEOMETRY + "BrepFace"): class BrepEdge(Base, speckle_type=GEOMETRY + "BrepEdge"): - _Brep: "Brep" = None - Curve3dIndex: int = None - TrimIndices: List[int] = None - StartIndex: int = None - EndIndex: int = None - ProxyCurveIsReversed: bool = None - Domain: Interval = None + _Brep: Optional["Brep"] = None + Curve3dIndex: Optional[int] = None + TrimIndices: Optional[List[int]] = None + StartIndex: Optional[int] = None + EndIndex: Optional[int] = None + ProxyCurveIsReversed: Optional[bool] = None + Domain: Optional[Interval] = None @property def _start_vertex(self): @@ -600,10 +647,10 @@ class BrepLoopType(int, Enum): class BrepLoop(Base, speckle_type=GEOMETRY + "BrepLoop"): - _Brep: "Brep" = None - FaceIndex: int = None - TrimIndices: List[int] = None - Type: BrepLoopType = None + _Brep: Optional["Brep"] = None + FaceIndex: Optional[Optional[int]] = None + TrimIndices: Optional[List[int]] = None + Type: Optional[BrepLoopType] = None @property def _face(self): @@ -644,17 +691,17 @@ class BrepTrimType(int, Enum): class BrepTrim(Base, speckle_type=GEOMETRY + "BrepTrim"): - _Brep: "Brep" = None - EdgeIndex: int = None - StartIndex: int = None - EndIndex: int = None - FaceIndex: int = None - LoopIndex: int = None - CurveIndex: int = None - IsoStatus: int = None - TrimType: BrepTrimType = None - IsReversed: bool = None - Domain: Interval = None + _Brep: Optional["Brep"] = None + EdgeIndex: Optional[int] = None + StartIndex: Optional[int] = None + EndIndex: Optional[int] = None + FaceIndex: Optional[int] = None + LoopIndex: Optional[int] = None + CurveIndex: Optional[int] = None + IsoStatus: Optional[int] = None + TrimType: Optional[BrepTrimType] = None + IsReversed: Optional[bool] = None + Domain: Optional[Interval] = None @property def _face(self): @@ -731,21 +778,21 @@ class Brep( "Faces", }, ): - provenance: str = None - bbox: Box = None - area: float = None - volume: float = None - _displayValue: List[Mesh] = None - Surfaces: List[Surface] = None - Curve3D: List[Base] = None - Curve2D: List[Base] = None - Vertices: List[Point] = None - Edges: List[BrepEdge] = None - Loops: List[BrepLoop] = None - Faces: List[BrepFace] = None - Trims: List[BrepTrim] = None - IsClosed: bool = None - Orientation: int = None + provenance: Optional[str] = None + bbox: Optional[Box] = None + area: Optional[float] = None + volume: Optional[float] = None + _displayValue: Optional[List[Mesh]] = None + Surfaces: Optional[List[Surface]] = None + Curve3D: Optional[List[Base]] = None + Curve2D: Optional[List[Base]] = None + Vertices: Optional[List[Point]] = None + Edges: Optional[List[BrepEdge]] = None + Loops: Optional[List[BrepLoop]] = None + Faces: Optional[List[BrepFace]] = None + Trims: Optional[List[BrepTrim]] = None + IsClosed: Optional[bool] = None + Orientation: Optional[int] = None def _inject_self_into_children(self, children: Optional[List[Base]]) -> List[Base]: if children is None: diff --git a/src/specklepy/objects/other.py b/src/specklepy/objects/other.py index 7d32bef..705e05d 100644 --- a/src/specklepy/objects/other.py +++ b/src/specklepy/objects/other.py @@ -1,5 +1,7 @@ -from typing import Any, List +from typing import Any, List, Optional + from specklepy.objects.geometry import Point, Vector + from .base import Base OTHER = "Objects.Other." @@ -24,8 +26,23 @@ IDENTITY_TRANSFORM = [ ] +class Material(Base, speckle_type=OTHER + "Material"): + """Generic class for materials containing generic parameters.""" + + name: Optional[str] = None + + +class RevitMaterial(Material, speckle_type="Objects.Other.Revit." + "RevitMaterial"): + materialCategory: Optional[str] = None + materialClass: Optional[str] = None + shininess: Optional[int] = None + smoothness: Optional[int] = None + transparency: Optional[int] = None + parameters: Optional[Base] = None + + class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"): - name: str = None + name: Optional[str] = None opacity: float = 1 metalness: float = 0 roughness: float = 1 @@ -33,6 +50,25 @@ class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"): emissive: int = -16777216 # black arbg +class MaterialQuantity(Base, speckle_type=OTHER + "MaterialQuantity"): + material: Optional[Material] = None + volume: Optional[float] = None + area: Optional[float] = None + + +class DisplayStyle(Base, speckle_type=OTHER + "DisplayStyle"): + """ + Minimal display style class. + Developed primarily for display styles in Rhino and AutoCAD. + Rhino object attributes uses OpenNURBS definition for linetypes and lineweights. + """ + + name: Optional[str] = None + color: int = -2894893 # light gray arbg + linetype: Optional[str] = None + lineweight: float = 0 + + class Transform( Base, speckle_type=OTHER + "Transform", @@ -44,7 +80,7 @@ class Transform( The 4th column defines translation, where the last value is a divisor (usually equal to 1). """ - _value: List[float] = None + _value: Optional[List[float]] = None @property def value(self) -> List[float]: @@ -188,27 +224,27 @@ class Transform( class BlockDefinition( Base, speckle_type=OTHER + "BlockDefinition", detachable={"geometry"} ): - name: str = None - basePoint: Point = None - geometry: List[Base] = None + name: Optional[str] = None + basePoint: Optional[Point] = None + geometry: Optional[List[Base]] = None class BlockInstance( Base, speckle_type=OTHER + "BlockInstance", detachable={"blockDefinition"} ): - blockDefinition: BlockDefinition = None - transform: Transform = None + blockDefinition: Optional[BlockDefinition] = None + transform: Optional[Transform] = None # TODO: prob move this into a built elements module, but just trialling this for now class RevitParameter(Base, speckle_type="Objects.BuiltElements.Revit.Parameter"): - name: str = None + name: Optional[str] = None value: Any = None - applicationUnitType: str = None # eg UnitType UT_Length - applicationUnit: str = None # DisplayUnitType eg DUT_MILLIMITERS - applicationInternalName: str = ( - None # BuiltInParameterName or GUID for shared parameter - ) + applicationUnitType: Optional[str] = None # eg UnitType UT_Length + applicationUnit: Optional[str] = None # DisplayUnitType eg DUT_MILLIMITERS + applicationInternalName: Optional[ + str + ] = None # BuiltInParameterName or GUID for shared parameter isShared: bool = False isReadOnly: bool = False isTypeParameter: bool = False diff --git a/src/specklepy/objects/primitive.py b/src/specklepy/objects/primitive.py new file mode 100644 index 0000000..d4a4aae --- /dev/null +++ b/src/specklepy/objects/primitive.py @@ -0,0 +1,25 @@ +from typing import Any, List + +from specklepy.objects.base import Base + +NAMESPACE = "Objects.Primitive" + + +class Interval(Base, speckle_type=f"{NAMESPACE}.Interval"): + start: float = 0.0 + end: float = 0.0 + + def length(self): + return abs(self.start - self.end) + + @classmethod + def from_list(cls, args: List[Any]) -> "Interval": + return cls(start=args[0], end=args[1]) + + def to_list(self) -> List[Any]: + return [self.start, self.end] + + +class Interval2d(Base, speckle_type=f"{NAMESPACE}.Interval2d"): + u: Interval + v: Interval diff --git a/src/specklepy/objects/structural/__init__.py b/src/specklepy/objects/structural/__init__.py index 4c182fd..c7bdae9 100644 --- a/src/specklepy/objects/structural/__init__.py +++ b/src/specklepy/objects/structural/__init__.py @@ -1,20 +1,99 @@ """Builtin Speckle object kit.""" -from specklepy.objects.structural.analysis import * -from specklepy.objects.structural.properties import * -from specklepy.objects.structural.material import * -from specklepy.objects.structural.geometry import * -from specklepy.objects.structural.loading import * +from specklepy.objects.structural.analysis import ( + Model, + ModelInfo, + ModelSettings, + ModelUnits, +) from specklepy.objects.structural.axis import Axis +from specklepy.objects.structural.geometry import ( + Element1D, + Element2D, + Element3D, + ElementType1D, + ElementType2D, + ElementType3D, + Node, + Restraint, +) +from specklepy.objects.structural.loading import ( + ActionType, + BeamLoadType, + CombinationType, + FaceLoadType, + Load, + LoadAxisType, + LoadBeam, + LoadCase, + LoadCombinations, + LoadDirection, + LoadDirection2D, + LoadFace, + LoadGravity, + LoadNode, + LoadType, +) +from specklepy.objects.structural.material import ( + Concrete, + MaterialType, + Steel, + StructuralMaterial, + Timber, +) +from specklepy.objects.structural.properties import ( + BaseReferencePoint, + MemberType, + Property, + Property1D, + Property2D, + Property3D, + PropertyDamper, + PropertyMass, + PropertySpring, + PropertyType2D, + PropertyType3D, + PropertyTypeDamper, + PropertyTypeSpring, + ReferenceSurface, + ReferenceSurfaceEnum, + SectionProfile, + ShapeType, + shapeType, +) +from specklepy.objects.structural.results import ( + Result, + Result1D, + Result2D, + Result3D, + ResultGlobal, + ResultNode, + ResultSet1D, + ResultSet2D, + ResultSet3D, + ResultSetAll, + ResultSetNode, +) __all__ = [ "Element1D", "Element2D", "Element3D", + "ElementType1D", + "ElementType2D", + "ElementType3D", "Axis", "Node", "Restraint", "Load", + "LoadType", + "ActionType", + "BeamLoadType", + "FaceLoadType", + "LoadDirection", + "LoadDirection2D", + "LoadAxisType", + "CombinationType", "LoadBeam", "LoadCase", "LoadCombinations", @@ -25,8 +104,9 @@ __all__ = [ "ModelInfo", "ModelSettings", "ModelUnits", + "MaterialType", "Concrete", - "Material", + "StructuralMaterial", "Steel", "Timber", "Property", @@ -37,4 +117,25 @@ __all__ = [ "PropertyMass", "PropertySpring", "SectionProfile", + "MemberType", + "BaseReferencePoint", + "ReferenceSurface", + "PropertyType2D", + "PropertyType3D", + "ShapeType", + "PropertyTypeSpring", + "PropertyTypeDamper", + "ReferenceSurfaceEnum", + "shapeType", + "Result", + "Result1D", + "ResultSet1D", + "Result2D", + "ResultSet2D", + "Result3D", + "ResultSet3D", + "ResultGlobal", + "ResultSetNode", + "ResultNode", + "ResultSetAll", ] diff --git a/src/specklepy/objects/structural/analysis.py b/src/specklepy/objects/structural/analysis.py index d1612dd..ee82eea 100644 --- a/src/specklepy/objects/structural/analysis.py +++ b/src/specklepy/objects/structural/analysis.py @@ -1,51 +1,49 @@ -from typing import List +from typing import List, Optional -from ..base import Base -from ..geometry import * -from .properties import * +from specklepy.objects.base import Base STRUCTURAL_ANALYSIS = "Objects.Structural.Analysis." class ModelUnits(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelUnits"): - length: str = None - sections: str = None - displacements: str = None - stress: str = None - force: str = None - mass: str = None - time: str = None - temperature: str = None - velocity: str = None - acceleration: str = None - energy: str = None - angle: str = None - strain: str = None + length: Optional[str] = None + sections: Optional[str] = None + displacements: Optional[str] = None + stress: Optional[str] = None + force: Optional[str] = None + mass: Optional[str] = None + time: Optional[str] = None + temperature: Optional[str] = None + velocity: Optional[str] = None + acceleration: Optional[str] = None + energy: Optional[str] = None + angle: Optional[str] = None + strain: Optional[str] = None class ModelSettings(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelSettings"): - modelUnits: ModelUnits = None - steelCode: str = None - concreteCode: str = None + modelUnits: Optional[ModelUnits] = None + steelCode: Optional[str] = None + concreteCode: Optional[str] = None coincidenceTolerance: float = 0.0 class ModelInfo(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelInfo"): - name: str = None - description: str = None - projectNumber: str = None - projectName: str = None - settings: ModelSettings = None - initials: str = None - application: str = None + name: Optional[str] = None + description: Optional[str] = None + projectNumber: Optional[str] = None + projectName: Optional[str] = None + settings: Optional[ModelSettings] = None + initials: Optional[str] = None + application: Optional[str] = None class Model(Base, speckle_type=STRUCTURAL_ANALYSIS + "Model"): - specs: ModelInfo = None - nodes: List = None - elements: List = None - loads: List = None - restraints: List = None - properties: List = None - materials: List = None - layerDescription: str = None + specs: Optional[ModelInfo] = None + nodes: Optional[List] = None + elements: Optional[List] = None + loads: Optional[List] = None + restraints: Optional[List] = None + properties: Optional[List] = None + materials: Optional[List] = None + layerDescription: Optional[str] = None diff --git a/src/specklepy/objects/structural/axis.py b/src/specklepy/objects/structural/axis.py index 574fe9c..a7fc06e 100644 --- a/src/specklepy/objects/structural/axis.py +++ b/src/specklepy/objects/structural/axis.py @@ -1,8 +1,10 @@ -from ..base import Base -from ..geometry import Plane +from typing import Optional + +from specklepy.objects.base import Base +from specklepy.objects.geometry import Plane class Axis(Base, speckle_type="Objects.Structural.Geometry.Axis"): - name: str = None - axisType: str = None - plane: Plane = None + name: Optional[str] = None + axisType: Optional[str] = None + plane: Optional[Plane] = None diff --git a/src/specklepy/objects/structural/geometry.py b/src/specklepy/objects/structural/geometry.py index ce9e63d..f232529 100644 --- a/src/specklepy/objects/structural/geometry.py +++ b/src/specklepy/objects/structural/geometry.py @@ -1,10 +1,17 @@ from enum import Enum -from typing import List +from typing import List, Optional -from ..base import Base -from ..geometry import * -from .properties import * -from .axis import Axis +from specklepy.objects.base import Base +from specklepy.objects.geometry import Line, Mesh, Plane, Point, Vector +from specklepy.objects.structural.axis import Axis +from specklepy.objects.structural.properties import ( + Property1D, + Property2D, + Property3D, + PropertyDamper, + PropertyMass, + PropertySpring, +) STRUCTURAL_GEOMETRY = "Objects.Structural.Geometry" @@ -41,68 +48,63 @@ class ElementType3D(int, Enum): class Restraint(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Restraint"): - code: str = None + code: Optional[str] = None stiffnessX: float = 0.0 stiffnessY: float = 0.0 stiffnessZ: float = 0.0 stiffnessXX: float = 0.0 stiffnessYY: float = 0.0 stiffnessZZ: float = 0.0 - units: str = None class Node(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Node"): - name: str = None - basePoint: Point = None - constraintAxis: Axis = None - restraint: Restraint = None - springProperty: PropertySpring = None - massProperty: PropertyMass = None - damperProperty: PropertyDamper = None - units: str = None + name: Optional[str] = None + basePoint: Optional[Point] = None + constraintAxis: Optional[Axis] = None + restraint: Optional[Restraint] = None + springProperty: Optional[PropertySpring] = None + massProperty: Optional[PropertyMass] = None + damperProperty: Optional[PropertyDamper] = None class Element1D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element1D"): - name: str = None - baseLine: Line = None - property: Property1D = None - type: ElementType1D = None - end1Releases: Restraint = None - end2Releases: Restraint = None - end1Offset: Vector = None - end2Offset: Vector = None - orientationNode: Node = None + name: Optional[str] = None + baseLine: Optional[Line] = None + property: Optional[Property1D] = None + type: Optional[ElementType1D] = None + end1Releases: Optional[Restraint] = None + end2Releases: Optional[Restraint] = None + end1Offset: Optional[Vector] = None + end2Offset: Optional[Vector] = None + orientationNode: Optional[Node] = None orinetationAngle: float = 0.0 - localAxis: Plane = None - parent: Base = None - end1Node: Node = Node - end2Node: Node = Node - topology: List = None - displayMesh: Mesh = None - units: str = None + localAxis: Optional[Plane] = None + parent: Optional[Base] = None + end1Node: Optional[Node] = None + end2Node: Optional[Node] = None + topology: Optional[List] = None + displayMesh: Optional[Mesh] = None class Element2D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element2D"): - name: str = None - property: Property2D = None - type: ElementType2D = None + name: Optional[str] = None + property: Optional[Property2D] = None + type: Optional[ElementType2D] = None offset: float = 0.0 orientationAngle: float = 0.0 - parent: Base = None - topology: List = None - displayMesh: Mesh = None - units: str = None + parent: Optional[Base] = None + topology: Optional[List] = None + displayMesh: Optional[Mesh] = None class Element3D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element3D"): - name: str = None - baseMesh: Mesh = None - property: Property3D = None - type: ElementType3D = None + name: Optional[str] = None + baseMesh: Optional[Mesh] = None + property: Optional[Property3D] = None + type: Optional[ElementType3D] = None orientationAngle: float = 0.0 - parent: Base = None + parent: Optional[Base] = None topology: List - units: str = None # class Storey needs ependency on built elements first diff --git a/src/specklepy/objects/structural/loading.py b/src/specklepy/objects/structural/loading.py index dc5ff9d..95d92fd 100644 --- a/src/specklepy/objects/structural/loading.py +++ b/src/specklepy/objects/structural/loading.py @@ -1,8 +1,9 @@ from enum import Enum -from typing import List +from typing import List, Optional -from ..base import Base -from .geometry import * +from specklepy.objects.base import Base +from specklepy.objects.geometry import Vector +from specklepy.objects.structural.axis import Axis STRUCTURAL_LOADING = "Objects.Structural.Loading." @@ -89,56 +90,55 @@ class CombinationType(int, Enum): class LoadCase(Base, speckle_type=STRUCTURAL_LOADING + "LoadCase"): - name: str = None - loadType: LoadType = None - group: str = None - actionType: ActionType = None - description: str = None + name: Optional[str] = None + loadType: Optional[LoadType] = None + group: Optional[str] = None + actionType: Optional[ActionType] = None + description: Optional[str] = None class Load(Base, speckle_type=STRUCTURAL_LOADING + "Load"): - name: str = None - units: str = None - loadCase: LoadCase = None + name: Optional[str] = None + loadCase: Optional[LoadCase] = None class LoadBeam(Load, speckle_type=STRUCTURAL_LOADING + "LoadBeam"): - elements: List = None - loadType: BeamLoadType = None - direction: LoadDirection = None - loadAxis: Axis = None - loadAxisType: LoadAxisType = None - isProjected: bool = None - values: List = None - positions: List = None + elements: Optional[List] = None + loadType: Optional[BeamLoadType] = None + direction: Optional[LoadDirection] = None + loadAxis: Optional[Axis] = None + loadAxisType: Optional[LoadAxisType] = None + isProjected: Optional[bool] = None + values: Optional[List] = None + positions: Optional[List] = None class LoadCombinations(Base, speckle_type=STRUCTURAL_LOADING + "LoadCombination"): - name: str = None + name: Optional[str] = None loadCases: List loadFactors: List combinationType: CombinationType class LoadFace(Load, speckle_type=STRUCTURAL_LOADING + "LoadFace"): - elements: List = None - loadType: FaceLoadType = None - direction: LoadDirection2D = None - loadAxis: Axis = None - loadAxisType: LoadAxisType = None - isProjected: bool = None - values: List = None - positions: List = None + elements: Optional[List] = None + loadType: Optional[FaceLoadType] = None + direction: Optional[LoadDirection2D] = None + loadAxis: Optional[Axis] = None + loadAxisType: Optional[LoadAxisType] = None + isProjected: Optional[bool] = None + values: Optional[List] = None + positions: Optional[List] = None class LoadGravity(Load, speckle_type=STRUCTURAL_LOADING + "LoadGravity"): - elements: List = None - nodes: List = None - gravityFactors: Vector = None + elements: Optional[List] = None + nodes: Optional[List] = None + gravityFactors: Optional[Vector] = None class LoadNode(Load, speckle_type=STRUCTURAL_LOADING + "LoadNode"): - nodes: List = None - loadAxis: Axis = None - direction: LoadDirection = None + nodes: Optional[List] = None + loadAxis: Optional[Axis] = None + direction: Optional[LoadDirection] = None value: float = 0.0 diff --git a/src/specklepy/objects/structural/material.py b/src/specklepy/objects/structural/material.py index 2ab0f67..a18e8d1 100644 --- a/src/specklepy/objects/structural/material.py +++ b/src/specklepy/objects/structural/material.py @@ -1,7 +1,7 @@ from enum import Enum +from typing import Optional -from ..base import Base - +from specklepy.objects.base import Base STRUCTURAL_MATERIALS = "Objects.Structural.Materials" @@ -21,12 +21,14 @@ class MaterialType(int, Enum): Other = 11 -class Material(Base, speckle_type=STRUCTURAL_MATERIALS): - name: str = None - grade: str = None - materialType: MaterialType = None - designCode: str = None - codeYear: str = None +class StructuralMaterial( + Base, speckle_type=STRUCTURAL_MATERIALS + ".StructuralMaterial" +): + name: Optional[str] = None + grade: Optional[str] = None + materialType: Optional[MaterialType] = None + designCode: Optional[str] = None + codeYear: Optional[str] = None strength: float = 0.0 elasticModulus: float = 0.0 poissonsRatio: float = 0.0 @@ -38,22 +40,22 @@ class Material(Base, speckle_type=STRUCTURAL_MATERIALS): materialSafetyFactor: float = 0.0 -class Concrete(Material, speckle_type=STRUCTURAL_MATERIALS + ".Concrete"): +class Concrete(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Concrete"): compressiveStrength: float = 0.0 tensileStrength: float = 0.0 flexuralStrength: float = 0.0 maxCompressiveStrain: float = 0.0 maxTensileStrain: float = 0.0 maxAggregateSize: float = 0.0 - lightweight: bool = None + lightweight: Optional[bool] = None -class Steel(Material, speckle_type=STRUCTURAL_MATERIALS + ".Steel"): +class Steel(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Steel"): yieldStrength: float = 0.0 ultimateStrength: float = 0.0 maxStrain: float = 0.0 strainHardeningModulus: float = 0.0 -class Timber(Material, speckle_type=STRUCTURAL_MATERIALS + ".Timber"): - species: str = None +class Timber(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Timber"): + species: Optional[str] = None diff --git a/src/specklepy/objects/structural/properties.py b/src/specklepy/objects/structural/properties.py index 8697f58..a3e3138 100644 --- a/src/specklepy/objects/structural/properties.py +++ b/src/specklepy/objects/structural/properties.py @@ -1,10 +1,9 @@ from enum import Enum +from typing import Optional -from ..base import Base - -from .material import * -from .axis import Axis - +from specklepy.objects.base import Base +from specklepy.objects.structural.axis import Axis +from specklepy.objects.structural.material import StructuralMaterial STRUCTURAL_PROPERTY = "Objectives.Structural.Properties" @@ -59,7 +58,7 @@ class PropertyType3D(int, Enum): class ShapeType(int, Enum): Rectangular = 0 Circular = 1 - I = 2 + I = 2 # noqa: E741 Tee = 3 Angle = 4 Channel = 5 @@ -89,36 +88,35 @@ class PropertyTypeDamper(int, Enum): class Property(Base, speckle_type=STRUCTURAL_PROPERTY): - name: str = None + name: Optional[str] = None class SectionProfile(Base, speckle_type=STRUCTURAL_PROPERTY + ".SectionProfile"): - name: str = None - shapeType: ShapeType = None + name: Optional[str] = None + shapeType: Optional[ShapeType] = None area: float = 0.0 Iyy: float = 0.0 Izz: float = 0.0 J: float = 0.0 Ky: float = 0.0 weight: float = 0.0 - units: str = None class Property1D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property1D"): - memberType: MemberType = None - material : Material = None - profile: SectionProfile = None - referencePoint : BaseReferencePoint = None + memberType: Optional[MemberType] = None + material: Optional[StructuralMaterial] = None + profile: Optional[SectionProfile] = None + referencePoint: Optional[BaseReferencePoint] = None offsetY: float = 0.0 offsetZ: float = 0.0 class Property2D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property2D"): - type: PropertyType2D = None + type: Optional[PropertyType2D] = None thickness: float = 0.0 - material: Material = None - orientationAxis : Axis = None - refSurface : ReferenceSurface = None + material: Optional[StructuralMaterial] = None + orientationAxis: Optional[Axis] = None + refSurface: Optional[ReferenceSurface] = None zOffset: float = 0.0 modifierInPlane: float = 0.0 modifierBending: float = 0.0 @@ -127,13 +125,13 @@ class Property2D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property2D"): class Property3D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property3D"): - type: PropertyType3D = None - material: Material = None - orientationAxis: Axis = None + type: Optional[PropertyType3D] = None + material: Optional[StructuralMaterial] = None + orientationAxis: Optional[Axis] = None class PropertyDamper(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyDamper"): - damperType: PropertyTypeDamper = None + damperType: Optional[PropertyTypeDamper] = None dampingX: float = 0.0 dampingY: float = 0.0 dampingZ: float = 0.0 @@ -150,14 +148,14 @@ class PropertyMass(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyMass") inertiaXY: float = 0.0 inertiaYZ: float = 0.0 inertiaZX: float = 0.0 - massModified: bool = None + massModified: Optional[bool] = None massModifierX: float = 0.0 massModifierY: float = 0.0 massModifierZ: float = 0.0 class PropertySpring(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertySpring"): - springType: PropertyTypeSpring = None + springType: Optional[PropertyTypeSpring] = None springCurveX: float = 0.0 stiffnessX: float = 0.0 springCurveY: float = 0.0 diff --git a/src/specklepy/objects/structural/results.py b/src/specklepy/objects/structural/results.py index 49e2c99..6a97d42 100644 --- a/src/specklepy/objects/structural/results.py +++ b/src/specklepy/objects/structural/results.py @@ -1,18 +1,16 @@ -from typing import List +from typing import List, Optional -from ..base import Base -from ..geometry import * -from .loading import * -from .geometry import * -from .analysis import Model +from specklepy.objects.base import Base +from specklepy.objects.structural.analysis import Model +from specklepy.objects.structural.geometry import Element1D, Element2D, Element3D, Node STRUCTURAL_RESULTS = "Objects.Structural.Results." class Result(Base, speckle_type=STRUCTURAL_RESULTS + "Result"): - resultCase: Base = None - permutation: str = None - description: str = None + resultCase: Optional[Base] = None + permutation: Optional[str] = None + description: Optional[str] = None class ResultSet1D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet1D"): @@ -20,7 +18,7 @@ class ResultSet1D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet1D"): class Result1D(Result, speckle_type=STRUCTURAL_RESULTS + "Result1D"): - element: Element1D = None + element: Optional[Element1D] = None position: float = 0.0 dispX: float = 0.0 dispY: float = 0.0 @@ -50,7 +48,7 @@ class ResultSet2D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet2D"): class Result2D(Result, speckle_type=STRUCTURAL_RESULTS + "Result2D"): - element: Element2D = None + element: Optional[Element2D] = None position: List dispX: float = 0.0 dispY: float = 0.0 @@ -88,7 +86,7 @@ class ResultSet3D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet3D"): class Result3D(Result, speckle_type=STRUCTURAL_RESULTS + "Result3D"): - element: Element3D = None + element: Optional[Element3D] = None position: List dispX: float = 0.0 dispY: float = 0.0 @@ -102,7 +100,7 @@ class Result3D(Result, speckle_type=STRUCTURAL_RESULTS + "Result3D"): class ResultGlobal(Result, speckle_type=STRUCTURAL_RESULTS + "ResultGlobal"): - model: Model = None + model: Optional[Model] = None loadX: float = 0.0 loadY: float = 0.0 loadZ: float = 0.0 @@ -133,7 +131,7 @@ class ResultSetNode(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSetNode"): class ResultNode(Result, speckle_type=STRUCTURAL_RESULTS + " ResultNode"): - node: Node = None + node: Optional[Node] = None dispX: float = 0.0 dispY: float = 0.0 dispZ: float = 0.0 @@ -167,8 +165,8 @@ class ResultNode(Result, speckle_type=STRUCTURAL_RESULTS + " ResultNode"): class ResultSetAll(Base, speckle_type=None): - resultSet1D: ResultSet1D = None - resultSet2D: ResultSet2D = None - resultSet3D: ResultSet3D = None - resultsGlobal: ResultGlobal = None - resultsNode: ResultSetNode = None + resultSet1D: Optional[ResultSet1D] = None + resultSet2D: Optional[ResultSet2D] = None + resultSet3D: Optional[ResultSet3D] = None + resultsGlobal: Optional[ResultGlobal] = None + resultsNode: Optional[ResultSetNode] = None diff --git a/src/specklepy/objects/units.py b/src/specklepy/objects/units.py index 4f12b27..e89dbc7 100644 --- a/src/specklepy/objects/units.py +++ b/src/specklepy/objects/units.py @@ -1,6 +1,14 @@ -from typing import Union -from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException from enum import Enum +from typing import Union + +from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException + +__all__ = [ + "Units", + "get_encoding_from_units", + "get_units_from_encoding", + "get_units_from_string", +] class Units(Enum): @@ -57,7 +65,10 @@ def get_units_from_encoding(unit: int): return name raise SpeckleException( - message=f"Could not understand what unit {unit} is referring to. Please enter a valid unit encoding (eg {UNITS_ENCODINGS})." + message=( + f"Could not understand what unit {unit} is referring to." + f"Please enter a valid unit encoding (eg {UNITS_ENCODINGS})." + ) ) @@ -66,5 +77,8 @@ def get_encoding_from_units(unit: Union[Units, None]): return UNITS_ENCODINGS[unit] except KeyError as e: raise SpeckleException( - message=f"No encoding exists for unit {unit}. Please enter a valid unit to encode (eg {UNITS_ENCODINGS})." + message=( + f"No encoding exists for unit {unit}." + f"Please enter a valid unit to encode (eg {UNITS_ENCODINGS})." + ) ) from e diff --git a/src/specklepy/paths.py b/src/specklepy/paths.py index 24f736c..d32385b 100644 --- a/src/specklepy/paths.py +++ b/src/specklepy/paths.py @@ -1,5 +1,6 @@ import sys from pathlib import Path + from appdirs import user_data_dir diff --git a/src/specklepy/serialization/base_object_serializer.py b/src/specklepy/serialization/base_object_serializer.py index d11faba..7d4f154 100644 --- a/src/specklepy/serialization/base_object_serializer.py +++ b/src/specklepy/serialization/base_object_serializer.py @@ -1,21 +1,19 @@ -import re -import ujson import hashlib +import re import warnings -from uuid import uuid4 from enum import Enum -from warnings import warn from typing import Any, Dict, List, Tuple -from specklepy.objects.base import Base, DataChunk -from specklepy.logging.exceptions import ( - SpeckleException, - SpeckleWarning, -) -from specklepy.transports.abstract_transport import AbstractTransport +from uuid import uuid4 +from warnings import warn + +import ujson # import for serialization import specklepy.objects.geometry import specklepy.objects.other +from specklepy.logging.exceptions import SpeckleException, SpeckleWarning +from specklepy.objects.base import Base, DataChunk +from specklepy.transports.abstract_transport import AbstractTransport PRIMITIVES = (int, float, str, bool) diff --git a/src/specklepy/transports/abstract_transport.py b/src/specklepy/transports/abstract_transport.py index b0e2277..3235557 100644 --- a/src/specklepy/transports/abstract_transport.py +++ b/src/specklepy/transports/abstract_transport.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod -from typing import Optional, List, Dict +from typing import Dict, List, Optional + from pydantic import BaseModel from pydantic.config import Extra diff --git a/src/specklepy/transports/memory.py b/src/specklepy/transports/memory.py index 0f61bff..530dd5b 100644 --- a/src/specklepy/transports/memory.py +++ b/src/specklepy/transports/memory.py @@ -1,4 +1,5 @@ -from typing import Any, List, Dict +from typing import Any, Dict, List + from specklepy.transports.abstract_transport import AbstractTransport diff --git a/src/specklepy/transports/server/batch_sender.py b/src/specklepy/transports/server/batch_sender.py index f25aaf5..55c7f2a 100644 --- a/src/specklepy/transports/server/batch_sender.py +++ b/src/specklepy/transports/server/batch_sender.py @@ -1,10 +1,11 @@ +import gzip import json import logging -import threading import queue -import gzip +import threading import requests + from specklepy.logging.exceptions import SpeckleException LOG = logging.getLogger(__name__) diff --git a/src/specklepy/transports/server/server.py b/src/specklepy/transports/server/server.py index 56ac4a3..805799f 100644 --- a/src/specklepy/transports/server/server.py +++ b/src/specklepy/transports/server/server.py @@ -1,8 +1,8 @@ import json -import requests +from typing import Any, Dict, List from warnings import warn -from typing import Any, Dict, List +import requests from specklepy.api.client import SpeckleClient from specklepy.api.credentials import Account, get_account_from_token diff --git a/src/specklepy/transports/sqlite.py b/src/specklepy/transports/sqlite.py index b62da42..d79b526 100644 --- a/src/specklepy/transports/sqlite.py +++ b/src/specklepy/transports/sqlite.py @@ -1,10 +1,11 @@ import os import sqlite3 -from typing import Any, List, Dict, Optional, Tuple from contextlib import closing -from specklepy.transports.abstract_transport import AbstractTransport +from typing import Any, Dict, List, Optional, Tuple + from specklepy.logging.exceptions import SpeckleException from specklepy.paths import base_path +from specklepy.transports.abstract_transport import AbstractTransport class SQLiteTransport(AbstractTransport): diff --git a/tests/conftest.py b/tests/conftest.py index 3dbfe01..b920134 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,15 @@ -import uuid import random +import uuid + import pytest import requests -from specklepy.api.models import Stream + from specklepy.api.client import SpeckleClient -from specklepy.objects.base import Base -from specklepy.objects.geometry import Point -from specklepy.objects.fakemesh import FakeDirection, FakeMesh +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() @@ -29,10 +31,10 @@ def seed_user(host): 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 + # 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 + allow_redirects=False, ) if not r.ok: raise Exception(f"Cannot seed user: {r.reason}") diff --git a/tests/test_active_user.py b/tests/test_active_user.py index b027025..8f3ff3f 100644 --- a/tests/test_active_user.py +++ b/tests/test_active_user.py @@ -1,4 +1,5 @@ import pytest + from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, User from specklepy.logging.exceptions import SpeckleException diff --git a/tests/test_base.py b/tests/test_base.py index c6dfb10..dd667a9 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,12 +1,12 @@ -from codecs import ascii_encode +from contextlib import ExitStack as does_not_raise from enum import Enum from typing import Dict, List, Optional, Union -from contextlib import ExitStack as does_not_raise import pytest + from specklepy.api import operations from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException -from specklepy.objects.base import Base, DataChunk +from specklepy.objects.base import Base from specklepy.objects.units import Units @@ -29,12 +29,15 @@ def test_empty_prop_names(invalid_prop_name: str) -> None: class FakeModel(Base): """Just a test class type.""" - foo: str = "" + +class FakeSub(FakeModel): + """Just a test class type.""" def test_new_type_registration() -> None: """Test if a new subclass is registered into the type register.""" - assert Base.get_registered_type("FakeModel") == FakeModel + assert Base.get_registered_type(FakeModel.speckle_type) == FakeModel + assert Base.get_registered_type(FakeSub.speckle_type) == FakeSub assert Base.get_registered_type("🐺️") is None @@ -150,6 +153,7 @@ def test_type_checking() -> None: order.flavours = ["strawberry", "lychee", "peach", "pineapple"] assert order.price == 7.0 + assert order.dietary == DietaryRestrictions.VEGAN def test_cached_deserialization() -> None: diff --git a/tests/test_branch.py b/tests/test_branch.py index 46ddf36..27bf8ba 100644 --- a/tests/test_branch.py +++ b/tests/test_branch.py @@ -1,7 +1,8 @@ import pytest + from specklepy.api import operations -from specklepy.transports.server import ServerTransport from specklepy.api.models import Branch, Commit, Stream +from specklepy.transports.server import ServerTransport class TestBranch: diff --git a/tests/test_client_and_ops.py b/tests/test_client_and_ops.py index ed47a6f..a534a5d 100644 --- a/tests/test_client_and_ops.py +++ b/tests/test_client_and_ops.py @@ -1,10 +1,11 @@ import pytest + from specklepy.api import operations from specklepy.api.client import SpeckleClient -from specklepy.objects.base import Base -from specklepy.transports.server import ServerTransport from specklepy.api.credentials import Account, get_account_from_token from specklepy.logging.exceptions import SpeckleException, SpeckleWarning +from specklepy.objects.base import Base +from specklepy.transports.server import ServerTransport def test_invalid_authentication(): diff --git a/tests/test_commit.py b/tests/test_commit.py index 0443315..f9347e2 100644 --- a/tests/test_commit.py +++ b/tests/test_commit.py @@ -1,4 +1,5 @@ import pytest + from specklepy.api import operations from specklepy.api.models import Commit, Stream from specklepy.transports.server.server import ServerTransport diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 8d14959..9c63cfd 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -2,10 +2,10 @@ import json import pytest + from specklepy.api import operations from specklepy.logging.exceptions import SpeckleException from specklepy.objects.base import Base -from specklepy.objects.units import Units from specklepy.objects.encoding import CurveArray, ObjectArray from specklepy.objects.geometry import ( Arc, @@ -30,6 +30,7 @@ from specklepy.objects.geometry import ( Surface, Vector, ) +from specklepy.objects.units import Units from specklepy.transports.memory import MemoryTransport diff --git a/tests/test_host_applications.py b/tests/test_host_applications.py index 0834c5b..de65bd3 100644 --- a/tests/test_host_applications.py +++ b/tests/test_host_applications.py @@ -1,7 +1,8 @@ import pytest + from specklepy.api.host_applications import ( - get_host_app_from_string, _app_name_host_app_mapping, + get_host_app_from_string, ) diff --git a/tests/test_objects.py b/tests/test_objects.py index 1018b71..f921ee6 100644 --- a/tests/test_objects.py +++ b/tests/test_objects.py @@ -1,4 +1,5 @@ import pytest + from specklepy.api.models import Stream from specklepy.objects import Base from specklepy.objects.encoding import ObjectArray diff --git a/tests/test_other_user.py b/tests/test_other_user.py index 92b0e83..bd17147 100644 --- a/tests/test_other_user.py +++ b/tests/test_other_user.py @@ -1,4 +1,5 @@ import pytest + from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, LimitedUser from specklepy.logging.exceptions import SpeckleException diff --git a/tests/test_registering_base.py b/tests/test_registering_base.py new file mode 100644 index 0000000..b3f3055 --- /dev/null +++ b/tests/test_registering_base.py @@ -0,0 +1,35 @@ +from typing import Type + +import pytest + +from specklepy.objects.base import Base +from specklepy.objects.structural import Concrete + + +class Foo(Base): + """This is a Foo inheriting from Base.""" + + +class Bar(Foo, speckle_type="Custom.Bar"): + """This is a Bar inheriting from Foo.""" + + +class Baz(Bar): + """This is a Bar inheriting from Foo.""" + + +@pytest.mark.parametrize( + "klass, speckle_type", + [ + (Base, "Base"), + (Foo, "Foo"), + (Bar, "Foo:Custom.Bar"), + (Baz, "Foo:Custom.Bar:Baz"), + ( + Concrete, + "Objects.Structural.Materials.StructuralMaterial:Objects.Structural.Materials.Concrete", + ), + ], +) +def test_determine_speckle_type(klass: Type[Base], speckle_type: str): + assert klass._determine_speckle_type() == speckle_type diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 5397b69..474bc70 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -1,12 +1,14 @@ import json + import pytest + from specklepy.api import operations -from specklepy.transports.server import ServerTransport -from specklepy.transports.memory import MemoryTransport -from specklepy.serialization.base_object_serializer import BaseObjectSerializer from specklepy.objects import Base -from specklepy.objects.geometry import Point from specklepy.objects.fakemesh import FakeMesh +from specklepy.objects.geometry import Point +from specklepy.serialization.base_object_serializer import BaseObjectSerializer +from specklepy.transports.memory import MemoryTransport +from specklepy.transports.server import ServerTransport @pytest.mark.run(order=5) diff --git a/tests/test_server.py b/tests/test_server.py index 5e0707c..884ef33 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,6 +1,7 @@ import pytest -from specklepy.api.models import ServerInfo + from specklepy.api.client import SpeckleClient +from specklepy.api.models import ServerInfo class TestServer: diff --git a/tests/test_stream.py b/tests/test_stream.py index 465072f..3111a6b 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -1,13 +1,15 @@ -import pytest from datetime import datetime + +import pytest + +from specklepy.api.client import SpeckleClient from specklepy.api.models import ( - ActivityCollection, Activity, + ActivityCollection, PendingStreamCollaborator, Stream, User, ) -from specklepy.api.client import SpeckleClient from specklepy.logging.exceptions import ( GraphQLException, SpeckleException, diff --git a/tests/test_structural.py b/tests/test_structural.py index 9c5f0be..95ea1b0 100644 --- a/tests/test_structural.py +++ b/tests/test_structural.py @@ -1,39 +1,24 @@ -import json -from typing import Callable - import pytest -from specklepy.api import operations -from specklepy.logging.exceptions import SpeckleException -from specklepy.objects.base import Base -from specklepy.objects.encoding import CurveArray, ObjectArray -from specklepy.objects.geometry import ( - Line, - Mesh, - Point, - Vector, -) -from specklepy.transports.memory import MemoryTransport + +from specklepy.objects.geometry import Line, Mesh, Point, Vector +from specklepy.objects.structural.analysis import Model from specklepy.objects.structural.geometry import ( - Node, Element1D, Element2D, - Restraint, ElementType1D, ElementType2D, + Node, + Restraint, ) +from specklepy.objects.structural.loading import LoadGravity +from specklepy.objects.structural.material import StructuralMaterial from specklepy.objects.structural.properties import ( + MemberType, Property1D, Property2D, SectionProfile, - MemberType, ShapeType, ) -from specklepy.objects.structural.material import ( - Material, -) -from specklepy.objects.structural.analysis import Model - -from specklepy.objects.structural.loading import LoadGravity @pytest.fixture() @@ -82,7 +67,7 @@ def node(restraint, point): @pytest.fixture() def material(): - return Material(name="TestMaterial") + return StructuralMaterial(name="TestMaterial") @pytest.fixture() diff --git a/tests/test_transforms.py b/tests/test_transforms.py index e276224..2e8c878 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,12 +1,14 @@ from typing import List + import pytest + from specklepy.api import operations from specklepy.objects.geometry import Point, Vector from specklepy.objects.other import ( - Transform, - BlockInstance, - BlockDefinition, IDENTITY_TRANSFORM, + BlockDefinition, + BlockInstance, + Transform, ) diff --git a/tests/test_type_validation.py b/tests/test_type_validation.py new file mode 100644 index 0000000..52581b5 --- /dev/null +++ b/tests/test_type_validation.py @@ -0,0 +1,88 @@ +from enum import Enum, IntEnum +from typing import Any, Dict, List, Optional, Tuple + +import pytest + +from specklepy.objects.base import Base, _validate_type +from specklepy.objects.primitive import Interval + +test_base = Base() + + +class FakeEnum(Enum): + foo = "foo" + bar = "bar" + + +class FakeIntEnum(IntEnum): + one = 1 + + +@pytest.mark.parametrize( + "input_type, value, is_valid, return_value", + [ + (str, 10, True, "10"), + (str, "foo_bar", True, "foo_bar"), + ( + str, + {"foo": "bar"}, + True, + "{'foo': 'bar'}", + ), + (float, 1, True, 1), + # why are we allowing this??? We're lying to our users and ourselves too. + (str, None, True, None), + (bool, None, True, None), + # any value is allowed for Any + (Any, "foo", True, "foo"), + (Any, test_base, True, test_base), + # any value is allowed for None as a type. Why is none as a type allowed? + (None, True, True, True), + (None, "True", True, "True"), + (None, {"foo": 1}, True, {"foo": 1}), + (FakeEnum, FakeEnum.bar, True, FakeEnum.bar), + (FakeEnum, FakeEnum.bar.value, True, FakeEnum.bar), + (FakeEnum, "baz", False, "baz"), + (FakeIntEnum, FakeIntEnum.one.value, True, FakeIntEnum.one), + (FakeIntEnum, FakeIntEnum.one, True, FakeIntEnum.one), + (FakeIntEnum, 2, False, 2), + (FakeIntEnum, 123.0, False, 123.0), + (Base, test_base, True, test_base), + (Base, 123, False, 123), + (Optional[int], 1, True, 1), + # this is just silly... + (Optional[int], [1, 2, 3], False, [1, 2, 3]), + (Optional[int], None, True, None), + (Optional[FakeEnum], None, True, None), + (Optional[FakeEnum], FakeEnum.bar, True, FakeEnum.bar), + (Optional[FakeEnum], FakeEnum.bar.value, True, FakeEnum.bar), + (Optional[FakeEnum], "baz", False, "baz"), + (Optional[Base], test_base, True, test_base), + (Optional[Base], None, True, None), + (List[int], [1, 2], True, [1, 2]), + (List[int], ["1", 2], False, ["1", 2]), + # same as the dict typing below... + (List[int], [None, 2], True, [None, 2]), + (List[Optional[int]], [None, 2], True, [None, 2]), + (Dict[str, int], {"foo": 1}, True, {"foo": 1}), + (Dict[str, Optional[int]], {"foo": None}, True, {"foo": None}), + # this case should be + # (Dict[int, Base], {1: None}, False, {1: None}), + # but type checking currently allows everything to be None + (Dict[int, Base], {1: None}, True, {1: None}), + (Dict[int, Base], {1: test_base}, True, {1: test_base}), + (Tuple[int, str, str], (1, "foo", "bar"), True, (1, "foo", "bar")), + # given our current rules, this is the reality. Its just sad... + (Tuple[str, str, str], (1, "foo", "bar"), True, ("1", "foo", "bar")), + (Tuple[str, Optional[str], str], (1, None, "bar"), True, ("1", None, "bar")), + ], +) +def test_validate_type( + input_type: type, value: Any, is_valid: bool, return_value: Any +) -> None: + assert (is_valid, return_value) == _validate_type(input_type, value) + + +def test_intervar_type(): + i = Interval(start=5, end=10) + assert i diff --git a/tests/test_user.py b/tests/test_user.py index 98a8430..5885985 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,4 +1,5 @@ import pytest + from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, User from specklepy.logging.exceptions import SpeckleException @@ -18,7 +19,9 @@ class TestUser: def test_user_search(self, client, second_user_dict): with pytest.deprecated_call(): - search_results = client.user.search(search_query=second_user_dict["name"][:5]) + search_results = client.user.search( + search_query=second_user_dict["name"][:5] + ) assert isinstance(search_results, list) assert isinstance(search_results[0], User) @@ -49,7 +52,6 @@ class TestUser: updated = client.user.update(bio=bio) assert updated is True - with pytest.deprecated_call(): me = client.user.get() assert me.bio == bio diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py index a818f3c..5099110 100644 --- a/tests/test_wrapper.py +++ b/tests/test_wrapper.py @@ -1,10 +1,12 @@ import json -from specklepy.api.wrapper import StreamWrapper -from specklepy.transports.sqlite import SQLiteTransport -from specklepy.paths import accounts_path from pathlib import Path + import pytest +from specklepy.api.wrapper import StreamWrapper +from specklepy.paths import accounts_path +from specklepy.transports.sqlite import SQLiteTransport + def test_parse_stream(): wrap = StreamWrapper("https://testing.speckle.dev/streams/a75ab4f10f")