Compare commits

...

25 Commits

Author SHA1 Message Date
izzy lyseggen fc7e3ca893 fix(serializer): wrap traverse_base
moving `begin` and `end_write` to the seriazlier due to the new
sqlite transport with batched writes necessitates a wrapper around
`traverse_base` so end/begin write can be called once at the top level.
just adding begin/end write to the original traversal method would make
tons of calls to `end_write` since the traversal is recursive
2022-06-20 11:48:45 +01:00
izzy lyseggen 9a4635c561 fix(client): don't parse obj create response 2022-06-20 11:42:18 +01:00
izzy lyseggen 9f873333f5 fix(serializer): warn but don't throw if ref not found
this is _not_ an issue with the transports, but an issue with using the
graphql api to fetch objects. since you are only receiving one obj and none of
the children, the transport has no way to find them and should simply
return the reference as is. idk why anyone would really use `object.get`
so tbh i'm not surprised no one has found this bug yet lol
2022-06-20 11:21:09 +01:00
izzy lyseggen 50c78d679e style: formatting 2022-06-20 11:17:32 +01:00
izzy lyseggen 72dcb674eb fix(serialization): move end and begin write 2022-06-20 07:52:58 +01:00
izzy lyseggen 9d038de3ab chore: dev container update 2022-06-20 07:52:27 +01:00
Gergő Jedlicska 52a667f28e add start and finish write method calls to base object serialize 2022-06-19 14:41:22 +02:00
Gergő Jedlicska 31cd12160b update test fixture auth to non deprecated token based method 2022-06-19 14:40:50 +02:00
Gergő Jedlicska 22e7f18648 update to new circleci redis baseimage 2022-06-19 14:40:18 +02:00
Gergő Jedlicska 97815ead9d Merge branch 'gergo/sqliteSpeedup' of github.com:specklesystems/specklepy into gergo/sqliteSpeedup 2022-06-19 14:06:18 +02:00
Gergő Jedlicska 332331465d update CI versions 2022-06-19 14:04:38 +02:00
izzy lyseggen b56f006103 ci: formatting 2022-06-17 17:21:06 +01:00
izzy lyseggen 1fdefc8929 ci: bump node version 2022-06-17 16:50:04 +01:00
izzy lyseggen f51dd28e46 chore: upgrade gql3
also removed py-spy as it's not used and i was getting install errors :/
2022-06-17 16:29:28 +01:00
izzy lyseggen e22bfd72ea feat(transports): batching sqlite inserts 2022-06-17 16:15:47 +01:00
Gergő Jedlicska 739d8bc189 quick and hacky sqlite batching 2022-06-17 16:35:15 +02:00
izzy lyseggen 61e7ebeabd fix(client): add faves count to mutation return (#186) 2022-04-25 11:45:54 +01:00
izzy lyseggen 3a8121c306 feat(client): stream model update + favoriting (#185)
* feat(models): a quickie lil update

- adds fave and comment count
(mutations to come)
- adds source app to commit within stream query

* feat(client): add favorite mutation
2022-04-25 11:32:24 +01:00
izzy lyseggen 209a95879f fix(metrics): patch app version check 2022-04-22 11:46:27 +01:00
izzy lyseggen 4f829d9908 feat(metrics): some cleanup and updates (#184)
- add metrics for client init / auth
- add server metrics
- remove incompatible server check in client
(at this point, it's been long enough that I think it's fine and will
save time on request / esp in places like blender)
2022-04-22 11:26:28 +01:00
luzpaz ac5345f528 Fix various typos (#181) 2022-04-21 17:56:11 +01:00
izzy lyseggen 1142481d89 fix(geometry): int(index vals) for curve encoding (#183)
* fix(geometry): int(index vals) for curve encoding

* fix(client): update poss invalid token check

server now returns `None` instead of a `GraphqlExcetion` when asking for
the user with an invalid token (or no scopes token)
2022-04-21 17:50:43 +01:00
izzy lyseggen b4690f082f feat(objects): revit params in objects for blender (#179) 2022-04-01 11:58:49 +01:00
izzy lyseggen 81a98ea938 feat(client): stream and user activity (#176)
* feat(models): `ActivityCollection` and `Activity`

* feat(client): stream activity method

* test(client): test for stream activity

* refactor(client): use datetime args for activity

* docs(client): clean up stream activity docstring

* feat/test(client): user activity
2022-03-23 17:34:10 +00:00
izzy lyseggen 9b387da77a feat(serialisation): enums (#175)
note that this won't re-serialise dynamic members as enums.
they will come back as ints for consistency w sharp

closes 🃏 Enum serialisation bug #174
2022-03-23 11:49:16 +00:00
28 changed files with 748 additions and 467 deletions
+8 -7
View File
@@ -1,16 +1,16 @@
version: 2.1
orbs:
python: circleci/python@1.3.2
python: circleci/python@2.0.3
codecov: codecov/codecov@3.2.2
jobs:
test:
docker:
- image: "cimg/python:<<parameters.tag>>"
- image: 'cimg/node:14.18'
- image: 'circleci/redis:6'
- image: 'cimg/postgres:12.8'
- image: "cimg/node:16.15"
- image: "cimg/redis:6.2"
- image: "cimg/postgres:14.2"
environment:
POSTGRES_DB: speckle2_test
POSTGRES_PASSWORD: speckle
@@ -27,6 +27,7 @@ jobs:
STRATEGY_LOCAL: "true"
CANONICAL_URL: "http://localhost:3000"
WAIT_HOSTS: localhost:5432, localhost:6379
DISABLE_FILE_UPLOADS: "true"
parameters:
tag:
default: "3.8"
@@ -51,7 +52,7 @@ jobs:
deploy:
docker:
- image: "circleci/python:3.8"
- image: "cimg/python:3.8"
steps:
- checkout
- run: python patch_version.py $CIRCLE_TAG
@@ -60,7 +61,7 @@ jobs:
workflows:
main:
jobs:
jobs:
- test:
matrix:
parameters:
@@ -70,7 +71,7 @@ workflows:
only: /.*/
- deploy:
requires:
- test
- test
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
+3 -2
View File
@@ -1,7 +1,7 @@
version: "3.3" # optional since v1.27.0
services:
postgres:
image: circleci/postgres:12
image: cimg/postgres:14.2
environment:
POSTGRES_DB: speckle2_test
POSTGRES_PASSWORD: speckle
@@ -10,7 +10,7 @@ services:
# - "5432:5432"
network_mode: host
redis:
image: circleci/redis:6
image: cimg/redis:6.2
# ports:
# - "6379:6379"
network_mode: host
@@ -27,6 +27,7 @@ services:
STRATEGY_LOCAL: "true"
CANONICAL_URL: "http://localhost:3000"
WAIT_HOSTS: localhost:5432, localhost:6379
DISABLE_FILE_UPLOADS: "true"
# ports:
# - "3000:3000"
network_mode: host
+6 -2
View File
@@ -4,6 +4,7 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
@@ -13,10 +14,13 @@
"justMyCode": false
},
{
"name": "Python: Test debug config",
"name": "Pytest",
"type": "python",
"request": "test",
"request": "launch",
"program": "poetry",
"args": ["run", "pytest"],
"console": "integratedTerminal",
"justMyCode": true
}
]
}
+63
View File
@@ -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}")
+2 -2
View File
@@ -50,10 +50,10 @@ if __name__ == "__main__":
)
# support for dynamic attributes
custom_sub.extra_extra = "what is this?"
debug(custom_sub.json())
debug(custom_sub)
serialized = operations.serialize(custom_sub)
deserialized = operations.deserialize(serialized)
# the only difference should be between the two data is that the deserialized
# instance id attribute is not None.
debug(deserialized.json())
debug(deserialized)
Generated
+127 -223
View File
@@ -1,23 +1,3 @@
[[package]]
name = "aiohttp"
version = "3.7.4.post0"
description = "Async http client/server framework (asyncio)"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
async-timeout = ">=3.0,<4.0"
attrs = ">=17.3.0"
chardet = ">=2.0,<5.0"
idna-ssl = {version = ">=1.0", markers = "python_version < \"3.7\""}
multidict = ">=4.5,<7.0"
typing-extensions = ">=3.6.5"
yarl = ">=1.0,<2.0"
[package.extras]
speedups = ["aiodns", "brotlipy", "cchardet"]
[[package]]
name = "appdirs"
version = "1.4.4"
@@ -27,12 +7,18 @@ optional = false
python-versions = "*"
[[package]]
name = "async-timeout"
version = "3.0.1"
description = "Timeout context manager for asyncio programs"
category = "main"
name = "asttokens"
version = "2.0.5"
description = "Annotate AST trees with source code positions"
category = "dev"
optional = false
python-versions = ">=3.5.3"
python-versions = "*"
[package.dependencies]
six = "*"
[package.extras]
test = ["astroid", "pytest"]
[[package]]
name = "atomicwrites"
@@ -46,7 +32,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
name = "attrs"
version = "21.2.0"
description = "Classes Without Boilerplate"
category = "main"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@@ -79,41 +65,17 @@ typing-extensions = ">=3.7.4"
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
[[package]]
name = "botocore"
version = "1.24.4"
description = "Low-level, data-driven core of boto 3."
category = "main"
optional = false
python-versions = ">= 3.6"
[package.dependencies]
jmespath = ">=0.7.1,<1.0.0"
python-dateutil = ">=2.1,<3.0.0"
urllib3 = ">=1.25.4,<1.27"
[package.extras]
crt = ["awscrt (==0.12.5)"]
[[package]]
name = "certifi"
version = "2021.10.8"
version = "2022.6.15"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "chardet"
version = "4.0.0"
description = "Universal encoding detector for Python 2 and 3"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
python-versions = ">=3.6"
[[package]]
name = "charset-normalizer"
version = "2.0.8"
version = "2.0.12"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
@@ -178,40 +140,61 @@ wrapt = ">=1.10,<2"
[package.extras]
dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"]
[[package]]
name = "devtools"
version = "0.8.0"
description = "Python's missing debug print command and other development tools."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
asttokens = ">=2.0.0,<3.0.0"
executing = ">=0.8.0,<1.0.0"
[package.extras]
pygments = ["Pygments (>=2.2.0)"]
[[package]]
name = "executing"
version = "0.8.3"
description = "Get the currently executing AST node of a frame, and other information"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "gql"
version = "3.0.0"
version = "3.3.0"
description = "GraphQL client for Python"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
aiohttp = {version = ">=3.7.1,<3.9.0", optional = true, markers = "extra == \"all\""}
botocore = {version = ">=1.21,<2", optional = true, markers = "extra == \"all\""}
graphql-core = ">=3.2,<3.3"
requests = {version = ">=2.26,<3", optional = true, markers = "extra == \"all\""}
requests_toolbelt = {version = ">=0.9.1,<1", optional = true, markers = "extra == \"all\""}
urllib3 = {version = ">=1.26", optional = true, markers = "extra == \"all\""}
requests = {version = ">=2.26,<3", optional = true, markers = "extra == \"requests\""}
requests-toolbelt = {version = ">=0.9.1,<1", optional = true, markers = "extra == \"requests\""}
urllib3 = {version = ">=1.26", optional = true, markers = "extra == \"requests\""}
websockets = [
{version = ">=9,<10", optional = true, markers = "python_version <= \"3.6\" or python_version <= \"3.6\" and extra == \"all\""},
{version = ">=10,<11", optional = true, markers = "python_version > \"3.6\" or python_version > \"3.6\" and extra == \"all\""},
{version = ">=9,<10", optional = true, markers = "python_version <= \"3.6\" or python_version <= \"3.6\" and extra == \"websockets\""},
{version = ">=10,<11", optional = true, markers = "python_version > \"3.6\" or python_version > \"3.6\" and extra == \"websockets\""},
]
yarl = ">=1.6,<2.0"
[package.extras]
aiohttp = ["aiohttp (>=3.7.1,<3.9.0)"]
all = ["aiohttp (>=3.7.1,<3.9.0)", "requests (>=2.26,<3)", "requests_toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)", "botocore (>=1.21,<2)", "websockets (>=9,<10)", "websockets (>=10,<11)"]
all = ["aiohttp (>=3.7.1,<3.9.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)", "botocore (>=1.21,<2)", "websockets (>=9,<10)", "websockets (>=10,<11)"]
botocore = ["botocore (>=1.21,<2)"]
dev = ["aiohttp (>=3.7.1,<3.9.0)", "requests (>=2.26,<3)", "requests_toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)", "botocore (>=1.21,<2)", "black (==19.10b0)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mypy (==0.910)", "sphinx (>=3.0.0,<4)", "sphinx_rtd_theme (>=0.4,<1)", "sphinx-argparse (==0.2.5)", "types-aiofiles", "types-mock", "types-requests", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-cov (==3.0.0)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles", "websockets (>=9,<10)", "websockets (>=10,<11)"]
requests = ["requests (>=2.26,<3)", "requests_toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)"]
test = ["aiohttp (>=3.7.1,<3.9.0)", "requests (>=2.26,<3)", "requests_toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)", "botocore (>=1.21,<2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-cov (==3.0.0)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles", "websockets (>=9,<10)", "websockets (>=10,<11)"]
test_no_transport = ["parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-cov (==3.0.0)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles"]
dev = ["aiohttp (>=3.7.1,<3.9.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)", "botocore (>=1.21,<2)", "black (==22.3.0)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mypy (==0.910)", "sphinx (>=3.0.0,<4)", "sphinx-rtd-theme (>=0.4,<1)", "sphinx-argparse (==0.2.5)", "types-aiofiles", "types-mock", "types-requests", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles", "websockets (>=9,<10)", "websockets (>=10,<11)"]
requests = ["requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)"]
test = ["aiohttp (>=3.7.1,<3.9.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)", "botocore (>=1.21,<2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles", "websockets (>=9,<10)", "websockets (>=10,<11)"]
test_no_transport = ["parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles"]
websockets = ["websockets (>=9,<10)", "websockets (>=10,<11)"]
[[package]]
name = "graphql-core"
version = "3.2.0"
version = "3.2.1"
description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL."
category = "main"
optional = false
@@ -225,17 +208,6 @@ category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "idna-ssl"
version = "1.1.0"
description = "Patch ssl.match_hostname for Unicode(idna) domains support"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
idna = ">=2.0"
[[package]]
name = "importlib-metadata"
version = "4.8.2"
@@ -275,14 +247,6 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"]
plugins = ["setuptools"]
[[package]]
name = "jmespath"
version = "0.10.0"
description = "JSON Matching Expressions"
category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "multidict"
version = "5.2.0"
@@ -416,17 +380,6 @@ python-versions = "*"
[package.dependencies]
pytest = "*"
[[package]]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
[package.dependencies]
six = ">=1.5"
[[package]]
name = "regex"
version = "2021.11.10"
@@ -437,7 +390,7 @@ python-versions = "*"
[[package]]
name = "requests"
version = "2.26.0"
version = "2.27.1"
description = "Python HTTP for Humans."
category = "main"
optional = false
@@ -468,7 +421,7 @@ requests = ">=2.0.1,<3.0.0"
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
@@ -514,14 +467,14 @@ python-versions = ">=3.6"
[[package]]
name = "urllib3"
version = "1.26.7"
version = "1.26.9"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
@@ -535,7 +488,7 @@ python-versions = ">=3.6.1"
[[package]]
name = "websockets"
version = "10.2"
version = "10.3"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
category = "main"
optional = false
@@ -577,55 +530,16 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
[metadata]
lock-version = "1.1"
python-versions = "^3.6.5"
content-hash = "78b3a6fe933735ef4f3c0dca34c08521ab2c11aceb2b7f5572846870d39d46f4"
content-hash = "460c5e149a49abcde84f74aa13dbf09eba66c7942af8d1cc65580f1e44927d17"
[metadata.files]
aiohttp = [
{file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"},
{file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"},
{file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"},
{file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"},
{file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"},
{file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"},
{file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"},
{file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"},
{file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"},
{file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"},
{file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"},
{file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"},
{file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"},
]
appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
async-timeout = [
{file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"},
{file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"},
asttokens = [
{file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"},
{file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"},
]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
@@ -638,21 +552,13 @@ attrs = [
black = [
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
]
botocore = [
{file = "botocore-1.24.4-py3-none-any.whl", hash = "sha256:692fcad31733674c8faffc65da8ddc8ab4839f407ad8470c103e0e7aed9c7556"},
{file = "botocore-1.24.4.tar.gz", hash = "sha256:8e9ae6b718ca318b4aeeee45aa9fad5f52e882a2291d919cfa4b753928223abb"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
chardet = [
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.8.tar.gz", hash = "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0"},
{file = "charset_normalizer-2.0.8-py3-none-any.whl", hash = "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405"},
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
click = [
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
@@ -719,20 +625,26 @@ deprecated = [
{file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
{file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
]
devtools = [
{file = "devtools-0.8.0-py3-none-any.whl", hash = "sha256:00717ef184223cf36c65bbd17c6eb412f8a7564f47957f9e8b2b7610661b17fb"},
{file = "devtools-0.8.0.tar.gz", hash = "sha256:6162a2f61c70242479dff3163e7837e6a9bf32451661af1347bfa3115602af16"},
]
executing = [
{file = "executing-0.8.3-py2.py3-none-any.whl", hash = "sha256:d1eef132db1b83649a3905ca6dd8897f71ac6f8cac79a7e58a1a09cf137546c9"},
{file = "executing-0.8.3.tar.gz", hash = "sha256:c6554e21c6b060590a6d3be4b82fb78f8f0194d809de5ea7df1c093763311501"},
]
gql = [
{file = "gql-3.0.0.tar.gz", hash = "sha256:a21ff880a69caec8786be5cc6de7de6fc2f2b87547af8cd30746b9401ffd47b2"},
{file = "gql-3.3.0-py2.py3-none-any.whl", hash = "sha256:77fbc0a47c2df4edb8a98afa8c9c23aead5e0b4ab26d424e2dcfeb3fa2a2319d"},
{file = "gql-3.3.0.tar.gz", hash = "sha256:efa405aebe0e7b2fe342b43cfc70fd57e2e385fcae04f79c509366ad48a64381"},
]
graphql-core = [
{file = "graphql-core-3.2.0.tar.gz", hash = "sha256:86e2a0be008bfde19ef78388de8a725a1d942a9190ca431c24a60837973803ce"},
{file = "graphql_core-3.2.0-py3-none-any.whl", hash = "sha256:0dda7e63676f119bb3d814621190fedad72fda07a8e9ab780bedd9f1957c6dc6"},
{file = "graphql-core-3.2.1.tar.gz", hash = "sha256:9d1bf141427b7d54be944587c8349df791ce60ade2e3cccaf9c56368c133c201"},
{file = "graphql_core-3.2.1-py3-none-any.whl", hash = "sha256:f83c658e4968998eed1923a2e3e3eddd347e005ac0315fbb7ca4d70ea9156323"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
idna-ssl = [
{file = "idna-ssl-1.1.0.tar.gz", hash = "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"},
]
importlib-metadata = [
{file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"},
{file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"},
@@ -745,10 +657,6 @@ isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
jmespath = [
{file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"},
{file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"},
]
multidict = [
{file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"},
{file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"},
@@ -884,10 +792,6 @@ pytest-ordering = [
{file = "pytest_ordering-0.6-py2-none-any.whl", hash = "sha256:27fba3fc265f5d0f8597e7557885662c1bdc1969497cd58aff6ed21c3b617de2"},
{file = "pytest_ordering-0.6-py3-none-any.whl", hash = "sha256:3f314a178dbeb6777509548727dc69edf22d6d9a2867bf2d310ab85c403380b6"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
]
regex = [
{file = "regex-2021.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf"},
{file = "regex-2021.11.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f"},
@@ -965,8 +869,8 @@ regex = [
{file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"},
]
requests = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
]
requests-toolbelt = [
{file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
@@ -1056,8 +960,8 @@ ujson = [
{file = "ujson-4.3.0.tar.gz", hash = "sha256:baee56eca35cb5fbe02c28bd9c0936be41a96fa5c0812d9d4b7edeb5c3d568a0"},
]
urllib3 = [
{file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
{file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
]
websockets = [
{file = "websockets-9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da"},
@@ -1093,54 +997,54 @@ websockets = [
{file = "websockets-9.1-cp39-cp39-win32.whl", hash = "sha256:be5fd35e99970518547edc906efab29afd392319f020c3c58b0e1a158e16ed20"},
{file = "websockets-9.1-cp39-cp39-win_amd64.whl", hash = "sha256:85db8090ba94e22d964498a47fdd933b8875a1add6ebc514c7ac8703eb97bbf0"},
{file = "websockets-9.1.tar.gz", hash = "sha256:276d2339ebf0df4f45df453923ebd2270b87900eda5dfd4a6b0cfa15f82111c3"},
{file = "websockets-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5396710f86a306cf52f87fd8ea594a0e894ba0cc5a36059eaca3a477dc332aa"},
{file = "websockets-10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b22bdc795e62e71118b63e14a08bacfa4f262fd2877de7e5b950f5ac16b0348f"},
{file = "websockets-10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b04270b5613f245ec84bb2c6a482a9d009aefad37c0575f6cda8499125d5d5c"},
{file = "websockets-10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5c335dc0e7dc271ef36df3f439868b3c790775f345338c2f61a562f1074187b"},
{file = "websockets-10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a009eb551c46fd79737791c0c833fc0e5b56bcd1c3057498b262d660b92e9cd"},
{file = "websockets-10.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a10c0c1ee02164246f90053273a42d72a3b2452a7e7486fdae781138cf7fbe2d"},
{file = "websockets-10.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7b38a5c9112e3dbbe45540f7b60c5204f49b3cb501b40950d6ab34cd202ab1d0"},
{file = "websockets-10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2aa9b91347ecd0412683f28aabe27f6bad502d89bd363b76e0a3508b1596402e"},
{file = "websockets-10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b7fe45ae43ac814beb8ca09d6995b56800676f2cfa8e23f42839dc69bba34a42"},
{file = "websockets-10.2-cp310-cp310-win32.whl", hash = "sha256:cef40a1b183dcf39d23b392e9dd1d9b07ab9c46aadf294fff1350fb79146e72b"},
{file = "websockets-10.2-cp310-cp310-win_amd64.whl", hash = "sha256:c21a67ab9a94bd53e10bba21912556027fea944648a09e6508415ad14e37c325"},
{file = "websockets-10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb316b87cbe3c0791c2ad92a5a36bf6adc87c457654335810b25048c1daa6fd5"},
{file = "websockets-10.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f14bd10e170abc01682a9f8b28b16e6f20acf6175945ef38db6ffe31b0c72c3f"},
{file = "websockets-10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fa35c5d1830d0fb7b810324e9eeab9aa92e8f273f11fdbdc0741dcded6d72b9f"},
{file = "websockets-10.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:71a4491cfe7a9f18ee57d41163cb6a8a3fa591e0f0564ca8b0ed86b2a30cced4"},
{file = "websockets-10.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6193bbc1ee63aadeb9a4d81de0e19477401d150d506aee772d8380943f118186"},
{file = "websockets-10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8beac786a388bb99a66c3be4ab0fb38273c0e3bc17f612a4e0a47c4fc8b9c045"},
{file = "websockets-10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c67d9cacb3f6537ca21e9b224d4fd08481538e43bcac08b3d93181b0816def39"},
{file = "websockets-10.2-cp37-cp37m-win32.whl", hash = "sha256:a03a25d95cc7400bd4d61a63460b5d85a7761c12075ee2f51de1ffe73aa593d3"},
{file = "websockets-10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f8296b8408ec6853b26771599990721a26403e62b9de7e50ac0a056772ac0b5e"},
{file = "websockets-10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7bb9d8a6beca478c7e9bdde0159bd810cc1006ad6a7cb460533bae39da692ca2"},
{file = "websockets-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:05f6e9757017270e7a92a2975e2ae88a9a582ffc4629086fd6039aa80e99cd86"},
{file = "websockets-10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1c9031e90ebfc486e9cdad532b94004ade3aa39a31d3c46c105bb0b579cd2490"},
{file = "websockets-10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82bc33db6d8309dc27a3bee11f7da2288ad925fcbabc2a4bb78f7e9c56249baf"},
{file = "websockets-10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:24b879ba7db12bb525d4e58089fcbe6a3df3ce4666523183654170e86d372cbe"},
{file = "websockets-10.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf931c33db9c87c53d009856045dd524e4a378445693382a920fa1e0eb77c36c"},
{file = "websockets-10.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:669e54228a4d9457abafed27cbf0e2b9f401445c4dfefc12bf8e4db9751703b8"},
{file = "websockets-10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bffc65442dd35c473ca9790a3fa3ba06396102a950794f536783f4b8060af8dd"},
{file = "websockets-10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4d110a84b63c5cfdd22485acc97b8b919aefeecd6300c0c9d551e055b9a88ea"},
{file = "websockets-10.2-cp38-cp38-win32.whl", hash = "sha256:117383d0a17a0dda349f7a8790763dde75c1508ff8e4d6e8328b898b7df48397"},
{file = "websockets-10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b66421f9f13d4df60cd48ab977ed2c2b6c9147ae1a33caf5a9f46294422fda1"},
{file = "websockets-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac081aa0307f263d63c5ff0727935c736c8dad51ddf2dc9f5d0c4759842aefaa"},
{file = "websockets-10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4059e2ccbe6587b6dc9a01db5fc49ead9a884faa4076eea96c5ec62cb32f42a"},
{file = "websockets-10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9ca2ca05a4c29179f06cf6727b45dba5d228da62623ec9df4184413d8aae6cb9"},
{file = "websockets-10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97950c7c844ec6f8d292440953ae18b99e3a6a09885e09d20d5e7ecd9b914cf8"},
{file = "websockets-10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:98f57b3120f8331cd7440dbe0e776474f5e3632fdaa474af1f6b754955a47d71"},
{file = "websockets-10.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a72b92f96e5e540d5dda99ee3346e199ade8df63152fa3c737260da1730c411f"},
{file = "websockets-10.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:038afef2a05893578d10dadbdbb5f112bd115c46347e1efe99f6a356ff062138"},
{file = "websockets-10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f09f46b1ff6d09b01c7816c50bd1903cf7d02ebbdb63726132717c2fcda835d5"},
{file = "websockets-10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2349fa81b6b959484bb2bda556ccb9eb70ba68987646a0f8a537a1a18319fb03"},
{file = "websockets-10.2-cp39-cp39-win32.whl", hash = "sha256:bef03a51f9657fb03d8da6ccd233fe96e04101a852f0ffd35f5b725b28221ff3"},
{file = "websockets-10.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c1f3b18c8162e3b09761d0c6a0305fd642934202541cc511ef972cb9463261e"},
{file = "websockets-10.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a38a0175ae82e4a8c4bac29fc01b9ee26d7d5a614e5ee11e7813c68a7d938ce"},
{file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e56606842bb24e16e36ae7eb308d866b4249cf0be8f63b212f287eeb76b124"},
{file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0f73cb2526d6da268e86977b2c4b58f2195994e53070fe567d5487c6436047e6"},
{file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cd02f36d37e503aca88ab23cc0a1a0e92a263d37acf6331521eb38040dcf77b"},
{file = "websockets-10.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:56d48eebe9e39ce0d68701bce3b21df923aa05dcc00f9fd8300de1df31a7c07c"},
{file = "websockets-10.2.tar.gz", hash = "sha256:8351c3c86b08156337b0e4ece0e3c5ec3e01fcd14e8950996832a23c99416098"},
{file = "websockets-10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978"},
{file = "websockets-10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500"},
{file = "websockets-10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b"},
{file = "websockets-10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c"},
{file = "websockets-10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8"},
{file = "websockets-10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677"},
{file = "websockets-10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e"},
{file = "websockets-10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f"},
{file = "websockets-10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47"},
{file = "websockets-10.3-cp310-cp310-win32.whl", hash = "sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae"},
{file = "websockets-10.3-cp310-cp310-win_amd64.whl", hash = "sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079"},
{file = "websockets-10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916"},
{file = "websockets-10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb"},
{file = "websockets-10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79"},
{file = "websockets-10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d"},
{file = "websockets-10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98"},
{file = "websockets-10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e"},
{file = "websockets-10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6"},
{file = "websockets-10.3-cp37-cp37m-win32.whl", hash = "sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1"},
{file = "websockets-10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4"},
{file = "websockets-10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36"},
{file = "websockets-10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69"},
{file = "websockets-10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd"},
{file = "websockets-10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2"},
{file = "websockets-10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c"},
{file = "websockets-10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e"},
{file = "websockets-10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991"},
{file = "websockets-10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442"},
{file = "websockets-10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76"},
{file = "websockets-10.3-cp38-cp38-win32.whl", hash = "sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559"},
{file = "websockets-10.3-cp38-cp38-win_amd64.whl", hash = "sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d"},
{file = "websockets-10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094"},
{file = "websockets-10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667"},
{file = "websockets-10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731"},
{file = "websockets-10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9"},
{file = "websockets-10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680"},
{file = "websockets-10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247"},
{file = "websockets-10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af"},
{file = "websockets-10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3"},
{file = "websockets-10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8"},
{file = "websockets-10.3-cp39-cp39-win32.whl", hash = "sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582"},
{file = "websockets-10.3-cp39-cp39-win_amd64.whl", hash = "sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02"},
{file = "websockets-10.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7"},
{file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f"},
{file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4"},
{file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755"},
{file = "websockets-10.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55"},
{file = "websockets-10.3.tar.gz", hash = "sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"},
]
wrapt = [
{file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"},
+2 -1
View File
@@ -14,7 +14,7 @@ homepage = "https://speckle.systems/"
python = "^3.6.5"
pydantic = "^1.8.2"
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"
Deprecated = "^1.2.13"
@@ -24,6 +24,7 @@ isort = "^5.7.0"
pytest = "^6.2.2"
pytest-ordering = "^0.6"
pytest-cov = "^3.0.0"
devtools = "^0.8.0"
[tool.black]
+17 -11
View File
@@ -2,8 +2,8 @@ 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 (
GraphQLException,
SpeckleException,
SpeckleWarning,
)
@@ -57,6 +57,7 @@ class SpeckleClient:
USE_SSL = True
def __init__(self, host: str = DEFAULT_HOST, use_ssl: bool = USE_SSL) -> None:
metrics.track(metrics.CLIENT, custom_props={"name": "create"})
ws_protocol = "ws"
http_protocol = "http"
@@ -79,15 +80,17 @@ class SpeckleClient:
self._init_resources()
# Check compatibility with the server
try:
serverInfo = self.server.get()
if isinstance(serverInfo, Exception):
raise serverInfo
if not isinstance(serverInfo, ServerInfo):
raise Exception("Couldn't get ServerInfo")
except Exception as ex:
raise SpeckleException(f"{self.url} is not a compatible Speckle Server", ex)
# ? Check compatibility with the server - i think we can skip this at this point? save a request
# try:
# server_info = self.server.get()
# if isinstance(server_info, Exception):
# raise server_info
# if not isinstance(server_info, ServerInfo):
# raise Exception("Couldn't get ServerInfo")
# except Exception as ex:
# raise SpeckleException(
# f"{self.url} is not a compatible Speckle Server", ex
# ) from ex
def __repr__(self):
return f"SpeckleClient( server: {self.url}, authenticated: {self.account.token is not None} )"
@@ -114,6 +117,7 @@ class SpeckleClient:
token {str} -- an api token
"""
self.account = get_account_from_token(token, self.url)
metrics.track(metrics.CLIENT, self.account, {"name": "authenticate with token"})
self._set_up_client()
def authenticate_with_account(self, account: Account) -> None:
@@ -123,10 +127,12 @@ class SpeckleClient:
Arguments:
account {Account} -- the account object which can be found with `get_default_account` or `get_local_accounts`
"""
metrics.track(metrics.CLIENT, account, {"name": "authenticate with account"})
self.account = account
self._set_up_client()
def _set_up_client(self) -> None:
metrics.track(metrics.CLIENT, self.account, {"name": "set up client"})
headers = {
"Authorization": f"Bearer {self.account.token}",
"Content-Type": "application/json",
@@ -143,7 +149,7 @@ class SpeckleClient:
self._init_resources()
if isinstance(self.user.get(), GraphQLException):
if self.user.get() is None:
warn(
SpeckleWarning(
f"Possibly invalid token - could not authenticate Speckle Client for server {self.url}"
+39 -6
View File
@@ -23,7 +23,7 @@ class Commit(BaseModel):
authorId: Optional[str]
authorAvatar: Optional[str]
branchName: Optional[str]
createdAt: Optional[str]
createdAt: Optional[datetime]
sourceApplication: Optional[str]
referencedObject: Optional[str]
totalChildrenCount: Optional[int]
@@ -38,7 +38,7 @@ class Commit(BaseModel):
class Commits(BaseModel):
totalCount: Optional[int]
cursor: Optional[Any]
cursor: Optional[datetime]
items: List[Commit] = []
@@ -47,7 +47,7 @@ class Object(BaseModel):
speckleType: Optional[str]
applicationId: Optional[str]
totalChildrenCount: Optional[int]
createdAt: Optional[str]
createdAt: Optional[datetime]
class Branch(BaseModel):
@@ -66,14 +66,18 @@ class Branches(BaseModel):
class Stream(BaseModel):
id: Optional[str]
name: Optional[str]
description: Optional[str]
role: Optional[str]
isPublic: Optional[bool]
createdAt: Optional[str]
updatedAt: Optional[str]
description: Optional[str]
createdAt: Optional[datetime]
updatedAt: Optional[datetime]
collaborators: List[Collaborator] = []
branches: Optional[Branches]
commit: Optional[Commit]
object: Optional[Object]
commentCount: Optional[int]
favoritedDate: Optional[datetime]
favoritesCount: Optional[int]
def __repr__(self):
return f"Stream( id: {self.id}, name: {self.name}, description: {self.description}, isPublic: {self.isPublic})"
@@ -106,6 +110,35 @@ class User(BaseModel):
return self.__repr__()
class Activity(BaseModel):
actionType: Optional[str]
info: Optional[dict]
userId: Optional[str]
streamId: Optional[str]
resourceId: Optional[str]
resourceType: Optional[str]
message: Optional[str]
time: Optional[datetime]
def __repr__(self) -> str:
return f"Activity( streamId: {self.streamId}, actionType: {self.actionType}, message: {self.message}, userId: {self.userId} )"
def __str__(self) -> str:
return self.__repr__()
class ActivityCollection(BaseModel):
totalCount: Optional[int]
items: Optional[List[Activity]]
cursor: Optional[datetime]
def __repr__(self) -> str:
return f"ActivityCollection( totalCount: {self.totalCount}, items: {len(self.items) if self.items else 0}, cursor: {self.cursor.isoformat()} )"
def __str__(self) -> str:
return self.__repr__()
class ServerInfo(BaseModel):
name: Optional[str]
company: Optional[str]
+1 -6
View File
@@ -40,13 +40,8 @@ def send(
serializer = BaseObjectSerializer(write_transports=transports)
for t in transports:
t.begin_write()
obj_hash, _ = serializer.write_json(base=base)
for t in transports:
t.end_write()
return obj_hash
@@ -72,7 +67,7 @@ def receive(
serializer = BaseObjectSerializer(read_transport=local_transport)
# try local transport first. if the parent is there, we assume all the children are there and continue wth deserialisation using the local transport
# try local transport first. if the parent is there, we assume all the children are there and continue with deserialisation using the local transport
obj_string = local_transport.get_object(obj_id)
if obj_string:
return serializer.read_json(obj_string=obj_string)
+1 -1
View File
@@ -164,7 +164,7 @@ class Resource(ResourceBase):
description {str} -- optional: the updated branch description
Returns:
bool -- True if update is successfull
bool -- True if update is successful
"""
metrics.track(metrics.BRANCH, self.account, {"name": "update"})
query = gql(
+3 -1
View File
@@ -52,7 +52,9 @@ class Resource(ResourceBase):
params = {"stream_id": stream_id, "object_id": object_id}
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:
+5
View File
@@ -2,6 +2,7 @@ from typing import Dict, List
from gql import gql
from specklepy.api.models import ServerInfo
from specklepy.api.resource import ResourceBase
from specklepy.logging import metrics
NAME = "server"
@@ -26,6 +27,7 @@ class Resource(ResourceBase):
Returns:
dict -- the server info in dictionary form
"""
metrics.track(metrics.SERVER, self.account, {"name": "get"})
query = gql(
"""
query Server {
@@ -65,6 +67,7 @@ class Resource(ResourceBase):
Returns:
dict -- a dictionary of apps registered on the server
"""
metrics.track(metrics.SERVER, self.account, {"name": "apps"})
query = gql(
"""
query Apps {
@@ -98,6 +101,7 @@ class Resource(ResourceBase):
Returns:
str -- the new API token. note: this is the only time you'll see the token!
"""
metrics.track(metrics.SERVER, self.account, {"name": "create_token"})
query = gql(
"""
mutation TokenCreate($token: ApiTokenCreateInput!) {
@@ -123,6 +127,7 @@ class Resource(ResourceBase):
Returns:
bool -- True if the token was successfully deleted
"""
metrics.track(metrics.SERVER, self.account, {"name": "revoke_token"})
query = gql(
"""
mutation TokenRevoke($token: String!) {
+204 -99
View File
@@ -1,19 +1,14 @@
from datetime import datetime, timezone
from gql import gql
from typing import List
from specklepy.logging import metrics
from specklepy.api.models import Stream
from specklepy.api.models import ActivityCollection, Stream
from specklepy.api.resource import ResourceBase
from specklepy.logging.exceptions import SpeckleException
NAME = "stream"
METHODS = [
"list",
"create",
"get",
"update",
"delete",
"search",
]
METHODS = ["list", "create", "get", "update", "delete", "search", "activity"]
class Resource(ResourceBase):
@@ -45,41 +40,45 @@ class Resource(ResourceBase):
query = gql(
"""
query Stream($id: String!, $branch_limit: Int!, $commit_limit: Int!) {
stream(id: $id) {
id
name
description
isPublic
createdAt
updatedAt
collaborators {
id
name
role
avatar
}
branches(limit: $branch_limit) {
totalCount
cursor
items {
stream(id: $id) {
id
name
role
description
commits(limit: $commit_limit) {
totalCount
cursor
items {
isPublic
createdAt
updatedAt
commentCount
favoritesCount
collaborators {
id
referencedObject
message
authorName
authorId
createdAt
}
name
role
avatar
}
}
branches(limit: $branch_limit) {
totalCount
cursor
items {
id
name
description
commits(limit: $commit_limit) {
totalCount
cursor
items {
id
message
authorId
createdAt
authorName
referencedObject
sourceApplication
}
}
}
}
}
}
}
"""
)
@@ -101,34 +100,37 @@ class Resource(ResourceBase):
query = gql(
"""
query User($stream_limit: Int!) {
user {
id
email
name
bio
company
avatar
verified
profiles
role
streams(limit: $stream_limit) {
totalCount
cursor
items {
user {
id
bio
name
description
isPublic
createdAt
updatedAt
collaborators {
id
name
role
}
email
avatar
company
verified
profiles
role
streams(limit: $stream_limit) {
totalCount
cursor
items {
id
name
role
isPublic
createdAt
updatedAt
description
commentCount
favoritesCount
collaborators {
id
name
role
}
}
}
}
}
}
}
"""
)
@@ -190,7 +192,7 @@ class Resource(ResourceBase):
query = gql(
"""
mutation StreamUpdate($stream: StreamUpdateInput!) {
streamUpdate(stream: $stream)
streamUpdate(stream: $stream)
}
"""
)
@@ -221,9 +223,9 @@ class Resource(ResourceBase):
query = gql(
"""
mutation StreamDelete($id: String!) {
streamDelete(id: $id)
streamDelete(id: $id)
}
"""
"""
)
params = {"id": id}
@@ -254,43 +256,44 @@ class Resource(ResourceBase):
query = gql(
"""
query StreamSearch($search_query: String!,$limit: Int!, $branch_limit:Int!, $commit_limit:Int!) {
streams(query: $search_query, limit: $limit) {
items {
id
name
description
isPublic
createdAt
updatedAt
collaborators {
id
name
role
avatar
}
branches(limit: $branch_limit) {
totalCount
cursor
streams(query: $search_query, limit: $limit) {
items {
id
name
description
commits(limit: $commit_limit) {
totalCount
cursor
items {
id
referencedObject
message
authorName
authorId
createdAt
id
name
role
description
isPublic
createdAt
updatedAt
collaborators {
id
name
role
avatar
}
branches(limit: $branch_limit) {
totalCount
cursor
items {
id
name
description
commits(limit: $commit_limit) {
totalCount
cursor
items {
id
referencedObject
message
authorName
authorId
createdAt
}
}
}
}
}
}
}
}
}
}
"""
)
@@ -306,6 +309,39 @@ class Resource(ResourceBase):
query=query, params=params, return_type=["streams", "items"]
)
def favorite(self, stream_id: str, favorited: bool = True):
"""Favorite or unfavorite the given stream.
Arguments:
stream_id {str} -- the id of the stream to favorite / unfavorite
favorited {bool} -- whether to favorite (True) or unfavorite (False) the stream
Returns:
Stream -- the stream with its `id`, `name`, and `favoritedDate`
"""
metrics.track(metrics.STREAM, self.account, {"name": "favorite"})
query = gql(
"""
mutation StreamFavorite($stream_id: String!, $favorited: Boolean!) {
streamFavorite(streamId: $stream_id, favorited: $favorited) {
id
name
favoritedDate
favoritesCount
}
}
"""
)
params = {
"stream_id": stream_id,
"favorited": favorited,
}
return self.make_request(
query=query, params=params, return_type=["streamFavorite"]
)
def grant_permission(self, stream_id: str, user_id: str, role: str):
"""Grant permissions to a user on a given stream
@@ -368,3 +404,72 @@ class Resource(ResourceBase):
return_type="streamRevokePermission",
parse_response=False,
)
def activity(
self,
stream_id: str,
action_type: str = None,
limit: int = 20,
before: datetime = None,
after: datetime = None,
cursor: datetime = None,
):
"""
Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity.
Note: all timestamps arguments should be `datetime` of any tz as they will be converted to UTC ISO format strings
stream_id {str} -- the id of the stream to get activity from
action_type {str} -- filter results to a single action type (eg: `commit_create` or `commit_receive`)
limit {int} -- max number of Activity items to return
before {datetime} -- latest cutoff for activity (ie: return all activity _before_ this time)
after {datetime} -- oldest cutoff for activity (ie: return all activity _after_ this time)
cursor {datetime} -- timestamp cursor for pagination
"""
query = gql(
"""
query StreamActivity($stream_id: String!, $action_type: String, $before:DateTime, $after: DateTime, $cursor: DateTime, $limit: Int){
stream(id: $stream_id) {
activity(actionType: $action_type, before: $before, after: $after, cursor: $cursor, limit: $limit) {
totalCount
cursor
items {
actionType
info
userId
streamId
resourceId
resourceType
message
time
}
}
}
}
"""
)
try:
params = {
"stream_id": stream_id,
"limit": limit,
"action_type": action_type,
"before": before.astimezone(timezone.utc).isoformat()
if before
else before,
"after": after.astimezone(timezone.utc).isoformat() if after else after,
"cursor": cursor.astimezone(timezone.utc).isoformat()
if cursor
else cursor,
}
except AttributeError as e:
raise SpeckleException(
"Could not get stream activity - `before`, `after`, and `cursor` must be in `datetime` format if provided",
ValueError,
) from e
return self.make_request(
query=query,
params=params,
return_type=["stream", "activity"],
schema=ActivityCollection,
)
+65 -2
View File
@@ -1,12 +1,13 @@
from datetime import datetime, timezone
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from typing import List
from gql import gql
from specklepy.api.resource import ResourceBase
from specklepy.api.models import User
from specklepy.api.models import ActivityCollection, User
NAME = "user"
METHODS = ["get", "search", "update"]
METHODS = ["get", "search", "update", "activity"]
class Resource(ResourceBase):
@@ -125,3 +126,65 @@ class Resource(ResourceBase):
return self.make_request(
query=query, params=params, return_type="userUpdate", parse_response=False
)
def activity(
self,
user_id: str = None,
limit: int = 20,
action_type: str = None,
before: datetime = None,
after: datetime = None,
cursor: datetime = None,
):
"""
Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity.
If no id argument is provided, will return the current authenticated user's activity (as extracted from the authorization header).
Note: all timestamps arguments should be `datetime` of any tz as they will be converted to UTC ISO format strings
user_id {str} -- the id of the user to get the activity from
action_type {str} -- filter results to a single action type (eg: `commit_create` or `commit_receive`)
limit {int} -- max number of Activity items to return
before {datetime} -- latest cutoff for activity (ie: return all activity _before_ this time)
after {datetime} -- oldest cutoff for activity (ie: return all activity _after_ this time)
cursor {datetime} -- timestamp cursor for pagination
"""
query = gql(
"""
query UserActivity($user_id: String, $action_type: String, $before:DateTime, $after: DateTime, $cursor: DateTime, $limit: Int){
user(id: $user_id) {
activity(actionType: $action_type, before: $before, after: $after, cursor: $cursor, limit: $limit) {
totalCount
cursor
items {
actionType
info
userId
streamId
resourceId
resourceType
message
time
}
}
}
}
"""
)
params = {
"user_id": user_id,
"limit": limit,
"action_type": action_type,
"before": before.astimezone(timezone.utc).isoformat() if before else before,
"after": after.astimezone(timezone.utc).isoformat() if after else after,
"cursor": cursor.astimezone(timezone.utc).isoformat() if cursor else cursor,
}
return self.make_request(
query=query,
params=params,
return_type=["user", "activity"],
schema=ActivityCollection,
)
+1 -1
View File
@@ -445,7 +445,7 @@ input ServerInfoUpdateInput {
stream( id: String! ): Stream
"""
All the streams of the current user, pass in the `query` parameter to seach by name, description or ID.
All the streams of the current user, pass in the `query` parameter to search by name, description or ID.
"""
streams( query: String, limit: Int = 25, cursor: String ): StreamCollection
@hasScope(scope: "streams:read")
+7 -4
View File
@@ -1,5 +1,3 @@
import json
import os
import socket
import sys
import queue
@@ -14,6 +12,7 @@ This really helps us to deliver a better open source project and product!
"""
TRACK = True
HOST_APP = "python"
HOST_APP_VERSION = f"python {'.'.join(map(str, sys.version_info[:3]))}"
PLATFORMS = {"win32": "Windows", "cygwin": "Windows", "darwin": "Mac OS X"}
LOG = logging.getLogger(__name__)
@@ -27,6 +26,8 @@ PERMISSION = "Permission Action"
COMMIT = "Commit Action"
BRANCH = "Branch Action"
USER = "User Action"
SERVER = "Server Action"
CLIENT = "Speckle Client"
STREAM_WRAPPER = "Stream Wrapper"
ACCOUNTS = "Get Local Accounts"
@@ -45,9 +46,10 @@ def enable():
TRACK = True
def set_host_app(host_app: str):
global HOST_APP
def set_host_app(host_app: str, host_app_version: str = None):
global HOST_APP, HOST_APP_VERSION
HOST_APP = host_app
HOST_APP_VERSION = host_app_version or HOST_APP_VERSION
def track(action: str, account: "Account" = None, custom_props: dict = None):
@@ -62,6 +64,7 @@ def track(action: str, account: "Account" = None, custom_props: dict = None):
"server_id": METRICS_TRACKER.last_server,
"token": METRICS_TRACKER.analytics_token,
"hostApp": HOST_APP,
"hostAppVersion": HOST_APP_VERSION,
"$os": METRICS_TRACKER.platform,
"type": "action",
},
+8 -4
View File
@@ -9,6 +9,9 @@ from typing import (
Type,
get_type_hints,
)
import contextlib
from enum import EnumMeta
from warnings import warn
from specklepy.logging.exceptions import SpeckleException
@@ -248,12 +251,15 @@ class Base(_RegisteringBase):
types = getattr(self, "_attr_types", {})
t = types.get(name, None)
if t is None:
if t is None or t is Any:
return 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 = (
@@ -275,13 +281,11 @@ class Base(_RegisteringBase):
if isinstance(t, tuple):
t = t[0]
try:
with contextlib.suppress(ValueError):
if t is float:
return float(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__}'"
+1 -1
View File
@@ -74,7 +74,7 @@ class ObjectArray:
index = 0
while index < len(data):
item_length = data[index]
item_length = int(data[index])
item_start = index + 1
item_end = item_start + item_length
item_data = data[item_start:item_end]
+10 -1
View File
@@ -1,5 +1,6 @@
from specklepy.objects.geometry import Point
from enum import Enum
from typing import List
from specklepy.objects.geometry import Point
from .base import Base
@@ -19,11 +20,19 @@ class FakeGeo(Base, chunkable={"dots": 50}, detachable={"pointslist"}):
dots: List[int] = None
class FakeDirection(Enum):
NORTH = 1
EAST = 2
SOUTH = 3
WEST = 4
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
+4 -4
View File
@@ -293,9 +293,9 @@ class Curve(
@classmethod
def from_list(cls, args: List[Any]) -> "Curve":
point_count = args[7]
weights_count = args[8]
knots_count = args[9]
point_count = int(args[7])
weights_count = int(args[8])
knots_count = int(args[9])
points_start = 10
weights_start = 10 + point_count
@@ -303,7 +303,7 @@ class Curve(
knots_end = knots_start + knots_count
return cls(
degree=args[1],
degree=int(args[1]),
periodic=bool(args[2]),
rational=bool(args[3]),
closed=bool(args[4]),
+19 -4
View File
@@ -1,4 +1,4 @@
from typing import List
from typing import Any, List
from specklepy.objects.geometry import Point, Vector
from .base import Base
@@ -55,10 +55,11 @@ class Transform(
def value(self, value: List[float]) -> None:
try:
value = [float(x) for x in value]
except (ValueError, TypeError):
except (ValueError, TypeError) as error:
raise ValueError(
f"Could not create a Transform object with the requested value. Input must be a 16 element list of numbers. Value provided: {value}"
)
) from error
if len(value) != 16:
raise ValueError(
f"Could not create a Transform object: input list should be 16 floats long, but was {len(value)} long"
@@ -196,4 +197,18 @@ class BlockInstance(
Base, speckle_type=OTHER + "BlockInstance", detachable={"blockDefinition"}
):
blockDefinition: BlockDefinition = None
transform: Transform = None
transform: 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
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
)
isShared: bool = False
isReadOnly: bool = False
isTypeParameter: bool = False
@@ -1,16 +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 (
SerializationException,
SpeckleException,
SpeckleWarning,
)
from specklepy.transports.abstract_transport import AbstractTransport
# import for serialization
import specklepy.objects.geometry
import specklepy.objects.other
@@ -49,9 +52,16 @@ class BaseObjectSerializer:
self.read_transport = read_transport
def write_json(self, base: Base):
self.__reset_writer()
self.detach_lineage = [True]
"""Serializes a given base object into a json string
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)
return hash, ujson.dumps(obj)
def traverse_base(self, base: Base) -> Tuple[str, Dict]:
@@ -63,6 +73,21 @@ class BaseObjectSerializer:
Returns:
(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:
self.detach_lineage = [True]
@@ -111,6 +136,11 @@ class BaseObjectSerializer:
object_builder[prop] = value
continue
# NOTE: for dynamic props, this won't be re-serialised as an enum but as an int
if isinstance(value, Enum):
object_builder[prop] = value.value
continue
# 2. handle Base objects
elif isinstance(value, Base):
child_obj = self.traverse_value(value, detach=detach)
@@ -135,7 +165,7 @@ class BaseObjectSerializer:
chunk_refs = []
for c in chunks:
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)
chunk_refs.append(ref_obj)
object_builder[prop] = chunk_refs
@@ -182,6 +212,10 @@ class BaseObjectSerializer:
if isinstance(obj, PRIMITIVES):
return obj
# NOTE: for dynamic props, this won't be re-serialised as an enum but as an int
if isinstance(obj, Enum):
return obj.value
elif isinstance(obj, (list, tuple, set)):
if not detach:
return [self.traverse_value(o) for o in obj]
@@ -190,7 +224,7 @@ class BaseObjectSerializer:
for o in obj:
if isinstance(o, Base):
self.detach_lineage.append(detach)
hash, _ = self.traverse_base(o)
hash, _ = self._traverse_base(o)
detached_list.append(self.detach_helper(ref_hash=hash))
else:
detached_list.append(self.traverse_value(o, detach))
@@ -206,7 +240,7 @@ class BaseObjectSerializer:
elif isinstance(obj, Base):
self.detach_lineage.append(detach)
_, base_obj = self.traverse_base(obj)
_, base_obj = self._traverse_base(obj)
return base_obj
else:
@@ -215,7 +249,7 @@ class BaseObjectSerializer:
except:
warn(
f"Failed to handle {type(obj)} in `BaseObjectSerializer.traverse_value`",
SerializationException,
SpeckleWarning,
)
return str(obj)
@@ -245,7 +279,7 @@ class BaseObjectSerializer:
def __reset_writer(self) -> None:
"""Reinitializes the lineage, and other variables that get used during the json writing process"""
self.detach_lineage = []
self.detach_lineage = [True]
self.lineage = []
self.family_tree = {}
self.closure_table = {}
@@ -311,12 +345,15 @@ class BaseObjectSerializer:
elif "referencedId" in value:
ref_hash = value["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}"
if ref_obj_str:
ref_obj = safe_json_loads(ref_obj_str, ref_hash)
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.recompose_base(obj=ref_obj))
base.__setattr__(prop, self.handle_value(value))
# 3. handle all other cases (base objects, lists, and dicts)
else:
@@ -370,8 +407,10 @@ class BaseObjectSerializer:
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}"
warnings.warn(
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)
+35 -61
View File
@@ -3,7 +3,7 @@ import sys
import time
import sched
import sqlite3
from typing import Any, List, Dict
from typing import Any, List, Dict, Tuple
from appdirs import user_data_dir
from contextlib import closing
from specklepy.transports.abstract_transport import AbstractTransport
@@ -14,25 +14,29 @@ class SQLiteTransport(AbstractTransport):
_name = "SQLite"
_base_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
app_name: str = ""
scope: str = ""
saved_obj_count: int = 0
max_size: int = None
_current_batch: List[Tuple[str, str]] = None
_current_batch_size: int = None
def __init__(
self,
base_path: str = None,
app_name: str = None,
scope: str = None,
max_batch_size_mb: float = 10.0,
**data: Any,
) -> None:
super().__init__(**data)
self.app_name = app_name or "Speckle"
self.scope = scope or "Objects"
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:
os.makedirs(self._base_path, exist_ok=True)
@@ -50,12 +54,6 @@ class SQLiteTransport(AbstractTransport):
def __repr__(self) -> str:
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
def get_base_path(app_name):
# from appdirs https://github.com/ActiveState/appdirs/blob/master/appdirs.py
@@ -74,36 +72,6 @@ class SQLiteTransport(AbstractTransport):
path = os.path.expanduser("~/.config/")
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(
self, id: str, source_transport: AbstractTransport
) -> None:
@@ -117,23 +85,41 @@ class SQLiteTransport(AbstractTransport):
self.save_object(id, serialized_object)
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:
id {str} -- the object id
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()
try:
with closing(self.__connection.cursor()) as c:
c.execute(
c.executemany(
"INSERT OR IGNORE INTO objects(hash, content) VALUES(?,?)",
(id, serialized_object),
self._current_batch,
)
self.__connection.commit()
except Exception as ex:
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:
@@ -156,10 +142,14 @@ class SQLiteTransport(AbstractTransport):
return ret
def begin_write(self):
self._object_cache = []
self.saved_obj_count = 0
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(
self, id: str, target_transport: AbstractTransport
@@ -199,19 +189,3 @@ class SQLiteTransport(AbstractTransport):
def __del__(self):
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()
+6 -3
View File
@@ -6,7 +6,7 @@ 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 FakeMesh
from specklepy.objects.fakemesh import FakeDirection, FakeMesh
from specklepy.logging import metrics
metrics.disable()
@@ -61,7 +61,7 @@ def second_user_dict(host):
@pytest.fixture(scope="session")
def client(host, user_dict):
client = SpeckleClient(host=host, use_ssl=False)
client.authenticate(user_dict["token"])
client.authenticate_with_token(user_dict["token"])
return client
@@ -81,9 +81,10 @@ def mesh():
mesh = FakeMesh()
mesh.name = "my_mesh"
mesh.vertices = [random.uniform(0, 10) for _ in range(1, 210)]
mesh.faces = [i for i in range(1, 210)]
mesh.faces = list(range(1, 210))
mesh["@(100)colours"] = [random.uniform(0, 10) for _ in range(1, 210)]
mesh["@()default_chunk"] = [random.uniform(0, 10) for _ in range(1, 210)]
mesh.cardinal_dir = FakeDirection.WEST
mesh.test_bases = [Base(name=f"test {i}") for i in range(1, 22)]
mesh.detach_this = Base(name="predefined detached base")
mesh["@detach"] = Base(name="detached base")
@@ -104,6 +105,8 @@ def base():
base.units = "millimetres"
base.null_val = None
base.null_dict = {"a null val": None}
base.tuple = (1, 2, "3")
base.set = {1, 2, "3"}
base.vertices = [random.uniform(0, 10) for _ in range(1, 120)]
base.test_bases = [Base(name=i) for i in range(1, 22)]
base["@detach"] = Base(name="detached base")
+12 -1
View File
@@ -1,5 +1,6 @@
from contextlib import ExitStack as does_not_raise
from enum import Enum
from typing import Dict, List, Optional
from contextlib import ExitStack as does_not_raise
import pytest
from specklepy.api import operations
@@ -95,6 +96,12 @@ def test_base_of_custom_speckle_type() -> None:
assert b1.name == "Tweety's Crib"
class DietaryRestrictions(Enum):
VEGAN = 1
GLUTEN_FREE = 2
NUT_FREE = 3
class FrozenYoghurt(Base):
"""Testing type checking"""
@@ -103,6 +110,7 @@ class FrozenYoghurt(Base):
customer: str
add_ons: Optional[Dict[str, float]] # dict item types won't be checked
price: float = 0.0
dietary: DietaryRestrictions
def test_type_checking() -> None:
@@ -111,6 +119,7 @@ def test_type_checking() -> None:
order.servings = 2
order.price = "7" # will get converted
order.customer = "izzy"
order.dietary = DietaryRestrictions.VEGAN
with pytest.raises(SpeckleException):
order.flavours = "not a list"
@@ -118,6 +127,8 @@ def test_type_checking() -> None:
order.servings = "five"
with pytest.raises(SpeckleException):
order.add_ons = ["sprinkles"]
with pytest.raises(SpeckleException):
order.dietary = "no nuts plz"
order.add_ons = {"sprinkles": 0.2, "chocolate": 1.0}
order.flavours = ["strawberry", "lychee", "peach", "pineapple"]
+27 -2
View File
@@ -1,5 +1,7 @@
import pytest
from specklepy.api.models import Stream
from datetime import datetime
from specklepy.api.models import ActivityCollection, Activity, Stream
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import GraphQLException
@@ -67,6 +69,16 @@ class TestStream:
assert len(search_results) == 1
assert search_results[0].name == updated_stream.name
def test_stream_favorite(self, client, stream):
favorited = client.stream.favorite(stream.id)
assert isinstance(favorited, Stream)
assert favorited.favoritedDate is not None
unfavorited = client.stream.favorite(stream.id, False)
assert isinstance(favorited, Stream)
assert unfavorited.favoritedDate is None
def test_stream_grant_permission(self, client, stream, second_user_dict):
granted = client.stream.grant_permission(
stream_id=stream.id,
@@ -87,9 +99,22 @@ class TestStream:
fetched_stream = client.stream.get(stream.id)
assert revoked == True
assert revoked is True
assert len(fetched_stream.collaborators) == 1
def test_stream_activity(self, client: SpeckleClient, stream: Stream):
activity = client.stream.activity(stream.id)
older_activity = client.stream.activity(
stream.id, before=activity.items[0].time
)
assert isinstance(activity, ActivityCollection)
assert isinstance(older_activity, ActivityCollection)
assert older_activity.totalCount < activity.totalCount
assert activity.items is not None
assert isinstance(activity.items[0], Activity)
def test_stream_delete(self, client, stream):
deleted = client.stream.delete(stream.id)
+17 -2
View File
@@ -1,6 +1,7 @@
from specklepy.logging.exceptions import SpeckleException
from specklepy.api.models import User
import pytest
from specklepy.api.client import SpeckleClient
from specklepy.api.models import Activity, ActivityCollection, User
from specklepy.logging.exceptions import SpeckleException
@pytest.mark.run(order=1)
@@ -43,3 +44,17 @@ class TestUser:
assert isinstance(failed_update, SpeckleException)
assert updated is True
assert me.bio == bio
def test_user_activity(self, client: SpeckleClient, second_user_dict):
my_activity = client.user.activity(limit=10)
their_activity = client.user.activity(second_user_dict["id"])
assert isinstance(my_activity, ActivityCollection)
assert isinstance(my_activity.items[0], Activity)
assert my_activity.totalCount > 0
assert isinstance(their_activity, ActivityCollection)
older_activity = client.user.activity(before=my_activity.items[0].time)
assert isinstance(older_activity, ActivityCollection)
assert older_activity.totalCount < my_activity.totalCount