Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69cd9706cf | |||
| 98075fa2cf | |||
| 782f70fb49 | |||
| 52ab27e60f |
@@ -1,16 +1,16 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
|
|
||||||
orbs:
|
orbs:
|
||||||
python: circleci/python@1.3.2
|
python: circleci/python@2.0.3
|
||||||
codecov: codecov/codecov@3.2.2
|
codecov: codecov/codecov@3.2.2
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
docker:
|
docker:
|
||||||
- image: "cimg/python:<<parameters.tag>>"
|
- image: "cimg/python:<<parameters.tag>>"
|
||||||
- image: 'cimg/node:14.18'
|
- image: "cimg/node:16.15"
|
||||||
- image: 'circleci/redis:6'
|
- image: "cimg/redis:6.2"
|
||||||
- image: 'cimg/postgres:12.8'
|
- image: "cimg/postgres:14.2"
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: speckle2_test
|
POSTGRES_DB: speckle2_test
|
||||||
POSTGRES_PASSWORD: speckle
|
POSTGRES_PASSWORD: speckle
|
||||||
@@ -27,6 +27,7 @@ jobs:
|
|||||||
STRATEGY_LOCAL: "true"
|
STRATEGY_LOCAL: "true"
|
||||||
CANONICAL_URL: "http://localhost:3000"
|
CANONICAL_URL: "http://localhost:3000"
|
||||||
WAIT_HOSTS: localhost:5432, localhost:6379
|
WAIT_HOSTS: localhost:5432, localhost:6379
|
||||||
|
DISABLE_FILE_UPLOADS: "true"
|
||||||
parameters:
|
parameters:
|
||||||
tag:
|
tag:
|
||||||
default: "3.8"
|
default: "3.8"
|
||||||
@@ -51,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
docker:
|
docker:
|
||||||
- image: "circleci/python:3.8"
|
- image: "cimg/python:3.8"
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: python patch_version.py $CIRCLE_TAG
|
- run: python patch_version.py $CIRCLE_TAG
|
||||||
@@ -60,17 +61,17 @@ jobs:
|
|||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
main:
|
main:
|
||||||
jobs:
|
jobs:
|
||||||
- test:
|
- test:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
tag: ["3.6", "3.7", "3.8", "3.9"]
|
tag: ["3.7", "3.8", "3.9", "3.10"]
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /.*/
|
only: /.*/
|
||||||
- deploy:
|
- deploy:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- test
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /[0-9]+(\.[0-9]+)*/
|
only: /[0-9]+(\.[0-9]+)*/
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version: "3.3" # optional since v1.27.0
|
version: "3.3" # optional since v1.27.0
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: circleci/postgres:12
|
image: cimg/postgres:14.2
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: speckle2_test
|
POSTGRES_DB: speckle2_test
|
||||||
POSTGRES_PASSWORD: speckle
|
POSTGRES_PASSWORD: speckle
|
||||||
@@ -10,7 +10,7 @@ services:
|
|||||||
# - "5432:5432"
|
# - "5432:5432"
|
||||||
network_mode: host
|
network_mode: host
|
||||||
redis:
|
redis:
|
||||||
image: circleci/redis:6
|
image: cimg/redis:6.2
|
||||||
# ports:
|
# ports:
|
||||||
# - "6379:6379"
|
# - "6379:6379"
|
||||||
network_mode: host
|
network_mode: host
|
||||||
@@ -27,6 +27,7 @@ services:
|
|||||||
STRATEGY_LOCAL: "true"
|
STRATEGY_LOCAL: "true"
|
||||||
CANONICAL_URL: "http://localhost:3000"
|
CANONICAL_URL: "http://localhost:3000"
|
||||||
WAIT_HOSTS: localhost:5432, localhost:6379
|
WAIT_HOSTS: localhost:5432, localhost:6379
|
||||||
|
DISABLE_FILE_UPLOADS: "true"
|
||||||
# ports:
|
# ports:
|
||||||
# - "3000:3000"
|
# - "3000:3000"
|
||||||
network_mode: host
|
network_mode: host
|
||||||
|
|||||||
Vendored
+6
-2
@@ -4,6 +4,7 @@
|
|||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Python: Current File",
|
"name": "Python: Current File",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
@@ -13,10 +14,13 @@
|
|||||||
"justMyCode": false
|
"justMyCode": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Python: Test debug config",
|
"name": "Pytest",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "test",
|
"request": "launch",
|
||||||
|
"program": "poetry",
|
||||||
|
"args": ["run", "pytest"],
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
from typing import List
|
||||||
|
from specklepy.objects import Base
|
||||||
|
from specklepy.api import operations
|
||||||
|
from specklepy.transports.sqlite import SQLiteTransport
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
class Sub(Base):
|
||||||
|
bar: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
def random_string():
|
||||||
|
letters = string.ascii_lowercase
|
||||||
|
return "".join(random.choice(letters) for _ in range(10))
|
||||||
|
|
||||||
|
|
||||||
|
BASE_PATH = SQLiteTransport.get_base_path("Speckle")
|
||||||
|
|
||||||
|
|
||||||
|
def clean_db():
|
||||||
|
os.remove(Path(BASE_PATH, "Objects.db"))
|
||||||
|
|
||||||
|
|
||||||
|
def one_pass(clean: bool, randomize: bool, child_count: int):
|
||||||
|
|
||||||
|
foo = Base()
|
||||||
|
for i in range(child_count):
|
||||||
|
stuff = random_string() if randomize else "stuff"
|
||||||
|
foo[f"@child_{i}"] = Sub(bar=["asdf", "bar", i, stuff])
|
||||||
|
|
||||||
|
if clean:
|
||||||
|
clean_db()
|
||||||
|
transport = SQLiteTransport()
|
||||||
|
start = time.time()
|
||||||
|
hash = operations.send(base=foo, transports=[transport])
|
||||||
|
send_time = time.time() - start
|
||||||
|
|
||||||
|
receive_start = time.time()
|
||||||
|
operations.receive(hash, transport)
|
||||||
|
receive_time = time.time() - receive_start
|
||||||
|
return send_time, receive_time
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sample_size = 4
|
||||||
|
|
||||||
|
test_permutations = [
|
||||||
|
(True, True),
|
||||||
|
(False, False),
|
||||||
|
(False, True),
|
||||||
|
(True, False),
|
||||||
|
]
|
||||||
|
for clean, randomize in test_permutations:
|
||||||
|
print(f"CLEAN: {clean}, RANDOMIZE: {randomize}")
|
||||||
|
for child_count in [10, 100, 1000, 10000]:
|
||||||
|
print(f"\tCHILD COUNT: {child_count}")
|
||||||
|
for _ in range(sample_size):
|
||||||
|
send_time, receive_time = one_pass(clean, randomize, child_count)
|
||||||
|
print(f"\t\tSend: {send_time} Receive: {receive_time}")
|
||||||
@@ -50,10 +50,10 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
# support for dynamic attributes
|
# support for dynamic attributes
|
||||||
custom_sub.extra_extra = "what is this?"
|
custom_sub.extra_extra = "what is this?"
|
||||||
debug(custom_sub.json())
|
debug(custom_sub)
|
||||||
|
|
||||||
serialized = operations.serialize(custom_sub)
|
serialized = operations.serialize(custom_sub)
|
||||||
deserialized = operations.deserialize(serialized)
|
deserialized = operations.deserialize(serialized)
|
||||||
# the only difference should be between the two data is that the deserialized
|
# the only difference should be between the two data is that the deserialized
|
||||||
# instance id attribute is not None.
|
# instance id attribute is not None.
|
||||||
debug(deserialized.json())
|
debug(deserialized)
|
||||||
|
|||||||
Generated
+531
-667
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -11,11 +11,11 @@ homepage = "https://speckle.systems/"
|
|||||||
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.6.5"
|
python = ">=3.7.0, <4.0"
|
||||||
pydantic = "^1.8.2"
|
pydantic = "^1.8.2"
|
||||||
appdirs = "^1.4.4"
|
appdirs = "^1.4.4"
|
||||||
gql = {version = ">=3.0.0b1", extras = ["all"], allow-prereleases = true}
|
gql = {extras = ["requests", "websockets"], version = "^3.3.0"}
|
||||||
ujson = "^4.3.0"
|
ujson = "^5.3.0"
|
||||||
Deprecated = "^1.2.13"
|
Deprecated = "^1.2.13"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
@@ -24,6 +24,7 @@ isort = "^5.7.0"
|
|||||||
pytest = "^6.2.2"
|
pytest = "^6.2.2"
|
||||||
pytest-ordering = "^0.6"
|
pytest-ordering = "^0.6"
|
||||||
pytest-cov = "^3.0.0"
|
pytest-cov = "^3.0.0"
|
||||||
|
devtools = "^0.8.0"
|
||||||
|
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
|
|||||||
@@ -40,13 +40,8 @@ def send(
|
|||||||
|
|
||||||
serializer = BaseObjectSerializer(write_transports=transports)
|
serializer = BaseObjectSerializer(write_transports=transports)
|
||||||
|
|
||||||
for t in transports:
|
|
||||||
t.begin_write()
|
|
||||||
obj_hash, _ = serializer.write_json(base=base)
|
obj_hash, _ = serializer.write_json(base=base)
|
||||||
|
|
||||||
for t in transports:
|
|
||||||
t.end_write()
|
|
||||||
|
|
||||||
return obj_hash
|
return obj_hash
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ class Resource(ResourceBase):
|
|||||||
params = {"stream_id": stream_id, "object_id": object_id}
|
params = {"stream_id": stream_id, "object_id": object_id}
|
||||||
|
|
||||||
return self.make_request(
|
return self.make_request(
|
||||||
query=query, params=params, return_type=["stream", "object", "data"]
|
query=query,
|
||||||
|
params=params,
|
||||||
|
return_type=["stream", "object", "data"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, stream_id: str, objects: List[Dict]) -> str:
|
def create(self, stream_id: str, objects: List[Dict]) -> str:
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ class Singleton(type):
|
|||||||
class MetricsTracker(metaclass=Singleton):
|
class MetricsTracker(metaclass=Singleton):
|
||||||
analytics_url = "https://analytics.speckle.systems/track?ip=1"
|
analytics_url = "https://analytics.speckle.systems/track?ip=1"
|
||||||
analytics_token = "acd87c5a50b56df91a795e999812a3a4"
|
analytics_token = "acd87c5a50b56df91a795e999812a3a4"
|
||||||
user_ip = None
|
|
||||||
last_user = None
|
last_user = None
|
||||||
last_server = None
|
last_server = None
|
||||||
platform = None
|
platform = None
|
||||||
@@ -114,7 +113,6 @@ class MetricsTracker(metaclass=Singleton):
|
|||||||
)
|
)
|
||||||
self.platform = PLATFORMS.get(sys.platform, "linux")
|
self.platform = PLATFORMS.get(sys.platform, "linux")
|
||||||
self.sending_thread.start()
|
self.sending_thread.start()
|
||||||
self.user_ip = socket.gethostbyname(socket.gethostname())
|
|
||||||
|
|
||||||
def set_last_user(self, email: str):
|
def set_last_user(self, email: str):
|
||||||
if not email:
|
if not email:
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class Base(_RegisteringBase):
|
|||||||
id: Optional[str] = None
|
id: Optional[str] = None
|
||||||
totalChildrenCount: Optional[int] = None
|
totalChildrenCount: Optional[int] = None
|
||||||
applicationId: Optional[str] = None
|
applicationId: Optional[str] = None
|
||||||
_units: str = "m"
|
_units: str = None
|
||||||
# dict of chunkable props and their max chunk size
|
# dict of chunkable props and their max chunk size
|
||||||
_chunkable: Dict[str, int] = {}
|
_chunkable: Dict[str, int] = {}
|
||||||
_chunk_size_default: int = 1000
|
_chunk_size_default: int = 1000
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ UNITS_STRINGS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UNITS_ENCODINGS = {
|
UNITS_ENCODINGS = {
|
||||||
|
None: 0,
|
||||||
"none": 0,
|
"none": 0,
|
||||||
"mm": 1,
|
"mm": 1,
|
||||||
"cm": 2,
|
"cm": 2,
|
||||||
@@ -58,7 +59,5 @@ def get_units_from_encoding(unit: int):
|
|||||||
def get_encoding_from_units(unit: str):
|
def get_encoding_from_units(unit: str):
|
||||||
try:
|
try:
|
||||||
return UNITS_ENCODINGS[unit]
|
return UNITS_ENCODINGS[unit]
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
raise SpeckleException(
|
raise SpeckleException(message=f"No encoding exists for unit {unit}. Please enter a valid unit to encode (eg {UNITS_ENCODINGS}).") from e
|
||||||
message=f"No encoding exists for unit {unit}. Please enter a valid unit to encode (eg {UNITS_ENCODINGS})."
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
|
import re
|
||||||
import ujson
|
import ujson
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import warnings
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
from typing import Any, Dict, List, Tuple
|
from typing import Any, Dict, List, Tuple
|
||||||
from specklepy.objects.base import Base, DataChunk
|
from specklepy.objects.base import Base, DataChunk
|
||||||
from specklepy.logging.exceptions import (
|
from specklepy.logging.exceptions import (
|
||||||
SerializationException,
|
|
||||||
SpeckleException,
|
SpeckleException,
|
||||||
SpeckleWarning,
|
SpeckleWarning,
|
||||||
)
|
)
|
||||||
from specklepy.transports.abstract_transport import AbstractTransport
|
from specklepy.transports.abstract_transport import AbstractTransport
|
||||||
|
|
||||||
|
# import for serialization
|
||||||
import specklepy.objects.geometry
|
import specklepy.objects.geometry
|
||||||
import specklepy.objects.other
|
import specklepy.objects.other
|
||||||
|
|
||||||
@@ -50,9 +52,16 @@ class BaseObjectSerializer:
|
|||||||
self.read_transport = read_transport
|
self.read_transport = read_transport
|
||||||
|
|
||||||
def write_json(self, base: Base):
|
def write_json(self, base: Base):
|
||||||
self.__reset_writer()
|
"""Serializes a given base object into a json string
|
||||||
self.detach_lineage = [True]
|
Arguments:
|
||||||
|
base {Base} -- the base object to be decomposed and serialized
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(str, str) -- a tuple containing the hash (id) of the base object and the serialized object string
|
||||||
|
"""
|
||||||
|
|
||||||
hash, obj = self.traverse_base(base)
|
hash, obj = self.traverse_base(base)
|
||||||
|
|
||||||
return hash, ujson.dumps(obj)
|
return hash, ujson.dumps(obj)
|
||||||
|
|
||||||
def traverse_base(self, base: Base) -> Tuple[str, Dict]:
|
def traverse_base(self, base: Base) -> Tuple[str, Dict]:
|
||||||
@@ -64,6 +73,21 @@ class BaseObjectSerializer:
|
|||||||
Returns:
|
Returns:
|
||||||
(str, dict) -- a tuple containing the hash (id) of the base object and the constructed serializable dictionary
|
(str, dict) -- a tuple containing the hash (id) of the base object and the constructed serializable dictionary
|
||||||
"""
|
"""
|
||||||
|
self.__reset_writer()
|
||||||
|
|
||||||
|
if self.write_transports:
|
||||||
|
for wt in self.write_transports:
|
||||||
|
wt.begin_write()
|
||||||
|
|
||||||
|
hash, obj = self._traverse_base(base)
|
||||||
|
|
||||||
|
if self.write_transports:
|
||||||
|
for wt in self.write_transports:
|
||||||
|
wt.end_write()
|
||||||
|
|
||||||
|
return hash, obj
|
||||||
|
|
||||||
|
def _traverse_base(self, base: Base) -> Tuple[str, Dict]:
|
||||||
if not self.detach_lineage:
|
if not self.detach_lineage:
|
||||||
self.detach_lineage = [True]
|
self.detach_lineage = [True]
|
||||||
|
|
||||||
@@ -141,7 +165,7 @@ class BaseObjectSerializer:
|
|||||||
chunk_refs = []
|
chunk_refs = []
|
||||||
for c in chunks:
|
for c in chunks:
|
||||||
self.detach_lineage.append(detach)
|
self.detach_lineage.append(detach)
|
||||||
ref_hash, _ = self.traverse_base(c)
|
ref_hash, _ = self._traverse_base(c)
|
||||||
ref_obj = self.detach_helper(ref_hash=ref_hash)
|
ref_obj = self.detach_helper(ref_hash=ref_hash)
|
||||||
chunk_refs.append(ref_obj)
|
chunk_refs.append(ref_obj)
|
||||||
object_builder[prop] = chunk_refs
|
object_builder[prop] = chunk_refs
|
||||||
@@ -200,7 +224,7 @@ class BaseObjectSerializer:
|
|||||||
for o in obj:
|
for o in obj:
|
||||||
if isinstance(o, Base):
|
if isinstance(o, Base):
|
||||||
self.detach_lineage.append(detach)
|
self.detach_lineage.append(detach)
|
||||||
hash, _ = self.traverse_base(o)
|
hash, _ = self._traverse_base(o)
|
||||||
detached_list.append(self.detach_helper(ref_hash=hash))
|
detached_list.append(self.detach_helper(ref_hash=hash))
|
||||||
else:
|
else:
|
||||||
detached_list.append(self.traverse_value(o, detach))
|
detached_list.append(self.traverse_value(o, detach))
|
||||||
@@ -216,7 +240,7 @@ class BaseObjectSerializer:
|
|||||||
|
|
||||||
elif isinstance(obj, Base):
|
elif isinstance(obj, Base):
|
||||||
self.detach_lineage.append(detach)
|
self.detach_lineage.append(detach)
|
||||||
_, base_obj = self.traverse_base(obj)
|
_, base_obj = self._traverse_base(obj)
|
||||||
return base_obj
|
return base_obj
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -255,7 +279,7 @@ class BaseObjectSerializer:
|
|||||||
|
|
||||||
def __reset_writer(self) -> None:
|
def __reset_writer(self) -> None:
|
||||||
"""Reinitializes the lineage, and other variables that get used during the json writing process"""
|
"""Reinitializes the lineage, and other variables that get used during the json writing process"""
|
||||||
self.detach_lineage = []
|
self.detach_lineage = [True]
|
||||||
self.lineage = []
|
self.lineage = []
|
||||||
self.family_tree = {}
|
self.family_tree = {}
|
||||||
self.closure_table = {}
|
self.closure_table = {}
|
||||||
@@ -321,12 +345,15 @@ class BaseObjectSerializer:
|
|||||||
elif "referencedId" in value:
|
elif "referencedId" in value:
|
||||||
ref_hash = value["referencedId"]
|
ref_hash = value["referencedId"]
|
||||||
ref_obj_str = self.read_transport.get_object(id=ref_hash)
|
ref_obj_str = self.read_transport.get_object(id=ref_hash)
|
||||||
if not ref_obj_str:
|
if ref_obj_str:
|
||||||
raise SpeckleException(
|
ref_obj = safe_json_loads(ref_obj_str, ref_hash)
|
||||||
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
|
base.__setattr__(prop, self.recompose_base(obj=ref_obj))
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}",
|
||||||
|
SpeckleWarning,
|
||||||
)
|
)
|
||||||
ref_obj = safe_json_loads(ref_obj_str, ref_hash)
|
base.__setattr__(prop, self.handle_value(value))
|
||||||
base.__setattr__(prop, self.recompose_base(obj=ref_obj))
|
|
||||||
|
|
||||||
# 3. handle all other cases (base objects, lists, and dicts)
|
# 3. handle all other cases (base objects, lists, and dicts)
|
||||||
else:
|
else:
|
||||||
@@ -380,8 +407,10 @@ class BaseObjectSerializer:
|
|||||||
ref_hash = obj["referencedId"]
|
ref_hash = obj["referencedId"]
|
||||||
ref_obj_str = self.read_transport.get_object(id=ref_hash)
|
ref_obj_str = self.read_transport.get_object(id=ref_hash)
|
||||||
if not ref_obj_str:
|
if not ref_obj_str:
|
||||||
raise SpeckleException(
|
warnings.warn(
|
||||||
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
|
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}",
|
||||||
|
SpeckleWarning,
|
||||||
)
|
)
|
||||||
|
return obj
|
||||||
|
|
||||||
return safe_json_loads(ref_obj_str, ref_hash)
|
return safe_json_loads(ref_obj_str, ref_hash)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import sched
|
import sched
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from typing import Any, List, Dict
|
from typing import Any, List, Dict, Tuple
|
||||||
from appdirs import user_data_dir
|
from appdirs import user_data_dir
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
from specklepy.transports.abstract_transport import AbstractTransport
|
from specklepy.transports.abstract_transport import AbstractTransport
|
||||||
@@ -14,25 +14,29 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
_name = "SQLite"
|
_name = "SQLite"
|
||||||
_base_path: str = None
|
_base_path: str = None
|
||||||
_root_path: str = None
|
_root_path: str = None
|
||||||
_is_writing: bool = False
|
|
||||||
_scheduler = sched.scheduler(time.time, time.sleep)
|
|
||||||
_polling_interval = 0.5 # seconds
|
|
||||||
__connection: sqlite3.Connection = None
|
__connection: sqlite3.Connection = None
|
||||||
app_name: str = ""
|
app_name: str = ""
|
||||||
scope: str = ""
|
scope: str = ""
|
||||||
saved_obj_count: int = 0
|
saved_obj_count: int = 0
|
||||||
|
max_size: int = None
|
||||||
|
_current_batch: List[Tuple[str, str]] = None
|
||||||
|
_current_batch_size: int = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_path: str = None,
|
base_path: str = None,
|
||||||
app_name: str = None,
|
app_name: str = None,
|
||||||
scope: str = None,
|
scope: str = None,
|
||||||
|
max_batch_size_mb: float = 10.0,
|
||||||
**data: Any,
|
**data: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
self.app_name = app_name or "Speckle"
|
self.app_name = app_name or "Speckle"
|
||||||
self.scope = scope or "Objects"
|
self.scope = scope or "Objects"
|
||||||
self._base_path = base_path or self.get_base_path(self.app_name)
|
self._base_path = base_path or self.get_base_path(self.app_name)
|
||||||
|
self.max_size = int(max_batch_size_mb * 1000 * 1000)
|
||||||
|
self._current_batch = []
|
||||||
|
self._current_batch_size = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(self._base_path, exist_ok=True)
|
os.makedirs(self._base_path, exist_ok=True)
|
||||||
@@ -50,12 +54,6 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"SQLiteTransport(app: '{self.app_name}', scope: '{self.scope}')"
|
return f"SQLiteTransport(app: '{self.app_name}', scope: '{self.scope}')"
|
||||||
|
|
||||||
# def __write_timer_elapsed(self):
|
|
||||||
# print("WRITE TIMER ELAPSED")
|
|
||||||
# proc = Process(target=_run_queue, args=(self.__queue, self._root_path))
|
|
||||||
# proc.start()
|
|
||||||
# proc.join()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_base_path(app_name):
|
def get_base_path(app_name):
|
||||||
# from appdirs https://github.com/ActiveState/appdirs/blob/master/appdirs.py
|
# from appdirs https://github.com/ActiveState/appdirs/blob/master/appdirs.py
|
||||||
@@ -74,36 +72,6 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
path = os.path.expanduser("~/.config/")
|
path = os.path.expanduser("~/.config/")
|
||||||
return os.path.join(path, app_name)
|
return os.path.join(path, app_name)
|
||||||
|
|
||||||
# def __consume_queue(self):
|
|
||||||
# if self._is_writing or self.__queue.empty():
|
|
||||||
# return
|
|
||||||
# print("CONSUME QUEUE")
|
|
||||||
# self._is_writing = True
|
|
||||||
# while not self.__queue.empty():
|
|
||||||
# data = self.__queue.get()
|
|
||||||
# self.save_object(data[0], data[1])
|
|
||||||
# self._is_writing = False
|
|
||||||
|
|
||||||
# self._scheduler.enter(
|
|
||||||
# delay=self._polling_interval, priority=1, action=self.__consume_queue
|
|
||||||
# )
|
|
||||||
# self._scheduler.run(blocking=True)
|
|
||||||
|
|
||||||
# def save_object(self, id: str, serialized_object: str) -> None:
|
|
||||||
# """Adds an object to the queue and schedules it to be saved.
|
|
||||||
|
|
||||||
# Arguments:
|
|
||||||
# id {str} -- the object id
|
|
||||||
# serialized_object {str} -- the full string representation of the object
|
|
||||||
# """
|
|
||||||
# print("SAVE OBJECT")
|
|
||||||
# self.__queue.put((id, serialized_object))
|
|
||||||
|
|
||||||
# self._scheduler.enter(
|
|
||||||
# delay=self._polling_interval, priority=1, action=self.__consume_queue
|
|
||||||
# )
|
|
||||||
# self._scheduler.run(blocking=True)
|
|
||||||
|
|
||||||
def save_object_from_transport(
|
def save_object_from_transport(
|
||||||
self, id: str, source_transport: AbstractTransport
|
self, id: str, source_transport: AbstractTransport
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -117,23 +85,41 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
self.save_object(id, serialized_object)
|
self.save_object(id, serialized_object)
|
||||||
|
|
||||||
def save_object(self, id: str, serialized_object: str) -> None:
|
def save_object(self, id: str, serialized_object: str) -> None:
|
||||||
"""Directly saves an object into the database.
|
"""
|
||||||
|
Adds an object to the current batch to be written to the db. If the current batch is full,
|
||||||
|
the batch is written to the db and the current batch is reset.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
id {str} -- the object id
|
id {str} -- the object id
|
||||||
serialized_object {str} -- the full string representation of the object
|
serialized_object {str} -- the full string representation of the object
|
||||||
"""
|
"""
|
||||||
|
obj_size = len(serialized_object)
|
||||||
|
if (
|
||||||
|
not self._current_batch
|
||||||
|
or self._current_batch_size + obj_size < self.max_size
|
||||||
|
):
|
||||||
|
self._current_batch.append((id, serialized_object))
|
||||||
|
self._current_batch_size += obj_size
|
||||||
|
return
|
||||||
|
|
||||||
|
self.save_current_batch()
|
||||||
|
self._current_batch = [(id, serialized_object)]
|
||||||
|
self._current_batch_size = obj_size
|
||||||
|
|
||||||
|
def save_current_batch(self) -> None:
|
||||||
|
"""Save the current batch of objects to the local db"""
|
||||||
self.__check_connection()
|
self.__check_connection()
|
||||||
try:
|
try:
|
||||||
with closing(self.__connection.cursor()) as c:
|
with closing(self.__connection.cursor()) as c:
|
||||||
c.execute(
|
c.executemany(
|
||||||
"INSERT OR IGNORE INTO objects(hash, content) VALUES(?,?)",
|
"INSERT OR IGNORE INTO objects(hash, content) VALUES(?,?)",
|
||||||
(id, serialized_object),
|
self._current_batch,
|
||||||
)
|
)
|
||||||
self.__connection.commit()
|
self.__connection.commit()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"Could not save the object to the local db. Inner exception: {ex}", ex
|
f"Could not save the batch of objects to the local db. Inner exception: {ex}",
|
||||||
|
ex,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_object(self, id: str) -> str or None:
|
def get_object(self, id: str) -> str or None:
|
||||||
@@ -156,10 +142,14 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def begin_write(self):
|
def begin_write(self):
|
||||||
|
self._object_cache = []
|
||||||
self.saved_obj_count = 0
|
self.saved_obj_count = 0
|
||||||
|
|
||||||
def end_write(self):
|
def end_write(self):
|
||||||
pass
|
if self._current_batch:
|
||||||
|
self.save_current_batch()
|
||||||
|
self._current_batch = []
|
||||||
|
self._current_batch_size = 0
|
||||||
|
|
||||||
def copy_object_and_children(
|
def copy_object_and_children(
|
||||||
self, id: str, target_transport: AbstractTransport
|
self, id: str, target_transport: AbstractTransport
|
||||||
@@ -199,19 +189,3 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.__connection.close()
|
self.__connection.close()
|
||||||
|
|
||||||
|
|
||||||
# def _run_queue(queue: Queue, root_path: str):
|
|
||||||
# if queue.empty():
|
|
||||||
# return
|
|
||||||
# print("RUN QUEUE")
|
|
||||||
# conn = sqlite3.connect(root_path)
|
|
||||||
# while not queue.empty():
|
|
||||||
# data = queue.get()
|
|
||||||
# with closing(conn.cursor()) as c:
|
|
||||||
# c.execute(
|
|
||||||
# "INSERT OR IGNORE INTO objects(hash, content) VALUES(?,?)",
|
|
||||||
# (data[0], data[1]),
|
|
||||||
# )
|
|
||||||
# conn.commit()
|
|
||||||
# conn.close()
|
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ def second_user_dict(host):
|
|||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def client(host, user_dict):
|
def client(host, user_dict):
|
||||||
client = SpeckleClient(host=host, use_ssl=False)
|
client = SpeckleClient(host=host, use_ssl=False)
|
||||||
client.authenticate(user_dict["token"])
|
client.authenticate_with_token(user_dict["token"])
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user