Compare commits

...

6 Commits

Author SHA1 Message Date
izzy lyseggen b1c149382a Merge pull request #128 from specklesystems/izzy/cereal-fixes
fix(deserialisation): type check bug and brep encoding hotfix
2021-10-14 17:09:27 +02:00
izzy lyseggen 393e98c8c2 fix(encoding): add none unit type 2021-10-14 16:07:34 +01:00
izzy lyseggen 8376329cbb fix(base): type check error with optional generics
reported by rob on the forum:
https://speckle.community/t/issue-with-type-checking-in-pyhton/1861
2021-10-14 15:41:30 +01:00
izzy lyseggen 1567fe9e68 fix(breps): temp hotfix for curve encoding fail
addresses 🥒 Bug with brep receiving (curve encoding) #127
2021-10-14 15:38:51 +01:00
izzy lyseggen 364b826a1b Merge pull request #126 from specklesystems/izzy/metrics-hotfix
fix(metrics): typo in tracking send
2021-10-13 11:28:52 +02:00
izzy lyseggen 297dbab479 fix(metrics): typo in tracking send
i'm a dumdum
2021-10-13 10:27:58 +01:00
5 changed files with 73 additions and 52 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ def send(
Returns:
str -- the object id of the sent object
"""
metrics.track(metrics.RECEIVE)
metrics.track(metrics.SEND)
if not transports and not use_default_cache:
raise SpeckleException(
message="You need to provide at least one transport: cannot send with an empty transport list and no default cache"
+33 -20
View File
@@ -1,6 +1,15 @@
import typing
from typing import (Any, Callable, ClassVar, Dict, List, Optional, Set, Type,
get_type_hints)
from typing import (
Any,
Callable,
ClassVar,
Dict,
List,
Optional,
Set,
Type,
get_type_hints,
)
from warnings import warn
from specklepy.logging.exceptions import SpeckleException
@@ -118,14 +127,12 @@ class _RegisteringBase:
except Exception:
cls._attr_types = getattr(cls, "__annotations__", {})
if chunkable:
chunkable = {k: v for k, v in chunkable.items()
if isinstance(v, int)}
chunkable = {k: v for k, v in chunkable.items() if isinstance(v, int)}
cls._chunkable = dict(cls._chunkable, **chunkable)
if detachable:
cls._detachable = cls._detachable.union(detachable)
if serialize_ignore:
cls._serialize_ignore = cls._serialize_ignore.union(
serialize_ignore)
cls._serialize_ignore = cls._serialize_ignore.union(serialize_ignore)
super().__init_subclass__(**kwargs)
@@ -215,15 +222,13 @@ class Base(_RegisteringBase):
try:
cls._attr_types = get_type_hints(cls)
except Exception as e:
warn(
f"Could not update forward refs for class {cls.__name__}: {e}")
warn(f"Could not update forward refs for class {cls.__name__}: {e}")
@classmethod
def validate_prop_name(cls, name: str) -> None:
"""Validator for dynamic attribute names."""
if name in {"", "@"}:
raise ValueError(
"Invalid Name: Base member names cannot be empty strings")
raise ValueError("Invalid Name: Base member names cannot be empty strings")
if name.startswith("@@"):
raise ValueError(
"Invalid Name: Base member names cannot start with more than one '@'",
@@ -249,7 +254,12 @@ class Base(_RegisteringBase):
if t.__module__ == "typing":
origin = getattr(t, "__origin__")
t = t.__args__ if origin is typing.Union else origin
t = (
tuple(getattr(sub_t, "__origin__", sub_t) for sub_t in t.__args__)
if origin is typing.Union
else origin
)
if not isinstance(t, (type, tuple)):
warn(
f"Unrecognised type '{t}' provided for attribute '{name}'. Type will not been validated."
@@ -260,13 +270,16 @@ class Base(_RegisteringBase):
# to be friendly, we'll parse ints and strs into floats, but not the other way around
# (to avoid unexpected rounding)
if t is float and isinstance(value, (int, str, float)):
try:
if isinstance(t, tuple):
t = t[0]
try:
if t is float:
return float(value)
except ValueError:
pass
if t is str and value is not None:
return str(value)
if t is str and value:
return str(value)
except ValueError:
pass
raise SpeckleException(
f"Cannot set '{self.__class__.__name__}.{name}': it expects type '{t.__name__}', but received type '{type(value).__name__}'"
@@ -327,7 +340,8 @@ class Base(_RegisteringBase):
def get_id(self, decompose: bool = False) -> str:
"""
Gets the id (a unique hash) of this object. ⚠️ This method fully serializes the object, which in the case of large objects (with many sub-objects), has a tangible cost. Avoid using it!
Gets the id (a unique hash) of this object. ⚠️ This method fully serializes the object which,
in the case of large objects (with many sub-objects), has a tangible cost. Avoid using it!
Note: the hash of a decomposed object differs from that of a non-decomposed object
@@ -337,8 +351,7 @@ class Base(_RegisteringBase):
Returns:
str -- the hash (id) of the fully serialized object
"""
from specklepy.serialization.base_object_serializer import \
BaseObjectSerializer
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
serializer = BaseObjectSerializer()
if decompose:
+35 -26
View File
@@ -17,6 +17,7 @@ class CurveTypeEncoding(int, Enum):
@property
def object_class(self) -> Type:
from . import geometry
if self == self.Arc:
return geometry.Arc
elif self == self.Circle:
@@ -32,7 +33,8 @@ class CurveTypeEncoding(int, Enum):
elif self == self.Polycurve:
return geometry.Polycurve
raise SpeckleException(
f'No corresponding object class for CurveTypeEncoding: {self}')
f"No corresponding object class for CurveTypeEncoding: {self}"
)
def curve_from_list(args: List[float]):
@@ -41,61 +43,68 @@ def curve_from_list(args: List[float]):
class ObjectArray:
def __init__(self) -> None:
self.data = []
@classmethod
def from_objects(cls, objects: List[Base]) -> 'ObjectArray':
data_chunk = cls()
if len(objects) == 0:
return data_chunk
def from_objects(cls, objects: List[Base]) -> "ObjectArray":
data_list = cls()
if not objects:
return data_list
speckle_type = objects[0].speckle_type
for obj in objects:
if speckle_type != obj.speckle_type:
raise SpeckleException(
'All objects in chunk should have the same speckle_type. '
f'Found {speckle_type} and {obj.speckle_type}'
"All objects in chunk should have the same speckle_type. "
f"Found {speckle_type} and {obj.speckle_type}"
)
data_chunk.encode_object(object=obj)
data_list.encode_object(object=obj)
return data_chunk
return data_list
@staticmethod
def decode_data(data: List[Any], decoder: Callable[[List[Any]], Base]) -> List[Base]:
def decode_data(
data: List[Any], decoder: Callable[[List[Any]], Base]
) -> List[Base]:
bases = []
if not data:
return bases
index = 0
unchunked_data = []
while index < len(data):
chunk_length = data[index]
chunk_start = int(index + 1)
chunk_end = int(chunk_start + chunk_length)
chunk_data = data[chunk_start:chunk_end]
decoded_data = decoder(chunk_data)
unchunked_data.append(decoded_data)
index = chunk_end
return unchunked_data
item_length = data[index]
item_start = index + 1
item_end = item_start + item_length
item_data = data[item_start:item_end]
index = item_end
# TODO: investigate what's going on w this fail
try:
decoded_data = decoder(item_data)
bases.append(decoded_data)
except ValueError:
continue
return bases
def decode(self, decoder: Callable[[List[Any]], Any]):
return self.decode_data(data=self.data, decoder=decoder)
def encode_object(self, object: Base):
chunk = object.to_list()
chunk.insert(0, len(chunk))
self.data.extend(chunk)
encoded = object.to_list()
encoded.insert(0, len(encoded))
self.data.extend(encoded)
class CurveArray(ObjectArray):
@classmethod
def from_curve(cls, curve: Base) -> 'CurveArray':
def from_curve(cls, curve: Base) -> "CurveArray":
crv_array = cls()
crv_array.data = curve.to_list()
return crv_array
@classmethod
def from_curves(cls, curves: List[Base]) -> 'CurveArray':
def from_curves(cls, curves: List[Base]) -> "CurveArray":
data = []
for curve in curves:
curve_list = curve.to_list()
+1
View File
@@ -15,6 +15,7 @@ UNITS_STRINGS = {
}
UNITS_ENCODINGS = {
"none": 0,
"mm": 1,
"cm": 2,
"m": 3,
+3 -5
View File
@@ -1,5 +1,5 @@
from contextlib import ExitStack as does_not_raise
from typing import Dict, List
from typing import Dict, List, Optional
import pytest
from specklepy.api import operations
@@ -87,9 +87,9 @@ class FrozenYoghurt(Base):
"""Testing type checking"""
servings: int
flavours: List[str] = None # list item types won't be checked
flavours: List[str] # list item types won't be checked
customer: str
add_ons: Dict[str, float] # dict item types won't be checked
add_ons: Optional[Dict[str, float]] # dict item types won't be checked
price: float = 0.0
@@ -111,5 +111,3 @@ def test_type_checking() -> None:
order.flavours = ["strawberry", "lychee", "peach", "pineapple"]
assert order.price == 7.0