Compare commits
540 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e26e1077e0 | |||
| aa739c30c6 | |||
| 0a321c66fe | |||
| c112f46f01 | |||
| a4f764e178 | |||
| 59f5ee5452 | |||
| f8b057b990 | |||
| e2ba8b144a | |||
| 8d320abe00 | |||
| b77e346736 | |||
| 9fa6b661eb | |||
| 0d1c2735d8 | |||
| 8f3a683851 | |||
| aa8c7b6f42 | |||
| 68036ee130 | |||
| 447f28c9f1 | |||
| 1e7291277e | |||
| 46773aa9d3 | |||
| 480ea91ebb | |||
| d1adccba00 | |||
| 9e30250446 | |||
| 3e7d657e2e | |||
| 7484d8441b | |||
| 682afce05f | |||
| 21b27e2f3b | |||
| 69cd9706cf | |||
| 98075fa2cf | |||
| 782f70fb49 | |||
| 52ab27e60f | |||
| 61e7ebeabd | |||
| 3a8121c306 | |||
| 209a95879f | |||
| 4f829d9908 | |||
| ac5345f528 | |||
| 1142481d89 | |||
| b4690f082f | |||
| 81a98ea938 | |||
| 9b387da77a | |||
| d0724c7d06 | |||
| 1414a3611b | |||
| a553c17c43 | |||
| 0be3fac6ab | |||
| 944e70221e | |||
| 21f13c4750 | |||
| be85ddd159 | |||
| 77c538ced9 | |||
| ee55680b03 | |||
| 0728239915 | |||
| 77016e6f0b | |||
| ce39aa5101 | |||
| f32196ce1b | |||
| 6b24e187a5 | |||
| 129d25df0e | |||
| fa31bd0223 | |||
| 21209b384d | |||
| 1a9c95871f | |||
| dc4d583121 | |||
| ed39f0288f | |||
| 3830706eb1 | |||
| f7ae62ade2 | |||
| 38ffbc27b7 | |||
| 8cebccf250 | |||
| 17aac0b552 | |||
| c281a329a4 | |||
| ca472716db | |||
| af50afe3ff | |||
| b6493df77f | |||
| 59d3c8c3ea | |||
| 4e3405f1fb | |||
| 3772c10b31 | |||
| 242be2fa60 | |||
| 49eabdd712 | |||
| 96a31f0678 | |||
| 91506b0b20 | |||
| b0de9e31b5 | |||
| 2075783134 | |||
| 071f2449c3 | |||
| ffa4f29200 | |||
| 40a691b098 | |||
| 487ce3aeb4 | |||
| 6c0f10ae45 | |||
| 436b26c91c | |||
| f7bac26aed | |||
| a31c049b51 | |||
| a419664461 | |||
| 4a0c07009b | |||
| 682bcbfa9f | |||
| ccf284e8fa | |||
| 23102a28ff | |||
| 5475edb253 | |||
| c52f80c1ef | |||
| 21eecfa24c | |||
| 5dde1bfcf1 | |||
| 82c9d874c9 | |||
| 9acf2c8a92 | |||
| 95012e60c1 | |||
| 19b6500bbd | |||
| 47a06e4630 | |||
| e5a8b40bb2 | |||
| 219456f5f8 | |||
| d1544ae89f | |||
| 8f7d4b2ca7 | |||
| a7d31d4983 | |||
| a89b12a02c | |||
| 15ae68f5d7 | |||
| 0709cd99b5 | |||
| faf06f7141 | |||
| b54e09f811 | |||
| 55b7e0d732 | |||
| 45c922679b | |||
| b1c149382a | |||
| 393e98c8c2 | |||
| 8376329cbb | |||
| 1567fe9e68 | |||
| 364b826a1b | |||
| 297dbab479 | |||
| 81680ed766 | |||
| c934720bb0 | |||
| 9297a5df49 | |||
| 7b8bf49769 | |||
| c834496b72 | |||
| f49491611f | |||
| 19b83ba191 | |||
| 8d81aab1ac | |||
| 16868fbf3b | |||
| 00892fc838 | |||
| 4987b33de2 | |||
| 1c0d6ce8f4 | |||
| 1431e306b8 | |||
| 766f1fa840 | |||
| 69a5248abb | |||
| 5c93e4f9dc | |||
| e20b9b73c9 | |||
| c06b20a963 | |||
| 5bc6b8c4ed | |||
| 3005e421a6 | |||
| 8fb03972d5 | |||
| 02702190c9 | |||
| 2bd31ae954 | |||
| d0f8f95e4e | |||
| fc3ae3b98e | |||
| a6b19025e6 | |||
| 2be82f0874 | |||
| 70191b97a2 | |||
| dd2825272d | |||
| 9303af6827 | |||
| 973dc07d5b | |||
| 7dd5b7a2a1 | |||
| f259f256c7 | |||
| 08986056a3 | |||
| f89b07eacb | |||
| c973d916b3 | |||
| 4ff6288317 | |||
| 8566674f2e | |||
| 1f3b6da9c7 | |||
| 5d99d5fcad | |||
| 4fc07f33d0 | |||
| 4e23a69b89 | |||
| 04a0ddc8c4 | |||
| 1b4d43e0aa | |||
| f78c8c407f | |||
| 892c11f38f | |||
| 72639bf4bb | |||
| b2c210abc1 | |||
| 2250e8a897 | |||
| cb07f55551 | |||
| d1b3d5e25e | |||
| 79cca557f5 | |||
| 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 | |||
| 83bca13c8b | |||
| 46437e7af4 | |||
| 6ba632b14d | |||
| c67eb0520e | |||
| b7b171289c | |||
| c4ac12d9de | |||
| 6abeafdd9e | |||
| a016ed9201 | |||
| 9e0f71e5c0 | |||
| e02c4a76e8 | |||
| 8e14cf1904 | |||
| 1c868b6b60 | |||
| 809dff5fb0 | |||
| e5074acff9 | |||
| 85d2065c29 | |||
| bb48054fea | |||
| 5a9b4137da | |||
| 6c7dc1eed9 | |||
| d47bf7d842 | |||
| d07bab61e7 | |||
| 3f7290ed12 | |||
| ed92678a31 | |||
| 0428d2149f | |||
| da2d646f78 | |||
| a6543b122f | |||
| 4e26ebfbe2 | |||
| 4f79698987 | |||
| 2c10134703 | |||
| 27346b6d38 | |||
| 816f4373dd | |||
| 434ada8bc1 | |||
| 066bc44457 | |||
| 633267b025 | |||
| bd4ae7c5c5 | |||
| 501f9b8648 | |||
| 7d2a03e19d | |||
| c6c7d1731a | |||
| a839073968 | |||
| 883ec6e6ae | |||
| 87002257be | |||
| 1bc0cf04f3 | |||
| d0e2350ab7 | |||
| 255681a887 | |||
| 614eefc393 | |||
| d5b506f298 | |||
| dcd28b5d79 | |||
| c739594ba8 | |||
| 1d4867fb9b | |||
| ec94a42ac6 | |||
| 6e64770380 | |||
| ef92127ed2 | |||
| fb8a40bf76 | |||
| 81fab3ec6b | |||
| 9a0c36c665 | |||
| 995526c786 | |||
| 5a66d912ae | |||
| 78abbb18c4 | |||
| 26abdc952a | |||
| 9a0207ba09 | |||
| 4369f8c5c8 | |||
| bc432c2f13 | |||
| d1d3876902 | |||
| b1b144969b | |||
| 4e3ee488be | |||
| b2e2455b16 | |||
| ede286f4c0 | |||
| 56547a4573 | |||
| 13886b9caf | |||
| 5342cc4827 | |||
| 6fe338628d | |||
| 48883466fb | |||
| 47917d99b0 | |||
| 7703de0a37 | |||
| 81b96cb6ac | |||
| 1bcef9faf6 | |||
| 2334aefb09 | |||
| 800b0018a0 | |||
| e6cf22e97a | |||
| f5abcec6d0 | |||
| fcd54a0899 | |||
| adfba846ce | |||
| 4933ca4abf | |||
| 8ce595cb4a | |||
| 1114b210f9 | |||
| 00c1e378d5 | |||
| 1291b5799b | |||
| 75fb2da7c2 | |||
| d760fa47ec | |||
| e61968d88c | |||
| 01e3e5aa47 | |||
| 7f2c26eb6b | |||
| 5f35da3be9 | |||
| 98c3b89d75 | |||
| 5796ff6cdb | |||
| df4706aa56 | |||
| 855d499eb8 | |||
| a19ce15168 | |||
| b2d89cad21 | |||
| f6c28689ce | |||
| 5b3076b775 | |||
| 16511f8dc0 | |||
| 8a673a54a9 | |||
| f98b40cce3 | |||
| 3a76358557 | |||
| b6b25f824e | |||
| aa032895b6 | |||
| 6f871f0523 | |||
| 5e7b3712b7 | |||
| 1db0fca68c | |||
| dff3496712 | |||
| dbcb691fbc | |||
| 4572d15b10 | |||
| 779c4e6d70 | |||
| 12ba5b347e | |||
| 8dc3355f51 | |||
| 9e095e8673 | |||
| 089c10facd | |||
| 236d53f78a | |||
| 257c32665e | |||
| 82f7ee6f0a | |||
| 748faf277d | |||
| 90f5e7602c | |||
| 6b4dc9a6b2 | |||
| efa8c46b61 | |||
| 9527425c38 | |||
| 6ab9252398 | |||
| 661c25c8fb | |||
| 28ee9ec201 | |||
| 6155962bd8 | |||
| 607ad2c721 | |||
| 7866e1082e | |||
| 0bd4b3b093 | |||
| 94f47eeb17 | |||
| 11fee54d36 | |||
| b6943bbe6f | |||
| 8b25fa1470 | |||
| 617e202a02 | |||
| 76da3cacfb | |||
| b3e9f425c0 | |||
| 739837bad2 | |||
| 0110b5b11a | |||
| 0d31337350 | |||
| 12eb88819e | |||
| 3d7d138841 | |||
| 60bc4df16a | |||
| d99c05761d | |||
| dc326e6c6b | |||
| eb10e93300 | |||
| 64434cd7f5 | |||
| 35a121abd9 | |||
| fe5e7b19b0 | |||
| adae615e29 | |||
| 203230fd8b | |||
| 64df2f241c | |||
| 110f10623e | |||
| 4dda9b8fe5 | |||
| f494a663de | |||
| ff44f4ecb1 | |||
| 8da5086419 | |||
| f46dd7e0c6 | |||
| dac3a86bcc | |||
| c7a603ad45 | |||
| aaaf323b43 | |||
| 0173e76c76 | |||
| 3c2a437f98 | |||
| 282623ce10 | |||
| 0409f2c0f3 | |||
| 12528205f5 | |||
| 063191e961 | |||
| f9da5422c6 | |||
| 41e66dda0d | |||
| 5eee3fee07 | |||
| e14c42dfd1 | |||
| 062808c568 | |||
| 8b9f2db176 | |||
| 5b458c630e | |||
| 25c4b35eb3 | |||
| 21b1b2c30a | |||
| 1a063bbbf7 | |||
| beb550624e | |||
| 50c6e2c840 | |||
| 3f986fced3 | |||
| ca234536bc | |||
| f155129f7c | |||
| 37d628d27b | |||
| 7b6e0aa6ed | |||
| 2d449a328b | |||
| ad47f649db | |||
| 750657db19 | |||
| 453a2e4211 | |||
| 2ca184538d | |||
| 79a832360b | |||
| b2945ad3ff | |||
| 8d3e511d18 | |||
| 3e7b620e1e | |||
| 028ca641ef | |||
| 11bc10d072 | |||
| 28e68e090c | |||
| 6eb73555ed | |||
| e6727a9552 | |||
| b36e7e000f | |||
| 2dba909eba | |||
| 21abd5181a | |||
| 969b6a92e5 | |||
| 77dcf53c4b | |||
| 7fbfdb4b92 | |||
| a4f7ce326e | |||
| a3830a95fd | |||
| 78068d0098 | |||
| 36d5abf7d4 | |||
| bce4490882 | |||
| 6d4fc17b4c | |||
| 827df8e574 | |||
| 075d77fea6 | |||
| 6b276243de | |||
| e671a6d086 | |||
| 824c0d8401 | |||
| 48e4ad1e93 | |||
| cf2c9d12a5 | |||
| 578ffdb8c5 | |||
| f58c13c0d1 | |||
| c8210342c2 | |||
| c6d6a3e025 | |||
| ba7911bcf5 | |||
| 2e428f9b3c | |||
| 3c8aff3487 | |||
| 9657bd370c | |||
| fd0d04b70e | |||
| 7310fbfdd4 | |||
| 90cae9a3c5 | |||
| 01091405d1 | |||
| cff738aa31 | |||
| 30a3cd8f05 | |||
| c9746a6d57 | |||
| 251f8fb330 | |||
| 0b74502dd7 | |||
| a06e682698 | |||
| f258de4794 | |||
| 9320b566f3 | |||
| 79ac5366a6 | |||
| 6cc909ddd9 | |||
| 0dca458a0a | |||
| e92562ccf3 | |||
| 6ae95e0f9c | |||
| 32954964f0 | |||
| 2f8ecf7430 | |||
| ad0a01c5ce | |||
| def3d3c27b | |||
| 6cde8d6e8a | |||
| d63d24201e | |||
| dd5b355305 | |||
| db7ef190fd | |||
| c20c805e19 | |||
| 665f6b8c32 | |||
| 9f5c453228 | |||
| 3d7fc85a79 | |||
| 99a80ba85e | |||
| 5d059b6770 | |||
| ee22740a93 | |||
| 0e2105c56e | |||
| 1d7b120f26 | |||
| b00cc3d08e | |||
| de4ea698b8 | |||
| 91bf8c111d | |||
| 72dfd4807e | |||
| e2079ff6a9 | |||
| cb5cbeaad6 | |||
| 18744d218a | |||
| 5f05e9853f | |||
| 7295689e12 | |||
| 09ce21e475 | |||
| 3ff444aa52 | |||
| 799d9428ec | |||
| ea8be095ed | |||
| 135c7215f5 | |||
| eee726e252 | |||
| 79dba3318f | |||
| 60d253343c | |||
| 2ea43d54d6 | |||
| 37480b1c9e | |||
| b1660d5dbf | |||
| 8b48167dca | |||
| f3dbddb6e1 | |||
| 19da6c0f5f | |||
| a8c75b0000 | |||
| 4e8c3cbb08 | |||
| d01d7824bc | |||
| f643ee8e89 | |||
| 9a27ed1544 | |||
| 162f999100 | |||
| 2765c4fa69 | |||
| 69cb2c79c7 | |||
| e2daad36e9 | |||
| d6b06298ed | |||
| 7ddd827340 | |||
| 2a30278e04 | |||
| ecd9089e29 | |||
| bcecaef380 | |||
| 8e986e59aa | |||
| 9e110a125b | |||
| ec8635401b | |||
| f7b867c219 | |||
| e69310619e | |||
| 4a924593b3 | |||
| 1f57e81ddc | |||
| e42a3d4147 | |||
| d3d53ef6a5 | |||
| acb7156bf2 | |||
| 42cda6a477 | |||
| c1dfe5f11f | |||
| 7e57b4cfb6 | |||
| b87237b88f | |||
| fb797e64cb | |||
| 040a49baea | |||
| 105ae0316c |
@@ -0,0 +1,79 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
python: circleci/python@2.0.3
|
||||
codecov: codecov/codecov@3.2.2
|
||||
|
||||
jobs:
|
||||
test:
|
||||
docker:
|
||||
- image: "cimg/python:<<parameters.tag>>"
|
||||
- image: "cimg/node:16.15"
|
||||
- image: "cimg/redis:6.2"
|
||||
- image: "cimg/postgres:14.2"
|
||||
environment:
|
||||
POSTGRES_DB: speckle2_test
|
||||
POSTGRES_PASSWORD: speckle
|
||||
POSTGRES_USER: speckle
|
||||
- image: "speckle/speckle-server"
|
||||
command: ["bash", "-c", "/wait && node bin/www"]
|
||||
environment:
|
||||
POSTGRES_URL: "localhost"
|
||||
POSTGRES_USER: "speckle"
|
||||
POSTGRES_PASSWORD: "speckle"
|
||||
POSTGRES_DB: "speckle2_test"
|
||||
REDIS_URL: "redis://localhost"
|
||||
SESSION_SECRET: "keyboard cat"
|
||||
STRATEGY_LOCAL: "true"
|
||||
CANONICAL_URL: "http://localhost:3000"
|
||||
WAIT_HOSTS: localhost:5432, localhost:6379
|
||||
DISABLE_FILE_UPLOADS: "true"
|
||||
parameters:
|
||||
tag:
|
||||
default: "3.8"
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: python --version
|
||||
- run:
|
||||
command: python -m pip install --upgrade pip
|
||||
name: upgrade pip
|
||||
- python/install-packages:
|
||||
pkg-manager: poetry
|
||||
- run: poetry run pytest --cov --cov-report xml:reports/coverage.xml --junitxml=reports/test-results.xml
|
||||
|
||||
- store_test_results:
|
||||
path: reports
|
||||
|
||||
- store_artifacts:
|
||||
path: reports
|
||||
|
||||
- codecov/upload
|
||||
|
||||
deploy:
|
||||
docker:
|
||||
- image: "cimg/python:3.8"
|
||||
steps:
|
||||
- checkout
|
||||
- run: python patch_version.py $CIRCLE_TAG
|
||||
- run: poetry build
|
||||
- run: poetry publish -u specklesystems -p $PYPI_PASSWORD
|
||||
|
||||
workflows:
|
||||
main:
|
||||
jobs:
|
||||
- test:
|
||||
matrix:
|
||||
parameters:
|
||||
tag: ["3.7", "3.8", "3.9", "3.10"]
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- deploy:
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/ # For testing only! /ci\/.*/
|
||||
@@ -0,0 +1,27 @@
|
||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/.devcontainer/base.Dockerfile
|
||||
|
||||
# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6
|
||||
ARG VARIANT="3.10"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
|
||||
|
||||
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
|
||||
ARG NODE_VERSION="16"
|
||||
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
||||
|
||||
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
|
||||
# COPY requirements.txt /tmp/pip-tmp/
|
||||
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
|
||||
# && rm -rf /tmp/pip-tmp
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment this line to install global node packages.
|
||||
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
|
||||
|
||||
USER vscode
|
||||
|
||||
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
|
||||
|
||||
ENV PATH=$PATH:$HOME/.poetry/env
|
||||
@@ -0,0 +1,55 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.191.1/containers/python-3
|
||||
{
|
||||
"name": "Python 3",
|
||||
// "build": {
|
||||
// "dockerfile": "Dockerfile",
|
||||
// "context": "..",
|
||||
// "args": {
|
||||
// // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9
|
||||
// "VARIANT": "3.6",
|
||||
// // Options
|
||||
// "NODE_VERSION": "lts/*"
|
||||
// }
|
||||
// },
|
||||
"dockerComposeFile": "./docker-compose.yaml",
|
||||
"service": "specklepy",
|
||||
"workspaceFolder": "/workspaces/specklepy",
|
||||
"shutdownAction": "stopCompose",
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.languageServer": "Pylance",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.pylintArgs": [
|
||||
"--max-line-length=120"
|
||||
],
|
||||
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
|
||||
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
|
||||
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
|
||||
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
|
||||
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
|
||||
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
|
||||
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
|
||||
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
|
||||
"python.testing.pytestArgs": [
|
||||
"tests/",
|
||||
"-s"
|
||||
],
|
||||
"python.testing.pytestEnabled": true,
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance"
|
||||
],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "poetry config virtualenvs.create false && poetry install",
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
version: "3.3" # optional since v1.27.0
|
||||
services:
|
||||
postgres:
|
||||
image: cimg/postgres:14.2
|
||||
environment:
|
||||
POSTGRES_DB: speckle2_test
|
||||
POSTGRES_PASSWORD: speckle
|
||||
POSTGRES_USER: speckle
|
||||
network_mode: host
|
||||
redis:
|
||||
image: cimg/redis:6.2
|
||||
network_mode: host
|
||||
speckle-server:
|
||||
image: speckle/speckle-server:latest
|
||||
command: ["bash", "-c", "/wait && node bin/www"]
|
||||
environment:
|
||||
POSTGRES_URL: "localhost"
|
||||
POSTGRES_USER: "speckle"
|
||||
POSTGRES_PASSWORD: "speckle"
|
||||
POSTGRES_DB: "speckle2_test"
|
||||
REDIS_URL: "redis://localhost"
|
||||
SESSION_SECRET: "keyboard cat"
|
||||
STRATEGY_LOCAL: "true"
|
||||
CANONICAL_URL: "http://localhost:3000"
|
||||
WAIT_HOSTS: localhost:5432, localhost:6379
|
||||
DISABLE_FILE_UPLOADS: "true"
|
||||
network_mode: host
|
||||
|
||||
specklepy:
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
args:
|
||||
VARIANT: 3.9
|
||||
NODE_VERSION: lts/*
|
||||
volumes:
|
||||
# Mounts the project folder to '/workspace'. While this file is in .devcontainer,
|
||||
# mounts are relative to the first file in the list, which is a level up.
|
||||
- ..:/workspaces/specklepy:cached
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||
network_mode: host
|
||||
# networks:
|
||||
# default:
|
||||
@@ -0,0 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
+12
-11
@@ -1,4 +1,5 @@
|
||||
# Speckle Contribution Guidelines
|
||||
|
||||
## Introduction
|
||||
|
||||
Thank you for reading this! Speckle's a rather wide network of parts that depend on each other, either directly, indirectly or even just cosmetically.
|
||||
@@ -9,41 +10,41 @@ This means that what might look like a simple quick change in one repo may have
|
||||
|
||||
## Bugs & Issues 🐞
|
||||
|
||||
### Found a new bug?
|
||||
### Found a new bug?
|
||||
|
||||
- First step is to check whether this is a new bug! We encourage you to search through the issues of the project in question **and** associated repos!
|
||||
|
||||
- If you come up with nothing, **open a new issue with a clear title and description**, as much relevant information as possible: system configuration, code samples & steps to reproduce the problem.
|
||||
- If you come up with nothing, **open a new issue with a clear title and description**, as much relevant information as possible: system configuration, code samples & steps to reproduce the problem.
|
||||
|
||||
- Can't mention this often enough: tells us how to reproduce the problem! We will ignore or flag as such issues without reproduction steps.
|
||||
- Can't mention this often enough: tells us how to reproduce the problem! We will ignore or flag as such issues without reproduction steps.
|
||||
|
||||
- Try to reference & note all potentially affected projects.
|
||||
|
||||
### Sending a PR for Bug Fixes
|
||||
|
||||
You fixed something! Great! We hope you logged it first :) Make sure though that you've covered the lateral thinking needed for a bug report, as described above, also in your implementation! If there any tests, make sure they all pass. If there are none, it means they're missing - so add them!
|
||||
You fixed something! Great! We hope you logged it first :) Make sure though that you've covered the lateral thinking needed for a bug report, as described above, also in your implementation! If there any tests, make sure they all pass. If there are none, it means they're missing - so add them!
|
||||
|
||||
## New Features 🎉
|
||||
|
||||
The golden rule is to Discuss First!
|
||||
|
||||
- Before embarking on adding a new feature, suggest it first as an issue with the `enhancement` label and/or title - this will allow relevant people to pitch in
|
||||
- We'll now discuss your requirements and see how and if they fit within the Speckle ecosystem.
|
||||
- The last step is to actually start writing code & submit a PR so we can follow along!
|
||||
- All new features should, if and where possible, come with tests. We won't merge without!
|
||||
- We'll now discuss your requirements and see how and if they fit within the Speckle ecosystem.
|
||||
- The last step is to actually start writing code & submit a PR so we can follow along!
|
||||
- All new features should, if and where possible, come with tests. We won't merge without!
|
||||
|
||||
> Many clients may potentially have overlapping scopes, some features might already be in dev somewhere else, or might have been postponed to the next major release due to api instability in that area. For example, adding a delete stream button in the accounts panel in rhino: this feature was planned for speckle admin, and the whole functionality of the accounts panel in rhino is to be greatly reduced!
|
||||
|
||||
## Cosmetic Patches ✨
|
||||
|
||||
Changes that are cosmetic in nature and do not add anything substantial to the stability or functionality of Speckle **will generally not be accepted**.
|
||||
Changes that are cosmetic in nature and do not add anything substantial to the stability or functionality of Speckle **will generally not be accepted**.
|
||||
|
||||
Why? However trivial the changes might seem, there might be subtle reasons for the original code to be as it is. Furthermore, there are a lot of potential hidden costs (that even maintainers themselves are not aware of fully!) and they eat up review time unncessarily.
|
||||
|
||||
> **Examples**: modifying the colour of an UI element in one client may have a big hidden cost and need propagation in several other clients that implement a similar ui element. Changing the default port or specifiying `localhost` instead of `0.0.0.0` breaks cross-vm debugging and developing.
|
||||
|
||||
> **Examples**: modifying the colour of an UI element in one client may have a big hidden cost and need propagation in several other clients that implement a similar ui element. Changing the default port or specifiying `localhost` instead of `0.0.0.0` breaks cross-vm debugging and developing.
|
||||
|
||||
## Wrap up
|
||||
Don't worry if you get things wrong. We all do, including project owners: this document should've been here a long time ago. There's plenty of room for discussion either on our community [forum](https://discourse.speckle.works) or [chat](https://speckle-works.slack.com/join/shared_invite/enQtNjY5Mzk2NTYxNTA4LTU4MWI5ZjdhMjFmMTIxZDIzOTAzMzRmMTZhY2QxMmM1ZjVmNzJmZGMzMDVlZmJjYWQxYWU0MWJkYmY3N2JjNGI).
|
||||
|
||||
Don't worry if you get things wrong. We all do, including project owners: this document should've been here a long time ago. There's plenty of room for discussion on our community [forum](https://discourse.speckle.works).
|
||||
|
||||
🙌❤️💙💚💜🙌
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
name: New issue
|
||||
about: Create a report to help us improve
|
||||
title:
|
||||
labels:
|
||||
assignees:
|
||||
---
|
||||
|
||||
If it's your first time here - or you forgot about them - make sure you read the [contribution guidelines](CONTRIBUTING.md), and then feel free to delete this line!
|
||||
|
||||
### Expected vs. Actual Behavior
|
||||
|
||||
Describe the problem here.
|
||||
|
||||
### Reproduction Steps & System Config (win, osx, web, etc.)
|
||||
|
||||
Let us know how we can reproduce this, and attach relevant files (if any).
|
||||
|
||||
### Proposed Solution (if any)
|
||||
|
||||
Let us know what how you would solve this.
|
||||
|
||||
#### Optional: Affected Projects
|
||||
|
||||
Does this issue propagate to other dependencies or dependents? If so, list them here!
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Help improve Speckle!
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!---
|
||||
|
||||
Provide a short summary in the Title above. Examples of good Issue titles:
|
||||
|
||||
* "Bug: Error from server when reticulating splines"
|
||||
* "Bug: Revit crashes when installing connector"
|
||||
|
||||
-->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<!---
|
||||
|
||||
Please answer the following questions before submitting an issue.
|
||||
|
||||
-->
|
||||
|
||||
- [ ] I read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)
|
||||
- [ ] I checked the [documentation](https://speckle.guide/) and found no answer.
|
||||
- [ ] I checked [existing issues](../issues?q=is%3Aissue) and found no similar issue. <!-- If you do find an existing issue, please show your support by liking it :+1: instead of creating a new issue -->
|
||||
- [ ] I checked the [community forum](https://speckle.community/) for related discussions and found no answer.
|
||||
- [ ] I'm reporting the issue to the correct repository (see also [speckle-server](https://github.com/specklesystems/speckle-server), [speckle-sharp](https://github.com/specklesystems/speckle-sharp), [specklepy](https://github.com/specklesystems/specklepy), [speckle-docs](https://github.com/specklesystems/speckle-docs), and [others](https://github.com/orgs/specklesystems/repositories))
|
||||
|
||||
## What package are you referring to?
|
||||
|
||||
<!---
|
||||
Is it related to the server (backend) only, or does this bug relate to the frontend, viewer, objectloader or any other package?
|
||||
-->
|
||||
|
||||
## Describe the bug
|
||||
|
||||
<!---
|
||||
A clear and concise description of what the bug is.
|
||||
-->
|
||||
|
||||
## To Reproduce
|
||||
|
||||
<!---
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
-->
|
||||
|
||||
## Expected behavior
|
||||
|
||||
<!---
|
||||
A clear and concise description of what you expected to happen.
|
||||
-->
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!---
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
-->
|
||||
|
||||
## System Info
|
||||
|
||||
If applicable, please fill in the below details - they help a lot!
|
||||
|
||||
### Desktop (please complete the following information):
|
||||
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
### Smartphone (please complete the following information):
|
||||
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
## Failure Logs
|
||||
|
||||
<!---
|
||||
Please include any relevant log snippets or files here, or upload as a file.
|
||||
|
||||
If including inline, please use markdown code block syntax. https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks
|
||||
For example:
|
||||
|
||||
```
|
||||
your log output here
|
||||
```
|
||||
-->
|
||||
|
||||
## Additional context
|
||||
|
||||
<!---
|
||||
Add any other context about the problem here.
|
||||
-->
|
||||
|
||||
## Proposed Solution (if any)
|
||||
|
||||
<!---
|
||||
Let us know what how you would solve this.
|
||||
-->
|
||||
|
||||
#### Optional: Affected Projects
|
||||
|
||||
<!---
|
||||
Does this issue propagate to other dependencies or dependents? If so, list them here with links!
|
||||
-->
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for Speckle!
|
||||
title: ''
|
||||
labels: enhancement, question
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!---
|
||||
|
||||
Provide a short summary in the Title above. Examples of good Issue titles:
|
||||
|
||||
* "Enhancement: Connector for Minecraft"
|
||||
* "Enhancement: Web viewer should support tesseracts"
|
||||
|
||||
-->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<!---
|
||||
|
||||
Please answer the following questions before submitting an issue.
|
||||
|
||||
-->
|
||||
|
||||
- [ ] I read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)
|
||||
- [ ] I checked the [documentation](https://speckle.guide/) and found no answer.
|
||||
- [ ] I checked [existing issues](../issues?q=is%3Aissue) and found no similar issue. <!-- If you do find an existing issue, please show your support by liking it :+1: instead of creating a new issue -->
|
||||
- [ ] I checked the [community forum](https://speckle.community/) for related discussions and found no answer.
|
||||
- [ ] I'm requesting the feature to the correct repository (see also [speckle-server](https://github.com/specklesystems/speckle-server), [speckle-sharp](https://github.com/specklesystems/speckle-sharp), [specklepy](https://github.com/specklesystems/specklepy), [speckle-docs](https://github.com/specklesystems/speckle-docs), and [others](https://github.com/orgs/specklesystems/repositories))
|
||||
|
||||
## What package are you referring to?
|
||||
|
||||
<!---
|
||||
Is it related to the server (backend) only, or does this feature request relate to the frontend, viewer, objectloader or any other package?
|
||||
-->
|
||||
|
||||
## Is your feature request related to a problem? Please describe.
|
||||
|
||||
<!---
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
-->
|
||||
|
||||
## Describe the solution you'd like
|
||||
|
||||
<!---
|
||||
A clear and concise description of what you want to happen.
|
||||
-->
|
||||
|
||||
## Describe alternatives you've considered
|
||||
|
||||
<!---
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
-->
|
||||
|
||||
## Additional context
|
||||
|
||||
<!---
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
Have you seen this feature implemented in any other software? Can you provide screenshots or links to video or documentation?
|
||||
What works well about these existing features in other software? What doesn't work well?
|
||||
-->
|
||||
|
||||
## Related issues or community discussions
|
||||
|
||||
<!---
|
||||
Is this feature request related to (but sufficiently distinct from) any existing issues?
|
||||
Does this feature request require other features to be available beforehand?
|
||||
Has this feature been discussed in the community forum, please link here? https://speckle.community/
|
||||
-->
|
||||
@@ -1,25 +0,0 @@
|
||||
Description of PR...
|
||||
|
||||
## Changes
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Unit tests
|
||||
- [ ] Documentation
|
||||
|
||||
## References
|
||||
|
||||
(optional)
|
||||
|
||||
Include **important** links regarding the implementation of this PR.
|
||||
This usually includes and RFC or an aggregation of issues and/or individual conversations
|
||||
that helped put this solution together. This helps ensure there is a good aggregation
|
||||
of resources regarding the implementation.
|
||||
|
||||
```text
|
||||
Fixes #85, Fixes #22, Fixes username/repo#123
|
||||
Connects #123
|
||||
```
|
||||
@@ -0,0 +1,102 @@
|
||||
<!---
|
||||
|
||||
Provide a short summary in the Title above. Examples of good PR titles:
|
||||
|
||||
* "Feature: adds metrics to component"
|
||||
|
||||
* "Fix: resolves duplication in comment thread"
|
||||
|
||||
* "Update: apollo v2.34.0"
|
||||
|
||||
-->
|
||||
|
||||
## Description & motivation
|
||||
|
||||
<!---
|
||||
|
||||
Describe your changes, and why you're making them. What benefit will this have to others?
|
||||
|
||||
Is this linked to an open Github issue, a thread in Speckle community,
|
||||
or another pull request? Link it here.
|
||||
|
||||
If it is related to a Github issue, and resolves it, please link to the issue number, e.g.:
|
||||
Fixes #85, Fixes #22, Fixes username/repo#123
|
||||
Connects #123
|
||||
|
||||
-->
|
||||
|
||||
## Changes:
|
||||
|
||||
<!---
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
|
||||
-->
|
||||
|
||||
## To-do before merge:
|
||||
|
||||
<!---
|
||||
|
||||
(Optional -- remove this section if not needed)
|
||||
|
||||
Include any notes about things that need to happen before this PR is merged, e.g.:
|
||||
|
||||
- [ ] Change the base branch
|
||||
|
||||
- [ ] Ensure PR #56 is merged
|
||||
|
||||
-->
|
||||
|
||||
## Screenshots:
|
||||
|
||||
<!---
|
||||
|
||||
Include a screenshot the before and after. This can be a screenshot of a plugin, web frontend, or output in a terminal.
|
||||
|
||||
-->
|
||||
|
||||
## Validation of changes:
|
||||
|
||||
<!---
|
||||
|
||||
Describe what tests have been added or amended, and why these demonstrate it works and will prevent this feature being accidentally broken by future changes.
|
||||
|
||||
-->
|
||||
|
||||
## Checklist:
|
||||
|
||||
<!---
|
||||
|
||||
This checklist is mostly useful as a reminder of small things that can easily be
|
||||
|
||||
forgotten – it is meant as a helpful tool rather than hoops to jump through.
|
||||
|
||||
Put an `x` between the square brackets, e.g. [x], for all the items that apply,
|
||||
|
||||
make notes next to any that haven't been addressed, and remove any items that are not relevant to this PR.
|
||||
|
||||
-->
|
||||
|
||||
- [ ] My pull request follows the guidelines in the [Contributing guide](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)?
|
||||
- [ ] My pull request does not duplicate any other open [Pull Requests](../../pulls) for the same update/change?
|
||||
- [ ] My commits are related to the pull request and do not amend unrelated code or documentation.
|
||||
- [ ] My code follows a similar style to existing code.
|
||||
- [ ] I have added appropriate tests.
|
||||
- [ ] I have updated or added relevant documentation.
|
||||
|
||||
## References
|
||||
|
||||
<!---
|
||||
|
||||
(Optional -- remove this section if not needed )
|
||||
|
||||
Include **important** links regarding the implementation of this PR.
|
||||
|
||||
This usually includes a RFC or an aggregation of issues and/or individual conversations
|
||||
|
||||
that helped put this solution together. This helps ensure we retain and share knowledge
|
||||
|
||||
regarding the implementation, and may help others understand motivation and design decisions etc..
|
||||
|
||||
-->
|
||||
@@ -0,0 +1,77 @@
|
||||
name: Update issue Status
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
update_issue:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get project data
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ORGANIZATION: specklesystems
|
||||
PROJECT_NUMBER: 9
|
||||
run: |
|
||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
query($org: String!, $number: Int!) {
|
||||
organization(login: $org){
|
||||
projectNext(number: $number) {
|
||||
id
|
||||
fields(first:20) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
settings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
|
||||
|
||||
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
||||
|
||||
echo "$PROJECT_ID"
|
||||
echo "$STATUS_FIELD_ID"
|
||||
|
||||
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
|
||||
echo "$DONE_ID"
|
||||
|
||||
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
||||
run: |
|
||||
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
mutation($project:ID!, $id:ID!) {
|
||||
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
|
||||
projectNextItem {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
|
||||
|
||||
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
|
||||
|
||||
- name: Update Status
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
||||
run: |
|
||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
|
||||
set_status: updateProjectNextItemField(
|
||||
input: {
|
||||
projectId: $project
|
||||
itemId: $id
|
||||
fieldId: $status
|
||||
value: $value
|
||||
}
|
||||
) {
|
||||
projectNextItem {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Move new issues into Project
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
track_issue:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get project data
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ORGANIZATION: specklesystems
|
||||
PROJECT_NUMBER: 9
|
||||
run: |
|
||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
query($org: String!, $number: Int!) {
|
||||
organization(login: $org){
|
||||
projectNext(number: $number) {
|
||||
id
|
||||
fields(first:20) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
settings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
|
||||
|
||||
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
||||
|
||||
- name: Add Issue to project
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
||||
run: |
|
||||
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
mutation($project:ID!, $id:ID!) {
|
||||
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
|
||||
projectNextItem {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
|
||||
|
||||
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
|
||||
@@ -1,3 +1,7 @@
|
||||
.tool-versions
|
||||
.envrc
|
||||
reports/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
Vendored
+10
@@ -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",
|
||||
@@ -11,6 +12,15 @@
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false
|
||||
},
|
||||
{
|
||||
"name": "Pytest",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "poetry",
|
||||
"args": ["run", "pytest"],
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,35 +1,88 @@
|
||||
# speckle-py 🥧
|
||||
<h1 align="center">
|
||||
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
|
||||
Speckle | specklepy 🐍
|
||||
</h1>
|
||||
<h3 align="center">
|
||||
The Python SDK
|
||||
</h3>
|
||||
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
|
||||
|
||||
[](https://twitter.com/SpeckleSystems) [](https://discourse.speckle.works)
|
||||
[](https://speckle-works.slack.com/join/shared_invite/enQtNjY5Mzk2NTYxNTA4LTU4MWI5ZjdhMjFmMTIxZDIzOTAzMzRmMTZhY2QxMmM1ZjVmNzJmZGMzMDVlZmJjYWQxYWU0MWJkYmY3N2JjNGI) [](https://speckle.systems)
|
||||
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&style=flat-square&logo=discourse&logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white" alt="docs"></a></p>
|
||||
<p align="center"><a href="https://github.com/specklesystems/specklepy/"><img src="https://circleci.com/gh/specklesystems/specklepy.svg?style=svg&circle-token=76eabd350ea243575cbb258b746ed3f471f7ac29" alt="Speckle-Next"></a><a href="https://codecov.io/gh/specklesystems/specklepy">
|
||||
<img src="https://codecov.io/gh/specklesystems/specklepy/branch/main/graph/badge.svg?token=8KQFL5N0YF"/>
|
||||
</a> </p>
|
||||
|
||||
## Introduction
|
||||
# About Speckle
|
||||
|
||||
> ⚠ This is the start of the Python client for Speckle 2.0. It is currently quite nebulous and may be trashed and rebuilt at any moment! It is compatible with Python 3.6+ ⚠
|
||||
What is Speckle? Check our 
|
||||
|
||||
### Features
|
||||
|
||||
- **Object-based:** say goodbye to files! Speckle is the first object based platform for the AEC industry
|
||||
- **Version control:** Speckle is the Git & Hub for geometry and BIM data
|
||||
- **Collaboration:** share your designs collaborate with others
|
||||
- **3D Viewer:** see your CAD and BIM models online, share and embed them anywhere
|
||||
- **Interoperability:** get your CAD and BIM models into other software without exporting or importing
|
||||
- **Real time:** get real time updates and notifications and changes
|
||||
- **GraphQL API:** get what you need anywhere you want it
|
||||
- **Webhooks:** the base for a automation and next-gen pipelines
|
||||
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
|
||||
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!
|
||||
|
||||
### Try Speckle now!
|
||||
|
||||
Give Speckle a try in no time by:
|
||||
|
||||
- [](https://speckle.xyz) ⇒ creating an account at our public server
|
||||
- [](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
|
||||
|
||||
### Resources
|
||||
|
||||
- [](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
|
||||
- [](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
|
||||
- [](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
|
||||
|
||||
|
||||
# Repo structure
|
||||
|
||||
## Usage
|
||||
|
||||
Send and receive data from a Speckle Server with `operations`, interact with the Speckle API with the `SpeckleClient`, create and extend your own custom Speckle Objects with `Base`, and more!
|
||||
|
||||
Head to the [**📚 specklepy docs**](https://speckle.guide/dev/python.html) for more information and usage examples.
|
||||
|
||||
## Developing & Debugging
|
||||
|
||||
To get started, create a virtual environment and pip install the requirements.
|
||||
### Installation
|
||||
|
||||
on windows:
|
||||
```
|
||||
python -m venv venv
|
||||
venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
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.
|
||||
|
||||
If this is your first time using poetry and you're used to creating your venvs within the project directory, run `poetry config virtualenvs.in-project true` to configure poetry to do the same.
|
||||
|
||||
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`
|
||||
|
||||
## Contributing
|
||||
|
||||
Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md) for an overview of the best practices we try to follow.
|
||||
Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md) and [code of conduct](.github/CODE_OF_CONDUCT.md) for an overview of the practices we try to follow.
|
||||
|
||||
## Community
|
||||
|
||||
The Speckle Community hangs out in two main places, usually:
|
||||
The Speckle Community hangs out on [the forum](https://discourse.speckle.works), do join and introduce yourself & feel free to ask us questions!
|
||||
|
||||
- on [the forum](https://discourse.speckle.works)
|
||||
- on [the chat](https://speckle-works.slack.com/join/shared_invite/enQtNjY5Mzk2NTYxNTA4LTU4MWI5ZjdhMjFmMTIxZDIzOTAzMzRmMTZhY2QxMmM1ZjVmNzJmZGMzMDVlZmJjYWQxYWU0MWJkYmY3N2JjNGI)
|
||||
## Security
|
||||
|
||||
Do join and introduce yourself!
|
||||
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.2.+ | :white_check_mark: |
|
||||
| < 2.2 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Hi! If you've found something off, we'd be more than happy if you would report it via security@speckle.systems. We will work together with you to correctly identify the cause and implement a fix. Thanks for helping make Speckle safer!
|
||||
@@ -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}")
|
||||
@@ -0,0 +1,59 @@
|
||||
"""This is an example showcasing the usage of speckle `Base` class."""
|
||||
|
||||
# the speckle.objects module exposes all speckle provided classes
|
||||
from specklepy.objects import Base
|
||||
from specklepy.api import operations
|
||||
from devtools import debug
|
||||
|
||||
|
||||
class ExampleSub(Base):
|
||||
"""
|
||||
Inheriting from `Base` is done with in the standard way by default.
|
||||
|
||||
The syntax is similar to the stdlib dataclass syntax.
|
||||
No __init__ method definition is required, that is done automatically by the base
|
||||
type. Also the attributes defined this way are instance attributes despite they
|
||||
might look like class attributes.
|
||||
|
||||
The speckle Base uses the pydantic BaseModel in the background, but ideally that
|
||||
is not the consumers concern.
|
||||
|
||||
**Important note:** currently the way how serialization works, requires
|
||||
each attribute to have a valid default value, just like `foo` has. This includes
|
||||
default values for all primitives and complex datastructures.
|
||||
Failing to provide a default, breaks the receiving end of the transport.
|
||||
"""
|
||||
|
||||
foo: str = "bar"
|
||||
|
||||
|
||||
class SpeckleSub(ExampleSub, speckle_type="custom_speckle_sub"):
|
||||
"""
|
||||
Example custom type name registration.
|
||||
|
||||
This is an optional feature.
|
||||
The default value of the speckle_type is generated from the name of the class, but
|
||||
optionally it may be overridden. This is useful, since the speckle_type has to be
|
||||
unique for each subclass of speckle Base.
|
||||
"""
|
||||
|
||||
magic: str = "trick"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# example usage
|
||||
custom_sub = SpeckleSub(
|
||||
foo=123,
|
||||
magic="trick",
|
||||
bar="baric",
|
||||
extra=123,
|
||||
)
|
||||
# support for dynamic attributes
|
||||
custom_sub.extra_extra = "what is this?"
|
||||
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)
|
||||
@@ -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
+1242
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
[tool.poetry]
|
||||
name = "specklepy"
|
||||
version = "2.4.0"
|
||||
description = "The Python SDK for Speckle 2.0"
|
||||
readme = "README.md"
|
||||
authors = ["Speckle Systems <devops@speckle.systems>"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/specklesystems/speckle-py"
|
||||
documentation = "https://speckle.guide/dev/py-examples.html"
|
||||
homepage = "https://speckle.systems/"
|
||||
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.7.2, <4.0"
|
||||
pydantic = "^1.8.2"
|
||||
appdirs = "^1.4.4"
|
||||
gql = {extras = ["requests", "websockets"], version = "^3.3.0"}
|
||||
ujson = "^5.3.0"
|
||||
Deprecated = "^1.2.13"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = "^20.8b1"
|
||||
isort = "^5.7.0"
|
||||
pytest = "^6.2.2"
|
||||
pytest-ordering = "^0.6"
|
||||
pytest-cov = "^3.0.0"
|
||||
devtools = "^0.8.0"
|
||||
pylint = "^2.14.4"
|
||||
|
||||
|
||||
[tool.black]
|
||||
exclude = '''
|
||||
/(
|
||||
\.eggs
|
||||
| \.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| _build
|
||||
| buck-out
|
||||
| build
|
||||
| dist
|
||||
)/
|
||||
'''
|
||||
include = '\.pyi?$'
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39", "py310"]
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -1,31 +0,0 @@
|
||||
aiohttp==3.7.1
|
||||
appdirs==1.4.4
|
||||
astroid==2.4.2
|
||||
async-timeout==3.0.1
|
||||
attrs==20.3.0
|
||||
black==20.8b1
|
||||
certifi==2020.11.8
|
||||
chardet==3.0.4
|
||||
click==7.1.2
|
||||
colorama==0.4.4
|
||||
gql==3.0.0a4
|
||||
graphql-core==3.1.2
|
||||
idna==2.10
|
||||
isort==5.6.4
|
||||
lazy-object-proxy==1.4.3
|
||||
mccabe==0.6.1
|
||||
multidict==5.0.0
|
||||
mypy-extensions==0.4.3
|
||||
pathspec==0.8.1
|
||||
pydantic==1.7.2
|
||||
pylint==2.6.0
|
||||
regex==2020.11.11
|
||||
requests==2.24.0
|
||||
six==1.15.0
|
||||
toml==0.10.2
|
||||
typed-ast==1.4.1
|
||||
typing-extensions==3.7.4.3
|
||||
urllib3==1.25.11
|
||||
websockets==8.1
|
||||
wrapt==1.12.1
|
||||
yarl==1.5.1
|
||||
@@ -1,85 +0,0 @@
|
||||
from gql.client import SyncClientSession
|
||||
from speckle.logging.exceptions import SpeckleException
|
||||
from typing import Dict
|
||||
|
||||
from speckle.api import resources
|
||||
from speckle.api.resources import stream, server, user, subscriptions
|
||||
from gql import Client, gql
|
||||
from gql.transport.requests import RequestsHTTPTransport
|
||||
from gql.transport.aiohttp import AIOHTTPTransport
|
||||
from gql.transport.websockets import WebsocketsTransport
|
||||
|
||||
|
||||
class SpeckleClient:
|
||||
DEFAULT_HOST = "staging.speckle.dev"
|
||||
USE_SSL = True
|
||||
|
||||
def __init__(self, host: str = DEFAULT_HOST, use_ssl: bool = USE_SSL) -> None:
|
||||
ws_protocol = "ws"
|
||||
http_protocol = "http"
|
||||
|
||||
if use_ssl:
|
||||
ws_protocol = "wss"
|
||||
http_protocol = "https"
|
||||
|
||||
self.url = f"{http_protocol}://{host}"
|
||||
self.graphql = self.url + "/graphql"
|
||||
self.ws_url = f"{ws_protocol}://{host}/graphql"
|
||||
self.me = None
|
||||
|
||||
self.httpclient = Client(
|
||||
transport=RequestsHTTPTransport(url=self.graphql, verify=True, retries=3)
|
||||
)
|
||||
self.wsclient = None
|
||||
|
||||
self._init_resources()
|
||||
|
||||
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
|
||||
|
||||
Arguments:
|
||||
token {str} -- an api token
|
||||
"""
|
||||
self.me = {"token": token}
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.me['token']}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
httptransport = RequestsHTTPTransport(
|
||||
url=self.graphql, headers=headers, verify=True, retries=3
|
||||
)
|
||||
wstransport = WebsocketsTransport(
|
||||
url=self.ws_url,
|
||||
init_payload={"Authorization": f"Bearer {self.me['token']}"},
|
||||
)
|
||||
self.httpclient = Client(transport=httptransport)
|
||||
self.wsclient = Client(transport=wstransport)
|
||||
|
||||
self._init_resources()
|
||||
|
||||
def execute_query(self, query: str) -> Dict:
|
||||
return self.httpclient.execute(query)
|
||||
|
||||
def _init_resources(self) -> None:
|
||||
self.stream = stream.Resource(
|
||||
me=self.me, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.server = server.Resource(
|
||||
me=self.me, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.user = user.Resource(me=self.me, basepath=self.url, client=self.httpclient)
|
||||
self.subscribe = subscriptions.Resource(
|
||||
me=self.me,
|
||||
basepath=self.ws_url,
|
||||
client=self.wsclient,
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
attr = getattr(resources, name)
|
||||
return attr.Resource(me=self.me, basepath=self.url, client=self.httpclient)
|
||||
except:
|
||||
raise SpeckleException(
|
||||
f"Method {name} is not supported by the SpeckleClient class"
|
||||
)
|
||||
@@ -1,86 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: stream_schema.json
|
||||
# timestamp: 2020-11-17T14:33:13+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Collaborator(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
role: Optional[str]
|
||||
avatar: Optional[str]
|
||||
|
||||
|
||||
class Commit(BaseModel):
|
||||
id: Optional[str]
|
||||
message: Optional[str]
|
||||
authorName: Optional[str]
|
||||
authorId: Optional[str]
|
||||
authorAvatar: Optional[str]
|
||||
createdAt: Optional[str]
|
||||
referencedObject: Optional[str]
|
||||
|
||||
|
||||
class Commits(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[Any]
|
||||
items: List[Commit] = []
|
||||
|
||||
|
||||
class Object(BaseModel):
|
||||
id: Optional[str]
|
||||
speckleType: Optional[str]
|
||||
applicationId: Optional[str]
|
||||
totalChildrenCount: Optional[int]
|
||||
createdAt: Optional[str]
|
||||
|
||||
|
||||
class Branch(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
description: Optional[str]
|
||||
commits: Optional[Commits]
|
||||
|
||||
|
||||
class Branches(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[datetime]
|
||||
items: List[Branch] = []
|
||||
|
||||
|
||||
class Streams(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[datetime]
|
||||
items: List[Stream] = []
|
||||
|
||||
|
||||
class Stream(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
description: Optional[str]
|
||||
isPublic: Optional[bool]
|
||||
createdAt: Optional[str]
|
||||
updatedAt: Optional[str]
|
||||
collaborators: List[Collaborator] = []
|
||||
branches: Optional[Branches]
|
||||
commit: Optional[Commit]
|
||||
object: Optional[Object]
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: Optional[str]
|
||||
email: Optional[str]
|
||||
name: Optional[str]
|
||||
bio: Optional[str]
|
||||
company: Optional[str]
|
||||
avatar: Optional[str]
|
||||
verified: Optional[bool]
|
||||
role: Optional[str]
|
||||
streams: Optional[Streams]
|
||||
@@ -1,76 +0,0 @@
|
||||
from logging import error
|
||||
from speckle.logging.exceptions import GraphQLException, SpeckleException
|
||||
from typing import Dict, List
|
||||
from gql.client import Client
|
||||
from gql.gql import gql
|
||||
from gql.transport.exceptions import TransportQueryError
|
||||
|
||||
|
||||
class ResourceBase(object):
|
||||
def __init__(
|
||||
self,
|
||||
me: Dict,
|
||||
basepath: str,
|
||||
client: Client,
|
||||
name: str,
|
||||
methods: list,
|
||||
) -> None:
|
||||
self.me = me
|
||||
self.basepath = basepath
|
||||
self.client = client
|
||||
self.name = name
|
||||
self.methods = methods
|
||||
self.schema = None
|
||||
|
||||
def _step_into_response(self, response: dict, return_type: str or List):
|
||||
"""Step into the dict to get the relevant data"""
|
||||
if return_type is None:
|
||||
return response
|
||||
elif isinstance(return_type, str):
|
||||
return response[return_type]
|
||||
elif isinstance(return_type, List):
|
||||
for key in return_type:
|
||||
response = response[key]
|
||||
return response
|
||||
|
||||
def _parse_response(self, response: dict or list, schema=None):
|
||||
"""Try to create a class instance from the response"""
|
||||
if isinstance(response, list):
|
||||
return [self._parse_response(response=r, schema=schema) for r in response]
|
||||
if schema:
|
||||
return schema.parse_obj(response)
|
||||
elif self.schema:
|
||||
return self.schema.parse_obj(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def make_request(
|
||||
self,
|
||||
query: gql,
|
||||
params: Dict = None,
|
||||
return_type: str or List = None,
|
||||
schema=None,
|
||||
parse_response: bool = True,
|
||||
) -> Dict or GraphQLException:
|
||||
"""Executes the GraphQL query"""
|
||||
try:
|
||||
response = self.client.execute(query, variable_values=params)
|
||||
except Exception as e:
|
||||
if isinstance(e, TransportQueryError):
|
||||
return GraphQLException(
|
||||
message=f"Failed to execute the GraphQL {self.name} request. Errors: {e.errors}",
|
||||
errors=e.errors,
|
||||
data=e.data,
|
||||
)
|
||||
else:
|
||||
return SpeckleException(
|
||||
message=f"Failed to execute the GraphQL {self.name} request. Inner exception: {e}",
|
||||
exception=e,
|
||||
)
|
||||
|
||||
response = self._step_into_response(response=response, return_type=return_type)
|
||||
|
||||
if parse_response:
|
||||
return self._parse_response(response=response, schema=schema)
|
||||
else:
|
||||
return response
|
||||
@@ -1,48 +0,0 @@
|
||||
from typing import List, Optional
|
||||
from gql import gql
|
||||
from pydantic.main import BaseModel
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.models import Branch
|
||||
|
||||
NAME = "branch"
|
||||
METHODS = ["create"]
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for branches"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
self.schema = Branch
|
||||
|
||||
def create(
|
||||
self, streamId: str, name: str, description: str = "No description provided"
|
||||
) -> str:
|
||||
"""Create a new branch on this stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the new branch
|
||||
description {str} -- a short description of the branch
|
||||
|
||||
Returns:
|
||||
id {str} -- the newly created branch's id
|
||||
"""
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation BranchCreate($branch: BranchCreateInput!){
|
||||
branchCreate(branch: $branch)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"branch": {
|
||||
"streamId": streamId,
|
||||
"name": name,
|
||||
"description": description,
|
||||
}
|
||||
}
|
||||
|
||||
return self.make_request(query=query, params=params, parse_response=False)
|
||||
@@ -1,19 +0,0 @@
|
||||
from typing import Optional, List
|
||||
from gql import gql
|
||||
from pydantic.main import BaseModel
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.models import Commit
|
||||
|
||||
|
||||
NAME = "commit"
|
||||
METHODS = []
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for commits"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
self.schema = Commit
|
||||
@@ -1,79 +0,0 @@
|
||||
from typing import Dict
|
||||
from gql import gql
|
||||
from gql.client import Client
|
||||
from speckle.api.resource import ResourceBase
|
||||
|
||||
|
||||
NAME = "server"
|
||||
METHODS = ["get", "apps"]
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for the server"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
|
||||
def get(self) -> Dict:
|
||||
"""Get the server info
|
||||
|
||||
Returns:
|
||||
dict -- the server info in dictionary form
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Server {
|
||||
serverInfo {
|
||||
name
|
||||
company
|
||||
description
|
||||
adminContact
|
||||
canonicalUrl
|
||||
roles {
|
||||
name
|
||||
description
|
||||
resourceTarget
|
||||
}
|
||||
scopes {
|
||||
name
|
||||
description
|
||||
}
|
||||
authStrategies{
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(query=query)
|
||||
|
||||
def apps(self) -> Dict:
|
||||
"""Get the apps registered on the server
|
||||
|
||||
Returns:
|
||||
dict -- a dictionary of apps registered on the server
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Apps {
|
||||
apps {
|
||||
id
|
||||
name
|
||||
description
|
||||
termsAndConditionsLink
|
||||
logo
|
||||
author {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(query=query)
|
||||
@@ -1,298 +0,0 @@
|
||||
from re import search
|
||||
from typing import Dict, List, Optional
|
||||
from pydantic import BaseModel
|
||||
from gql import gql
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.models import Stream
|
||||
from speckle.logging.exceptions import GraphQLException
|
||||
|
||||
NAME = "stream"
|
||||
METHODS = [
|
||||
"list",
|
||||
"create",
|
||||
"get",
|
||||
"update",
|
||||
"delete",
|
||||
"search",
|
||||
]
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for streams"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
|
||||
self.schema = Stream
|
||||
|
||||
def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream:
|
||||
"""Get the specified stream from the server
|
||||
|
||||
Arguments:
|
||||
id {str} -- the stream id
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
Stream -- the retrieved stream
|
||||
"""
|
||||
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 {
|
||||
id
|
||||
name
|
||||
description
|
||||
commits(limit: $commit_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
referencedObject
|
||||
message
|
||||
authorName
|
||||
authorId
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id, "branch_limit": branch_limit, "commit_limit": commit_limit}
|
||||
|
||||
return self.make_request(query=query, params=params, return_type="stream")
|
||||
|
||||
def list(self, stream_limit: int = 10) -> List[Stream]:
|
||||
"""Get a list of the user's streams
|
||||
|
||||
Arguments:
|
||||
stream_limit {int} -- The maximum number of streams to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- A list of Stream objects
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query User($stream_limit: Int!) {
|
||||
user {
|
||||
id
|
||||
email
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
streams(limit: $stream_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"stream_limit": stream_limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["user", "streams", "items"]
|
||||
)
|
||||
|
||||
def create(
|
||||
self,
|
||||
name: str = "Anonymous Python Stream",
|
||||
description: str = "No description provided",
|
||||
is_public: bool = True,
|
||||
) -> str:
|
||||
"""Create a new stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool} -- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
id {str} -- the id of the newly created stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamCreate($stream: StreamCreateInput!) {
|
||||
streamCreate(stream: $stream)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"stream": {"name": name, "description": description, "isPublic": is_public}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamCreate", parse_response=False
|
||||
)
|
||||
|
||||
def update(
|
||||
self, id: str, name: str = None, description: str = None, is_public: bool = None
|
||||
) -> bool:
|
||||
"""Update an existing stream
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to be updated
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool} -- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
bool -- whether the stream update was successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamUpdate($stream: StreamUpdateInput!) {
|
||||
streamUpdate(stream: $stream)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"isPublic": is_public,
|
||||
}
|
||||
# remove None values so graphql doesn't cry
|
||||
params = {"stream": {k: v for k, v in params.items() if v is not None}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamUpdate", parse_response=False
|
||||
)
|
||||
|
||||
def delete(self, id: str) -> bool:
|
||||
"""Delete a stream given its id
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to delete
|
||||
|
||||
Returns:
|
||||
bool -- whether the deletion was successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamDelete($id: String!) {
|
||||
streamDelete(id: $id)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamDelete", parse_response=False
|
||||
)
|
||||
|
||||
def search(
|
||||
self,
|
||||
search_query: str,
|
||||
limit: int = 25,
|
||||
branch_limit: int = 10,
|
||||
commit_limit: int = 10,
|
||||
):
|
||||
"""Search for streams by name, description, or id
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- a list of Streams that match the search query
|
||||
"""
|
||||
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
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
commits(limit: $commit_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
referencedObject
|
||||
message
|
||||
authorName
|
||||
authorId
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"search_query": search_query,
|
||||
"limit": limit,
|
||||
"branch_limit": branch_limit,
|
||||
"commit_limit": commit_limit,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["streams", "items"]
|
||||
)
|
||||
@@ -1,86 +0,0 @@
|
||||
from speckle.logging.exceptions import SpeckleException
|
||||
from typing import List, Optional
|
||||
from gql import gql
|
||||
from pydantic.main import BaseModel
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.models import User
|
||||
|
||||
NAME = "user"
|
||||
METHODS = ["get"]
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for users"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
self.schema = User
|
||||
|
||||
def get(self, id: str = None) -> User:
|
||||
"""Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
||||
|
||||
Arguments:
|
||||
id {str} -- the user id
|
||||
|
||||
Returns:
|
||||
User -- the retrieved user
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query User($id: String) {
|
||||
user(id: $id) {
|
||||
id
|
||||
email
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id}
|
||||
|
||||
return self.make_request(query=query, params=params, return_type="user")
|
||||
|
||||
def search(self, search_query: str, limit: int = 25) -> List[User]:
|
||||
"""Searches for user by name or email. The search query must be at least 3 characters long
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
Returns:
|
||||
List[User] -- a list of User objects that match the search query
|
||||
"""
|
||||
if len(search_query) < 3:
|
||||
return SpeckleException(
|
||||
message="User search query must be at least 3 characters"
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query UserSearch($search_query: String!, $limit: Int!) {
|
||||
userSearch(query: $search_query, limit: $limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"search_query": search_query, "limit": limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["userSearch", "items"]
|
||||
)
|
||||
@@ -1,17 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
class SpeckleException(Exception):
|
||||
def __init__(self, message: str, exception: Exception = None) -> None:
|
||||
self.message = message
|
||||
self.exception = exception
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"SpeckleException: {self.message}"
|
||||
|
||||
|
||||
class GraphQLException(SpeckleException):
|
||||
def __init__(self, message: str, errors: List, data=None) -> None:
|
||||
super().__init__(message=message)
|
||||
self.errors = errors
|
||||
self.data = data
|
||||
@@ -1,51 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Beam.json
|
||||
# timestamp: 2020-11-24T16:33:00+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Beam(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,51 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Brace.json
|
||||
# timestamp: 2020-11-24T16:33:02+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Brace(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,52 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Column.json
|
||||
# timestamp: 2020-11-24T16:33:04+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Column(BaseModel):
|
||||
height: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,57 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Duct.json
|
||||
# timestamp: 2020-11-24T16:33:07+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Duct(BaseModel):
|
||||
width: Optional[float] = None
|
||||
height: Optional[float] = None
|
||||
diameter: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
level: Optional[Level] = None
|
||||
velocity: Optional[float] = None
|
||||
system: Optional[Optional[str]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,51 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Element.json
|
||||
# timestamp: 2020-11-24T16:33:08+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Element(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,56 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Floor.json
|
||||
# timestamp: 2020-11-24T16:33:10+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Floor(BaseModel):
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,67 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: GridLine.json
|
||||
# timestamp: 2020-11-24T16:33:12+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class GridLine(BaseModel):
|
||||
baseLine: Optional[Line] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,40 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Level.json
|
||||
# timestamp: 2020-11-24T16:33:15+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,51 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Opening.json
|
||||
# timestamp: 2020-11-24T16:33:18+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Opening(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,56 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Roof.json
|
||||
# timestamp: 2020-11-24T16:33:33+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Roof(BaseModel):
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,59 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Room.json
|
||||
# timestamp: 2020-11-24T16:33:34+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Room(BaseModel):
|
||||
number: Optional[Optional[str]] = None
|
||||
area: Optional[float] = None
|
||||
volume: Optional[float] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,28 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Topography.json
|
||||
# timestamp: 2020-11-24T16:33:35+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Topography(BaseModel):
|
||||
baseGeometry: Optional[Mesh] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,52 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Wall.json
|
||||
# timestamp: 2020-11-24T16:33:37+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Wall(BaseModel):
|
||||
height: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,61 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Arc.json
|
||||
# timestamp: 2020-11-24T16:32:59+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Arc(BaseModel):
|
||||
radius: Optional[Optional[float]] = None
|
||||
startAngle: Optional[Optional[float]] = None
|
||||
endAngle: Optional[Optional[float]] = None
|
||||
angleRadians: Optional[Optional[float]] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
startPoint: Optional[Point] = None
|
||||
midPoint: Optional[Point] = None
|
||||
endPoint: Optional[Point] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,25 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Block.json
|
||||
# timestamp: 2020-11-24T16:33:01+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Block(BaseModel):
|
||||
description: Optional[Optional[str]] = None
|
||||
objects: Optional[List[Base]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,56 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Box.json
|
||||
# timestamp: 2020-11-24T16:33:02+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Box(BaseModel):
|
||||
basePlane: Optional[Plane] = None
|
||||
xSize: Optional[Interval] = None
|
||||
ySize: Optional[Interval] = None
|
||||
zSize: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,55 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Circle.json
|
||||
# timestamp: 2020-11-24T16:33:04+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Circle(BaseModel):
|
||||
radius: Optional[Optional[float]] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,44 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Curve.json
|
||||
# timestamp: 2020-11-24T16:33:05+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Polyline(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
closed: Optional[bool] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Curve(BaseModel):
|
||||
degree: Optional[int] = None
|
||||
periodic: Optional[bool] = None
|
||||
rational: Optional[bool] = None
|
||||
points: Optional[List[float]] = None
|
||||
weights: Optional[List[float]] = None
|
||||
knots: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
displayValue: Optional[Polyline] = None
|
||||
closed: Optional[bool] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,56 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Ellipse.json
|
||||
# timestamp: 2020-11-24T16:33:08+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Ellipse(BaseModel):
|
||||
firstRadius: Optional[Optional[float]] = None
|
||||
secondRadius: Optional[Optional[float]] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,39 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Extrusion.json
|
||||
# timestamp: 2020-11-24T16:33:09+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Extrusion(BaseModel):
|
||||
length: Optional[Optional[float]] = None
|
||||
capped: Optional[Optional[bool]] = None
|
||||
profile: Optional[Base] = None
|
||||
pathStart: Optional[Point] = None
|
||||
pathEnd: Optional[Point] = None
|
||||
pathCurve: Optional[Base] = None
|
||||
pathTangent: Optional[Base] = None
|
||||
profiles: Optional[List[Base]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,27 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Line.json
|
||||
# timestamp: 2020-11-24T16:33:16+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,20 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Mesh.json
|
||||
# timestamp: 2020-11-24T16:33:16+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,36 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Plane.json
|
||||
# timestamp: 2020-11-24T16:33:19+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,17 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Point.json
|
||||
# timestamp: 2020-11-24T16:33:20+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,32 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Polycurve.json
|
||||
# timestamp: 2020-11-24T16:33:20+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Polycurve(BaseModel):
|
||||
segments: Optional[List[ICurve]] = None
|
||||
domain: Optional[Interval] = None
|
||||
closed: Optional[bool] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,28 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Polyline.json
|
||||
# timestamp: 2020-11-24T16:33:21+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Polyline(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
closed: Optional[bool] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,17 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Vector.json
|
||||
# timestamp: 2020-11-24T16:33:36+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,18 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Interval.json
|
||||
# timestamp: 2020-11-24T16:33:14+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,27 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Interval2d.json
|
||||
# timestamp: 2020-11-24T16:33:14+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Interval2d(BaseModel):
|
||||
u: Optional[Interval] = None
|
||||
v: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,54 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: AdaptiveComponent.json
|
||||
# timestamp: 2020-11-24T16:32:59+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class AdaptiveComponent(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
flipped: Optional[bool] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,22 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: DetailCurve.json
|
||||
# timestamp: 2020-11-24T16:33:06+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class DetailCurve(BaseModel):
|
||||
baseCurve: Optional[ICurve] = None
|
||||
lineStyle: Optional[Optional[str]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,94 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: DirectShape.json
|
||||
# timestamp: 2020-11-24T16:33:06+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Category(Enum):
|
||||
integer_0 = 0
|
||||
integer_1 = 1
|
||||
integer_2 = 2
|
||||
integer_3 = 3
|
||||
integer_4 = 4
|
||||
integer_5 = 5
|
||||
integer_6 = 6
|
||||
integer_7 = 7
|
||||
integer_8 = 8
|
||||
integer_9 = 9
|
||||
integer_10 = 10
|
||||
integer_11 = 11
|
||||
integer_12 = 12
|
||||
integer_13 = 13
|
||||
integer_14 = 14
|
||||
integer_15 = 15
|
||||
integer_16 = 16
|
||||
integer_17 = 17
|
||||
integer_18 = 18
|
||||
integer_19 = 19
|
||||
integer_20 = 20
|
||||
integer_21 = 21
|
||||
integer_22 = 22
|
||||
integer_23 = 23
|
||||
integer_24 = 24
|
||||
integer_25 = 25
|
||||
integer_26 = 26
|
||||
integer_27 = 27
|
||||
integer_28 = 28
|
||||
integer_29 = 29
|
||||
integer_30 = 30
|
||||
integer_31 = 31
|
||||
integer_32 = 32
|
||||
integer_33 = 33
|
||||
integer_34 = 34
|
||||
integer_35 = 35
|
||||
integer_36 = 36
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class DirectShape(BaseModel):
|
||||
category: Optional[Category] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,66 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: FamilyInstance.json
|
||||
# timestamp: 2020-11-24T16:33:10+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Element(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class FamilyInstance(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
flipped: Optional[bool] = None
|
||||
host: Optional[Element] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,22 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: ModelCurve.json
|
||||
# timestamp: 2020-11-24T16:33:17+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class ModelCurve(BaseModel):
|
||||
baseCurve: Optional[ICurve] = None
|
||||
lineStyle: Optional[Optional[str]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,53 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitBeam.json
|
||||
# timestamp: 2020-11-24T16:33:22+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitBeam(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,53 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitBrace.json
|
||||
# timestamp: 2020-11-24T16:33:22+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitBrace(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,47 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitCategory.json
|
||||
# timestamp: 2020-11-24T16:33:23+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class RevitCategory(Enum):
|
||||
integer_0 = 0
|
||||
integer_1 = 1
|
||||
integer_2 = 2
|
||||
integer_3 = 3
|
||||
integer_4 = 4
|
||||
integer_5 = 5
|
||||
integer_6 = 6
|
||||
integer_7 = 7
|
||||
integer_8 = 8
|
||||
integer_9 = 9
|
||||
integer_10 = 10
|
||||
integer_11 = 11
|
||||
integer_12 = 12
|
||||
integer_13 = 13
|
||||
integer_14 = 14
|
||||
integer_15 = 15
|
||||
integer_16 = 16
|
||||
integer_17 = 17
|
||||
integer_18 = 18
|
||||
integer_19 = 19
|
||||
integer_20 = 20
|
||||
integer_21 = 21
|
||||
integer_22 = 22
|
||||
integer_23 = 23
|
||||
integer_24 = 24
|
||||
integer_25 = 25
|
||||
integer_26 = 26
|
||||
integer_27 = 27
|
||||
integer_28 = 28
|
||||
integer_29 = 29
|
||||
integer_30 = 30
|
||||
integer_31 = 31
|
||||
integer_32 = 32
|
||||
integer_33 = 33
|
||||
integer_34 = 34
|
||||
integer_35 = 35
|
||||
integer_36 = 36
|
||||
@@ -1,57 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitColumn.json
|
||||
# timestamp: 2020-11-24T16:33:24+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitColumn(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
topLevel: Optional[Level] = None
|
||||
baseOffset: Optional[float] = None
|
||||
topOffset: Optional[float] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
height: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,59 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitDuct.json
|
||||
# timestamp: 2020-11-24T16:33:25+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitDuct(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
width: Optional[float] = None
|
||||
height: Optional[float] = None
|
||||
diameter: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
level: Optional[Level] = None
|
||||
velocity: Optional[float] = None
|
||||
system: Optional[Optional[str]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,78 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitExtrusionRoof.json
|
||||
# timestamp: 2020-11-24T16:33:25+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitExtrusionRoof(BaseModel):
|
||||
start: Optional[float] = None
|
||||
end: Optional[float] = None
|
||||
referenceLine: Optional[Line] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,57 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitFloor.json
|
||||
# timestamp: 2020-11-24T16:33:26+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitFloor(BaseModel):
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,58 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitFootprintRoof.json
|
||||
# timestamp: 2020-11-24T16:33:27+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitFootprintRoof(BaseModel):
|
||||
cutOffLevel: Optional[Level] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,55 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitLevel.json
|
||||
# timestamp: 2020-11-24T16:33:28+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitLevel(BaseModel):
|
||||
createView: Optional[bool] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,68 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitRoom.json
|
||||
# timestamp: 2020-11-24T16:33:28+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitRoom(BaseModel):
|
||||
basePoint: Optional[Point] = None
|
||||
number: Optional[Optional[str]] = None
|
||||
area: Optional[float] = None
|
||||
volume: Optional[float] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,53 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitShaft.json
|
||||
# timestamp: 2020-11-24T16:33:29+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitShaft(BaseModel):
|
||||
topLevel: Optional[Level] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,28 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitTopography.json
|
||||
# timestamp: 2020-11-24T16:33:30+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitTopography(BaseModel):
|
||||
baseGeometry: Optional[Mesh] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,11 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitUtils.json
|
||||
# timestamp: 2020-11-24T16:33:30+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RevitUtils(BaseModel):
|
||||
pass
|
||||
@@ -1,64 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitVerticalOpening.json
|
||||
# timestamp: 2020-11-24T16:33:31+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Element(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitVerticalOpening(BaseModel):
|
||||
host: Optional[Element] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,45 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitWall.json
|
||||
# timestamp: 2020-11-24T16:33:32+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitWall(BaseModel):
|
||||
topLevel: Optional[Level] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,58 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitWallOpening.json
|
||||
# timestamp: 2020-11-24T16:33:33+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitWall(BaseModel):
|
||||
topLevel: Optional[Level] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
|
||||
|
||||
class RevitWallOpening(BaseModel):
|
||||
host: Optional[RevitWall] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -1,22 +0,0 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RoomBoundaryLine.json
|
||||
# timestamp: 2020-11-24T16:33:35+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class RoomBoundaryLine(BaseModel):
|
||||
baseCurve: Optional[ICurve] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,207 @@
|
||||
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 (
|
||||
SpeckleException,
|
||||
SpeckleWarning,
|
||||
)
|
||||
from typing import Dict
|
||||
|
||||
from specklepy.api import resources
|
||||
from specklepy.api.resources import (
|
||||
branch,
|
||||
commit,
|
||||
stream,
|
||||
object,
|
||||
server,
|
||||
user,
|
||||
subscriptions,
|
||||
)
|
||||
from specklepy.api.models import ServerInfo
|
||||
from gql import Client
|
||||
from gql.transport.requests import RequestsHTTPTransport
|
||||
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 an account (account has been added in Speckle Manager)
|
||||
account = get_default_account()
|
||||
client.authenticate_with_account(account)
|
||||
|
||||
# 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
|
||||
|
||||
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"
|
||||
|
||||
if use_ssl:
|
||||
ws_protocol = "wss"
|
||||
http_protocol = "https"
|
||||
|
||||
# sanitise host input by removing protocol and trailing slash
|
||||
host = re.sub(r"((^\w+:|^)\/\/)|(\/$)", "", host)
|
||||
|
||||
self.url = f"{http_protocol}://{host}"
|
||||
self.graphql = f"{self.url}/graphql"
|
||||
self.ws_url = f"{ws_protocol}://{host}/graphql"
|
||||
self.account = Account()
|
||||
|
||||
self.httpclient = Client(
|
||||
transport=RequestsHTTPTransport(url=self.graphql, verify=True, retries=3)
|
||||
)
|
||||
self.wsclient = None
|
||||
|
||||
self._init_resources()
|
||||
|
||||
# ? 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} )"
|
||||
|
||||
@deprecated(
|
||||
version="2.6.0",
|
||||
reason="Renamed: please use `authenticate_with_account` or `authenticate_with_token` instead.",
|
||||
)
|
||||
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
|
||||
|
||||
Arguments:
|
||||
token {str} -- an api token
|
||||
"""
|
||||
self.authenticate_with_token(token)
|
||||
self._set_up_client()
|
||||
|
||||
def authenticate_with_token(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
|
||||
|
||||
Arguments:
|
||||
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:
|
||||
"""Authenticate the client using an Account object
|
||||
The account is saved in the client object and a synchronous GraphQL entrypoint is created
|
||||
|
||||
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",
|
||||
}
|
||||
httptransport = RequestsHTTPTransport(
|
||||
url=self.graphql, headers=headers, verify=True, retries=3
|
||||
)
|
||||
wstransport = WebsocketsTransport(
|
||||
url=self.ws_url,
|
||||
init_payload={"Authorization": f"Bearer {self.account.token}"},
|
||||
)
|
||||
self.httpclient = Client(transport=httptransport)
|
||||
self.wsclient = Client(transport=wstransport)
|
||||
|
||||
self._init_resources()
|
||||
|
||||
if self.user.get() is None:
|
||||
warn(
|
||||
SpeckleWarning(
|
||||
f"Possibly invalid token - could not authenticate Speckle Client for server {self.url}"
|
||||
)
|
||||
)
|
||||
|
||||
def execute_query(self, query: str) -> Dict:
|
||||
return self.httpclient.execute(query)
|
||||
|
||||
def _init_resources(self) -> None:
|
||||
self.server = server.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
server_version = None
|
||||
try:
|
||||
server_version = self.server.version()
|
||||
except:
|
||||
pass
|
||||
self.user = user.Resource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.stream = stream.Resource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.commit = commit.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.branch = branch.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.object = object.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.subscribe = subscriptions.Resource(
|
||||
account=self.account,
|
||||
basepath=self.ws_url,
|
||||
client=self.wsclient,
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
attr = getattr(resources, name)
|
||||
return attr.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
except:
|
||||
raise SpeckleException(
|
||||
f"Method {name} is not supported by the SpeckleClient class"
|
||||
)
|
||||
@@ -0,0 +1,135 @@
|
||||
import os
|
||||
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
|
||||
from typing import List, Optional
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.api.models import ServerInfo
|
||||
from specklepy.transports.sqlite import SQLiteTransport
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
name: Optional[str]
|
||||
email: Optional[str]
|
||||
company: Optional[str]
|
||||
id: Optional[str]
|
||||
|
||||
|
||||
class Account(BaseModel):
|
||||
isDefault: bool = False
|
||||
token: Optional[str] = None
|
||||
refreshToken: Optional[str] = None
|
||||
serverInfo: ServerInfo = Field(default_factory=ServerInfo)
|
||||
userInfo: UserInfo = Field(default_factory=UserInfo)
|
||||
id: Optional[str] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Account(email: {self.userInfo.email}, server: {self.serverInfo.url}, isDefault: {self.isDefault})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
@classmethod
|
||||
def from_token(cls, token: str, server_url: str = None):
|
||||
acct = cls(token=token)
|
||||
acct.serverInfo.url = server_url
|
||||
return acct
|
||||
|
||||
|
||||
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)
|
||||
# pylint: disable=protected-access
|
||||
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: List[Account] = []
|
||||
res = account_storage.get_all_objects()
|
||||
account_storage.close()
|
||||
|
||||
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,
|
||||
) from ex
|
||||
|
||||
metrics.track(
|
||||
metrics.ACCOUNTS,
|
||||
next(
|
||||
(acc for acc in accounts if acc.isDefault),
|
||||
accounts[0] if accounts else None,
|
||||
),
|
||||
)
|
||||
|
||||
return accounts
|
||||
|
||||
|
||||
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(base_path=base_path)
|
||||
if not accounts:
|
||||
return None
|
||||
|
||||
default = next((acc for acc in accounts if acc.isDefault), None)
|
||||
if not default:
|
||||
default = accounts[0]
|
||||
default.isDefault = True
|
||||
metrics.initialise_tracker(default)
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def get_account_from_token(token: str, server_url: str = None) -> Account:
|
||||
"""Gets the local account for the token if it exists
|
||||
Arguments:
|
||||
token {str} -- the api token
|
||||
|
||||
Returns:
|
||||
Account -- the local account with this token or a shell account containing just the token and url if no local account is found
|
||||
"""
|
||||
accounts = get_local_accounts()
|
||||
if not accounts:
|
||||
return Account.from_token(token, server_url)
|
||||
|
||||
acct = next((acc for acc in accounts if acc.token == token), None)
|
||||
if acct:
|
||||
return acct
|
||||
|
||||
if server_url:
|
||||
url = server_url.lower()
|
||||
acct = next(
|
||||
(acc for acc in accounts if url in acc.serverInfo.url.lower()), None
|
||||
)
|
||||
if acct:
|
||||
return acct
|
||||
|
||||
return Account.from_token(token, server_url)
|
||||
|
||||
|
||||
class StreamWrapper:
|
||||
def __init__(self, url: str = None) -> None:
|
||||
raise SpeckleException(
|
||||
message="The StreamWrapper has moved as of v2.6.0! Please import from specklepy.api.wrapper",
|
||||
exception=DeprecationWarning,
|
||||
)
|
||||
@@ -0,0 +1,170 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: stream_schema.json
|
||||
# timestamp: 2020-11-17T14:33:13+00:00
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
from pydantic import BaseModel # pylint: disable=no-name-in-module
|
||||
|
||||
|
||||
class Collaborator(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
role: Optional[str]
|
||||
avatar: Optional[str]
|
||||
|
||||
|
||||
class Commit(BaseModel):
|
||||
id: Optional[str]
|
||||
message: Optional[str]
|
||||
authorName: Optional[str]
|
||||
authorId: Optional[str]
|
||||
authorAvatar: Optional[str]
|
||||
branchName: Optional[str]
|
||||
createdAt: Optional[datetime]
|
||||
sourceApplication: Optional[str]
|
||||
referencedObject: Optional[str]
|
||||
totalChildrenCount: Optional[int]
|
||||
parents: Optional[List[str]]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Commit( id: {self.id}, message: {self.message}, referencedObject: {self.referencedObject}, authorName: {self.authorName}, branchName: {self.branchName}, createdAt: {self.createdAt} )"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class Commits(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[datetime]
|
||||
items: List[Commit] = []
|
||||
|
||||
|
||||
class Object(BaseModel):
|
||||
id: Optional[str]
|
||||
speckleType: Optional[str]
|
||||
applicationId: Optional[str]
|
||||
totalChildrenCount: Optional[int]
|
||||
createdAt: Optional[datetime]
|
||||
|
||||
|
||||
class Branch(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
description: Optional[str]
|
||||
commits: Optional[Commits]
|
||||
|
||||
|
||||
class Branches(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[datetime]
|
||||
items: List[Branch] = []
|
||||
|
||||
|
||||
class Stream(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
role: Optional[str]
|
||||
isPublic: Optional[bool]
|
||||
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})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class Streams(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[datetime]
|
||||
items: List[Stream] = []
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: Optional[str]
|
||||
email: Optional[str]
|
||||
name: Optional[str]
|
||||
bio: Optional[str]
|
||||
company: Optional[str]
|
||||
avatar: Optional[str]
|
||||
verified: Optional[bool]
|
||||
role: Optional[str]
|
||||
streams: Optional[Streams]
|
||||
|
||||
def __repr__(self):
|
||||
return f"User( id: {self.id}, name: {self.name}, email: {self.email}, company: {self.company} )"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class PendingStreamCollaborator(BaseModel):
|
||||
id: Optional[str]
|
||||
inviteId: Optional[str]
|
||||
streamId: Optional[str]
|
||||
streamName: Optional[str]
|
||||
title: Optional[str]
|
||||
role: Optional[str]
|
||||
invitedBy: Optional[User]
|
||||
user: Optional[User]
|
||||
token: Optional[str]
|
||||
|
||||
def __repr__(self):
|
||||
return f"PendingStreamCollaborator( inviteId: {self.inviteId}, streamId: {self.streamId}, role: {self.role}, title: {self.title}, invitedBy: {self.user.name if self.user else None})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
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() if self.cursor else None} )"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class ServerInfo(BaseModel):
|
||||
name: Optional[str]
|
||||
company: Optional[str]
|
||||
url: Optional[str]
|
||||
description: Optional[str]
|
||||
adminContact: Optional[str]
|
||||
canonicalUrl: Optional[str]
|
||||
roles: Optional[List[dict]]
|
||||
scopes: Optional[List[dict]]
|
||||
authStrategies: Optional[List[dict]]
|
||||
version: Optional[str]
|
||||
@@ -0,0 +1,126 @@
|
||||
from typing import List
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.transports.sqlite import SQLiteTransport
|
||||
from specklepy.transports.server import ServerTransport
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.transports.abstract_transport import AbstractTransport
|
||||
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
||||
|
||||
|
||||
def send(
|
||||
base: Base,
|
||||
transports: List[AbstractTransport] = None,
|
||||
use_default_cache: bool = True,
|
||||
):
|
||||
"""Sends an object via the provided transports. Defaults to the local cache.
|
||||
|
||||
Arguments:
|
||||
obj {Base} -- the object you want to send
|
||||
transports {list} -- where you want to send them
|
||||
use_default_cache {bool} -- toggle for the default cache. If set to false, it will only send to the provided transports
|
||||
|
||||
Returns:
|
||||
str -- the object id of the sent object
|
||||
"""
|
||||
|
||||
if not transports and not use_default_cache:
|
||||
raise SpeckleException(
|
||||
message="You need to provide at least one transport: cannot send with an empty transport list and no default cache"
|
||||
)
|
||||
|
||||
if isinstance(transports, AbstractTransport):
|
||||
transports = [transports]
|
||||
|
||||
if transports is None:
|
||||
metrics.track(metrics.SEND)
|
||||
transports = []
|
||||
else:
|
||||
metrics.track(metrics.SEND, getattr(transports[0], "account", None))
|
||||
|
||||
if use_default_cache:
|
||||
transports.insert(0, SQLiteTransport())
|
||||
|
||||
serializer = BaseObjectSerializer(write_transports=transports)
|
||||
|
||||
obj_hash, _ = serializer.write_json(base=base)
|
||||
|
||||
return obj_hash
|
||||
|
||||
|
||||
def receive(
|
||||
obj_id: str,
|
||||
remote_transport: AbstractTransport = None,
|
||||
local_transport: AbstractTransport = None,
|
||||
) -> Base:
|
||||
"""Receives an object from a transport.
|
||||
|
||||
Arguments:
|
||||
obj_id {str} -- the id of the object to receive
|
||||
remote_transport {Transport} -- the transport to receive from
|
||||
local_transport {Transport} -- the local cache to check for existing objects
|
||||
(defaults to `SQLiteTransport`)
|
||||
|
||||
Returns:
|
||||
Base -- the base object
|
||||
"""
|
||||
metrics.track(metrics.RECEIVE, getattr(remote_transport, "account", None))
|
||||
if not local_transport:
|
||||
local_transport = SQLiteTransport()
|
||||
|
||||
serializer = BaseObjectSerializer(read_transport=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)
|
||||
|
||||
if not remote_transport:
|
||||
raise SpeckleException(
|
||||
message="Could not find the specified object using the local transport, and you didn't provide a fallback remote from which to pull it."
|
||||
)
|
||||
|
||||
obj_string = remote_transport.copy_object_and_children(
|
||||
id=obj_id, target_transport=local_transport
|
||||
)
|
||||
|
||||
return serializer.read_json(obj_string=obj_string)
|
||||
|
||||
|
||||
def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str:
|
||||
"""
|
||||
Serialize a base object. If no write transports are provided, the object will be serialized
|
||||
without detaching or chunking any of the attributes.
|
||||
|
||||
Arguments:
|
||||
base {Base} -- the object to serialize
|
||||
write_transports {List[AbstractTransport]} -- optional: the transports to write to
|
||||
|
||||
Returns:
|
||||
str -- the serialized object
|
||||
"""
|
||||
metrics.track(metrics.SERIALIZE)
|
||||
serializer = BaseObjectSerializer(write_transports=write_transports)
|
||||
|
||||
return serializer.write_json(base)[1]
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Arguments:
|
||||
obj_string {str} -- the string object to deserialize
|
||||
read_transport {AbstractTransport} -- the transport to fetch children objects from
|
||||
(defaults to SQLiteTransport)
|
||||
|
||||
Returns:
|
||||
Base -- the deserialized object
|
||||
"""
|
||||
metrics.track(metrics.DESERIALIZE)
|
||||
if not read_transport:
|
||||
read_transport = SQLiteTransport()
|
||||
|
||||
serializer = BaseObjectSerializer(read_transport=read_transport)
|
||||
|
||||
return serializer.read_json(obj_string=obj_string)
|
||||
@@ -0,0 +1,114 @@
|
||||
from graphql import DocumentNode
|
||||
from specklepy.api.credentials import Account
|
||||
from specklepy.transports.sqlite import SQLiteTransport
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
||||
from gql.client import Client
|
||||
from gql.transport.exceptions import TransportQueryError
|
||||
from specklepy.logging.exceptions import (
|
||||
GraphQLException,
|
||||
SpeckleException,
|
||||
UnsupportedException,
|
||||
)
|
||||
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
||||
|
||||
|
||||
class ResourceBase(object):
|
||||
def __init__(
|
||||
self,
|
||||
account: Account,
|
||||
basepath: str,
|
||||
client: Client,
|
||||
name: str,
|
||||
server_version: Optional[Tuple[Any, ...]] = None,
|
||||
) -> None:
|
||||
self.account = account
|
||||
self.basepath = basepath
|
||||
self.client = client
|
||||
self.name = name
|
||||
self.server_version = server_version
|
||||
self.schema: Optional[Type] = None
|
||||
|
||||
def _step_into_response(self, response: dict, return_type: Union[str, List, None]):
|
||||
"""Step into the dict to get the relevant data"""
|
||||
if return_type is None:
|
||||
return response
|
||||
if isinstance(return_type, str):
|
||||
return response[return_type]
|
||||
if isinstance(return_type, List):
|
||||
for key in return_type:
|
||||
response = response[key]
|
||||
return response
|
||||
|
||||
def _parse_response(self, response: Union[dict, list, None], schema=None):
|
||||
"""Try to create a class instance from the response"""
|
||||
if response is None:
|
||||
return None
|
||||
if isinstance(response, list):
|
||||
return [self._parse_response(response=r, schema=schema) for r in response]
|
||||
if schema:
|
||||
return schema.parse_obj(response)
|
||||
elif self.schema:
|
||||
try:
|
||||
return self.schema.parse_obj(response)
|
||||
except:
|
||||
s = BaseObjectSerializer(read_transport=SQLiteTransport())
|
||||
return s.recompose_base(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def make_request(
|
||||
self,
|
||||
query: DocumentNode,
|
||||
params: Dict = None,
|
||||
return_type: Union[str, List, None] = None,
|
||||
schema=None,
|
||||
parse_response: bool = True,
|
||||
) -> Any:
|
||||
"""Executes the GraphQL query"""
|
||||
try:
|
||||
response = self.client.execute(query, variable_values=params)
|
||||
except Exception as ex:
|
||||
if isinstance(ex, TransportQueryError):
|
||||
return GraphQLException(
|
||||
message=f"Failed to execute the GraphQL {self.name} request. Errors: {ex.errors}",
|
||||
errors=ex.errors,
|
||||
data=ex.data,
|
||||
)
|
||||
else:
|
||||
return SpeckleException(
|
||||
message=f"Failed to execute the GraphQL {self.name} request. Inner exception: {ex}",
|
||||
exception=ex,
|
||||
)
|
||||
|
||||
response = self._step_into_response(response=response, return_type=return_type)
|
||||
|
||||
if parse_response:
|
||||
return self._parse_response(response=response, schema=schema)
|
||||
else:
|
||||
return response
|
||||
|
||||
def _check_server_version_at_least(
|
||||
self, target_version: Tuple[Any, ...], unsupported_message: str = None
|
||||
):
|
||||
"""Use this check to guard against making unsupported requests on older servers.
|
||||
|
||||
Arguments:
|
||||
target_version {tuple} -- the minimum server version in the format (major, minor, patch, (tag, build))
|
||||
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
||||
"""
|
||||
if not unsupported_message:
|
||||
unsupported_message = f"The client method used is not supported on Speckle Server versios prior to v{'.'.join(target_version)}"
|
||||
if self.server_version and self.server_version < target_version:
|
||||
raise UnsupportedException(unsupported_message)
|
||||
|
||||
def _check_invites_supported(self):
|
||||
"""Invites are only supported for Speckle Server >= 2.6.4.
|
||||
Use this check to guard against making unsupported requests on older servers.
|
||||
"""
|
||||
self._check_server_version_at_least(
|
||||
(2, 6, 4),
|
||||
(
|
||||
"Stream invites are only supported as of Speckle Server v2.6.4. "
|
||||
"Please update your Speckle Server to use this method or use the `grant_permission` flow instead."
|
||||
),
|
||||
)
|
||||
@@ -1,13 +1,12 @@
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import inspect
|
||||
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__)
|
||||
|
||||
if hasattr(imported_module, "Resource"):
|
||||
setattr(sys.modules[__name__], name, imported_module)
|
||||
setattr(sys.modules[__name__], name, imported_module)
|
||||
@@ -0,0 +1,214 @@
|
||||
from gql import gql
|
||||
from specklepy.api.resource import ResourceBase
|
||||
from specklepy.api.models import Branch
|
||||
from specklepy.logging import metrics
|
||||
|
||||
NAME = "branch"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for branches"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
self.schema = Branch
|
||||
|
||||
def create(
|
||||
self, stream_id: str, name: str, description: str = "No description provided"
|
||||
) -> str:
|
||||
"""Create a new branch on this stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the new branch
|
||||
description {str} -- a short description of the branch
|
||||
|
||||
Returns:
|
||||
id {str} -- the newly created branch's id
|
||||
"""
|
||||
metrics.track(metrics.BRANCH, self.account, {"name": "create"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation BranchCreate($branch: BranchCreateInput!) {
|
||||
branchCreate(branch: $branch)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"branch": {
|
||||
"streamId": stream_id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="branchCreate", parse_response=False
|
||||
)
|
||||
|
||||
def get(self, stream_id: str, name: str, commits_limit: int = 10):
|
||||
"""Get a branch by name from a stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to get the branch from
|
||||
name {str} -- the name of the branch to get
|
||||
commits_limit {int} -- maximum number of commits to get
|
||||
|
||||
Returns:
|
||||
Branch -- the fetched branch with its latest commits
|
||||
"""
|
||||
metrics.track(metrics.BRANCH, self.account, {"name": "get"})
|
||||
query = gql(
|
||||
"""
|
||||
query BranchGet($stream_id: String!, $name: String!, $commits_limit: Int!) {
|
||||
stream(id: $stream_id) {
|
||||
branch(name: $name) {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
commits (limit: $commits_limit) {
|
||||
totalCount,
|
||||
cursor,
|
||||
items {
|
||||
id,
|
||||
referencedObject,
|
||||
sourceApplication,
|
||||
totalChildrenCount,
|
||||
message,
|
||||
authorName,
|
||||
authorId,
|
||||
branchName,
|
||||
parents,
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"stream_id": stream_id, "name": name, "commits_limit": commits_limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["stream", "branch"]
|
||||
)
|
||||
|
||||
def list(self, stream_id: str, branches_limit: int = 10, commits_limit: int = 10):
|
||||
"""Get a list of branches from a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to get the branches from
|
||||
branches_limit {int} -- maximum number of branches to get
|
||||
commits_limit {int} -- maximum number of commits to get
|
||||
|
||||
Returns:
|
||||
List[Branch] -- the branches on the stream
|
||||
"""
|
||||
metrics.track(metrics.BRANCH, self.account, {"name": "get"})
|
||||
query = gql(
|
||||
"""
|
||||
query BranchesGet($stream_id: String!, $branches_limit: Int!, $commits_limit: Int!) {
|
||||
stream(id: $stream_id) {
|
||||
branches(limit: $branches_limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
commits(limit: $commits_limit) {
|
||||
totalCount
|
||||
items{
|
||||
id
|
||||
message
|
||||
referencedObject
|
||||
sourceApplication
|
||||
parents
|
||||
authorId
|
||||
authorName
|
||||
branchName
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"stream_id": stream_id,
|
||||
"branches_limit": branches_limit,
|
||||
"commits_limit": commits_limit,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["stream", "branches", "items"]
|
||||
)
|
||||
|
||||
def update(
|
||||
self, stream_id: str, branch_id: str, name: str = None, description: str = None
|
||||
):
|
||||
"""Update a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream containing the branch to update
|
||||
branch_id {str} -- the id of the branch to update
|
||||
name {str} -- optional: the updated branch name
|
||||
description {str} -- optional: the updated branch description
|
||||
|
||||
Returns:
|
||||
bool -- True if update is successful
|
||||
"""
|
||||
metrics.track(metrics.BRANCH, self.account, {"name": "update"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation BranchUpdate($branch: BranchUpdateInput!) {
|
||||
branchUpdate(branch: $branch)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"branch": {
|
||||
"streamId": stream_id,
|
||||
"id": branch_id,
|
||||
}
|
||||
}
|
||||
|
||||
if name:
|
||||
params["branch"]["name"] = name
|
||||
if description:
|
||||
params["branch"]["description"] = description
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="branchUpdate", parse_response=False
|
||||
)
|
||||
|
||||
def delete(self, stream_id: str, branch_id: str):
|
||||
"""Delete a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream containing the branch to delete
|
||||
branch_id {str} -- the branch to delete
|
||||
|
||||
Returns:
|
||||
bool -- True if deletion is successful
|
||||
"""
|
||||
metrics.track(metrics.BRANCH, self.account, {"name": "delete"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation BranchDelete($branch: BranchDeleteInput!) {
|
||||
branchDelete(branch: $branch)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"branch": {"streamId": stream_id, "id": branch_id}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="branchDelete", parse_response=False
|
||||
)
|
||||
@@ -0,0 +1,233 @@
|
||||
from typing import Optional, List
|
||||
from gql import gql
|
||||
from specklepy.api.resource import ResourceBase
|
||||
from specklepy.api.models import Commit
|
||||
from specklepy.logging import metrics
|
||||
|
||||
|
||||
NAME = "commit"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for commits"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
self.schema = Commit
|
||||
|
||||
def get(self, stream_id: str, commit_id: str) -> Commit:
|
||||
"""
|
||||
Gets a commit given a stream and the commit id
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream where we can find the commit
|
||||
commit_id {str} -- the id of the commit you want to get
|
||||
|
||||
Returns:
|
||||
Commit -- the retrieved commit object
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Commit($stream_id: String!, $commit_id: String!) {
|
||||
stream(id: $stream_id) {
|
||||
commit(id: $commit_id) {
|
||||
id
|
||||
message
|
||||
referencedObject
|
||||
authorId
|
||||
authorName
|
||||
authorAvatar
|
||||
branchName
|
||||
createdAt
|
||||
sourceApplication
|
||||
totalChildrenCount
|
||||
parents
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"stream_id": stream_id, "commit_id": commit_id}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["stream", "commit"]
|
||||
)
|
||||
|
||||
def list(self, stream_id: str, limit: int = 10) -> List[Commit]:
|
||||
"""
|
||||
Get a list of commits on a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream where the commits are
|
||||
limit {int} -- the maximum number of commits to fetch (default = 10)
|
||||
|
||||
Returns:
|
||||
List[Commit] -- a list of the most recent commit objects
|
||||
"""
|
||||
metrics.track(metrics.COMMIT, self.account, {"name": "get"})
|
||||
query = gql(
|
||||
"""
|
||||
query Commits($stream_id: String!, $limit: Int!) {
|
||||
stream(id: $stream_id) {
|
||||
commits(limit: $limit) {
|
||||
items {
|
||||
id
|
||||
message
|
||||
referencedObject
|
||||
authorName
|
||||
authorId
|
||||
authorName
|
||||
authorAvatar
|
||||
branchName
|
||||
createdAt
|
||||
sourceApplication
|
||||
totalChildrenCount
|
||||
parents
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"stream_id": stream_id, "limit": limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["stream", "commits", "items"]
|
||||
)
|
||||
|
||||
def create(
|
||||
self,
|
||||
stream_id: str,
|
||||
object_id: str,
|
||||
branch_name: str = "main",
|
||||
message: str = "",
|
||||
source_application: str = "python",
|
||||
parents: List[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Creates a commit on a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream you want to commit to
|
||||
object_id {str} -- the hash of your commit object
|
||||
branch_name {str} -- the name of the branch to commit to (defaults to "main")
|
||||
message {str} -- optional: a message to give more information about the commit
|
||||
source_application{str} -- optional: the application from which the commit was created (defaults to "python")
|
||||
parents {List[str]} -- optional: the id of the parent commits
|
||||
|
||||
Returns:
|
||||
str -- the id of the created commit
|
||||
"""
|
||||
metrics.track(metrics.COMMIT, self.account, {"name": "create"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation CommitCreate ($commit: CommitCreateInput!){ commitCreate(commit: $commit)}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"commit": {
|
||||
"streamId": stream_id,
|
||||
"branchName": branch_name,
|
||||
"objectId": object_id,
|
||||
"message": message,
|
||||
"sourceApplication": source_application,
|
||||
}
|
||||
}
|
||||
if parents:
|
||||
params["commit"]["parents"] = parents
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="commitCreate", parse_response=False
|
||||
)
|
||||
|
||||
def update(self, stream_id: str, commit_id: str, message: str) -> bool:
|
||||
"""
|
||||
Update a commit
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream that contains the commit you'd like to update
|
||||
commit_id {str} -- the id of the commit you'd like to update
|
||||
message {str} -- the updated commit message
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation succeeded
|
||||
"""
|
||||
metrics.track(metrics.COMMIT, self.account, {"name": "update"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation CommitUpdate($commit: CommitUpdateInput!){ commitUpdate(commit: $commit)}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"commit": {"streamId": stream_id, "id": commit_id, "message": message}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="commitUpdate", parse_response=False
|
||||
)
|
||||
|
||||
def delete(self, stream_id: str, commit_id: str) -> bool:
|
||||
"""
|
||||
Delete a commit
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream that contains the commit you'd like to delete
|
||||
commit_id {str} -- the id of the commit you'd like to delete
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation succeeded
|
||||
"""
|
||||
metrics.track(metrics.COMMIT, self.account, {"name": "delete"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation CommitDelete($commit: CommitDeleteInput!){ commitDelete(commit: $commit)}
|
||||
"""
|
||||
)
|
||||
params = {"commit": {"streamId": stream_id, "id": commit_id}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="commitDelete", parse_response=False
|
||||
)
|
||||
|
||||
def received(
|
||||
self,
|
||||
stream_id: str,
|
||||
commit_id: str,
|
||||
source_application: str = "python",
|
||||
message: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Mark a commit object a received by the source application.
|
||||
"""
|
||||
metrics.track(metrics.COMMIT, self.account, {"name": "received"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation CommitReceive($receivedInput:CommitReceivedInput!){
|
||||
commitReceive(input:$receivedInput)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"receivedInput": {
|
||||
"sourceApplication": source_application,
|
||||
"streamId": stream_id,
|
||||
"commitId": commit_id,
|
||||
"message": "message",
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="commitReceive",
|
||||
parse_response=False,
|
||||
)
|
||||
except Exception as ex:
|
||||
print(ex.with_traceback)
|
||||
return False
|
||||
@@ -0,0 +1,83 @@
|
||||
from typing import Dict, List
|
||||
from gql import gql
|
||||
from specklepy.api.resource import ResourceBase
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
NAME = "object"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for objects"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
self.schema = Base
|
||||
|
||||
def get(self, stream_id: str, object_id: str) -> Base:
|
||||
"""
|
||||
Get a stream object
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream for the object
|
||||
object_id {str} -- the hash of the object you want to get
|
||||
|
||||
Returns:
|
||||
Base -- the returned Base object
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Object($stream_id: String!, $object_id: String!) {
|
||||
stream(id: $stream_id) {
|
||||
id
|
||||
name
|
||||
object(id: $object_id) {
|
||||
id
|
||||
speckleType
|
||||
applicationId
|
||||
createdAt
|
||||
totalChildrenCount
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"stream_id": stream_id, "object_id": object_id}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type=["stream", "object", "data"],
|
||||
)
|
||||
|
||||
def create(self, stream_id: str, objects: List[Dict]) -> str:
|
||||
"""
|
||||
Not advised - generally, you want to use `operations.send()`.
|
||||
|
||||
Create a new object on a stream. To send a base object, you can prepare it by running it through the
|
||||
`BaseObjectSerializer.traverse_base()` function to get a valid (serialisable) object to send.
|
||||
|
||||
NOTE: this does not create a commit - you can create one with `SpeckleClient.commit.create`. Dynamic fields will be located in the 'data' dict of the received `Base` object
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream you want to send the object to
|
||||
objects {List[Dict]} -- a list of base dictionary objects (NOTE: must be json serialisable)
|
||||
|
||||
Returns:
|
||||
str -- the id of the object
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation ObjectCreate($object_input: ObjectCreateInput!) { objectCreate(objectInput: $object_input) }
|
||||
"""
|
||||
)
|
||||
params = {"object_input": {"streamId": stream_id, "objects": objects}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="objectCreate", parse_response=False
|
||||
)
|
||||
@@ -0,0 +1,178 @@
|
||||
import re
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from gql import gql
|
||||
from specklepy.api.models import ServerInfo
|
||||
from specklepy.api.resource import ResourceBase
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.logging.exceptions import GraphQLException
|
||||
|
||||
|
||||
NAME = "server"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for the server"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
|
||||
def get(self) -> ServerInfo:
|
||||
"""Get the server info
|
||||
|
||||
Returns:
|
||||
dict -- the server info in dictionary form
|
||||
"""
|
||||
metrics.track(metrics.SERVER, self.account, {"name": "get"})
|
||||
query = gql(
|
||||
"""
|
||||
query Server {
|
||||
serverInfo {
|
||||
name
|
||||
company
|
||||
description
|
||||
adminContact
|
||||
canonicalUrl
|
||||
version
|
||||
roles {
|
||||
name
|
||||
description
|
||||
resourceTarget
|
||||
}
|
||||
scopes {
|
||||
name
|
||||
description
|
||||
}
|
||||
authStrategies{
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(
|
||||
query=query, return_type="serverInfo", schema=ServerInfo
|
||||
)
|
||||
|
||||
def version(self) -> Tuple[Any, ...]:
|
||||
"""Get the server version
|
||||
|
||||
Returns:
|
||||
tuple -- the server version in the format (major, minor, patch, (tag, build))
|
||||
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
||||
"""
|
||||
# not tracking as it will be called along with other mutations / queries as a check
|
||||
query = gql(
|
||||
"""
|
||||
query Server {
|
||||
serverInfo {
|
||||
version
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
ver = self.make_request(
|
||||
query=query, return_type=["serverInfo", "version"], parse_response=False
|
||||
)
|
||||
if isinstance(ver, Exception):
|
||||
raise GraphQLException(
|
||||
f"Could not get server version for {self.basepath}", [ver]
|
||||
)
|
||||
|
||||
# pylint: disable=consider-using-generator; (list comp is faster)
|
||||
return tuple(
|
||||
[
|
||||
int(segment) if segment.isdigit() else segment
|
||||
for segment in re.split(r"\.|-", ver)
|
||||
]
|
||||
)
|
||||
|
||||
def apps(self) -> Dict:
|
||||
"""Get the apps registered on the server
|
||||
|
||||
Returns:
|
||||
dict -- a dictionary of apps registered on the server
|
||||
"""
|
||||
metrics.track(metrics.SERVER, self.account, {"name": "apps"})
|
||||
query = gql(
|
||||
"""
|
||||
query Apps {
|
||||
apps{
|
||||
id
|
||||
name
|
||||
description
|
||||
termsAndConditionsLink
|
||||
trustByDefault
|
||||
logo
|
||||
author {
|
||||
id
|
||||
name
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(query=query, return_type="apps", parse_response=False)
|
||||
|
||||
def create_token(self, name: str, scopes: List[str], lifespan: int) -> str:
|
||||
"""Create a personal API token
|
||||
|
||||
Arguments:
|
||||
scopes {List[str]} -- the scopes to grant with this token
|
||||
name {str} -- a name for your new token
|
||||
lifespan {int} -- duration before the token expires
|
||||
|
||||
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!) {
|
||||
apiTokenCreate(token: $token)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"token": {"scopes": scopes, "name": name, "lifespan": lifespan}}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="apiTokenCreate",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
def revoke_token(self, token: str) -> bool:
|
||||
"""Revokes (deletes) a personal API token
|
||||
|
||||
Arguments:
|
||||
token {str} -- the token to revoke (delete)
|
||||
|
||||
Returns:
|
||||
bool -- True if the token was successfully deleted
|
||||
"""
|
||||
metrics.track(metrics.SERVER, self.account, {"name": "revoke_token"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation TokenRevoke($token: String!) {
|
||||
apiTokenRevoke(token: $token)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"token": token}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="apiTokenRevoke",
|
||||
parse_response=False,
|
||||
)
|
||||
@@ -0,0 +1,769 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, Stream
|
||||
from specklepy.api.resource import ResourceBase
|
||||
from specklepy.logging.exceptions import UnsupportedException, SpeckleException
|
||||
|
||||
|
||||
NAME = "stream"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for streams"""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
server_version=server_version,
|
||||
)
|
||||
|
||||
self.schema = Stream
|
||||
|
||||
def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream:
|
||||
"""Get the specified stream from the server
|
||||
|
||||
Arguments:
|
||||
id {str} -- the stream id
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
Stream -- the retrieved stream
|
||||
"""
|
||||
metrics.track(metrics.STREAM, self.account, {"name": "get"})
|
||||
query = gql(
|
||||
"""
|
||||
query Stream($id: String!, $branch_limit: Int!, $commit_limit: Int!) {
|
||||
stream(id: $id) {
|
||||
id
|
||||
name
|
||||
role
|
||||
description
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
commentCount
|
||||
favoritesCount
|
||||
collaborators {
|
||||
id
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id, "branch_limit": branch_limit, "commit_limit": commit_limit}
|
||||
|
||||
return self.make_request(query=query, params=params, return_type="stream")
|
||||
|
||||
def list(self, stream_limit: int = 10) -> List[Stream]:
|
||||
"""Get a list of the user's streams
|
||||
|
||||
Arguments:
|
||||
stream_limit {int} -- The maximum number of streams to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- A list of Stream objects
|
||||
"""
|
||||
metrics.track(metrics.STREAM, self.account, {"name": "get"})
|
||||
query = gql(
|
||||
"""
|
||||
query User($stream_limit: Int!) {
|
||||
user {
|
||||
id
|
||||
bio
|
||||
name
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"stream_limit": stream_limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["user", "streams", "items"]
|
||||
)
|
||||
|
||||
def create(
|
||||
self,
|
||||
name: str = "Anonymous Python Stream",
|
||||
description: str = "No description provided",
|
||||
is_public: bool = True,
|
||||
) -> str:
|
||||
"""Create a new stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool} -- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
id {str} -- the id of the newly created stream
|
||||
"""
|
||||
metrics.track(metrics.STREAM, self.account, {"name": "create"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamCreate($stream: StreamCreateInput!) {
|
||||
streamCreate(stream: $stream)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"stream": {"name": name, "description": description, "isPublic": is_public}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamCreate", parse_response=False
|
||||
)
|
||||
|
||||
def update(
|
||||
self, id: str, name: str = None, description: str = None, is_public: bool = None
|
||||
) -> bool:
|
||||
"""Update an existing stream
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to be updated
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool} -- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
bool -- whether the stream update was successful
|
||||
"""
|
||||
metrics.track(metrics.STREAM, self.account, {"name": "update"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamUpdate($stream: StreamUpdateInput!) {
|
||||
streamUpdate(stream: $stream)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"isPublic": is_public,
|
||||
}
|
||||
# remove None values so graphql doesn't cry
|
||||
params = {"stream": {k: v for k, v in params.items() if v is not None}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamUpdate", parse_response=False
|
||||
)
|
||||
|
||||
def delete(self, id: str) -> bool:
|
||||
"""Delete a stream given its id
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to delete
|
||||
|
||||
Returns:
|
||||
bool -- whether the deletion was successful
|
||||
"""
|
||||
metrics.track(metrics.STREAM, self.account, {"name": "delete"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamDelete($id: String!) {
|
||||
streamDelete(id: $id)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamDelete", parse_response=False
|
||||
)
|
||||
|
||||
def search(
|
||||
self,
|
||||
search_query: str,
|
||||
limit: int = 25,
|
||||
branch_limit: int = 10,
|
||||
commit_limit: int = 10,
|
||||
):
|
||||
"""Search for streams by name, description, or id
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- a list of Streams that match the search query
|
||||
"""
|
||||
metrics.track(metrics.STREAM, self.account, {"name": "search"})
|
||||
query = gql(
|
||||
"""
|
||||
query StreamSearch($search_query: String!,$limit: Int!, $branch_limit:Int!, $commit_limit:Int!) {
|
||||
streams(query: $search_query, limit: $limit) {
|
||||
items {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"search_query": search_query,
|
||||
"limit": limit,
|
||||
"branch_limit": branch_limit,
|
||||
"commit_limit": commit_limit,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
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"]
|
||||
)
|
||||
|
||||
@deprecated(
|
||||
version="2.6.4",
|
||||
reason=(
|
||||
"As of Speckle Server v2.6.4, this method is deprecated. "
|
||||
"Users need to be invited and accept the invite before being added to a stream"
|
||||
),
|
||||
)
|
||||
def grant_permission(self, stream_id: str, user_id: str, role: str):
|
||||
"""Grant permissions to a user on a given stream
|
||||
|
||||
Valid for Speckle Server version < 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to grant permissions to
|
||||
user_id {str} -- the id of the user to grant permissions for
|
||||
role {str} -- the role to grant the user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.PERMISSION, self.account, {"name": "add", "role": role})
|
||||
if self.server_version and self.server_version >= (2, 6, 4):
|
||||
raise UnsupportedException(
|
||||
(
|
||||
"Server mutation `grant_permission` is no longer supported as of Speckle Server v2.6.4. "
|
||||
"Please use the new `update_permission` method to change an existing user's permission "
|
||||
"or use the `invite` method to invite a user to a stream."
|
||||
)
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamGrantPermission($permission_params: StreamGrantPermissionInput !) {
|
||||
streamGrantPermission(permissionParams: $permission_params)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"permission_params": {
|
||||
"streamId": stream_id,
|
||||
"userId": user_id,
|
||||
"role": role,
|
||||
}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamGrantPermission",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
def get_all_pending_invites(
|
||||
self, stream_id: str
|
||||
) -> List[PendingStreamCollaborator]:
|
||||
"""Get all of the pending invites on a stream.
|
||||
You must be a `stream:owner` to query this.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream id from which to get the pending invites
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator] -- a list of pending invites for the specified stream
|
||||
"""
|
||||
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query StreamInvites($streamId: String!) {
|
||||
stream(id: $streamId){
|
||||
pendingCollaborators {
|
||||
id
|
||||
token
|
||||
inviteId
|
||||
streamId
|
||||
streamName
|
||||
title
|
||||
role
|
||||
invitedBy{
|
||||
id
|
||||
name
|
||||
company
|
||||
avatar
|
||||
}
|
||||
user {
|
||||
id
|
||||
name
|
||||
company
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"streamId": stream_id}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type=["stream", "pendingCollaborators"],
|
||||
schema=PendingStreamCollaborator,
|
||||
)
|
||||
|
||||
def invite(
|
||||
self,
|
||||
stream_id: str,
|
||||
email: str = None,
|
||||
user_id: str = None,
|
||||
role: str = "stream:contributor", # should default be reviewer?
|
||||
message: str = None,
|
||||
):
|
||||
"""Invite someone to a stream using either their email or user id
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to invite the user to
|
||||
email {str} -- the email of the user to invite (use this OR `user_id`)
|
||||
user_id {str} -- the id of the user to invite (use this OR `email`)
|
||||
role {str} -- the role to assing to the user (defaults to `stream:contributor`)
|
||||
message {str} -- a message to send along with this invite to the specified user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.INVITE, self.account, {"name": "create"})
|
||||
self._check_invites_supported()
|
||||
|
||||
if email is None and user_id is None:
|
||||
raise SpeckleException(
|
||||
"You must provide either an email or a user id to use the `stream.invite` method"
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamInviteCreate($input: StreamInviteCreateInput!) {
|
||||
streamInviteCreate(input: $input)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"email": email,
|
||||
"userId": user_id,
|
||||
"streamId": stream_id,
|
||||
"message": message,
|
||||
"role": role,
|
||||
}
|
||||
params = {"input": {k: v for k, v in params.items() if v is not None}}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInviteCreate",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
def invite_batch(
|
||||
self,
|
||||
stream_id: str,
|
||||
emails: List[str] = None,
|
||||
user_ids: List[None] = None,
|
||||
message: str = None,
|
||||
) -> bool:
|
||||
"""Invite a batch of users to a specified stream.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to invite the user to
|
||||
emails {List[str]} -- the email of the user to invite (use this and/or `user_ids`)
|
||||
user_id {List[str]} -- the id of the user to invite (use this and/or `emails`)
|
||||
message {str} -- a message to send along with this invite to the specified user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.INVITE, self.account, {"name": "batch create"})
|
||||
self._check_invites_supported()
|
||||
if emails is None and user_ids is None:
|
||||
raise SpeckleException(
|
||||
"You must provide either an email or a user id to use the `stream.invite` method"
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamInviteBatchCreate($input: [StreamInviteCreateInput!]!) {
|
||||
streamInviteBatchCreate(input: $input)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
email_invites = [
|
||||
{"streamId": stream_id, "message": message, "email": email}
|
||||
for email in emails
|
||||
if emails is not None
|
||||
]
|
||||
|
||||
user_invites = [
|
||||
{"streamId": stream_id, "message": message, "userId": user_id}
|
||||
for user_id in user_ids
|
||||
if user_ids is not None
|
||||
]
|
||||
|
||||
params = {"input": [*email_invites, *user_invites]}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInviteBatchCreate",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
def invite_cancel(self, stream_id: str, invite_id: str) -> bool:
|
||||
"""Cancel an existing stream invite
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream invite
|
||||
invite_id {str} -- the id of the invite to use
|
||||
|
||||
Returns:
|
||||
bool -- true if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.INVITE, self.account, {"name": "cancel"})
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamInviteCancel($streamId: String!, $inviteId: String!) {
|
||||
streamInviteCancel(streamId: $streamId, inviteId: $inviteId)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"streamId": stream_id, "inviteId": invite_id}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInviteCancel",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
def invite_use(self, stream_id: str, token: str, accept: bool = True) -> bool:
|
||||
"""Accept or decline a stream invite
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream for which the user has a pending invite
|
||||
token {str} -- the token of the invite to use
|
||||
accept {bool} -- whether or not to accept the invite (defaults to True)
|
||||
|
||||
Returns:
|
||||
bool -- true if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.INVITE, self.account, {"name": "use"})
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamInviteUse($accept: Boolean!, $streamId: String!, $token: String!) {
|
||||
streamInviteUse(accept: $accept, streamId: $streamId, token: $token)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"streamId": stream_id, "token": token, "accept": accept}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInviteUse",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
def update_permission(self, stream_id: str, user_id: str, role: str):
|
||||
"""Updates permissions for a user on a given stream
|
||||
|
||||
Valid for Speckle Server >=2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to grant permissions to
|
||||
user_id {str} -- the id of the user to grant permissions for
|
||||
role {str} -- the role to grant the user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.PERMISSION, self.account, {"name": "update", "role": role}
|
||||
)
|
||||
if self.server_version and self.server_version < (2, 6, 4):
|
||||
raise UnsupportedException(
|
||||
(
|
||||
"Server mutation `update_permission` is only supported as of Speckle Server v2.6.4. "
|
||||
"Please update your Speckle Server to use this method or use the `grant_permission` method instead."
|
||||
)
|
||||
)
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamUpdatePermission($permission_params: StreamUpdatePermissionInput !) {
|
||||
streamUpdatePermission(permissionParams: $permission_params)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"permission_params": {
|
||||
"streamId": stream_id,
|
||||
"userId": user_id,
|
||||
"role": role,
|
||||
}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamUpdatePermission",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
def revoke_permission(self, stream_id: str, user_id: str):
|
||||
"""Revoke permissions from a user on a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to revoke permissions from
|
||||
user_id {str} -- the id of the user to revoke permissions from
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.PERMISSION, self.account, {"name": "revoke"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamRevokePermission($permission_params: StreamRevokePermissionInput !) {
|
||||
streamRevokePermission(permissionParams: $permission_params)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"permission_params": {"streamId": stream_id, "userId": user_id}}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
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,
|
||||
)
|
||||
@@ -1,16 +1,12 @@
|
||||
from typing import Callable, Dict, List, Optional, Any
|
||||
from typing import Callable, Dict, List, Union
|
||||
from functools import wraps
|
||||
from gql import gql
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.resources.stream import Stream
|
||||
from speckle.logging.exceptions import GraphQLException, SpeckleException
|
||||
from graphql import DocumentNode
|
||||
from specklepy.api.resource import ResourceBase
|
||||
from specklepy.api.resources.stream import Stream
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
NAME = "subscribe"
|
||||
METHODS = [
|
||||
"stream_added",
|
||||
"stream_updated",
|
||||
"stream_removed",
|
||||
]
|
||||
|
||||
|
||||
def check_wsclient(function):
|
||||
@@ -29,9 +25,12 @@ def check_wsclient(function):
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for subscriptions"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
|
||||
@check_wsclient
|
||||
@@ -105,15 +104,15 @@ class Resource(ResourceBase):
|
||||
@check_wsclient
|
||||
async def subscribe(
|
||||
self,
|
||||
query: gql,
|
||||
query: DocumentNode,
|
||||
params: Dict = None,
|
||||
callback: Callable = None,
|
||||
return_type: str or List = None,
|
||||
return_type: Union[str, List] = None,
|
||||
schema=None,
|
||||
parse_response: bool = True,
|
||||
):
|
||||
# if self.client.transport.websocket is None:
|
||||
# TODO: add multiple subs to the same ws connection
|
||||
# TODO: add multiple subs to the same ws connection
|
||||
async with self.client as session:
|
||||
async for res in session.subscribe(query, variable_values=params):
|
||||
res = self._step_into_response(response=res, return_type=return_type)
|
||||
@@ -122,4 +121,4 @@ class Resource(ResourceBase):
|
||||
if callback is not None:
|
||||
callback(res)
|
||||
else:
|
||||
return res
|
||||
return res
|
||||
@@ -0,0 +1,280 @@
|
||||
from typing import List, Optional, Union
|
||||
from datetime import datetime, timezone
|
||||
from gql import gql
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.api.resource import ResourceBase
|
||||
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
|
||||
|
||||
NAME = "user"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for users"""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.schema = User
|
||||
|
||||
def get(self, id: str = None) -> User:
|
||||
"""Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
||||
|
||||
Arguments:
|
||||
id {str} -- the user id
|
||||
|
||||
Returns:
|
||||
User -- the retrieved user
|
||||
"""
|
||||
metrics.track(metrics.USER, self.account, {"name": "get"})
|
||||
query = gql(
|
||||
"""
|
||||
query User($id: String) {
|
||||
user(id: $id) {
|
||||
id
|
||||
email
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id}
|
||||
|
||||
return self.make_request(query=query, params=params, return_type="user")
|
||||
|
||||
def search(
|
||||
self, search_query: str, limit: int = 25
|
||||
) -> Union[List[User], SpeckleException]:
|
||||
"""Searches for user by name or email. The search query must be at least 3 characters long
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
Returns:
|
||||
List[User] -- a list of User objects that match the search query
|
||||
"""
|
||||
if len(search_query) < 3:
|
||||
return SpeckleException(
|
||||
message="User search query must be at least 3 characters"
|
||||
)
|
||||
|
||||
metrics.track(metrics.USER, self.account, {"name": "search"})
|
||||
query = gql(
|
||||
"""
|
||||
query UserSearch($search_query: String!, $limit: Int!) {
|
||||
userSearch(query: $search_query, limit: $limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"search_query": search_query, "limit": limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["userSearch", "items"]
|
||||
)
|
||||
|
||||
def update(
|
||||
self, name: str = None, company: str = None, bio: str = None, avatar: str = None
|
||||
):
|
||||
"""Updates your user profile. All arguments are optional.
|
||||
|
||||
Arguments:
|
||||
name {str} -- your name
|
||||
company {str} -- the company you may or may not work for
|
||||
bio {str} -- tell us about yourself
|
||||
avatar {str} -- a nice photo of yourself
|
||||
|
||||
Returns:
|
||||
bool -- True if your profile was updated successfully
|
||||
"""
|
||||
metrics.track(metrics.USER, self.account, {"name": "update"})
|
||||
query = gql(
|
||||
"""
|
||||
mutation UserUpdate($user: UserUpdateInput!) {
|
||||
userUpdate(user: $user)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"name": name, "company": company, "bio": bio, "avatar": avatar}
|
||||
|
||||
params = {"user": {k: v for k, v in params.items() if v is not None}}
|
||||
|
||||
if not params["user"]:
|
||||
return SpeckleException(
|
||||
message="You must provide at least one field to update your user profile"
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||
"""Get all of the active user's pending stream invites
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator] -- a list of pending invites for the current user
|
||||
"""
|
||||
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query StreamInvites {
|
||||
streamInvites{
|
||||
id
|
||||
token
|
||||
inviteId
|
||||
streamId
|
||||
streamName
|
||||
title
|
||||
role
|
||||
invitedBy {
|
||||
id
|
||||
name
|
||||
company
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
return_type="streamInvites",
|
||||
schema=PendingStreamCollaborator,
|
||||
)
|
||||
|
||||
def get_pending_invite(
|
||||
self, stream_id: str, token: str = None
|
||||
) -> Optional[PendingStreamCollaborator]:
|
||||
"""Get a particular pending invite for the active user on a given stream.
|
||||
If no invite_id is provided, any valid invite will be returned.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to look for invites on
|
||||
token {str} -- the token of the invite to look for (optional)
|
||||
|
||||
Returns:
|
||||
PendingStreamCollaborator -- the invite for the given stream (or None if it isn't found)
|
||||
"""
|
||||
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query StreamInvite($streamId: String!, $token: String) {
|
||||
streamInvite(streamId: $streamId, token: $token) {
|
||||
id
|
||||
token
|
||||
streamId
|
||||
streamName
|
||||
title
|
||||
role
|
||||
invitedBy {
|
||||
id
|
||||
name
|
||||
company
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"streamId": stream_id}
|
||||
if token:
|
||||
params["token"] = token
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInvite",
|
||||
schema=PendingStreamCollaborator,
|
||||
)
|
||||
@@ -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")
|
||||
@@ -0,0 +1,166 @@
|
||||
from warnings import warn
|
||||
from urllib.parse import urlparse, unquote
|
||||
from specklepy.api.credentials import (
|
||||
Account,
|
||||
get_account_from_token,
|
||||
get_local_accounts,
|
||||
)
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.transports.server.server import ServerTransport
|
||||
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||
|
||||
|
||||
class StreamWrapper:
|
||||
"""
|
||||
The `StreamWrapper` gives you some handy helpers to deal with urls and get authenticated clients and transports.
|
||||
|
||||
Construct a `StreamWrapper` with a stream, branch, commit, or object URL. The corresponding ids will be stored
|
||||
in the wrapper. If you have local accounts on the machine, you can use the `get_account` and `get_client` methods
|
||||
to get a local account for the server. You can also pass a token into `get_client` if you don't have a corresponding
|
||||
local account for the server.
|
||||
|
||||
```py
|
||||
from specklepy.api.wrapper import StreamWrapper
|
||||
|
||||
# provide any stream, branch, commit, object, or globals url
|
||||
wrapper = StreamWrapper("https://speckle.xyz/streams/3073b96e86/commits/604bea8cc6")
|
||||
|
||||
# get an authenticated SpeckleClient if you have a local account for the server
|
||||
client = wrapper.get_client()
|
||||
|
||||
# get an authenticated ServerTransport if you have a local account for the server
|
||||
transport = wrapper.get_transport()
|
||||
```
|
||||
"""
|
||||
|
||||
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("/", 3)
|
||||
metrics.track(metrics.STREAM_WRAPPER, self.get_account())
|
||||
|
||||
if not segments 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."
|
||||
)
|
||||
|
||||
@property
|
||||
def server_url(self):
|
||||
return f"{'https' if self.use_ssl else 'http'}://{self.host}"
|
||||
|
||||
def get_account(self, token: str = None) -> Account:
|
||||
"""
|
||||
Gets an account object for this server from the local accounts db (added via Speckle Manager or a json file)
|
||||
"""
|
||||
if self._account and self._account.token:
|
||||
return self._account
|
||||
|
||||
self._account = next(
|
||||
(a for a in get_local_accounts() if self.host in a.serverInfo.url),
|
||||
None,
|
||||
)
|
||||
|
||||
if not self._account:
|
||||
self._account = get_account_from_token(token, self.server_url)
|
||||
|
||||
if self._client:
|
||||
self._client.authenticate_with_account(self._account)
|
||||
|
||||
return self._account
|
||||
|
||||
def get_client(self, token: str = None) -> SpeckleClient:
|
||||
"""
|
||||
Gets an authenticated client for this server. You may provide a token if there aren't any local accounts on this
|
||||
machine. If no account is found and no token is provided, an unauthenticated client is returned.
|
||||
|
||||
Arguments:
|
||||
token {str} -- optional token if no local account is available (defaults to None)
|
||||
|
||||
Returns:
|
||||
SpeckleClient -- authenticated with a corresponding local account or the provided token
|
||||
"""
|
||||
if self._client and token is None:
|
||||
return self._client
|
||||
|
||||
if not self._account or not self._account.token:
|
||||
self.get_account(token)
|
||||
|
||||
if not self._client:
|
||||
self._client = SpeckleClient(host=self.host, use_ssl=self.use_ssl)
|
||||
|
||||
if self._account.token is None and token is None:
|
||||
warn(f"No local account found for server {self.host}", SpeckleWarning)
|
||||
return self._client
|
||||
|
||||
if self._account.token:
|
||||
self._client.authenticate_with_account(self._account)
|
||||
else:
|
||||
self._client.authenticate_with_token(token)
|
||||
|
||||
return self._client
|
||||
|
||||
def get_transport(self, token: str = None) -> ServerTransport:
|
||||
"""
|
||||
Gets a server transport for this stream using an authenticated client. If there is no local account for this
|
||||
server and the client was not authenticated with a token, this will throw an exception.
|
||||
|
||||
Returns:
|
||||
ServerTransport -- constructed for this stream with a pre-authenticated client
|
||||
"""
|
||||
if not self._account or not self._account.token:
|
||||
self.get_account(token)
|
||||
return ServerTransport(self.stream_id, account=self._account)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user