Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e6e66a90a | |||
| 09d84cf64a | |||
| 3ccb0ae2a8 | |||
| 6028a38355 | |||
| 07418cfc9c | |||
| 1ada797d81 | |||
| 73703f6237 | |||
| 7644af22df | |||
| 564e1d4432 | |||
| fc4511ad02 | |||
| ad710b72da | |||
| 041d9f56ce | |||
| e1c0b705ad | |||
| 7b011b1122 | |||
| 3f09cd9d77 | |||
| 29a361892b | |||
| 2672b40aff | |||
| 35b6911b27 | |||
| a4f0a2cc2b | |||
| 1970890ecc | |||
| 13df5135b8 | |||
| 4e206b5c60 | |||
| e696091555 | |||
| a512dbb4e4 | |||
| 9a1f28516d | |||
| 92892b83d8 | |||
| 8904e9eeb4 | |||
| 68dc1794ee | |||
| 7e7940f25b | |||
| 29c97cde45 | |||
| 02f4f4fe41 | |||
| b2dd5bfedd | |||
| 09b3edcc23 | |||
| a44036863d | |||
| 5806c032dd | |||
| cff20aec54 | |||
| 144d51b147 | |||
| 09f61a6efd | |||
| e61bf0f78f | |||
| 6ac72ce8ee | |||
| 0b9ef942f5 | |||
| 6988eae46f | |||
| 31fa619f82 | |||
| 409ac68df0 | |||
| 81051a87c1 | |||
| fca386706b | |||
| 80036b0b98 | |||
| 92c9a0882e | |||
| 239c466264 | |||
| 7dd490b24f | |||
| 54c3d6fbaf | |||
| 7b7cd86f50 | |||
| 3fde452d1e | |||
| 113d1f1993 | |||
| d2bacb9ec2 | |||
| d461e64b97 | |||
| dc923c3105 | |||
| 46437e7af4 | |||
| 6ba632b14d | |||
| c67eb0520e | |||
| b7b171289c | |||
| c4ac12d9de | |||
| 6abeafdd9e | |||
| a016ed9201 | |||
| 9e0f71e5c0 | |||
| e02c4a76e8 | |||
| 8e14cf1904 |
+12
-9
@@ -4,7 +4,7 @@ orbs:
|
||||
python: circleci/python@1.3.2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
docker:
|
||||
- image: "cimg/python:<<parameters.tag>>"
|
||||
- image: "circleci/node:12"
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
POSTGRES_DB: speckle2_test
|
||||
POSTGRES_PASSWORD: speckle
|
||||
POSTGRES_USER: speckle
|
||||
- image: "speckle/speckle-server:5f8cf11cba07ea6a54000243f9cb343b61cbba13"
|
||||
- image: "speckle/speckle-server"
|
||||
command: ["bash", "-c", "/wait && node bin/www"]
|
||||
environment:
|
||||
POSTGRES_URL: "localhost"
|
||||
@@ -38,27 +38,30 @@ jobs:
|
||||
name: upgrade pip
|
||||
- python/install-packages:
|
||||
pkg-manager: poetry
|
||||
- run: poetry run pytest --version
|
||||
- run: poetry run pytest
|
||||
|
||||
deploy:
|
||||
docker:
|
||||
- image: "circleci/python:3.8"
|
||||
steps:
|
||||
- checkout
|
||||
- run: python patch_version.py $CIRCLE_TAG
|
||||
- run: poetry build
|
||||
- run: poetry publish --dry-run -u specklesystems -p $PYPI_PASSWORD
|
||||
- run: poetry publish -u specklesystems -p $PYPI_PASSWORD
|
||||
|
||||
workflows:
|
||||
main:
|
||||
jobs:
|
||||
- build:
|
||||
jobs:
|
||||
- test:
|
||||
matrix:
|
||||
parameters:
|
||||
tag: ["3.6", "3.7", "3.8", "3.9"]
|
||||
|
||||
publish:
|
||||
jobs:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- deploy:
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+(\.[0-9]+)*/
|
||||
|
||||
@@ -14,6 +14,9 @@ Comprehensive developer and user documentation can be found in our:
|
||||
#### 📚 [Speckle Docs website](https://speckle.guide/dev/)
|
||||
|
||||
## Developing & Debugging
|
||||
|
||||
### Installation
|
||||
|
||||
This project uses python-poetry for dependency management, make sure you follow the official [docs](https://python-poetry.org/docs/#installation) to get poetry.
|
||||
|
||||
To bootstrap the project environment run `$ poetry install`. This will create a new virtual-env for the project and install both the package and dev dependencies.
|
||||
@@ -24,6 +27,13 @@ To execute any python script run `$ poetry run python my_script.py`
|
||||
|
||||
> Alternatively you may roll your own virtual-env with either venv, virtualenv, pyenv-virtualenv etc. Poetry will play along an recognize if it is invoked from inside a virtual environment.
|
||||
|
||||
### Local Data Paths
|
||||
|
||||
It may be helpful to know where the local accounts and object cache dbs are stored. Depending on on your OS, you can find the dbs at:
|
||||
- Windows: `APPDATA` or `<USER>\AppData\Roaming\Speckle`
|
||||
- Linux: `$XDG_DATA_HOME` or by default `~/.local/share/Speckle`
|
||||
- Mac: `~/.config/Speckle`
|
||||
|
||||
## Overview of functionality
|
||||
|
||||
The `SpeckleClient` is the entry point for interacting with the GraphQL API. You'll need to have a running server to use this.
|
||||
@@ -35,7 +45,7 @@ from specklepy.api.credentials import get_default_account, get_local_accounts
|
||||
all_accounts = get_local_accounts() # get back a list
|
||||
account = get_default_account()
|
||||
|
||||
client = SpeckleClient(host="localhost:3000", use_ssl=False)
|
||||
client = SpeckleClient(host="speckle.xyz")
|
||||
# client = SpeckleClient(host="yourserver.com") or whatever your host is
|
||||
|
||||
client.authenticate(account.token)
|
||||
@@ -135,6 +145,10 @@ Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md)
|
||||
|
||||
The Speckle Community hangs out on [the forum](https://discourse.speckle.works), do join and introduce yourself & feel free to ask us questions!
|
||||
|
||||
## Security
|
||||
|
||||
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
|
||||
|
||||
## License
|
||||
|
||||
Unless otherwise described, the code in this repository is licensed under the Apache-2.0 License. Please note that some modules, extensions or code herein might be otherwise licensed. This is indicated either in the root of the containing folder under a different license file, or in the respective file's header. If you have any questions, don't hesitate to get in touch with us via [email](mailto:hello@speckle.systems).
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def patch(tag):
|
||||
print(f"Patching version: {tag}")
|
||||
|
||||
with open("pyproject.toml", "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
if "version" not in lines[2]:
|
||||
raise Exception(f"Invalid pyproject.toml. Could not patch version.")
|
||||
|
||||
lines[2] = f'version = "{tag}"\n'
|
||||
with open("pyproject.toml", "w") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
return
|
||||
|
||||
tag = sys.argv[1]
|
||||
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
|
||||
raise ValueError(f"Invalid tag provided: {tag}")
|
||||
|
||||
patch(tag)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Generated
+98
-84
@@ -1,6 +1,6 @@
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.7.3"
|
||||
version = "3.7.4.post0"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -9,7 +9,7 @@ python-versions = ">=3.6"
|
||||
[package.dependencies]
|
||||
async-timeout = ">=3.0,<4.0"
|
||||
attrs = ">=17.3.0"
|
||||
chardet = ">=2.0,<4.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"
|
||||
@@ -81,7 +81,7 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2020.12.5"
|
||||
version = "2021.5.30"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -89,11 +89,11 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "chardet"
|
||||
version = "3.0.4"
|
||||
version = "4.0.0"
|
||||
description = "Universal encoding detector for Python 2 and 3"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
@@ -121,28 +121,31 @@ python-versions = ">=3.6, <3.7"
|
||||
|
||||
[[package]]
|
||||
name = "gql"
|
||||
version = "3.0.0a5"
|
||||
version = "3.0.0a6"
|
||||
description = "GraphQL client for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
graphql-core = ">=3.1,<3.2"
|
||||
aiohttp = {version = ">=3.7.1,<3.8.0", optional = true, markers = "extra == \"all\""}
|
||||
graphql-core = ">=3.1.5,<3.2"
|
||||
requests = {version = ">=2.23,<3", optional = true, markers = "extra == \"all\""}
|
||||
websockets = {version = ">=9,<10", optional = true, markers = "extra == \"all\""}
|
||||
yarl = ">=1.6,<2.0"
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp (>=3.7.1,<3.8.0)"]
|
||||
all = ["aiohttp (>=3.7.1,<3.8.0)", "requests (>=2.23,<3)", "websockets (>=8.1,<9)"]
|
||||
dev = ["aiohttp (>=3.7.1,<3.8.0)", "requests (>=2.23,<3)", "websockets (>=8.1,<9)", "black (==19.10b0)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mypy (==0.770)", "sphinx (>=3.0.0,<4)", "sphinx_rtd_theme (>=0.4,<1)", "sphinx-argparse (==0.2.5)", "parse (==1.15.0)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)"]
|
||||
all = ["aiohttp (>=3.7.1,<3.8.0)", "requests (>=2.23,<3)", "websockets (>=9,<10)"]
|
||||
dev = ["aiohttp (>=3.7.1,<3.8.0)", "requests (>=2.23,<3)", "websockets (>=9,<10)", "black (==19.10b0)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mypy (==0.770)", "sphinx (>=3.0.0,<4)", "sphinx_rtd_theme (>=0.4,<1)", "sphinx-argparse (==0.2.5)", "parse (==1.15.0)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles"]
|
||||
requests = ["requests (>=2.23,<3)"]
|
||||
test = ["aiohttp (>=3.7.1,<3.8.0)", "requests (>=2.23,<3)", "websockets (>=8.1,<9)", "parse (==1.15.0)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)"]
|
||||
test_no_transport = ["parse (==1.15.0)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)"]
|
||||
websockets = ["websockets (>=8.1,<9)"]
|
||||
test = ["aiohttp (>=3.7.1,<3.8.0)", "requests (>=2.23,<3)", "websockets (>=9,<10)", "parse (==1.15.0)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles"]
|
||||
test_no_transport = ["parse (==1.15.0)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "aiofiles"]
|
||||
websockets = ["websockets (>=9,<10)"]
|
||||
|
||||
[[package]]
|
||||
name = "graphql-core"
|
||||
version = "3.1.2"
|
||||
version = "3.1.5"
|
||||
description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -370,7 +373,7 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.3"
|
||||
version = "1.26.5"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -383,7 +386,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "8.1"
|
||||
version = "9.1"
|
||||
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -417,47 +420,47 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.6.5"
|
||||
content-hash = "5f77d3e90d0b7a555d5e53d35caedc4d539d21f9c67805219fb94dbb99b186e1"
|
||||
content-hash = "84e846c1bb02924ceada07406e95032e0632d229a36657ba2b85129e68f1526d"
|
||||
|
||||
[metadata.files]
|
||||
aiohttp = [
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:328b552513d4f95b0a2eea4c8573e112866107227661834652a8984766aa7656"},
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c733ef3bdcfe52a1a75564389bad4064352274036e7e234730526d155f04d914"},
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2858b2504c8697beb9357be01dc47ef86438cc1cb36ecb6991796d19475faa3e"},
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:d2cfac21e31e841d60dc28c0ec7d4ec47a35c608cb8906435d47ef83ffb22150"},
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:3228b7a51e3ed533f5472f54f70fd0b0a64c48dc1649a0f0e809bec312934d7a"},
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:dcc119db14757b0c7bce64042158307b9b1c76471e655751a61b57f5a0e4d78e"},
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:7d9b42127a6c0bdcc25c3dcf252bb3ddc70454fac593b1b6933ae091396deb13"},
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-win32.whl", hash = "sha256:df48a623c58180874d7407b4d9ec06a19b84ed47f60a3884345b1a5099c1818b"},
|
||||
{file = "aiohttp-3.7.3-cp36-cp36m-win_amd64.whl", hash = "sha256:0b795072bb1bf87b8620120a6373a3c61bfcb8da7e5c2377f4bb23ff4f0b62c9"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:0d438c8ca703b1b714e82ed5b7a4412c82577040dadff479c08405e2a715564f"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8389d6044ee4e2037dca83e3f6994738550f6ee8cfb746762283fad9b932868f"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3ea8c252d8df5e9166bcf3d9edced2af132f4ead8ac422eac723c5781063709a"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:78e2f18a82b88cbc37d22365cf8d2b879a492faedb3f2975adb4ed8dfe994d3a"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:df3a7b258cc230a65245167a202dd07320a5af05f3d41da1488ba0fa05bc9347"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:f326b3c1bbfda5b9308252ee0dcb30b612ee92b0e105d4abec70335fab5b1245"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5e479df4b2d0f8f02133b7e4430098699450e1b2a826438af6bec9a400530957"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-win32.whl", hash = "sha256:6d42debaf55450643146fabe4b6817bb2a55b23698b0434107e892a43117285e"},
|
||||
{file = "aiohttp-3.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9c58b0b84055d8bc27b7df5a9d141df4ee6ff59821f922dd73155861282f6a3"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:f411cb22115cb15452d099fec0ee636b06cf81bfb40ed9c02d30c8dc2bc2e3d1"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1e0920909d916d3375c7a1fdb0b1c78e46170e8bb42792312b6eb6676b2f87f"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:59d11674964b74a81b149d4ceaff2b674b3b0e4d0f10f0be1533e49c4a28408b"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:41608c0acbe0899c852281978492f9ce2c6fbfaf60aff0cefc54a7c4516b822c"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:16a3cb5df5c56f696234ea9e65e227d1ebe9c18aa774d36ff42f532139066a5f"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:6ccc43d68b81c424e46192a778f97da94ee0630337c9bbe5b2ecc9b0c1c59001"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:d03abec50df423b026a5aa09656bd9d37f1e6a49271f123f31f9b8aed5dc3ea3"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-win32.whl", hash = "sha256:39f4b0a6ae22a1c567cb0630c30dd082481f95c13ca528dc501a7766b9c718c0"},
|
||||
{file = "aiohttp-3.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:c68fdf21c6f3573ae19c7ee65f9ff185649a060c9a06535e9c3a0ee0bbac9235"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:710376bf67d8ff4500a31d0c207b8941ff4fba5de6890a701d71680474fe2a60"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2406dc1dda01c7f6060ab586e4601f18affb7a6b965c50a8c90ff07569cf782a"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:2a7b7640167ab536c3cb90cfc3977c7094f1c5890d7eeede8b273c175c3910fd"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:684850fb1e3e55c9220aad007f8386d8e3e477c4ec9211ae54d968ecdca8c6f9"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:1edfd82a98c5161497bbb111b2b70c0813102ad7e0aa81cbeb34e64c93863005"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:77149002d9386fae303a4a162e6bce75cc2161347ad2ba06c2f0182561875d45"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:756ae7efddd68d4ea7d89c636b703e14a0c686688d42f588b90778a3c2fc0564"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-win32.whl", hash = "sha256:3b0036c978cbcc4a4512278e98e3e6d9e6b834dc973206162eddf98b586ef1c6"},
|
||||
{file = "aiohttp-3.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:e1b95972a0ae3f248a899cdbac92ba2e01d731225f566569311043ce2226f5e7"},
|
||||
{file = "aiohttp-3.7.3.tar.gz", hash = "sha256:9c1a81af067e72261c9cbe33ea792893e83bc6aa987bfbd6fdc1e5e7b22777c4"},
|
||||
{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"},
|
||||
@@ -479,12 +482,12 @@ black = [
|
||||
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
|
||||
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
|
||||
{file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
|
||||
{file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
|
||||
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
|
||||
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
|
||||
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
||||
@@ -499,11 +502,11 @@ dataclasses = [
|
||||
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
|
||||
]
|
||||
gql = [
|
||||
{file = "gql-3.0.0a5.tar.gz", hash = "sha256:a87bc42934a70902c005a317567cc57e460b160a9c606db8be62e618e29c336c"},
|
||||
{file = "gql-3.0.0a6.tar.gz", hash = "sha256:bdcbf60bc37b11d6d2f2ed271f69292c4e96d56df7000ba1dad52e487330bdce"},
|
||||
]
|
||||
graphql-core = [
|
||||
{file = "graphql-core-3.1.2.tar.gz", hash = "sha256:c056424cbdaa0ff67446e4379772f43746bad50a44ec23d643b9bdcd052f5b3a"},
|
||||
{file = "graphql_core-3.1.2-py3-none-any.whl", hash = "sha256:b1826fbd1c6c290f7180d758ecf9c3859a46574cff324bf35a10167533c0e463"},
|
||||
{file = "graphql-core-3.1.5.tar.gz", hash = "sha256:a755635d1d364a17e8d270347000722351aaa03f1ab7d280878aae82fc68b1f3"},
|
||||
{file = "graphql_core-3.1.5-py3-none-any.whl", hash = "sha256:91d96ef0e86665777bb7115d3bbb6b0326f43dc7dbcdd60da5486a27a50cfb11"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||
@@ -709,32 +712,43 @@ typing-extensions = [
|
||||
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"},
|
||||
{file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"},
|
||||
{file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"},
|
||||
{file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"},
|
||||
]
|
||||
websockets = [
|
||||
{file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"},
|
||||
{file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"},
|
||||
{file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"},
|
||||
{file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"},
|
||||
{file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"},
|
||||
{file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"},
|
||||
{file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"},
|
||||
{file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"},
|
||||
{file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"},
|
||||
{file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"},
|
||||
{file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"},
|
||||
{file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"},
|
||||
{file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"},
|
||||
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
|
||||
{file = "websockets-9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da"},
|
||||
{file = "websockets-9.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b4ad84b156cf50529b8ac5cc1638c2cf8680490e3fccb6121316c8c02620a2e4"},
|
||||
{file = "websockets-9.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2cf04601633a4ec176b9cc3d3e73789c037641001dbfaf7c411f89cd3e04fcaf"},
|
||||
{file = "websockets-9.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5c8f0d82ea2468282e08b0cf5307f3ad022290ed50c45d5cb7767957ca782880"},
|
||||
{file = "websockets-9.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:caa68c95bc1776d3521f81eeb4d5b9438be92514ec2a79fececda814099c8314"},
|
||||
{file = "websockets-9.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d2c2d9b24d3c65b5a02cac12cbb4e4194e590314519ed49db2f67ef561c3cf58"},
|
||||
{file = "websockets-9.1-cp36-cp36m-win32.whl", hash = "sha256:f31722f1c033c198aa4a39a01905951c00bd1c74f922e8afc1b1c62adbcdd56a"},
|
||||
{file = "websockets-9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3ddff38894c7857c476feb3538dd847514379d6dc844961dc99f04b0384b1b1b"},
|
||||
{file = "websockets-9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:51d04df04ed9d08077d10ccbe21e6805791b78eac49d16d30a1f1fe2e44ba0af"},
|
||||
{file = "websockets-9.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f68c352a68e5fdf1e97288d5cec9296664c590c25932a8476224124aaf90dbcd"},
|
||||
{file = "websockets-9.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b43b13e5622c5a53ab12f3272e6f42f1ce37cd5b6684b2676cb365403295cd40"},
|
||||
{file = "websockets-9.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9147868bb0cc01e6846606cd65cbf9c58598f187b96d14dd1ca17338b08793bb"},
|
||||
{file = "websockets-9.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:836d14eb53b500fd92bd5db2fc5894f7c72b634f9c2a28f546f75967503d8e25"},
|
||||
{file = "websockets-9.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:48c222feb3ced18f3dc61168ca18952a22fb88e5eb8902d2bf1b50faefdc34a2"},
|
||||
{file = "websockets-9.1-cp37-cp37m-win32.whl", hash = "sha256:900589e19200be76dd7cbaa95e9771605b5ce3f62512d039fb3bc5da9014912a"},
|
||||
{file = "websockets-9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ab5ee15d3462198c794c49ccd31773d8a2b8c17d622aa184f669d2b98c2f0857"},
|
||||
{file = "websockets-9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85e701a6c316b7067f1e8675c638036a796fe5116783a4c932e7eb8e305a3ffe"},
|
||||
{file = "websockets-9.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b2e71c4670ebe1067fa8632f0d081e47254ee2d3d409de54168b43b0ba9147e0"},
|
||||
{file = "websockets-9.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:230a3506df6b5f446fed2398e58dcaafdff12d67fe1397dff196411a9e820d02"},
|
||||
{file = "websockets-9.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:7df3596838b2a0c07c6f6d67752c53859a54993d4f062689fdf547cb56d0f84f"},
|
||||
{file = "websockets-9.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:826ccf85d4514609219725ba4a7abd569228c2c9f1968e8be05be366f68291ec"},
|
||||
{file = "websockets-9.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0dd4eb8e0bbf365d6f652711ce21b8fd2b596f873d32aabb0fbb53ec604418cc"},
|
||||
{file = "websockets-9.1-cp38-cp38-win32.whl", hash = "sha256:1d0971cc7251aeff955aa742ec541ee8aaea4bb2ebf0245748fbec62f744a37e"},
|
||||
{file = "websockets-9.1-cp38-cp38-win_amd64.whl", hash = "sha256:7189e51955f9268b2bdd6cc537e0faa06f8fffda7fb386e5922c6391de51b077"},
|
||||
{file = "websockets-9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9e5fd6dbdf95d99bc03732ded1fc8ef22ebbc05999ac7e0c7bf57fe6e4e5ae2"},
|
||||
{file = "websockets-9.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9e7fdc775fe7403dbd8bc883ba59576a6232eac96dacb56512daacf7af5d618d"},
|
||||
{file = "websockets-9.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:597c28f3aa7a09e8c070a86b03107094ee5cdafcc0d55f2f2eac92faac8dc67d"},
|
||||
{file = "websockets-9.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:ad893d889bc700a5835e0a95a3e4f2c39e91577ab232a3dc03c262a0f8fc4b5c"},
|
||||
{file = "websockets-9.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:1d6b4fddb12ab9adf87b843cd4316c4bd602db8d5efd2fb83147f0458fe85135"},
|
||||
{file = "websockets-9.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:ebf459a1c069f9866d8569439c06193c586e72c9330db1390af7c6a0a32c4afd"},
|
||||
{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"},
|
||||
]
|
||||
yarl = [
|
||||
{file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"},
|
||||
|
||||
+1
-4
@@ -13,11 +13,8 @@ homepage = "https://speckle.systems/"
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.6.5"
|
||||
pydantic = "^1.7.3"
|
||||
gql = "^3.0.0a5"
|
||||
aiohttp = "^3.7.3"
|
||||
appdirs = "^1.4.4"
|
||||
requests = "^2.25.1"
|
||||
websockets = "^8.1"
|
||||
gql = {version = ">=3.0.0a6", extras = ["all"], allow-prereleases = true}
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = "^20.8b1"
|
||||
|
||||
@@ -13,6 +13,7 @@ from specklepy.api.resources import (
|
||||
user,
|
||||
subscriptions,
|
||||
)
|
||||
from specklepy.api.models import ServerInfo
|
||||
from gql import Client, gql
|
||||
from gql.transport.requests import RequestsHTTPTransport
|
||||
from gql.transport.aiohttp import AIOHTTPTransport
|
||||
@@ -20,6 +21,33 @@ from gql.transport.websockets import WebsocketsTransport
|
||||
|
||||
|
||||
class SpeckleClient:
|
||||
"""
|
||||
The `SpeckleClient` is your entry point for interacting with your Speckle Server's GraphQL API.
|
||||
You'll need to have access to a server to use it, or you can use our public server `speckle.xyz`.
|
||||
|
||||
To authenticate the client, you'll need to have downloaded the [Speckle Manager](https://speckle.guide/#speckle-manager)
|
||||
and added your account.
|
||||
|
||||
```py
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.api.credentials import get_default_account
|
||||
|
||||
# initialise the client
|
||||
client = SpeckleClient(host="speckle.xyz") # or whatever your host is
|
||||
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
|
||||
|
||||
# authenticate the client with a token (account has been added in Speckle Manager)
|
||||
account = get_default_account()
|
||||
client.authenticate(token=account.token)
|
||||
|
||||
# create a new stream. this returns the stream id
|
||||
new_stream_id = client.stream.create(name="a shiny new stream")
|
||||
|
||||
# use that stream id to get the stream from the server
|
||||
new_stream = client.stream.get(id=new_stream_id)
|
||||
```
|
||||
"""
|
||||
|
||||
DEFAULT_HOST = "speckle.xyz"
|
||||
USE_SSL = True
|
||||
|
||||
@@ -46,6 +74,19 @@ class SpeckleClient:
|
||||
|
||||
self._init_resources()
|
||||
|
||||
# Check compatibility with the server
|
||||
try:
|
||||
serverInfo = self.server.get()
|
||||
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)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"SpeckleClient( server: {self.url}, authenticated: {self.me is not None} )"
|
||||
)
|
||||
|
||||
def authenticate(self, token: str) -> None:
|
||||
"""Authenticate the client using a personal access token
|
||||
The token is saved in the client object and a synchronous GraphQL entrypoint is created
|
||||
|
||||
+136
-10
@@ -1,9 +1,13 @@
|
||||
from typing import List, Optional
|
||||
import os
|
||||
from specklepy.transports.server.server import ServerTransport
|
||||
from warnings import warn
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from urllib.parse import urlparse, unquote
|
||||
from specklepy.api.models import ServerInfo
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.transports.sqlite import SQLiteTransport
|
||||
|
||||
account_storage = SQLiteTransport(scope="Accounts")
|
||||
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
@@ -14,12 +18,12 @@ class UserInfo(BaseModel):
|
||||
|
||||
|
||||
class Account(BaseModel):
|
||||
isDefault: bool
|
||||
isDefault: bool = None
|
||||
token: str
|
||||
refreshToken: str
|
||||
refreshToken: str = None
|
||||
serverInfo: ServerInfo
|
||||
userInfo: UserInfo
|
||||
id: str
|
||||
id: str = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Account(email: {self.userInfo.email}, server: {self.serverInfo.url}, isDefault: {self.isDefault})"
|
||||
@@ -28,23 +32,48 @@ class Account(BaseModel):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
def get_local_accounts() -> List[Account]:
|
||||
def get_local_accounts(base_path: str = None) -> List[Account]:
|
||||
"""Gets all the accounts present in this environment
|
||||
|
||||
Arguments:
|
||||
base_path {str} -- custom base path if you are not using the system default
|
||||
|
||||
Returns:
|
||||
List[Account] -- list of all local accounts or an empty list if no accounts were found
|
||||
"""
|
||||
account_storage = SQLiteTransport(scope="Accounts", base_path=base_path)
|
||||
json_path = os.path.join(account_storage._base_path, "Accounts")
|
||||
os.makedirs(json_path, exist_ok=True)
|
||||
json_acct_files = [file for file in os.listdir(json_path) if file.endswith(".json")]
|
||||
|
||||
accounts = []
|
||||
res = account_storage.get_all_objects()
|
||||
return [Account.parse_raw(r[1]) for r in res] if res else []
|
||||
if res:
|
||||
accounts.extend(Account.parse_raw(r[1]) for r in res)
|
||||
if json_acct_files:
|
||||
try:
|
||||
accounts.extend(
|
||||
Account.parse_file(os.path.join(json_path, json_file))
|
||||
for json_file in json_acct_files
|
||||
)
|
||||
except Exception as ex:
|
||||
raise SpeckleException(
|
||||
"Invalid json accounts could not be read. Please fix or remove them.",
|
||||
ex,
|
||||
)
|
||||
|
||||
return accounts
|
||||
|
||||
|
||||
def get_default_account() -> Account:
|
||||
def get_default_account(base_path: str = None) -> Account:
|
||||
"""Gets this environment's default account if any. If there is no default, the first found will be returned and set as default.
|
||||
Arguments:
|
||||
base_path {str} -- custom base path if you are not using the system default
|
||||
|
||||
Returns:
|
||||
Account -- the default account or None if no local accounts were found
|
||||
"""
|
||||
accounts = get_local_accounts()
|
||||
accounts = get_local_accounts(base_path=base_path)
|
||||
if not accounts:
|
||||
return None
|
||||
|
||||
@@ -54,3 +83,100 @@ def get_default_account() -> Account:
|
||||
default.isDefault = True
|
||||
|
||||
return default
|
||||
|
||||
|
||||
class StreamWrapper:
|
||||
stream_url: str = None
|
||||
use_ssl: bool = True
|
||||
host: str = None
|
||||
stream_id: str = None
|
||||
commit_id: str = None
|
||||
object_id: str = None
|
||||
branch_name: str = None
|
||||
client: SpeckleClient = None
|
||||
account: Account = None
|
||||
|
||||
def __repr__(self):
|
||||
return f"StreamWrapper( server: {self.host}, stream_id: {self.stream_id}, type: {self.type} )"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
if self.object_id:
|
||||
return "object"
|
||||
elif self.commit_id:
|
||||
return "commit"
|
||||
elif self.branch_name:
|
||||
return "branch"
|
||||
else:
|
||||
return "stream" if self.stream_id else "invalid"
|
||||
|
||||
def __init__(self, url: str) -> None:
|
||||
self.stream_url = url
|
||||
parsed = urlparse(url)
|
||||
self.host = parsed.netloc
|
||||
self.use_ssl = parsed.scheme == "https"
|
||||
segments = parsed.path.strip("/").split("/")
|
||||
|
||||
if not segments or len(segments) > 4 or len(segments) < 2:
|
||||
raise SpeckleException(
|
||||
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
|
||||
)
|
||||
|
||||
while segments:
|
||||
segment = segments.pop(0)
|
||||
if segments and segment.lower() == "streams":
|
||||
self.stream_id = segments.pop(0)
|
||||
elif segments and segment.lower() == "commits":
|
||||
self.commit_id = segments.pop(0)
|
||||
elif segments and segment.lower() == "branches":
|
||||
self.branch_name = unquote(segments.pop(0))
|
||||
elif segments and segment.lower() == "objects":
|
||||
self.object_id = segments.pop(0)
|
||||
elif segment.lower() == "globals":
|
||||
self.branch_name = "globals"
|
||||
if segments:
|
||||
self.commit_id = segments.pop(0)
|
||||
else:
|
||||
raise SpeckleException(
|
||||
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
|
||||
)
|
||||
|
||||
if not self.stream_id:
|
||||
raise SpeckleException(
|
||||
f"Cannot parse {url} into a stream wrapper class - no stream id found."
|
||||
)
|
||||
|
||||
def get_account(self) -> Account:
|
||||
if self.account:
|
||||
return self.account
|
||||
|
||||
self.account = next(
|
||||
(a for a in get_local_accounts() if self.host in a.serverInfo.url),
|
||||
None,
|
||||
)
|
||||
|
||||
return self.account
|
||||
|
||||
def get_client(self) -> SpeckleClient:
|
||||
if self.client:
|
||||
return self.client
|
||||
|
||||
if not self.account:
|
||||
self.get_account()
|
||||
|
||||
self.client = SpeckleClient(host=self.host, use_ssl=self.use_ssl)
|
||||
|
||||
if self.account is None:
|
||||
warn(f"No local account found for server {self.host}", SpeckleWarning)
|
||||
return self.client
|
||||
|
||||
self.client.authenticate(acct.token)
|
||||
return self.client
|
||||
|
||||
def get_transport(self) -> ServerTransport:
|
||||
if not self.client:
|
||||
self.get_client()
|
||||
return ServerTransport(self.client, self.stream_id)
|
||||
@@ -22,6 +22,7 @@ class Commit(BaseModel):
|
||||
authorName: Optional[str]
|
||||
authorId: Optional[str]
|
||||
authorAvatar: Optional[str]
|
||||
branchName: Optional[str]
|
||||
createdAt: Optional[str]
|
||||
sourceApplication: Optional[str]
|
||||
referencedObject: Optional[str]
|
||||
@@ -115,3 +116,4 @@ class ServerInfo(BaseModel):
|
||||
roles: Optional[List[dict]]
|
||||
scopes: Optional[List[dict]]
|
||||
authStrategies: Optional[List[dict]]
|
||||
version: Optional[str]
|
||||
|
||||
@@ -52,7 +52,8 @@ def receive(
|
||||
Arguments:
|
||||
obj_id {str} -- the id of the object to receive
|
||||
remote_transport {Transport} -- the transport to receive from
|
||||
local_transport {Transport} -- the transport to send from
|
||||
local_transport {Transport} -- the local cache to check for existing objects
|
||||
(defaults to `SQLiteTransport`)
|
||||
|
||||
Returns:
|
||||
Base -- the base object
|
||||
@@ -97,9 +98,7 @@ def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str
|
||||
return serializer.write_json(base)[1]
|
||||
|
||||
|
||||
def deserialize(
|
||||
obj_string: str, read_transport: AbstractTransport = SQLiteTransport()
|
||||
) -> Base:
|
||||
def deserialize(obj_string: str, read_transport: AbstractTransport = None) -> Base:
|
||||
"""
|
||||
Deserialize a string object into a Base object. If the object contains referenced child objects that are not stored in the local db, a read transport needs to be provided in order to recompose the base with the children objects.
|
||||
|
||||
@@ -111,6 +110,9 @@ def deserialize(
|
||||
Returns:
|
||||
Base -- the deserialized object
|
||||
"""
|
||||
if not read_transport:
|
||||
read_transport = SQLiteTransport()
|
||||
|
||||
serializer = BaseObjectSerializer(read_transport=read_transport)
|
||||
|
||||
return serializer.read_json(obj_string=obj_string)
|
||||
|
||||
@@ -5,7 +5,7 @@ import pkgutil
|
||||
from importlib import import_module
|
||||
|
||||
|
||||
for (_, name, _) in pkgutil.iter_modules([Path(__file__).parent]):
|
||||
for (_, name, _) in pkgutil.iter_modules(__path__):
|
||||
|
||||
imported_module = import_module("." + name, package=__name__)
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ class Resource(ResourceBase):
|
||||
authorId
|
||||
authorName
|
||||
authorAvatar
|
||||
branchName
|
||||
createdAt
|
||||
sourceApplication
|
||||
totalChildrenCount
|
||||
|
||||
@@ -32,6 +32,7 @@ class Resource(ResourceBase):
|
||||
description
|
||||
adminContact
|
||||
canonicalUrl
|
||||
version
|
||||
roles {
|
||||
name
|
||||
description
|
||||
|
||||
@@ -28,3 +28,8 @@ class GraphQLException(SpeckleException):
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"GraphQLException: {self.message}"
|
||||
|
||||
|
||||
class SpeckleWarning(Warning):
|
||||
def __init__(self, *args: object) -> None:
|
||||
super().__init__(*args)
|
||||
@@ -98,7 +98,7 @@ class Base(_RegisteringBase):
|
||||
@classmethod
|
||||
def validate_prop_name(cls, name: str) -> None:
|
||||
"""Validator for dynamic attribute names."""
|
||||
if name in ("", "@"):
|
||||
if name in {"", "@"}:
|
||||
raise ValueError("Invalid Name: Base member names cannot be empty strings")
|
||||
if name.startswith("@@"):
|
||||
raise ValueError(
|
||||
@@ -172,7 +172,7 @@ class Base(_RegisteringBase):
|
||||
if not name.startswith("_")
|
||||
and name
|
||||
!= "fields" # soon to be removed as this pydantic prop is depreciated
|
||||
and isinstance(getattr(self, name, None), property)
|
||||
and isinstance(getattr(type(self), name, None), property)
|
||||
]
|
||||
return attrs + properties
|
||||
|
||||
@@ -215,15 +215,11 @@ class Base(_RegisteringBase):
|
||||
return 0
|
||||
parsed.append(base)
|
||||
|
||||
count = 0
|
||||
|
||||
for name, value in base.__dict__.items():
|
||||
if name.startswith("@"):
|
||||
continue
|
||||
else:
|
||||
count += self._handle_object_count(value, parsed)
|
||||
|
||||
return count
|
||||
return sum(
|
||||
self._handle_object_count(value, parsed)
|
||||
for name, value in base.__dict__.items()
|
||||
if not name.startswith("@")
|
||||
)
|
||||
|
||||
def _handle_object_count(self, obj: Any, parsed: List) -> int:
|
||||
count = 0
|
||||
|
||||
@@ -6,6 +6,7 @@ UNITS_STRINGS = {
|
||||
"mm": ["mm", "mil", "millimeters", "millimetres"],
|
||||
"cm": ["cm", "centimetre", "centimeter", "centimetres", "centimeters"],
|
||||
"m": ["m", "meter", "meters", "metre", "metres"],
|
||||
"km": ["km", "kilometer", "kilometre", "kilometers", "kilometres"],
|
||||
"in": ["in", "inch", "inches"],
|
||||
"ft": ["ft", "foot", "feet"],
|
||||
"yd": ["yd", "yard", "yards"],
|
||||
|
||||
@@ -277,8 +277,8 @@ class BaseObjectSerializer:
|
||||
base.totalChildrenCount = len(closure)
|
||||
|
||||
for prop, value in obj.items():
|
||||
# 1. handle primitives (ints, floats, strings, and bools)
|
||||
if isinstance(value, PRIMITIVES):
|
||||
# 1. handle primitives (ints, floats, strings, and bools) or None
|
||||
if isinstance(value, PRIMITIVES) or value is None:
|
||||
base.__setattr__(prop, value)
|
||||
continue
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Optional, List, Dict
|
||||
from pydantic import BaseModel
|
||||
from pydantic.main import Extra
|
||||
|
||||
@@ -64,6 +64,18 @@ class AbstractTransport(ABC, BaseModel):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
|
||||
"""Checks the presence of multiple objects.
|
||||
|
||||
Arguments:
|
||||
id_list -- List of object id to be checked
|
||||
|
||||
Returns:
|
||||
Dict[str, bool] -- keys: input ids, values: whether the transport has that object
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def copy_object_and_children(
|
||||
self, id: str, target_transport: "AbstractTransport"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import json
|
||||
from typing import Any
|
||||
from typing import Any, List, Dict
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.transports.abstract_transport import AbstractTransport
|
||||
|
||||
@@ -33,6 +33,9 @@ class MemoryTransport(AbstractTransport):
|
||||
else:
|
||||
return None
|
||||
|
||||
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
|
||||
return {id: (id in self.objects) for id in id_list}
|
||||
|
||||
def begin_write(self) -> None:
|
||||
self.saved_object_count = 0
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
import queue
|
||||
@@ -13,13 +14,15 @@ LOG = logging.getLogger(__name__)
|
||||
class BatchSender(object):
|
||||
def __init__(
|
||||
self,
|
||||
endpoint,
|
||||
server_url,
|
||||
stream_id,
|
||||
token,
|
||||
max_batch_size_mb=1,
|
||||
batch_buffer_length=10,
|
||||
thread_count=4,
|
||||
):
|
||||
self.endpoint = endpoint
|
||||
self.server_url = server_url
|
||||
self.stream_id = stream_id
|
||||
self._token = token
|
||||
|
||||
self.max_size = int(max_batch_size_mb * 1000 * 1000)
|
||||
@@ -31,18 +34,18 @@ class BatchSender(object):
|
||||
self._send_threads = []
|
||||
self._exception = None
|
||||
|
||||
def send_object(self, obj: str):
|
||||
def send_object(self, id: str, obj: str):
|
||||
if not self._send_threads:
|
||||
self._create_threads()
|
||||
|
||||
crt_obj_size = len(obj)
|
||||
if not self._crt_batch or self._crt_batch_size + crt_obj_size < self.max_size:
|
||||
self._crt_batch.append(obj)
|
||||
self._crt_batch.append((id, obj))
|
||||
self._crt_batch_size += crt_obj_size
|
||||
return
|
||||
|
||||
self._batches.put(self._crt_batch)
|
||||
self._crt_batch = [obj]
|
||||
self._crt_batch = [(id, obj)]
|
||||
self._crt_batch_size = crt_obj_size
|
||||
|
||||
def flush(self):
|
||||
@@ -88,15 +91,29 @@ class BatchSender(object):
|
||||
LOG.error("ServerTransport sending thread error: " + str(ex))
|
||||
|
||||
def _bg_send_batch(self, session, batch):
|
||||
upload_data = "[" + ",".join(batch) + "]"
|
||||
object_ids = [obj[0] for obj in batch]
|
||||
server_has_object = session.post(
|
||||
url=f"{self.server_url}/api/diff/{self.stream_id}",
|
||||
data={"objects": json.dumps(object_ids)},
|
||||
).json()
|
||||
|
||||
new_object_ids = [x for x in object_ids if not server_has_object[x]]
|
||||
new_object_ids = set(new_object_ids)
|
||||
new_objects = [obj[1] for obj in batch if obj[0] in new_object_ids]
|
||||
|
||||
if not new_objects:
|
||||
LOG.info(f"Uploading batch of {len(batch)} objects: all objects are already in the server")
|
||||
return
|
||||
|
||||
upload_data = "[" + ",".join(new_objects) + "]"
|
||||
upload_data_gzip = gzip.compress(upload_data.encode())
|
||||
LOG.info(
|
||||
"Uploading batch of %s objects (size: %s, compressed size: %s)"
|
||||
% (len(batch), len(upload_data), len(upload_data_gzip))
|
||||
"Uploading batch of %s objects (%s new): (size: %s, compressed size: %s)"
|
||||
% (len(batch), len(new_objects), len(upload_data), len(upload_data_gzip))
|
||||
)
|
||||
|
||||
r = session.post(
|
||||
url=self.endpoint,
|
||||
url=f"{self.server_url}/objects/{self.stream_id}",
|
||||
files={"batch-1": ("batch-1", upload_data_gzip, "application/gzip")},
|
||||
)
|
||||
if r.status_code != 201:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import json
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
from typing import Any, Dict, List, Type
|
||||
@@ -19,12 +22,15 @@ class ServerTransport(AbstractTransport):
|
||||
def __init__(self, client: SpeckleClient, stream_id: str, **data: Any) -> None:
|
||||
super().__init__(**data)
|
||||
# TODO: replace client with account or some other auth avenue
|
||||
if not client.me:
|
||||
raise SpeckleException("The provided SpeckleClient was not authenticated.")
|
||||
self.url = client.url
|
||||
self.stream_id = stream_id
|
||||
|
||||
token = client.me["token"]
|
||||
endpoint = f"{self.url}/objects/{self.stream_id}"
|
||||
self._batch_sender = BatchSender(endpoint, token, max_batch_size_mb=1)
|
||||
self._batch_sender = BatchSender(
|
||||
self.url, self.stream_id, token, max_batch_size_mb=1
|
||||
)
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update(
|
||||
@@ -38,7 +44,7 @@ class ServerTransport(AbstractTransport):
|
||||
self._batch_sender.flush()
|
||||
|
||||
def save_object(self, id: str, serialized_object: str) -> None:
|
||||
self._batch_sender.send_object(serialized_object)
|
||||
self._batch_sender.send_object(id, serialized_object)
|
||||
|
||||
def save_object_from_transport(
|
||||
self, id: str, source_transport: AbstractTransport
|
||||
@@ -59,26 +65,50 @@ class ServerTransport(AbstractTransport):
|
||||
NotImplementedError,
|
||||
)
|
||||
|
||||
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
|
||||
return {id: False for id in id_list}
|
||||
|
||||
def copy_object_and_children(
|
||||
self, id: str, target_transport: AbstractTransport
|
||||
) -> str:
|
||||
endpoint = f"{self.url}/objects/{self.stream_id}/{id}"
|
||||
r = self.session.get(endpoint, stream=True)
|
||||
endpoint = f"{self.url}/objects/{self.stream_id}/{id}/single"
|
||||
r = self.session.get(endpoint)
|
||||
if r.encoding is None:
|
||||
r.encoding = "utf-8"
|
||||
|
||||
if r.status_code != 200:
|
||||
raise SpeckleException(
|
||||
f"Can't get object {self.stream_id}/{id}: HTTP error {r.status_code} ({r.text[:1000]})"
|
||||
)
|
||||
root_obj_serialized = r.text
|
||||
root_obj = json.loads(root_obj_serialized)
|
||||
closures = root_obj.get("__closure", {})
|
||||
|
||||
# Check which children are not already in the target transport
|
||||
children_ids = list(closures.keys())
|
||||
children_found_map = target_transport.has_objects(children_ids)
|
||||
new_children_ids = [
|
||||
id for id in children_found_map if not children_found_map[id]
|
||||
]
|
||||
|
||||
# Get the new children
|
||||
endpoint = f"{self.url}/api/getobjects/{self.stream_id}"
|
||||
r = self.session.post(
|
||||
endpoint, data={"objects": json.dumps(new_children_ids)}, stream=True
|
||||
)
|
||||
if r.encoding is None:
|
||||
r.encoding = "utf-8"
|
||||
lines = r.iter_lines(decode_unicode=True)
|
||||
|
||||
# save first (root) obj for return
|
||||
root_hash, root_obj = next(lines).split("\t")
|
||||
target_transport.save_object(root_hash, root_obj)
|
||||
|
||||
# iter through returned objects saving them as we go
|
||||
for line in lines:
|
||||
if line:
|
||||
hash, obj = line.split("\t")
|
||||
target_transport.save_object(hash, obj)
|
||||
|
||||
return root_obj
|
||||
target_transport.save_object(id, root_obj_serialized)
|
||||
|
||||
return root_obj_serialized
|
||||
|
||||
# async def stream_res(self, endpoint: str) -> str:
|
||||
# data = b""
|
||||
|
||||
@@ -3,22 +3,21 @@ import sys
|
||||
import time
|
||||
import sched
|
||||
import sqlite3
|
||||
from typing import Any
|
||||
from typing import Any, List, Dict
|
||||
from appdirs import user_data_dir
|
||||
from contextlib import closing
|
||||
from multiprocessing import Process, Queue
|
||||
from specklepy.transports.abstract_transport import AbstractTransport
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
|
||||
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
|
||||
__queue: Queue = Queue()
|
||||
app_name: str = ""
|
||||
scope: str = ""
|
||||
saved_obj_count: int = 0
|
||||
@@ -33,12 +32,20 @@ class SQLiteTransport(AbstractTransport):
|
||||
super().__init__(**data)
|
||||
self.app_name = app_name or "Speckle"
|
||||
self.scope = scope or "Objects"
|
||||
base_path = base_path or self.__get_base_path()
|
||||
self._base_path = base_path or self.__get_base_path()
|
||||
|
||||
os.makedirs(base_path, exist_ok=True)
|
||||
try:
|
||||
os.makedirs(self._base_path, exist_ok=True)
|
||||
|
||||
self._root_path = os.path.join(os.path.join(base_path, f"{self.scope}.db"))
|
||||
self.__initialise()
|
||||
self._root_path = os.path.join(
|
||||
os.path.join(self._base_path, f"{self.scope}.db")
|
||||
)
|
||||
self.__initialise()
|
||||
except Exception as ex:
|
||||
raise SpeckleException(
|
||||
f"SQLiteTransport could not initialise {self.scope}.db at {self._base_path}. Either provide a different `base_path` or use an alternative transport.",
|
||||
ex,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"SQLiteTransport(app: '{self.app_name}', scope: '{self.scope}')"
|
||||
@@ -60,26 +67,26 @@ class SQLiteTransport(AbstractTransport):
|
||||
if os_name.startswith("Mac"):
|
||||
system = "darwin"
|
||||
|
||||
if system == "darwin":
|
||||
path = os.path.expanduser("~/.config/")
|
||||
return os.path.join(path, self.app_name)
|
||||
else:
|
||||
if system != "darwin":
|
||||
return user_data_dir(appname=self.app_name, appauthor=False, roaming=True)
|
||||
|
||||
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
|
||||
path = os.path.expanduser("~/.config/")
|
||||
return os.path.join(path, self.app_name)
|
||||
|
||||
self._scheduler.enter(
|
||||
delay=self._polling_interval, priority=1, action=self.__consume_queue
|
||||
)
|
||||
self._scheduler.run(blocking=True)
|
||||
# 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.
|
||||
@@ -99,15 +106,14 @@ class SQLiteTransport(AbstractTransport):
|
||||
def save_object_from_transport(
|
||||
self, id: str, source_transport: AbstractTransport
|
||||
) -> None:
|
||||
"""Adds an object from the given transport to the queue and schedules it to be saved.
|
||||
"""Adds an object from the given transport to the the local db
|
||||
|
||||
Arguments:
|
||||
id {str} -- the object id
|
||||
source_transport {AbstractTransport) -- the transport through which the object can be found
|
||||
"""
|
||||
serialized_object = source_transport.get_object(id)
|
||||
self.__queue.put((id, serialized_object))
|
||||
raise NotImplementedError
|
||||
self.save_object(id, serialized_object)
|
||||
|
||||
def save_object(self, id: str, serialized_object: str) -> None:
|
||||
"""Directly saves an object into the database.
|
||||
@@ -137,6 +143,17 @@ class SQLiteTransport(AbstractTransport):
|
||||
).fetchone()
|
||||
return row[1] if row else None
|
||||
|
||||
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
|
||||
ret = {}
|
||||
self.__check_connection()
|
||||
with closing(self.__connection.cursor()) as c:
|
||||
for id in id_list:
|
||||
row = c.execute(
|
||||
"SELECT 1 FROM objects WHERE hash = ? LIMIT 1", (id,)
|
||||
).fetchone()
|
||||
ret[id] = bool(row)
|
||||
return ret
|
||||
|
||||
def begin_write(self):
|
||||
self.saved_obj_count = 0
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import pytest
|
||||
from specklepy.api.credentials import StreamWrapper
|
||||
|
||||
|
||||
class TestWrapper:
|
||||
def test_parse_stream(self):
|
||||
wrap = StreamWrapper("https://testing.speckle.dev/streams/a75ab4f10f")
|
||||
assert wrap.type == "stream"
|
||||
|
||||
def test_parse_branch(self):
|
||||
wacky_wrap = StreamWrapper(
|
||||
"https://testing.speckle.dev/streams/4c3ce1459c/branches/%F0%9F%8D%95%E2%AC%85%F0%9F%8C%9F%20you%20wat%3F"
|
||||
)
|
||||
wrap = StreamWrapper(
|
||||
"https://testing.speckle.dev/streams/4c3ce1459c/branches/next%20level"
|
||||
)
|
||||
assert wacky_wrap.type == "branch"
|
||||
assert wacky_wrap.branch_name == "🍕⬅🌟 you wat?"
|
||||
assert wrap.type == "branch"
|
||||
|
||||
def test_parse_commit(self):
|
||||
wrap = StreamWrapper(
|
||||
"https://testing.speckle.dev/streams/4c3ce1459c/commits/8b9b831792"
|
||||
)
|
||||
assert wrap.type == "commit"
|
||||
|
||||
def test_parse_object(self):
|
||||
wrap = StreamWrapper(
|
||||
"https://testing.speckle.dev/streams/a75ab4f10f/objects/5530363e6d51c904903dafc3ea1d2ec6"
|
||||
)
|
||||
assert wrap.type == "object"
|
||||
|
||||
def test_parse_globals_as_branch(self):
|
||||
wrap = StreamWrapper("https://testing.speckle.dev/streams/0c6ad366c4/globals/")
|
||||
assert wrap.type == "branch"
|
||||
|
||||
def test_parse_globals_as_commit(self):
|
||||
wrap = StreamWrapper(
|
||||
"https://testing.speckle.dev/streams/0c6ad366c4/globals/abd3787893"
|
||||
)
|
||||
assert wrap.type == "commit"
|
||||
Reference in New Issue
Block a user