Merge pull request #17 from specklesystems/izzy/chunking

🍰 Chunking of long lists
This commit is contained in:
izzy lyseggen
2020-12-24 11:45:06 +00:00
committed by GitHub
6 changed files with 140 additions and 20 deletions
+14 -2
View File
@@ -66,7 +66,7 @@ def receive(
# try local transport first. if the parent is there, we assume all the children are there and continue wth deserialisation using the local transport
obj_string = local_transport.get_object(obj_id)
if obj_string:
base = serializer.read_json(id=obj_id, obj_string=obj_string)
base = serializer.read_json(obj_string=obj_string)
return base
if not remote_transport:
@@ -78,4 +78,16 @@ def receive(
id=obj_id, target_transport=local_transport
)
return serializer.read_json(id=obj_id, obj_string=obj_string)
return serializer.read_json(obj_string=obj_string)
def serialize(base: Base) -> str:
serializer = BaseObjectSerializer()
return serializer.write_json(base)[1]
def deserialize(obj_string: str) -> Base:
serializer = BaseObjectSerializer()
return serializer.read_json(obj_string=obj_string)
+14
View File
@@ -0,0 +1,14 @@
from pathlib import Path
import sys
import inspect
import pkgutil
from importlib import import_module
from .base import Base
for (_, name, _) in pkgutil.iter_modules([Path(__file__).parent]):
imported_module = import_module("." + name, package=__name__)
classes = inspect.getmembers(imported_module, inspect.isclass)
for c in classes:
if issubclass(c[1], Base):
setattr(sys.modules[__name__], c[0], c[1])
+28 -5
View File
@@ -1,10 +1,11 @@
from __future__ import annotations
from speckle.logging.exceptions import SpeckleException
from typing import Dict, List, Optional, Any
from pydantic import BaseModel
from pydantic.main import Extra
from typing import Dict, List, Optional, Any
from speckle.transports.memory import MemoryTransport
from speckle.logging.exceptions import SpeckleException
PRIMITIVES = (int, float, str, bool)
@@ -14,13 +15,18 @@ class Base(BaseModel):
totalChildrenCount: Optional[int] = None
applicationId: Optional[str] = None
speckle_type: Optional[str] = "Base"
_chunkable: Dict[str, int] = {} # dict of chunkable props and their max chunk size
def __init__(self, **kwargs) -> None:
super().__init__()
self.speckle_type = self.__class__.__name__
self.__dict__.update(kwargs)
def __repr__(self) -> str:
return f"{self.__class__.__name__}(id: {self.id}, speckle_type: {self.speckle_type}, totalChildrenCount: {self.totalChildrenCount})"
def __str__(self) -> str:
return f"Base(id: {self.id}, speckle_type: {self.speckle_type}, totalChildrenCount: {self.totalChildrenCount})"
return self.__repr__()
def __setitem__(self, name: str, value: Any) -> None:
self.__dict__[name] = value
@@ -74,6 +80,19 @@ class Base(BaseModel):
parsed = []
return 1 + self._count_descendants(self, parsed)
def get_id(self, decompose: bool = False) -> str:
if self.id and not decompose:
return self.id
else:
from speckle.serialization.base_object_serializer import (
BaseObjectSerializer,
)
serializer = BaseObjectSerializer()
if decompose:
serializer.write_transports = [MemoryTransport()]
return serializer.traverse_base(self)[0]
def _count_descendants(self, base: Base, parsed: List) -> int:
if base in parsed:
return 0
@@ -114,4 +133,8 @@ class Base(BaseModel):
return count
class Config:
extra = Extra.allow
extra = Extra.allow
class DataChunk(Base):
data: List[Any] = []
+24
View File
@@ -0,0 +1,24 @@
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from .base import Base
CHUNKABLE_PROPS = {
"vertices": 2000,
"faces": 2000,
"colors": 2000,
"textureCoordinates": 2000,
}
class Mesh(Base):
vertices: List[float] = None
faces: List[int] = None
colors: List[int] = None
textureCoordinates: List[float] = None
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self._chunkable.update(CHUNKABLE_PROPS)
+59 -12
View File
@@ -1,9 +1,10 @@
import json
import hashlib
from speckle import objects
from uuid import uuid4
from typing import Any, Dict, List, Tuple
from speckle.objects.base import Base
from speckle.objects.base import Base, DataChunk
from speckle.logging.exceptions import SerializationException, SpeckleException
from speckle.transports.abstract_transport import AbstractTransport
@@ -54,11 +55,16 @@ class BaseObjectSerializer:
prop = props.pop(0)
value = obj[prop]
# skip nulls or props marked to be ignored with "__"
if not value or prop.startswith("__"):
# skip nulls or props marked to be ignored with "__" or "_"
if not value or prop.startswith(("__", "_")):
continue
detach = True if prop.startswith("@") else False
# don't prepopulate id as this will mess up hashing
if prop == "id":
continue
chunkable = True if prop in base._chunkable else False
detach = True if prop.startswith("@") or chunkable else False
# 1. handle primitives (ints, floats, strings, and bools)
if isinstance(value, PRIMITIVES):
@@ -68,13 +74,33 @@ class BaseObjectSerializer:
# 2. handle Base objects
elif isinstance(value, Base):
child_obj = self.traverse_value(value, detach=detach)
if detach:
if detach and self.write_transports:
ref_hash = child_obj["id"]
object_builder[prop] = self.detach_helper(ref_hash=ref_hash)
else:
object_builder[prop] = child_obj
# 3. handle all other cases
# 3. handle chunkable props
elif chunkable and self.write_transports:
chunks = []
max_size = base._chunkable[prop]
chunk = DataChunk()
for count, item in enumerate(value):
if count and count % max_size == 0:
chunks.append(chunk)
chunk = DataChunk()
chunk.data.append(item)
chunks.append(chunk)
chunk_refs = []
for c in chunks:
self.detach_lineage.append(detach)
ref_hash, _ = self.traverse_base(c)
ref_obj = self.detach_helper(ref_hash=ref_hash)
chunk_refs.append(ref_obj)
object_builder[prop] = chunk_refs
# 4. handle all other cases
else:
child_obj = self.traverse_value(value)
object_builder[prop] = child_obj
@@ -92,7 +118,7 @@ class BaseObjectSerializer:
}
# write detached or root objects to transports
if detached:
if detached and self.write_transports:
for t in self.write_transports:
t.save_object(id=hash, serialized_object=json.dumps(object_builder))
@@ -168,11 +194,10 @@ class BaseObjectSerializer:
self.family_tree = {}
self.closure_table = {}
def read_json(self, id: str, obj_string: str) -> Base:
def read_json(self, obj_string: str) -> Base:
"""Recomposes a Base object from the string representation of the object
Arguments:
id {str} -- the hash of the object
obj_string {str} -- the string representation of the object
Returns:
@@ -199,9 +224,11 @@ class BaseObjectSerializer:
return
if isinstance(obj, str):
obj = json.loads(obj)
if obj["speckle_type"] == "reference":
obj = self.get_child(obj=obj)
# initialise the base object
base = Base()
# initialise the base object using `speckle_type`
base = getattr(objects, obj["speckle_type"], Base)()
# get total children count
if "__closure" in obj:
@@ -247,12 +274,23 @@ class BaseObjectSerializer:
if isinstance(obj, PRIMITIVES):
return obj
# lists (regular and chunked)
if isinstance(obj, list):
return [self.handle_value(o) for o in obj]
obj_list = [self.handle_value(o) for o in obj]
# handle chunked lists
if isinstance(obj_list[0], DataChunk):
data = []
for o in obj_list:
data.extend(o["data"])
return data
else:
return obj_list
# bases
if isinstance(obj, dict) and "speckle_type" in obj:
return self.recompose_base(obj=obj)
# dictionaries
if isinstance(obj, dict):
for k, v in obj.items():
if isinstance(v, PRIMITIVES):
@@ -260,3 +298,12 @@ class BaseObjectSerializer:
else:
obj[k] = self.handle_value(v)
return obj
def get_child(self, obj: Dict):
ref_hash = obj["referencedId"]
ref_obj_str = self.read_transport.get_object(id=ref_hash)
if not ref_obj_str:
raise SpeckleException(
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
)
return json.loads(ref_obj_str)
+1 -1
View File
@@ -29,7 +29,7 @@ class MemoryTransport(AbstractTransport):
def get_object(self, id: str) -> str or None:
if id in self.objects:
return json.dumps(self.objects[id])
return self.objects[id]
else:
return None