From 780126528d6be4373fd27c58b657e5664918fa44 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 18 Oct 2024 13:24:38 +0100 Subject: [PATCH 01/26] Added project resource and fe2 models --- poetry.lock | 1110 ++++++++++------- src/specklepy/api/client.py | 22 +- src/specklepy/api/resources/active_user.py | 2 +- src/specklepy/api/resources/comment.py | 0 src/specklepy/api/resources/model.py | 0 src/specklepy/api/resources/project.py | 22 + src/specklepy/api/resources/version.py | 0 src/specklepy/core/api/enums.py | 21 + .../core/api/inputs/comment_inputs.py | 26 + src/specklepy/core/api/inputs/model_inputs.py | 26 + .../core/api/inputs/project_inputs.py | 63 + .../core/api/inputs/subscription_inputs.py | 8 + .../core/api/inputs/version_inputs.py | 33 + src/specklepy/core/api/models.py | 114 +- src/specklepy/core/api/new_models.py | 201 +++ src/specklepy/core/api/resource.py | 34 +- .../core/api/resources/active_user.py | 4 +- src/specklepy/core/api/resources/branch.py | 10 +- src/specklepy/core/api/resources/commit.py | 12 +- .../core/api/resources/graphql.config.yml | 2 + src/specklepy/core/api/resources/project.py | 305 +++++ .../core/api/resources/project_invite.py | 342 +++++ src/specklepy/core/api/resources/stream.py | 4 + src/specklepy/core/api/responses.py | 19 + .../{ => client}/test_active_user.py | 0 tests/integration/{ => client}/test_branch.py | 0 .../{ => client}/test_client_and_ops.py | 0 tests/integration/{ => client}/test_commit.py | 0 .../integration/{ => client}/test_objects.py | 0 .../{ => client}/test_other_user.py | 0 tests/integration/client/test_project.py | 179 +++ tests/integration/{ => client}/test_server.py | 0 tests/integration/{ => client}/test_stream.py | 0 tests/integration/{ => client}/test_user.py | 0 34 files changed, 1996 insertions(+), 563 deletions(-) create mode 100644 src/specklepy/api/resources/comment.py create mode 100644 src/specklepy/api/resources/model.py create mode 100644 src/specklepy/api/resources/project.py create mode 100644 src/specklepy/api/resources/version.py create mode 100644 src/specklepy/core/api/enums.py create mode 100644 src/specklepy/core/api/inputs/comment_inputs.py create mode 100644 src/specklepy/core/api/inputs/model_inputs.py create mode 100644 src/specklepy/core/api/inputs/project_inputs.py create mode 100644 src/specklepy/core/api/inputs/subscription_inputs.py create mode 100644 src/specklepy/core/api/inputs/version_inputs.py create mode 100644 src/specklepy/core/api/new_models.py create mode 100644 src/specklepy/core/api/resources/graphql.config.yml create mode 100644 src/specklepy/core/api/resources/project.py create mode 100644 src/specklepy/core/api/resources/project_invite.py create mode 100644 src/specklepy/core/api/responses.py rename tests/integration/{ => client}/test_active_user.py (100%) rename tests/integration/{ => client}/test_branch.py (100%) rename tests/integration/{ => client}/test_client_and_ops.py (100%) rename tests/integration/{ => client}/test_commit.py (100%) rename tests/integration/{ => client}/test_objects.py (100%) rename tests/integration/{ => client}/test_other_user.py (100%) create mode 100644 tests/integration/client/test_project.py rename tests/integration/{ => client}/test_server.py (100%) rename tests/integration/{ => client}/test_stream.py (100%) rename tests/integration/{ => client}/test_user.py (100%) diff --git a/poetry.lock b/poetry.lock index 46a38dce..fe771809 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -16,13 +16,13 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "anyio" -version = "4.4.0" +version = "4.5.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, + {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, + {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, ] [package.dependencies] @@ -32,9 +32,9 @@ sniffio = ">=1.1" typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +trio = ["trio (>=0.26.1)"] [[package]] name = "appdirs" @@ -77,8 +77,8 @@ files = [ lazy-object-proxy = ">=1.4.0" typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, ] [[package]] @@ -173,13 +173,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -258,63 +258,83 @@ typing-extensions = ">=4.0.1,<5.0.0" [[package]] name = "coverage" -version = "7.5.3" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, - {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, - {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, - {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, - {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, - {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, - {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, - {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, - {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, - {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, - {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, - {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, - {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, - {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] @@ -371,13 +391,13 @@ pygments = ["Pygments (>=2.2.0)"] [[package]] name = "dill" -version = "0.3.8" +version = "0.3.9" description = "serialize all of Python" optional = false python-versions = ">=3.8" files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, + {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, + {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, ] [package.extras] @@ -386,24 +406,24 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "distlib" -version = "0.3.8" +version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -422,19 +442,19 @@ files = [ [[package]] name = "filelock" -version = "3.14.0" +version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "gql" @@ -469,15 +489,18 @@ websockets = ["websockets (>=10,<12)"] [[package]] name = "graphql-core" -version = "3.2.3" +version = "3.2.5" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." optional = false -python-versions = ">=3.6,<4" +python-versions = "<4,>=3.6" files = [ - {file = "graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"}, - {file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, + {file = "graphql_core-3.2.5-py3-none-any.whl", hash = "sha256:2f150d5096448aa4f8ab26268567bbfeef823769893b39c1a2e1409590939c8a"}, + {file = "graphql_core-3.2.5.tar.gz", hash = "sha256:e671b90ed653c808715645e3998b7ab67d382d55467b7e2978549111bbabf8d5"}, ] +[package.dependencies] +typing-extensions = {version = ">=4,<5", markers = "python_version < \"3.10\""} + [[package]] name = "h11" version = "0.14.0" @@ -491,13 +514,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.5" +version = "1.0.6" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, + {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, + {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, ] [package.dependencies] @@ -508,7 +531,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" @@ -536,13 +559,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.5.36" +version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, - {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, + {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, + {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, ] [package.extras] @@ -550,15 +573,18 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -729,103 +755,108 @@ files = [ [[package]] name = "multidict" -version = "6.0.5" +version = "6.1.0" description = "multidict implementation" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + [[package]] name = "mypy" version = "0.982" @@ -893,13 +924,13 @@ files = [ [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -915,19 +946,19 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" @@ -964,123 +995,244 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.46" +version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.46-py3-none-any.whl", hash = "sha256:45abe60a8300f3c618b23c16c4bb98c6fc80af8ce8b17c7ae92db48db3ee63c1"}, - {file = "prompt_toolkit-3.0.46.tar.gz", hash = "sha256:869c50d682152336e23c4db7f74667639b5047494202ffe7670817053fd57795"}, + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] wcwidth = "*" +[[package]] +name = "propcache" +version = "0.2.0" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.8" +files = [ + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, + {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, + {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, + {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, + {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, + {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, + {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, + {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, + {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, + {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, + {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, + {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, + {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, + {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, + {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, +] + [[package]] name = "pydantic" -version = "2.7.3" +version = "2.9.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, - {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.18.4" -typing-extensions = ">=4.6.1" +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] [package.extras] email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.18.4" +version = "2.23.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, - {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, - {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, - {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, - {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, - {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, - {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, - {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, - {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, - {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, - {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, - {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, - {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, - {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, - {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, - {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, - {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, - {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, - {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, - {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, - {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, - {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, - {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, - {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, - {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, - {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, - {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, - {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, - {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, - {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, - {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, - {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, - {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, ] [package.dependencies] @@ -1088,13 +1240,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.3.1" +version = "2.6.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.3.1-py3-none-any.whl", hash = "sha256:acb2c213140dfff9669f4fe9f8180d43914f51626db28ab2db7308a576cce51a"}, - {file = "pydantic_settings-2.3.1.tar.gz", hash = "sha256:e34bbd649803a6bb3e2f0f58fb0edff1f0c7f556849fda106cc21bcce12c30ab"}, + {file = "pydantic_settings-2.6.0-py3-none-any.whl", hash = "sha256:4a819166f119b74d7f8c765196b165f95cc7487ce58ea27dec8a5a26be0970e0"}, + {file = "pydantic_settings-2.6.0.tar.gz", hash = "sha256:44a1804abffac9e6a30372bb45f6cafab945ef5af25e66b1c634c01dd39e0188"}, ] [package.dependencies] @@ -1102,6 +1254,7 @@ pydantic = ">=2.7.0" python-dotenv = ">=0.21.0" [package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] @@ -1120,8 +1273,8 @@ files = [ astroid = ">=2.15.8,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, + {version = ">=0.2", markers = "python_version < \"3.11\""}, ] isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" @@ -1205,62 +1358,64 @@ cli = ["click (>=5.0)"] [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] @@ -1317,28 +1472,28 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "ruff" -version = "0.4.8" +version = "0.4.10" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7663a6d78f6adb0eab270fa9cf1ff2d28618ca3a652b60f2a234d92b9ec89066"}, - {file = "ruff-0.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eeceb78da8afb6de0ddada93112869852d04f1cd0f6b80fe464fd4e35c330913"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aad360893e92486662ef3be0a339c5ca3c1b109e0134fcd37d534d4be9fb8de3"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:284c2e3f3396fb05f5f803c9fffb53ebbe09a3ebe7dda2929ed8d73ded736deb"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7354f921e3fbe04d2a62d46707e569f9315e1a613307f7311a935743c51a764"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:72584676164e15a68a15778fd1b17c28a519e7a0622161eb2debdcdabdc71883"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9678d5c9b43315f323af2233a04d747409d1e3aa6789620083a82d1066a35199"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704977a658131651a22b5ebeb28b717ef42ac6ee3b11e91dc87b633b5d83142b"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05f8d6f0c3cce5026cecd83b7a143dcad503045857bc49662f736437380ad45"}, - {file = "ruff-0.4.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ea874950daca5697309d976c9afba830d3bf0ed66887481d6bca1673fc5b66a"}, - {file = "ruff-0.4.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fc95aac2943ddf360376be9aa3107c8cf9640083940a8c5bd824be692d2216dc"}, - {file = "ruff-0.4.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:384154a1c3f4bf537bac69f33720957ee49ac8d484bfc91720cc94172026ceed"}, - {file = "ruff-0.4.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e9d5ce97cacc99878aa0d084c626a15cd21e6b3d53fd6f9112b7fc485918e1fa"}, - {file = "ruff-0.4.8-py3-none-win32.whl", hash = "sha256:6d795d7639212c2dfd01991259460101c22aabf420d9b943f153ab9d9706e6a9"}, - {file = "ruff-0.4.8-py3-none-win_amd64.whl", hash = "sha256:e14a3a095d07560a9d6769a72f781d73259655919d9b396c650fc98a8157555d"}, - {file = "ruff-0.4.8-py3-none-win_arm64.whl", hash = "sha256:14019a06dbe29b608f6b7cbcec300e3170a8d86efaddb7b23405cb7f7dcaf780"}, - {file = "ruff-0.4.8.tar.gz", hash = "sha256:16d717b1d57b2e2fd68bd0bf80fb43931b79d05a7131aa477d66fc40fbd86268"}, + {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, + {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, + {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, + {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, + {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, + {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, ] [[package]] @@ -1389,24 +1544,24 @@ tests = ["pytest", "pytest-cov"] [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] name = "tomlkit" -version = "0.12.5" +version = "0.13.2" description = "Style preserving TOML library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, - {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, ] [[package]] @@ -1422,13 +1577,13 @@ files = [ [[package]] name = "types-requests" -version = "2.32.0.20240602" +version = "2.32.0.20241016" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {file = "types-requests-2.32.0.20240602.tar.gz", hash = "sha256:3f98d7bbd0dd94ebd10ff43a7fbe20c3b8528acace6d8efafef0b6a184793f06"}, - {file = "types_requests-2.32.0.20240602-py3-none-any.whl", hash = "sha256:ed3946063ea9fbc6b5fc0c44fa279188bae42d582cb63760be6cb4b9d06c3de8"}, + {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, + {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, ] [package.dependencies] @@ -1447,13 +1602,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.1" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -1545,13 +1700,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -1562,13 +1717,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.2" +version = "20.27.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, - {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, + {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, + {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, ] [package.dependencies] @@ -1751,106 +1906,115 @@ files = [ [[package]] name = "yarl" -version = "1.9.4" +version = "1.15.2" description = "Yet another URL library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, + {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, + {file = "yarl-1.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43ebdcc120e2ca679dba01a779333a8ea76b50547b55e812b8b92818d604662c"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433da95b51a75692dcf6cc8117a31410447c75a9a8187888f02ad45c0a86c50"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38d0124fa992dbacd0c48b1b755d3ee0a9f924f427f95b0ef376556a24debf01"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ded1b1803151dd0f20a8945508786d57c2f97a50289b16f2629f85433e546d47"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace4cad790f3bf872c082366c9edd7f8f8f77afe3992b134cfc810332206884f"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c77494a2f2282d9bbbbcab7c227a4d1b4bb829875c96251f66fb5f3bae4fb053"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b7f227ca6db5a9fda0a2b935a2ea34a7267589ffc63c8045f0e4edb8d8dcf956"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:31561a5b4d8dbef1559b3600b045607cf804bae040f64b5f5bca77da38084a8a"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e52474256a7db9dcf3c5f4ca0b300fdea6c21cca0148c8891d03a025649d935"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1af74a9529a1137c67c887ed9cde62cff53aa4d84a3adbec329f9ec47a3936"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:15c87339490100c63472a76d87fe7097a0835c705eb5ae79fd96e343473629ed"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:74abb8709ea54cc483c4fb57fb17bb66f8e0f04438cff6ded322074dbd17c7ec"}, + {file = "yarl-1.15.2-cp310-cp310-win32.whl", hash = "sha256:ffd591e22b22f9cb48e472529db6a47203c41c2c5911ff0a52e85723196c0d75"}, + {file = "yarl-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:1695497bb2a02a6de60064c9f077a4ae9c25c73624e0d43e3aa9d16d983073c2"}, + {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9fcda20b2de7042cc35cf911702fa3d8311bd40055a14446c1e62403684afdc5"}, + {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0545de8c688fbbf3088f9e8b801157923be4bf8e7b03e97c2ecd4dfa39e48e0e"}, + {file = "yarl-1.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbda058a9a68bec347962595f50546a8a4a34fd7b0654a7b9697917dc2bf810d"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ac2bc069f4a458634c26b101c2341b18da85cb96afe0015990507efec2e417"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd126498171f752dd85737ab1544329a4520c53eed3997f9b08aefbafb1cc53b"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3db817b4e95eb05c362e3b45dafe7144b18603e1211f4a5b36eb9522ecc62bcf"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:076b1ed2ac819933895b1a000904f62d615fe4533a5cf3e052ff9a1da560575c"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8cfd847e6b9ecf9f2f2531c8427035f291ec286c0a4944b0a9fce58c6446046"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:32b66be100ac5739065496c74c4b7f3015cef792c3174982809274d7e51b3e04"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34a2d76a1984cac04ff8b1bfc939ec9dc0914821264d4a9c8fd0ed6aa8d4cfd2"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0afad2cd484908f472c8fe2e8ef499facee54a0a6978be0e0cff67b1254fd747"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c68e820879ff39992c7f148113b46efcd6ec765a4865581f2902b3c43a5f4bbb"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:98f68df80ec6ca3015186b2677c208c096d646ef37bbf8b49764ab4a38183931"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56ec1eacd0a5d35b8a29f468659c47f4fe61b2cab948ca756c39b7617f0aa5"}, + {file = "yarl-1.15.2-cp311-cp311-win32.whl", hash = "sha256:eedc3f247ee7b3808ea07205f3e7d7879bc19ad3e6222195cd5fbf9988853e4d"}, + {file = "yarl-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:0ccaa1bc98751fbfcf53dc8dfdb90d96e98838010fc254180dd6707a6e8bb179"}, + {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82d5161e8cb8f36ec778fd7ac4d740415d84030f5b9ef8fe4da54784a1f46c94"}, + {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa2bea05ff0a8fb4d8124498e00e02398f06d23cdadd0fe027d84a3f7afde31e"}, + {file = "yarl-1.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99e12d2bf587b44deb74e0d6170fec37adb489964dbca656ec41a7cd8f2ff178"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:243fbbbf003754fe41b5bdf10ce1e7f80bcc70732b5b54222c124d6b4c2ab31c"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:856b7f1a7b98a8c31823285786bd566cf06226ac4f38b3ef462f593c608a9bd6"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:553dad9af802a9ad1a6525e7528152a015b85fb8dbf764ebfc755c695f488367"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30c3ff305f6e06650a761c4393666f77384f1cc6c5c0251965d6bfa5fbc88f7f"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:353665775be69bbfc6d54c8d134bfc533e332149faeddd631b0bc79df0897f46"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f4fe99ce44128c71233d0d72152db31ca119711dfc5f2c82385ad611d8d7f897"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9c1e3ff4b89cdd2e1a24c214f141e848b9e0451f08d7d4963cb4108d4d798f1f"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:711bdfae4e699a6d4f371137cbe9e740dc958530cb920eb6f43ff9551e17cfbc"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4388c72174868884f76affcdd3656544c426407e0043c89b684d22fb265e04a5"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0e1844ad47c7bd5d6fa784f1d4accc5f4168b48999303a868fe0f8597bde715"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5cafb02cf097a82d74403f7e0b6b9df3ffbfe8edf9415ea816314711764a27b"}, + {file = "yarl-1.15.2-cp312-cp312-win32.whl", hash = "sha256:156ececdf636143f508770bf8a3a0498de64da5abd890c7dbb42ca9e3b6c05b8"}, + {file = "yarl-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:435aca062444a7f0c884861d2e3ea79883bd1cd19d0a381928b69ae1b85bc51d"}, + {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:416f2e3beaeae81e2f7a45dc711258be5bdc79c940a9a270b266c0bec038fb84"}, + {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:173563f3696124372831007e3d4b9821746964a95968628f7075d9231ac6bb33"}, + {file = "yarl-1.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ce2e0f6123a60bd1a7f5ae3b2c49b240c12c132847f17aa990b841a417598a2"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaea112aed589131f73d50d570a6864728bd7c0c66ef6c9154ed7b59f24da611"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4ca3b9f370f218cc2a0309542cab8d0acdfd66667e7c37d04d617012485f904"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23ec1d3c31882b2a8a69c801ef58ebf7bae2553211ebbddf04235be275a38548"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75119badf45f7183e10e348edff5a76a94dc19ba9287d94001ff05e81475967b"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e6fdc976ec966b99e4daa3812fac0274cc28cd2b24b0d92462e2e5ef90d368"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8657d3f37f781d987037f9cc20bbc8b40425fa14380c87da0cb8dfce7c92d0fb"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:93bed8a8084544c6efe8856c362af08a23e959340c87a95687fdbe9c9f280c8b"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69d5856d526802cbda768d3e6246cd0d77450fa2a4bc2ea0ea14f0d972c2894b"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ccad2800dfdff34392448c4bf834be124f10a5bc102f254521d931c1c53c455a"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a880372e2e5dbb9258a4e8ff43f13888039abb9dd6d515f28611c54361bc5644"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c998d0558805860503bc3a595994895ca0f7835e00668dadc673bbf7f5fbfcbe"}, + {file = "yarl-1.15.2-cp313-cp313-win32.whl", hash = "sha256:533a28754e7f7439f217550a497bb026c54072dbe16402b183fdbca2431935a9"}, + {file = "yarl-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:5838f2b79dc8f96fdc44077c9e4e2e33d7089b10788464609df788eb97d03aad"}, + {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fbbb63bed5fcd70cd3dd23a087cd78e4675fb5a2963b8af53f945cbbca79ae16"}, + {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2e93b88ecc8f74074012e18d679fb2e9c746f2a56f79cd5e2b1afcf2a8a786b"}, + {file = "yarl-1.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af8ff8d7dc07ce873f643de6dfbcd45dc3db2c87462e5c387267197f59e6d776"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66f629632220a4e7858b58e4857927dd01a850a4cef2fb4044c8662787165cf7"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833547179c31f9bec39b49601d282d6f0ea1633620701288934c5f66d88c3e50"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa738e0282be54eede1e3f36b81f1e46aee7ec7602aa563e81e0e8d7b67963f"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a13a07532e8e1c4a5a3afff0ca4553da23409fad65def1b71186fb867eeae8d"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c45817e3e6972109d1a2c65091504a537e257bc3c885b4e78a95baa96df6a3f8"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:670eb11325ed3a6209339974b276811867defe52f4188fe18dc49855774fa9cf"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:d417a4f6943112fae3924bae2af7112562285848d9bcee737fc4ff7cbd450e6c"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bc8936d06cd53fddd4892677d65e98af514c8d78c79864f418bbf78a4a2edde4"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:954dde77c404084c2544e572f342aef384240b3e434e06cecc71597e95fd1ce7"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5bc0df728e4def5e15a754521e8882ba5a5121bd6b5a3a0ff7efda5d6558ab3d"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b71862a652f50babab4a43a487f157d26b464b1dedbcc0afda02fd64f3809d04"}, + {file = "yarl-1.15.2-cp38-cp38-win32.whl", hash = "sha256:63eab904f8630aed5a68f2d0aeab565dcfc595dc1bf0b91b71d9ddd43dea3aea"}, + {file = "yarl-1.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:2cf441c4b6e538ba0d2591574f95d3fdd33f1efafa864faa077d9636ecc0c4e9"}, + {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a32d58f4b521bb98b2c0aa9da407f8bd57ca81f34362bcb090e4a79e9924fefc"}, + {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:766dcc00b943c089349d4060b935c76281f6be225e39994c2ccec3a2a36ad627"}, + {file = "yarl-1.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bed1b5dbf90bad3bfc19439258c97873eab453c71d8b6869c136346acfe497e7"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed20a4bdc635f36cb19e630bfc644181dd075839b6fc84cac51c0f381ac472e2"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d538df442c0d9665664ab6dd5fccd0110fa3b364914f9c85b3ef9b7b2e157980"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c6cf1d92edf936ceedc7afa61b07e9d78a27b15244aa46bbcd534c7458ee1b"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce44217ad99ffad8027d2fde0269ae368c86db66ea0571c62a000798d69401fb"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47a6000a7e833ebfe5886b56a31cb2ff12120b1efd4578a6fcc38df16cc77bd"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e52f77a0cd246086afde8815039f3e16f8d2be51786c0a39b57104c563c5cbb0"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f9ca0e6ce7774dc7830dc0cc4bb6b3eec769db667f230e7c770a628c1aa5681b"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:136f9db0f53c0206db38b8cd0c985c78ded5fd596c9a86ce5c0b92afb91c3a19"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:173866d9f7409c0fb514cf6e78952e65816600cb888c68b37b41147349fe0057"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:6e840553c9c494a35e449a987ca2c4f8372668ee954a03a9a9685075228e5036"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:458c0c65802d816a6b955cf3603186de79e8fdb46d4f19abaec4ef0a906f50a7"}, + {file = "yarl-1.15.2-cp39-cp39-win32.whl", hash = "sha256:5b48388ded01f6f2429a8c55012bdbd1c2a0c3735b3e73e221649e524c34a58d"}, + {file = "yarl-1.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:81dadafb3aa124f86dc267a2168f71bbd2bfb163663661ab0038f6e4b8edb810"}, + {file = "yarl-1.15.2-py3-none-any.whl", hash = "sha256:0d3105efab7c5c091609abacad33afff33bdff0035bece164c98bcf5a85ef90a"}, + {file = "yarl-1.15.2.tar.gz", hash = "sha256:a39c36f4218a5bb668b4f06874d676d35a035ee668e6e7e3538835c703634b84"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" +propcache = ">=0.2.0" [metadata] lock-version = "2.0" diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index cd538d0e..9835f406 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -3,16 +3,17 @@ from specklepy.api.credentials import Account from specklepy.api.resources import ( active_user, + other_user, + server, + user, branch, commit, object, - other_user, - server, stream, subscriptions, - user, ) from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient +from specklepy.core.api.resources.project import ProjectResource from specklepy.logging import metrics @@ -67,24 +68,33 @@ 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 Exception: pass - self.user = user.Resource( + + self.other_user = other_user.Resource( account=self.account, basepath=self.url, client=self.httpclient, server_version=server_version, ) - self.other_user = other_user.Resource( + self.active_user = active_user.Resource( account=self.account, basepath=self.url, client=self.httpclient, server_version=server_version, ) - self.active_user = active_user.Resource( + self.project = ProjectResource( + account=self.account, + basepath=self.url, + client=self.httpclient, + server_version=server_version, + ) + # Deprecated Resources + self.user = user.Resource( account=self.account, basepath=self.url, client=self.httpclient, diff --git a/src/specklepy/api/resources/active_user.py b/src/specklepy/api/resources/active_user.py index 014bcb28..26016e68 100644 --- a/src/specklepy/api/resources/active_user.py +++ b/src/specklepy/api/resources/active_user.py @@ -35,7 +35,7 @@ def update( company: Optional[str] = None, bio: Optional[str] = None, avatar: Optional[str] = None, - ): + ) -> bool: """Updates your user profile. All arguments are optional. Args: diff --git a/src/specklepy/api/resources/comment.py b/src/specklepy/api/resources/comment.py new file mode 100644 index 00000000..e69de29b diff --git a/src/specklepy/api/resources/model.py b/src/specklepy/api/resources/model.py new file mode 100644 index 00000000..e69de29b diff --git a/src/specklepy/api/resources/project.py b/src/specklepy/api/resources/project.py new file mode 100644 index 00000000..fe310808 --- /dev/null +++ b/src/specklepy/api/resources/project.py @@ -0,0 +1,22 @@ +from datetime import datetime +from typing import List, Optional + +from specklepy.api.models import PendingStreamCollaborator, Stream +from specklepy.core.api.resources.stream import Resource as CoreResource +from specklepy.logging import metrics + + +class Resource(CoreResource): + """API Access class for projects""" + + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + server_version=server_version, + ) + + self.schema = Stream + + diff --git a/src/specklepy/api/resources/version.py b/src/specklepy/api/resources/version.py new file mode 100644 index 00000000..e69de29b diff --git a/src/specklepy/core/api/enums.py b/src/specklepy/core/api/enums.py new file mode 100644 index 00000000..1c64943f --- /dev/null +++ b/src/specklepy/core/api/enums.py @@ -0,0 +1,21 @@ +from enum import Enum + + +class FileUploadConversionStatus(Enum): + QUEUED = 0 + PROCESSING = 1 + SUCCESS = 2 + ERROR = 3 + + +class ProjectVisibility(str, Enum): + PRIVATE = "PRIVATE" + PUBLIC = "PUBLIC" + UNLISTEd = "UNLISTEd" + + +class ResourceType(str, Enum): + COMMIT = "COMMIT" + STREAM = "STREAM" + OBJECT = "OBJECT" + COMMENT = "COMMENT" diff --git a/src/specklepy/core/api/inputs/comment_inputs.py b/src/specklepy/core/api/inputs/comment_inputs.py new file mode 100644 index 00000000..0406d3ab --- /dev/null +++ b/src/specklepy/core/api/inputs/comment_inputs.py @@ -0,0 +1,26 @@ +from typing import Any, Optional, Sequence + +from pydantic import BaseModel + + +class CommentContentInput(BaseModel): + blobIds: Optional[Sequence[str]] + doc: Any + + +class CreateCommentInput(BaseModel): + content: CommentContentInput + projectId: str + resourceIdString: str + screenshot: Optional[str] + viewerState: Optional[Any] + + +class EditCommentInput(BaseModel): + content: CommentContentInput + commentId: str + + +class CreateCommentReplyInput(BaseModel): + content: CommentContentInput + threadId: str diff --git a/src/specklepy/core/api/inputs/model_inputs.py b/src/specklepy/core/api/inputs/model_inputs.py new file mode 100644 index 00000000..90b8b072 --- /dev/null +++ b/src/specklepy/core/api/inputs/model_inputs.py @@ -0,0 +1,26 @@ +from typing import Optional, Sequence + +from pydantic import BaseModel + + +class CreateModelInput(BaseModel): + name: str + description: Optional[str] + projectId: str + + +class DeleteModelInput(BaseModel): + id: str + projectId: str + + +class UpdateModelInput(BaseModel): + id: str + name: Optional[str] + description: Optional[str] + projectId: str + + +class ModelVersionsFilter(BaseModel): + priorityIds: Sequence[str] + priorityIdsOnly: Optional[bool] diff --git a/src/specklepy/core/api/inputs/project_inputs.py b/src/specklepy/core/api/inputs/project_inputs.py new file mode 100644 index 00000000..bc713c1e --- /dev/null +++ b/src/specklepy/core/api/inputs/project_inputs.py @@ -0,0 +1,63 @@ +from typing import Optional, Sequence +from pydantic import BaseModel + +from specklepy.core.api.models import ProjectVisibility + + +class ProjectCommentsFilter(BaseModel): + includeArchived: Optional[bool] + loadedVersionsOnly: Optional[bool] + resourceIdString: Optional[str] + + +class ProjectCreateInput(BaseModel): + name: Optional[str] + description: Optional[str] + visibility: Optional[ProjectVisibility] + + +class ProjectInviteCreateInput(BaseModel): + email: Optional[str] + role: Optional[str] + serverRole: Optional[str] + userId: Optional[str] + + +class ProjectInviteUseInput(BaseModel): + accept: bool + projectId: str + token: str + + +class ProjectModelsFilter(BaseModel): + contributors: Optional[Sequence[str]] + excludeIds: Optional[Sequence[str]] + ids: Optional[Sequence[str]] + onlyWithVersions: Optional[bool] + search: Optional[str] + sourceApps: Sequence[str] + + +class ProjectModelsTreeFilter(BaseModel): + contributors: Optional[Sequence[str]] + search: Optional[str] + sourceApps: Sequence[str] + + +class ProjectUpdateInput(BaseModel): + id: str + name: Optional[str] = None + description: Optional[str] = None + allowPublicComments: Optional[bool] = None + visibility: Optional[ProjectVisibility] = None + + +class ProjectUpdateRoleInput(BaseModel): + userId: str + projectId: str + role: Optional[str] + + +class UserProjectsFilter(BaseModel): + search: str + onlyWithRole: Optional[Sequence[str]] = None diff --git a/src/specklepy/core/api/inputs/subscription_inputs.py b/src/specklepy/core/api/inputs/subscription_inputs.py new file mode 100644 index 00000000..314394fd --- /dev/null +++ b/src/specklepy/core/api/inputs/subscription_inputs.py @@ -0,0 +1,8 @@ +from typing import Optional +from pydantic import BaseModel + + +class ViewerUpdateTrackingTarget(BaseModel): + projectId: str + resourceIdString: str + loadedVersionsOnly: Optional[bool] = None diff --git a/src/specklepy/core/api/inputs/version_inputs.py b/src/specklepy/core/api/inputs/version_inputs.py new file mode 100644 index 00000000..40df6ff5 --- /dev/null +++ b/src/specklepy/core/api/inputs/version_inputs.py @@ -0,0 +1,33 @@ +from typing import Optional, Sequence +from pydantic import BaseModel + + +class UpdateVersionInput(BaseModel): + versionId: str + message: Optional[str] + + +class MoveVersionsInput(BaseModel): + targetModelName: str + versionIds: Sequence[str] + + +class DeleteVersionsInput(BaseModel): + versionIds: Sequence[str] + + +class CreateVersionInput(BaseModel): + objectId: str + modelId: str + projectId: str + message: Optional[str] = None + sourceApplication: Optional[str] = "py" + totalChildrenCount: Optional[int] = None + parents: Optional[Sequence[str]] = None + + +class MarkReceivedVersionInput(BaseModel): + versionId: str + projectId: str + sourceApplication: str + message: Optional[str] = None diff --git a/src/specklepy/core/api/models.py b/src/specklepy/core/api/models.py index a1afbc47..eab806ad 100644 --- a/src/specklepy/core/api/models.py +++ b/src/specklepy/core/api/models.py @@ -1,9 +1,39 @@ from datetime import datetime from typing import List, Optional +from deprecated import deprecated from pydantic import BaseModel, Field +from specklepy.core.api.responses import ResourceCollection +FE1_DEPRECATION_REASON = "Stream/Branch/Commit API is now deprecated, Use the new Project/Model/Version API functions in Client}" +FE1_DEPRECATION_VERSION = "2.20" +from specklepy.core.api.new_models import * + + +class User(BaseModel): + id: str + email: Optional[str] = None + name: str + bio: Optional[str] = None + company: Optional[str] = None + avatar: Optional[str] = None + verified: Optional[bool] = None + role: Optional[str] = None + streams: Optional["Streams"] = None + projects: ResourceCollection[Project] + + def __repr__(self): + return ( + f"User( id: {self.id}, name: {self.name}, email: {self.email}, company:" + f" {self.company} )" + ) + + def __str__(self) -> str: + return self.__repr__() + + +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Collaborator(BaseModel): id: Optional[str] = None name: Optional[str] = None @@ -11,6 +41,7 @@ class Collaborator(BaseModel): avatar: Optional[str] = None +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Commit(BaseModel): id: Optional[str] = None message: Optional[str] = None @@ -35,12 +66,14 @@ def __str__(self) -> str: return self.__repr__() +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Commits(BaseModel): totalCount: Optional[int] = None cursor: Optional[datetime] = None items: List[Commit] = [] +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Object(BaseModel): id: Optional[str] = None speckleType: Optional[str] = None @@ -49,6 +82,7 @@ class Object(BaseModel): createdAt: Optional[datetime] = None +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Branch(BaseModel): id: Optional[str] = None name: Optional[str] = None @@ -56,12 +90,14 @@ class Branch(BaseModel): commits: Optional[Commits] = None +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Branches(BaseModel): totalCount: Optional[int] = None cursor: Optional[datetime] = None items: List[Branch] = [] +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Stream(BaseModel): id: Optional[str] = None name: Optional[str] = None @@ -88,67 +124,14 @@ def __str__(self) -> str: return self.__repr__() +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Streams(BaseModel): totalCount: Optional[int] = None cursor: Optional[datetime] = None items: List[Stream] = [] -class User(BaseModel): - id: Optional[str] = None - email: Optional[str] = None - name: Optional[str] = None - bio: Optional[str] = None - company: Optional[str] = None - avatar: Optional[str] = None - verified: Optional[bool] = None - role: Optional[str] = None - streams: Optional[Streams] = None - - def __repr__(self): - return ( - f"User( id: {self.id}, name: {self.name}, email: {self.email}, company:" - f" {self.company} )" - ) - - def __str__(self) -> str: - return self.__repr__() - - -class LimitedUser(BaseModel): - """Limited user type, for showing public info about a user to another user.""" - - id: str - name: Optional[str] = None - bio: Optional[str] = None - company: Optional[str] = None - avatar: Optional[str] = None - verified: Optional[bool] = None - role: Optional[str] = None - - -class PendingStreamCollaborator(BaseModel): - id: Optional[str] = None - inviteId: Optional[str] = None - streamId: Optional[str] = None - streamName: Optional[str] = None - title: Optional[str] = None - role: Optional[str] = None - invitedBy: Optional[User] = None - user: Optional[User] = None - token: Optional[str] = None - - def __repr__(self): - return ( - f"PendingStreamCollaborator( inviteId: {self.inviteId}, streamId:" - f" {self.streamId}, role: {self.role}, title: {self.title}, invitedBy:" - f" {self.user.name if self.user else None})" - ) - - def __str__(self) -> str: - return self.__repr__() - - +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Activity(BaseModel): actionType: Optional[str] = None info: Optional[dict] = None @@ -169,6 +152,7 @@ def __str__(self) -> str: return self.__repr__() +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class ActivityCollection(BaseModel): totalCount: Optional[int] = None items: Optional[List[Activity]] = None @@ -183,23 +167,3 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.__repr__() - - -class ServerMigration(BaseModel): - movedTo: Optional[str] = None - movedFrom: Optional[str] = None - - -class ServerInfo(BaseModel): - name: Optional[str] = None - company: Optional[str] = None - url: Optional[str] = None - description: Optional[str] = None - adminContact: Optional[str] = None - canonicalUrl: Optional[str] = None - roles: Optional[List[dict]] = None - scopes: Optional[List[dict]] = None - authStrategies: Optional[List[dict]] = None - version: Optional[str] = None - frontend2: Optional[bool] = None - migration: Optional[ServerMigration] = None diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/new_models.py new file mode 100644 index 00000000..2e8a0ad8 --- /dev/null +++ b/src/specklepy/core/api/new_models.py @@ -0,0 +1,201 @@ +from datetime import datetime +from typing import List, Optional +from pydantic import BaseModel + +from specklepy.core.api.enums import ( + FileUploadConversionStatus, + ProjectVisibility, + ResourceType, +) +from specklepy.core.api.responses import ResourceCollection + + +class ServerMigration(BaseModel): + movedFrom: Optional[str] = None + movedTo: Optional[str] = None + + +class AuthStrategy(BaseModel): + color: Optional[str] = None + icon: str + id: str + name: str + url: str + + +class ServerConfiguration(BaseModel): + blobSizeLimitBytes: int + objectMultipartUploadSizeLimitBytes: int + objectSizeLimitBytes: int + + +# Keeping this one all Optionals at the minute, because its used both as a deserialization model for GQL and Account Management +class ServerInfo(BaseModel): + name: Optional[str] = None + company: Optional[str] = None + url: Optional[str] = None + adminContact: Optional[str] = None + description: Optional[str] = None + canonicalUrl: Optional[str] = None + roles: Optional[List[dict]] = None + scopes: Optional[List[dict]] = None + authStrategies: Optional[List[dict]] = None + version: Optional[str] = None + frontend2: Optional[bool] = None + migration: Optional[ServerMigration] = None + + # TODO separate gql model from account management model + + +class LimitedUser(BaseModel): + """Limited user type, for showing public info about a user to another user.""" + + id: str + name: str + bio: Optional[str] = None + company: Optional[str] = None + avatar: Optional[str] = None + verified: Optional[bool] = None + role: Optional[str] = None + + +class PendingStreamCollaborator(BaseModel): + id: str + inviteId: str + streamId: Optional[str] = None + projectId: str + streamName: Optional[str] = None + projectName: str + title: str + role: str + invitedBy: LimitedUser + user: Optional[LimitedUser] = None + token: str + + def __repr__(self): + return ( + f"PendingStreamCollaborator( inviteId: {self.inviteId}, streamId:" + f" {self.streamId}, role: {self.role}, title: {self.title}, invitedBy:" + f" {self.user.name if self.user else None})" + ) + + def __str__(self) -> str: + return self.__repr__() + + +class ProjectCollaborator(BaseModel): + id: str + role: str + user: LimitedUser + + +class ResourceIdentifier(BaseModel): + resourceId: str + resourceType: ResourceType + + +class ViewerResourceItem(BaseModel): + modelId: Optional[str] = None + objectId: str + versionId: Optional[str] = None + + +class ViewerResourceGroup(BaseModel): + identifier: str + items: List[ViewerResourceItem] + + +class Comment(BaseModel): + archived: bool + author: LimitedUser + authorId: str + createdAt: datetime + hasParent: bool + id: str + parent: Optional["Comment"] = None + rawText: str + replies: ResourceCollection["Comment"] + replyAuthors: ResourceCollection[LimitedUser] + resources: List[ResourceIdentifier] + screenshot: Optional[str] = None + updatedAt: datetime + viewedAt: Optional[datetime] = None + viewerResources: List[ViewerResourceItem] + + +class Version(BaseModel): + authorUser: Optional[LimitedUser] = None + commentThreads: List[Comment] + createdAt: datetime + id: str + message: Optional[str] = None + model: "Model" + previewUrl: str + referencedObject: str + sourceApplication: Optional[str] = None + + +class ModelsTreeItem(BaseModel): + children: List["ModelsTreeItem"] + fullName: str + hasChildren: bool + id: str + model: Optional["Model"] = None + name: str + updatedAt: datetime + + +class FileUpload(BaseModel): + convertedCommitId: Optional[str] = None + convertedLastUpdate: datetime + convertedMessage: Optional[str] = None + convertedStatus: FileUploadConversionStatus + convertedVersionId: Optional[str] = None + fileName: str + fileSize: int + fileType: str + id: str + model: Optional["Model"] = None + modelName: str + projectId: str + uploadComplete: bool + uploadDate: datetime + userId: str + + +class Model(BaseModel): + author: LimitedUser + childrenTree: List[ModelsTreeItem] + commentThreads: ResourceCollection[Comment] + createdAt: datetime + description: str + displayName: str + id: str + name: str + pendingImportedVersions: List[FileUpload] + previewUrl: Optional[str] = None + updatedAt: datetime + versions: ResourceCollection[Version] + version: Version + + +class Project(BaseModel): + allowPublicComments: bool + createdAt: datetime + description: Optional[str] = None + id: str + name: str + role: Optional[str] = None + sourceApps: List[str] + updatedAt: datetime + visibility: ProjectVisibility + workspaceId: Optional[str] = None + + +class ProjectWithModels(Project): + models: ResourceCollection[Model] + + +class ProjectWithTeam(Project): + invitedTeam: List[PendingStreamCollaborator] + team: List[ProjectCollaborator] diff --git a/src/specklepy/core/api/resource.py b/src/specklepy/core/api/resource.py index acefc639..9f66e942 100644 --- a/src/specklepy/core/api/resource.py +++ b/src/specklepy/core/api/resource.py @@ -1,9 +1,10 @@ from threading import Lock -from typing import Any, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union from gql.client import Client from gql.transport.exceptions import TransportQueryError from graphql import DocumentNode +from pydantic import BaseModel from specklepy.core.api.credentials import Account from specklepy.logging.exceptions import ( @@ -14,6 +15,8 @@ from specklepy.serialization.base_object_serializer import BaseObjectSerializer from specklepy.transports.sqlite import SQLiteTransport +T = TypeVar("T", bound=BaseModel) + class ResourceBase(object): def __init__( @@ -43,6 +46,35 @@ def _step_into_response(self, response: dict, return_type: Union[str, List, None response = response[key] return response + def make_request_and_parse_response( + self, + schema: Type[T], + query: DocumentNode, + variables: Optional[Dict[str, Any]] = None, + ) -> T: + try: + with self.__lock: + response = self.client.execute(query, variable_values=variables) + except TransportQueryError as ex: + raise GraphQLException( + message=( + f"Failed to execute the GraphQL {self.name} request. Errors:" + f" {ex.errors}" + ), + errors=ex.errors, + data=ex.data, + ) from ex + except Exception as ex: + raise SpeckleException( + message=( + f"Failed to execute the GraphQL {self.name} request. Inner" + f" exception: {ex}" + ), + exception=ex, + ) from ex + + return schema.model_validate(response, strict=True) + def _parse_response(self, response: Union[dict, list, None], schema=None): """Try to create a class instance from the response""" if response is None: diff --git a/src/specklepy/core/api/resources/active_user.py b/src/specklepy/core/api/resources/active_user.py index 8ce871e8..2f651bcd 100644 --- a/src/specklepy/core/api/resources/active_user.py +++ b/src/specklepy/core/api/resources/active_user.py @@ -66,7 +66,7 @@ def update( company: Optional[str] = None, bio: Optional[str] = None, avatar: Optional[str] = None, - ): + ) -> bool: """Updates your user profile. All arguments are optional. Arguments: @@ -107,7 +107,7 @@ def activity( before: Optional[datetime] = None, after: Optional[datetime] = None, cursor: Optional[datetime] = None, - ): + ) -> ActivityCollection: """ Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity. diff --git a/src/specklepy/core/api/resources/branch.py b/src/specklepy/core/api/resources/branch.py index 362982c3..478a881f 100644 --- a/src/specklepy/core/api/resources/branch.py +++ b/src/specklepy/core/api/resources/branch.py @@ -1,14 +1,20 @@ from typing import Optional +from deprecated import deprecated from gql import gql -from specklepy.core.api.models import Branch +from specklepy.core.api.models import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, + Branch, +) from specklepy.core.api.resource import ResourceBase from specklepy.logging.exceptions import SpeckleException NAME = "branch" +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Resource(ResourceBase): """API Access class for branches""" @@ -41,7 +47,7 @@ def create( """ ) if len(name) < 3: - return SpeckleException(message="Branch Name must be at least 3 characters") + raise SpeckleException(message="Branch Name must be at least 3 characters") params = { "branch": { "streamId": stream_id, diff --git a/src/specklepy/core/api/resources/commit.py b/src/specklepy/core/api/resources/commit.py index 4a076b1c..3917b615 100644 --- a/src/specklepy/core/api/resources/commit.py +++ b/src/specklepy/core/api/resources/commit.py @@ -1,14 +1,20 @@ -from typing import List, Optional, Union +from typing import Any, List, Optional, Union +from deprecated import deprecated from gql import gql -from specklepy.core.api.models import Commit +from specklepy.core.api.models import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, + Commit, +) from specklepy.core.api.resource import ResourceBase from specklepy.logging.exceptions import SpeckleException NAME = "commit" +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Resource(ResourceBase): """API Access class for commits""" @@ -133,7 +139,7 @@ def create( { commitCreate(commit: $commit)} """ ) - params = { + params: dict[str, dict[str, Any]] = { "commit": { "streamId": stream_id, "branchName": branch_name, diff --git a/src/specklepy/core/api/resources/graphql.config.yml b/src/specklepy/core/api/resources/graphql.config.yml new file mode 100644 index 00000000..64c50ab2 --- /dev/null +++ b/src/specklepy/core/api/resources/graphql.config.yml @@ -0,0 +1,2 @@ +schema: https://app.speckle.systems/graphql +documents: '**/*.graphql' diff --git a/src/specklepy/core/api/resources/project.py b/src/specklepy/core/api/resources/project.py new file mode 100644 index 00000000..1194b8dd --- /dev/null +++ b/src/specklepy/core/api/resources/project.py @@ -0,0 +1,305 @@ +from typing import Optional + +from gql import gql + +from specklepy.core.api.inputs.project_inputs import ( + ProjectCreateInput, + ProjectModelsFilter, + ProjectUpdateInput, + ProjectUpdateRoleInput, +) +from specklepy.core.api.models import Project +from specklepy.core.api.new_models import ProjectWithModels, ProjectWithTeam +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.responses import DataResponse + +NAME = "project" + + +class ProjectResource(ResourceBase): + """API Access class for projects""" + + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + name=NAME, + server_version=server_version, + ) + + def get(self, project_id: str) -> Project: + QUERY = gql( + """ + query Project($projectId: String!) { + data:project(id: $projectId) { + allowPublicComments + createdAt + description + id + name + role + sourceApps + updatedAt + visibility + workspaceId + } + } + """ + ) + + variables = {"projectId": project_id} + return self.make_request_and_parse_response( + DataResponse[Project], QUERY, variables + ).data + + def get_with_models( + self, + project_id: str, + models_limit: int = 25, + models_cursor: Optional[str] = None, + models_filter: Optional[ProjectModelsFilter] = None, + ) -> ProjectWithModels: + QUERY = gql( + """ + query ProjectGetWithModels($projectId: String!, $modelsLimit: Int!, $modelsCursor: String, $modelsFilter: ProjectModelsFilter) { + data:project(id: $projectId) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) { + items { + id + name + previewUrl + updatedAt + displayName + description + createdAt + } + cursor + totalCount + } + } + } + """ + ) + + variables = { + "projectId": project_id, + "modelsLimit": models_limit, + "modelsCursor": models_cursor, + "modelsFilter": models_filter, + } + + return self.make_request_and_parse_response( + DataResponse[ProjectWithModels], QUERY, variables + ).data + + def get_with_team(self, project_id: str) -> ProjectWithTeam: + QUERY = gql( + """ + query ProjectGetWithTeam($projectId: String!) { + data:project(id: $projectId) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + team { + role + user { + id + name + bio + company + avatar + verified + role + } + } + invitedTeam { + id + inviteId + projectId + projectName + title + role + token + user { + id + name + bio + company + avatar + verified + role + } + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + workspaceId + } + } + """ + ) + + variables = {"projectId": project_id} + + return self.make_request_and_parse_response( + DataResponse[ProjectWithTeam], QUERY, variables + ).data + + def create(self, input: ProjectCreateInput) -> Project: + QUERY = gql( + """ + mutation ProjectCreate($input: ProjectCreateInput) { + data:projectMutations { + data:create(input: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + workspaceId + } + } + } + """ + ) + + variables = {"input": input} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[Project]], QUERY, variables + ).data.data + + def update(self, input: ProjectUpdateInput) -> Project: + QUERY = gql( + """ + mutation ProjectUpdate($input: ProjectUpdateInput!) { + data:projectMutations{ + data:update(update: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + workspaceId + } + } + } + """ + ) + + variables = {"input": input} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[Project]], QUERY, variables + ).data.data + + def delete(self, project_id: str) -> bool: + QUERY = gql( + """ + mutation ProjectDelete($projectId: String!) { + data:projectMutations { + data:delete(id: $projectId) + } + } + """ + ) + + variables = {"projectId": project_id} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[bool]], QUERY, variables + ).data.data + + def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam: + QUERY = gql( + """ + mutation ProjectUpdateRole($input: ProjectUpdateRoleInput!) { + data:projectMutations { + data:updateRole(input: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + team { + role + user { + id + name + bio + company + avatar + verified + role + } + } + invitedTeam { + id + inviteId + projectId + projectName + title + role + token + user { + id + name + bio + company + avatar + verified + role + } + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + workspaceId + } + } + } + """ + ) + + variables = {"input": input} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[ProjectWithTeam]], QUERY, variables + ).data.data diff --git a/src/specklepy/core/api/resources/project_invite.py b/src/specklepy/core/api/resources/project_invite.py new file mode 100644 index 00000000..1ebef6be --- /dev/null +++ b/src/specklepy/core/api/resources/project_invite.py @@ -0,0 +1,342 @@ +from typing import Optional + +from gql import gql + +from specklepy.core.api.inputs.project_inputs import ( + ProjectInviteCreateInput, +) +from specklepy.core.api.models import Project, ProjectMutation +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.responses import DataResponse + +NAME = "project" + + +class ProjectInviteResource(ResourceBase): + """API Access class for project invites""" + + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + name=NAME, + server_version=server_version, + ) + + def create(self, project_id: str, input: ProjectInviteCreateInput) -> Project: + QUERY = gql( + """ + mutation ProjectInviteCreate($projectId: ID!, $input: ProjectInviteCreateInput!) { + projectMutations { + invites { + create(projectId: $projectId, input: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + team { + role + user { + id + name + bio + company + avatar + verified + role + } + } + invitedTeam { + id + inviteId + projectId + projectName + title + role + token + user { + id + name + bio + company + avatar + verified + role + } + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + } + } + } + } + """ + ) + + params = {"projectId": project_id, "input": input} + + return self.make_request_and_parse_response( + DataResponse[ProjectMutation], QUERY, params + ).data.invites.create + + def get_with_models( + self, + project_id: str, + models_limit: int = 25, + models_cursor: Optional[str] = None, + models_filter: Optional[ProjectModelsFilter] = None, + ) -> Project: + QUERY = gql( + """ + query ProjectGetWithModels($projectId: String!, $modelsLimit: Int!, $modelsCursor: String, $modelsFilter: ProjectModelsFilter) { + project(id: $projectId) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) { + items { + id + name + previewUrl + updatedAt + displayName + description + createdAt + } + cursor + totalCount + } + } + } + """ + ) + + params = { + "projectId": project_id, + "modelsLimit": models_limit, + "modelsCursor": models_cursor, + "modelsFilter": models_filter, + } + + return self.make_request(query=QUERY, params=params, return_type="project") + + def get_with_team(self, project_id: str) -> Project: + QUERY = gql( + """ + query ProjectGetWithTeam($projectId: String!) { + project(id: $projectId) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + team { + role + user { + id + name + bio + company + avatar + verified + role + } + } + invitedTeam { + id + inviteId + projectId + projectName + title + role + token + user { + id + name + bio + company + avatar + verified + role + } + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + workspaceId + } + } + """ + ) + + params = {"projectId": project_id} + + return self.make_request(query=QUERY, params=params, return_type="project") + + def create(self, input: ProjectCreateInput) -> Project: + QUERY = gql( + """ + mutation ProjectCreate($input: ProjectCreateInput) { + projectMutations { + create(input: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + workspaceId + } + } + } + """ + ) + + params = {"input": input} + + return self.make_request( + query=QUERY, params=params, return_type="projectMutations" + ).create + + def update(self, input: ProjectUpdateInput) -> Project: + QUERY = gql( + """ + mutation ProjectUpdate($input: ProjectUpdateInput!) { + projectMutations{ + update(update: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + workspaceId + } + } + } + """ + ) + + params = {"input": input} + + return self.make_request( + query=QUERY, params=params, return_type="projectMutations" + ).update + + def delete(self, project_id: str) -> Project: + QUERY = gql( + """ + mutation ProjectDelete($projectId: String!) { + projectMutations { + delete(id: $projectId) + } + } + """ + ) + + params = {"projectId": project_id} + + return self.make_request( + query=QUERY, params=params, return_type="projectMutations" + ).delete + + def update_role(self, input: ProjectUpdateRoleInput) -> Project: + QUERY = gql( + """ + mutation ProjectUpdateRole($input: ProjectUpdateRoleInput!) { + projectMutations { + updateRole(input: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + team { + role + user { + id + name + bio + company + avatar + verified + role + } + } + invitedTeam { + id + inviteId + projectId + projectName + title + role + token + user { + id + name + bio + company + avatar + verified + role + } + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + workspaceId + } + } + } + """ + ) + + params = {"input": input} + + return self.make_request( + query=QUERY, + params=params, + return_type=["projectMutations", "delete"], + schema=ProjectMutation, + ) diff --git a/src/specklepy/core/api/resources/stream.py b/src/specklepy/core/api/resources/stream.py index 4d83aeb5..f8bdb6f6 100644 --- a/src/specklepy/core/api/resources/stream.py +++ b/src/specklepy/core/api/resources/stream.py @@ -1,9 +1,12 @@ from datetime import datetime, timezone from typing import List, Optional +from deprecated import deprecated from gql import gql from specklepy.core.api.models import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, ActivityCollection, PendingStreamCollaborator, Stream, @@ -14,6 +17,7 @@ NAME = "stream" +@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Resource(ResourceBase): """API Access class for streams""" diff --git a/src/specklepy/core/api/responses.py b/src/specklepy/core/api/responses.py new file mode 100644 index 00000000..2f2fd369 --- /dev/null +++ b/src/specklepy/core/api/responses.py @@ -0,0 +1,19 @@ +from typing import Generic, List, TypeVar, Optional + +from pydantic import BaseModel + +T = TypeVar("T") + + +class ResourceCollection(BaseModel, Generic[T]): + totalCount: int + items: List[T] + cursor: Optional[str] = None + + +class ProjectCommentCollection(ResourceCollection[T], Generic[T]): + totalArchivedCount: int + + +class DataResponse(BaseModel, Generic[T]): + data: T diff --git a/tests/integration/test_active_user.py b/tests/integration/client/test_active_user.py similarity index 100% rename from tests/integration/test_active_user.py rename to tests/integration/client/test_active_user.py diff --git a/tests/integration/test_branch.py b/tests/integration/client/test_branch.py similarity index 100% rename from tests/integration/test_branch.py rename to tests/integration/client/test_branch.py diff --git a/tests/integration/test_client_and_ops.py b/tests/integration/client/test_client_and_ops.py similarity index 100% rename from tests/integration/test_client_and_ops.py rename to tests/integration/client/test_client_and_ops.py diff --git a/tests/integration/test_commit.py b/tests/integration/client/test_commit.py similarity index 100% rename from tests/integration/test_commit.py rename to tests/integration/client/test_commit.py diff --git a/tests/integration/test_objects.py b/tests/integration/client/test_objects.py similarity index 100% rename from tests/integration/test_objects.py rename to tests/integration/client/test_objects.py diff --git a/tests/integration/test_other_user.py b/tests/integration/client/test_other_user.py similarity index 100% rename from tests/integration/test_other_user.py rename to tests/integration/client/test_other_user.py diff --git a/tests/integration/client/test_project.py b/tests/integration/client/test_project.py new file mode 100644 index 00000000..1df6bf66 --- /dev/null +++ b/tests/integration/client/test_project.py @@ -0,0 +1,179 @@ +import pytest + +from specklepy.api.client import SpeckleClient +from specklepy.core.api.models import ( + Project, +) +from specklepy.core.api.enums import ProjectVisibility +from specklepy.core.api.inputs.project_inputs import ( + ProjectCreateInput, + ProjectUpdateInput, +) +from specklepy.logging.exceptions import GraphQLException + + +@pytest.mark.run(order=3) +class TestProject: + @pytest.fixture(scope="session") + def test_project(self, client: SpeckleClient) -> Project: + project = client.project.create( + ProjectCreateInput( + name="test project123", + description="desc", + visibility=ProjectVisibility.PRIVATE, + ) + ) + return project + + @pytest.mark.parametrize( + "name, description, visibility", + [ + ("Very private project", "My secret project", ProjectVisibility.PRIVATE), + ("Very public project", None, ProjectVisibility.PUBLIC), + ], + ) + def test_project_create( + self, + client: SpeckleClient, + name: str, + description: str, + visibility: ProjectVisibility, + ): + input_data = ProjectCreateInput( + name=name, + description=description, + visibility=visibility, + ) + result = client.project.create(input_data) + + assert result is not None + assert result.id is not None + assert result.name == name + assert result.description == (description or "") + assert result.visibility == visibility + + def test_project_get(self, client: SpeckleClient, test_project: Project): + result = client.project.get(test_project.id) + + assert result.id == test_project.id + assert result.name == test_project.name + assert result.description == test_project.description + assert result.visibility == test_project.visibility + assert result.createdAt == test_project.createdAt + + def test_project_update(self, client: SpeckleClient, test_project: Project): + new_name = "MY new name" + new_description = "MY new desc" + new_visibility = ProjectVisibility.PUBLIC + + update_data = ProjectUpdateInput( + id=test_project.id, + name=new_name, + description=new_description, + visibility=new_visibility, + ) + + updated_project = client.project.update(update_data) + + assert updated_project.id == test_project.id + assert updated_project.name == new_name + assert updated_project.description == new_description + assert updated_project.visibility == new_visibility + + def test_project_delete(self, client: SpeckleClient): + """Test deleting a project.""" + project_to_delete = client.project.create( + ProjectCreateInput(name="Delete me", description=None, visibility=None) + ) + + response = client.project.delete(project_to_delete.id) + assert response is True + + with pytest.raises(GraphQLException): + client.project.get(project_to_delete.id) + + # @pytest.fixture(scope="session") + # def (self) -> Project: + # return Project( + # id= "TO BE SET LATER", + # name="a wonderful project", + # description="a project created for testing", + # visibility=ProjectVisibility.PUBLIC, + # allowPublicComments=false, + # createdAt=None, + # sourceApps=[] + + # ) + + # @pytest.fixture(scope="module") + # def updated_project( + # self, + # ) -> Project: + # return Project( + # name="a wonderful updated project", + # description="an updated project description for testing", + # visibility=ProjectVisibility.PRIVATE, + # ) + + # def test_project_create( + # self, client: SpeckleClient, project: Project, updated_project: Project + # ): + # result = client.project.create( + # ProjectCreateInput( + # name=project.name, + # description=project.description, + # visibility=project.visibility, + # ) + # ) + + # assert isinstance(result.id, str) + # assert result.name == project.name + # assert result.description == project.description + # assert result.visibility == project.visibility + + # project = updated_project = result + + # _ = updated_project # check if this actually is needed, are we mutating the right instance + + # def test_project_create_short_name( + # self, client: SpeckleClient, project: Project, updated_project: Project + # ): + # new_project_id = client.project.create( + # ProjectCreateInput( + # name="x", + # description=project.description, + # visibility=project.visibility, + # ) + # ) + # assert isinstance(new_project_id, SpeckleException) + + # def test_project_get(self, client: SpeckleClient, project: Project): + # result = client.project.get(project.id) + + # assert result.id == project.id + # assert result.name == project.name + # assert result.description == project.description + # assert result.visibility == project.visibility + # assert result.createdAt == project.createdAt + + # def test_stream_update(self, client, updated_stream): + # updated = client.stream.update( + # id=updated_stream.id, + # name=updated_stream.name, + # description=updated_stream.description, + # is_public=updated_stream.isPublic, + # ) + # fetched_stream = client.stream.get(updated_stream.id) + + # assert updated is True + # assert fetched_stream.name == updated_stream.name + # assert fetched_stream.description == updated_stream.description + # assert fetched_stream.isPublic == updated_stream.isPublic + + # def test_stream_delete(self, client: SpeckleClient, project: Project): + # deleted = client.project.delete(project.id) + + # stream_get = client.project.get(project.id) + + # assert deleted is True + # assert isinstance(stream_get, GraphQLException) diff --git a/tests/integration/test_server.py b/tests/integration/client/test_server.py similarity index 100% rename from tests/integration/test_server.py rename to tests/integration/client/test_server.py diff --git a/tests/integration/test_stream.py b/tests/integration/client/test_stream.py similarity index 100% rename from tests/integration/test_stream.py rename to tests/integration/client/test_stream.py diff --git a/tests/integration/test_user.py b/tests/integration/client/test_user.py similarity index 100% rename from tests/integration/test_user.py rename to tests/integration/client/test_user.py From 6c03dc82c840647516780d0e71cb64a38e9e0834 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 18 Oct 2024 13:34:23 +0100 Subject: [PATCH 02/26] black + isort --- src/specklepy/api/client.py | 6 +++--- src/specklepy/api/resources/project.py | 2 -- src/specklepy/core/api/inputs/project_inputs.py | 1 + src/specklepy/core/api/inputs/subscription_inputs.py | 1 + src/specklepy/core/api/inputs/version_inputs.py | 1 + src/specklepy/core/api/models.py | 1 + src/specklepy/core/api/new_models.py | 1 + src/specklepy/core/api/resources/project_invite.py | 4 +--- src/specklepy/core/api/responses.py | 2 +- tests/integration/client/test_project.py | 4 +--- 10 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index 9835f406..ce926ca0 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -3,14 +3,14 @@ from specklepy.api.credentials import Account from specklepy.api.resources import ( active_user, - other_user, - server, - user, branch, commit, object, + other_user, + server, stream, subscriptions, + user, ) from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient from specklepy.core.api.resources.project import ProjectResource diff --git a/src/specklepy/api/resources/project.py b/src/specklepy/api/resources/project.py index fe310808..b232dbd3 100644 --- a/src/specklepy/api/resources/project.py +++ b/src/specklepy/api/resources/project.py @@ -18,5 +18,3 @@ def __init__(self, account, basepath, client, server_version) -> None: ) self.schema = Stream - - diff --git a/src/specklepy/core/api/inputs/project_inputs.py b/src/specklepy/core/api/inputs/project_inputs.py index bc713c1e..db30acb1 100644 --- a/src/specklepy/core/api/inputs/project_inputs.py +++ b/src/specklepy/core/api/inputs/project_inputs.py @@ -1,4 +1,5 @@ from typing import Optional, Sequence + from pydantic import BaseModel from specklepy.core.api.models import ProjectVisibility diff --git a/src/specklepy/core/api/inputs/subscription_inputs.py b/src/specklepy/core/api/inputs/subscription_inputs.py index 314394fd..99a40427 100644 --- a/src/specklepy/core/api/inputs/subscription_inputs.py +++ b/src/specklepy/core/api/inputs/subscription_inputs.py @@ -1,4 +1,5 @@ from typing import Optional + from pydantic import BaseModel diff --git a/src/specklepy/core/api/inputs/version_inputs.py b/src/specklepy/core/api/inputs/version_inputs.py index 40df6ff5..8fa0aa4a 100644 --- a/src/specklepy/core/api/inputs/version_inputs.py +++ b/src/specklepy/core/api/inputs/version_inputs.py @@ -1,4 +1,5 @@ from typing import Optional, Sequence + from pydantic import BaseModel diff --git a/src/specklepy/core/api/models.py b/src/specklepy/core/api/models.py index eab806ad..8d31b496 100644 --- a/src/specklepy/core/api/models.py +++ b/src/specklepy/core/api/models.py @@ -3,6 +3,7 @@ from deprecated import deprecated from pydantic import BaseModel, Field + from specklepy.core.api.responses import ResourceCollection FE1_DEPRECATION_REASON = "Stream/Branch/Commit API is now deprecated, Use the new Project/Model/Version API functions in Client}" diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/new_models.py index 2e8a0ad8..b35db723 100644 --- a/src/specklepy/core/api/new_models.py +++ b/src/specklepy/core/api/new_models.py @@ -1,5 +1,6 @@ from datetime import datetime from typing import List, Optional + from pydantic import BaseModel from specklepy.core.api.enums import ( diff --git a/src/specklepy/core/api/resources/project_invite.py b/src/specklepy/core/api/resources/project_invite.py index 1ebef6be..26d0ccca 100644 --- a/src/specklepy/core/api/resources/project_invite.py +++ b/src/specklepy/core/api/resources/project_invite.py @@ -2,9 +2,7 @@ from gql import gql -from specklepy.core.api.inputs.project_inputs import ( - ProjectInviteCreateInput, -) +from specklepy.core.api.inputs.project_inputs import ProjectInviteCreateInput from specklepy.core.api.models import Project, ProjectMutation from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse diff --git a/src/specklepy/core/api/responses.py b/src/specklepy/core/api/responses.py index 2f2fd369..1fd331ae 100644 --- a/src/specklepy/core/api/responses.py +++ b/src/specklepy/core/api/responses.py @@ -1,4 +1,4 @@ -from typing import Generic, List, TypeVar, Optional +from typing import Generic, List, Optional, TypeVar from pydantic import BaseModel diff --git a/tests/integration/client/test_project.py b/tests/integration/client/test_project.py index 1df6bf66..db9512c8 100644 --- a/tests/integration/client/test_project.py +++ b/tests/integration/client/test_project.py @@ -1,14 +1,12 @@ import pytest from specklepy.api.client import SpeckleClient -from specklepy.core.api.models import ( - Project, -) from specklepy.core.api.enums import ProjectVisibility from specklepy.core.api.inputs.project_inputs import ( ProjectCreateInput, ProjectUpdateInput, ) +from specklepy.core.api.models import Project from specklepy.logging.exceptions import GraphQLException From 537a504121db971dc60df500e508dfafefd71b54 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:02:09 +0100 Subject: [PATCH 03/26] removed unimplemented file --- .../core/api/resources/project_invite.py | 340 ------------------ 1 file changed, 340 deletions(-) delete mode 100644 src/specklepy/core/api/resources/project_invite.py diff --git a/src/specklepy/core/api/resources/project_invite.py b/src/specklepy/core/api/resources/project_invite.py deleted file mode 100644 index 26d0ccca..00000000 --- a/src/specklepy/core/api/resources/project_invite.py +++ /dev/null @@ -1,340 +0,0 @@ -from typing import Optional - -from gql import gql - -from specklepy.core.api.inputs.project_inputs import ProjectInviteCreateInput -from specklepy.core.api.models import Project, ProjectMutation -from specklepy.core.api.resource import ResourceBase -from specklepy.core.api.responses import DataResponse - -NAME = "project" - - -class ProjectInviteResource(ResourceBase): - """API Access class for project invites""" - - def __init__(self, account, basepath, client, server_version) -> None: - super().__init__( - account=account, - basepath=basepath, - client=client, - name=NAME, - server_version=server_version, - ) - - def create(self, project_id: str, input: ProjectInviteCreateInput) -> Project: - QUERY = gql( - """ - mutation ProjectInviteCreate($projectId: ID!, $input: ProjectInviteCreateInput!) { - projectMutations { - invites { - create(projectId: $projectId, input: $input) { - id - name - description - visibility - allowPublicComments - role - createdAt - updatedAt - team { - role - user { - id - name - bio - company - avatar - verified - role - } - } - invitedTeam { - id - inviteId - projectId - projectName - title - role - token - user { - id - name - bio - company - avatar - verified - role - } - invitedBy { - id - name - bio - company - avatar - verified - role - } - } - } - } - } - } - """ - ) - - params = {"projectId": project_id, "input": input} - - return self.make_request_and_parse_response( - DataResponse[ProjectMutation], QUERY, params - ).data.invites.create - - def get_with_models( - self, - project_id: str, - models_limit: int = 25, - models_cursor: Optional[str] = None, - models_filter: Optional[ProjectModelsFilter] = None, - ) -> Project: - QUERY = gql( - """ - query ProjectGetWithModels($projectId: String!, $modelsLimit: Int!, $modelsCursor: String, $modelsFilter: ProjectModelsFilter) { - project(id: $projectId) { - id - name - description - visibility - allowPublicComments - role - createdAt - updatedAt - sourceApps - models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) { - items { - id - name - previewUrl - updatedAt - displayName - description - createdAt - } - cursor - totalCount - } - } - } - """ - ) - - params = { - "projectId": project_id, - "modelsLimit": models_limit, - "modelsCursor": models_cursor, - "modelsFilter": models_filter, - } - - return self.make_request(query=QUERY, params=params, return_type="project") - - def get_with_team(self, project_id: str) -> Project: - QUERY = gql( - """ - query ProjectGetWithTeam($projectId: String!) { - project(id: $projectId) { - id - name - description - visibility - allowPublicComments - role - createdAt - updatedAt - team { - role - user { - id - name - bio - company - avatar - verified - role - } - } - invitedTeam { - id - inviteId - projectId - projectName - title - role - token - user { - id - name - bio - company - avatar - verified - role - } - invitedBy { - id - name - bio - company - avatar - verified - role - } - } - workspaceId - } - } - """ - ) - - params = {"projectId": project_id} - - return self.make_request(query=QUERY, params=params, return_type="project") - - def create(self, input: ProjectCreateInput) -> Project: - QUERY = gql( - """ - mutation ProjectCreate($input: ProjectCreateInput) { - projectMutations { - create(input: $input) { - id - name - description - visibility - allowPublicComments - role - createdAt - updatedAt - sourceApps - workspaceId - } - } - } - """ - ) - - params = {"input": input} - - return self.make_request( - query=QUERY, params=params, return_type="projectMutations" - ).create - - def update(self, input: ProjectUpdateInput) -> Project: - QUERY = gql( - """ - mutation ProjectUpdate($input: ProjectUpdateInput!) { - projectMutations{ - update(update: $input) { - id - name - description - visibility - allowPublicComments - role - createdAt - updatedAt - workspaceId - } - } - } - """ - ) - - params = {"input": input} - - return self.make_request( - query=QUERY, params=params, return_type="projectMutations" - ).update - - def delete(self, project_id: str) -> Project: - QUERY = gql( - """ - mutation ProjectDelete($projectId: String!) { - projectMutations { - delete(id: $projectId) - } - } - """ - ) - - params = {"projectId": project_id} - - return self.make_request( - query=QUERY, params=params, return_type="projectMutations" - ).delete - - def update_role(self, input: ProjectUpdateRoleInput) -> Project: - QUERY = gql( - """ - mutation ProjectUpdateRole($input: ProjectUpdateRoleInput!) { - projectMutations { - updateRole(input: $input) { - id - name - description - visibility - allowPublicComments - role - createdAt - updatedAt - team { - role - user { - id - name - bio - company - avatar - verified - role - } - } - invitedTeam { - id - inviteId - projectId - projectName - title - role - token - user { - id - name - bio - company - avatar - verified - role - } - invitedBy { - id - name - bio - company - avatar - verified - role - } - } - workspaceId - } - } - } - """ - ) - - params = {"input": input} - - return self.make_request( - query=QUERY, - params=params, - return_type=["projectMutations", "delete"], - schema=ProjectMutation, - ) From 09ca501a7445ab3abd743e66352fa020f59a45dc Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:14:33 +0100 Subject: [PATCH 04/26] project integration tests & pydantic serializaiton --- src/specklepy/core/api/enums.py | 2 +- src/specklepy/core/api/models.py | 1 - src/specklepy/core/api/new_models.py | 2 +- src/specklepy/core/api/resource.py | 2 +- .../core/api/resources/active_user.py | 11 +++++++++++ src/specklepy/core/api/resources/project.py | 19 +++++++++++-------- src/specklepy/core/api/resources/stream.py | 8 ++++++++ 7 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/specklepy/core/api/enums.py b/src/specklepy/core/api/enums.py index 1c64943f..e43c8c15 100644 --- a/src/specklepy/core/api/enums.py +++ b/src/specklepy/core/api/enums.py @@ -11,7 +11,7 @@ class FileUploadConversionStatus(Enum): class ProjectVisibility(str, Enum): PRIVATE = "PRIVATE" PUBLIC = "PUBLIC" - UNLISTEd = "UNLISTEd" + UNLISTEd = "UNLISTED" class ResourceType(str, Enum): diff --git a/src/specklepy/core/api/models.py b/src/specklepy/core/api/models.py index 8d31b496..9f67db93 100644 --- a/src/specklepy/core/api/models.py +++ b/src/specklepy/core/api/models.py @@ -22,7 +22,6 @@ class User(BaseModel): verified: Optional[bool] = None role: Optional[str] = None streams: Optional["Streams"] = None - projects: ResourceCollection[Project] def __repr__(self): return ( diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/new_models.py index b35db723..b1bf23b7 100644 --- a/src/specklepy/core/api/new_models.py +++ b/src/specklepy/core/api/new_models.py @@ -71,7 +71,7 @@ class PendingStreamCollaborator(BaseModel): role: str invitedBy: LimitedUser user: Optional[LimitedUser] = None - token: str + token: Optional[str] def __repr__(self): return ( diff --git a/src/specklepy/core/api/resource.py b/src/specklepy/core/api/resource.py index 9f66e942..bf15b5a5 100644 --- a/src/specklepy/core/api/resource.py +++ b/src/specklepy/core/api/resource.py @@ -73,7 +73,7 @@ def make_request_and_parse_response( exception=ex, ) from ex - return schema.model_validate(response, strict=True) + return schema.model_validate(response) def _parse_response(self, response: Union[dict, list, None], schema=None): """Try to create a class instance from the response""" diff --git a/src/specklepy/core/api/resources/active_user.py b/src/specklepy/core/api/resources/active_user.py index 2f651bcd..aeb190bd 100644 --- a/src/specklepy/core/api/resources/active_user.py +++ b/src/specklepy/core/api/resources/active_user.py @@ -198,13 +198,18 @@ def get_all_pending_invites(self) -> List[PendingStreamCollaborator]: inviteId streamId streamName + projectId + projectName title role invitedBy { id name + bio company avatar + verified + role } } } @@ -241,15 +246,21 @@ def get_pending_invite( streamInvite(streamId: $streamId, token: $token) { id token + inviteId streamId streamName + projectId + projectName title role invitedBy { id name + bio company avatar + verified + role } } } diff --git a/src/specklepy/core/api/resources/project.py b/src/specklepy/core/api/resources/project.py index 1194b8dd..1f5f5a87 100644 --- a/src/specklepy/core/api/resources/project.py +++ b/src/specklepy/core/api/resources/project.py @@ -95,7 +95,9 @@ def get_with_models( "projectId": project_id, "modelsLimit": models_limit, "modelsCursor": models_cursor, - "modelsFilter": models_filter, + "modelsFilter": models_filter.model_dump(warnings="error") + if models_filter + else None, } return self.make_request_and_parse_response( @@ -188,7 +190,7 @@ def create(self, input: ProjectCreateInput) -> Project: """ ) - variables = {"input": input} + variables = {"input": input.model_dump(warnings="error")} return self.make_request_and_parse_response( DataResponse[DataResponse[Project]], QUERY, variables @@ -200,14 +202,15 @@ def update(self, input: ProjectUpdateInput) -> Project: mutation ProjectUpdate($input: ProjectUpdateInput!) { data:projectMutations{ data:update(update: $input) { + allowPublicComments + createdAt + description id name - description - visibility - allowPublicComments role - createdAt + sourceApps updatedAt + visibility workspaceId } } @@ -215,7 +218,7 @@ def update(self, input: ProjectUpdateInput) -> Project: """ ) - variables = {"input": input} + variables = {"input": input.model_dump(warnings="error")} return self.make_request_and_parse_response( DataResponse[DataResponse[Project]], QUERY, variables @@ -298,7 +301,7 @@ def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam: """ ) - variables = {"input": input} + variables = {"input": input.model_dump(warnings="error")} return self.make_request_and_parse_response( DataResponse[DataResponse[ProjectWithTeam]], QUERY, variables diff --git a/src/specklepy/core/api/resources/stream.py b/src/specklepy/core/api/resources/stream.py index f8bdb6f6..812b2c31 100644 --- a/src/specklepy/core/api/resources/stream.py +++ b/src/specklepy/core/api/resources/stream.py @@ -382,19 +382,27 @@ def get_all_pending_invites( inviteId streamId streamName + projectName + projectId title role invitedBy{ id name + bio company avatar + verified + role } user { id name + bio company avatar + verified + role } } } From 976a52bdc8a78c119f6e5837d18f755e38580390 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 21 Oct 2024 19:45:30 +0100 Subject: [PATCH 05/26] models --- src/specklepy/api/client.py | 7 + src/specklepy/core/api/new_models.py | 53 ++-- src/specklepy/core/api/resources/model.py | 271 ++++++++++++++++++++ src/specklepy/core/api/resources/project.py | 10 + tests/integration/client/test_model.py | 113 ++++++++ tests/integration/client/test_project.py | 88 +------ 6 files changed, 428 insertions(+), 114 deletions(-) create mode 100644 src/specklepy/core/api/resources/model.py create mode 100644 tests/integration/client/test_model.py diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index ce926ca0..e62c0321 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -13,6 +13,7 @@ user, ) from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient +from specklepy.core.api.resources.model import ModelResource from specklepy.core.api.resources.project import ProjectResource from specklepy.logging import metrics @@ -93,6 +94,12 @@ def _init_resources(self) -> None: client=self.httpclient, server_version=server_version, ) + self.model = ModelResource( + account=self.account, + basepath=self.url, + client=self.httpclient, + server_version=server_version, + ) # Deprecated Resources self.user = user.Resource( account=self.account, diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/new_models.py index b1bf23b7..c1241318 100644 --- a/src/specklepy/core/api/new_models.py +++ b/src/specklepy/core/api/new_models.py @@ -12,12 +12,12 @@ class ServerMigration(BaseModel): - movedFrom: Optional[str] = None - movedTo: Optional[str] = None + movedFrom: Optional[str] + movedTo: Optional[str] class AuthStrategy(BaseModel): - color: Optional[str] = None + color: Optional[str] icon: str id: str name: str @@ -53,11 +53,11 @@ class LimitedUser(BaseModel): id: str name: str - bio: Optional[str] = None - company: Optional[str] = None - avatar: Optional[str] = None - verified: Optional[bool] = None - role: Optional[str] = None + bio: Optional[str] + company: Optional[str] + avatar: Optional[str] + verified: Optional[bool] + role: Optional[str] class PendingStreamCollaborator(BaseModel): @@ -96,9 +96,9 @@ class ResourceIdentifier(BaseModel): class ViewerResourceItem(BaseModel): - modelId: Optional[str] = None + modelId: Optional[str] objectId: str - versionId: Optional[str] = None + versionId: Optional[str] class ViewerResourceGroup(BaseModel): @@ -118,22 +118,22 @@ class Comment(BaseModel): replies: ResourceCollection["Comment"] replyAuthors: ResourceCollection[LimitedUser] resources: List[ResourceIdentifier] - screenshot: Optional[str] = None + screenshot: Optional[str] updatedAt: datetime - viewedAt: Optional[datetime] = None + viewedAt: Optional[datetime] viewerResources: List[ViewerResourceItem] class Version(BaseModel): - authorUser: Optional[LimitedUser] = None + authorUser: Optional[LimitedUser] commentThreads: List[Comment] createdAt: datetime id: str - message: Optional[str] = None + message: Optional[str] model: "Model" previewUrl: str referencedObject: str - sourceApplication: Optional[str] = None + sourceApplication: Optional[str] class ModelsTreeItem(BaseModel): @@ -147,11 +147,11 @@ class ModelsTreeItem(BaseModel): class FileUpload(BaseModel): - convertedCommitId: Optional[str] = None + convertedCommitId: Optional[str] convertedLastUpdate: datetime - convertedMessage: Optional[str] = None + convertedMessage: Optional[str] convertedStatus: FileUploadConversionStatus - convertedVersionId: Optional[str] = None + convertedVersionId: Optional[str] fileName: str fileSize: int fileType: str @@ -166,31 +166,30 @@ class FileUpload(BaseModel): class Model(BaseModel): author: LimitedUser - childrenTree: List[ModelsTreeItem] - commentThreads: ResourceCollection[Comment] createdAt: datetime - description: str + description: Optional[str] displayName: str id: str name: str - pendingImportedVersions: List[FileUpload] - previewUrl: Optional[str] = None + previewUrl: Optional[str] updatedAt: datetime + + +class ModelWithVersions(BaseModel): versions: ResourceCollection[Version] - version: Version class Project(BaseModel): allowPublicComments: bool createdAt: datetime - description: Optional[str] = None + description: Optional[str] id: str name: str - role: Optional[str] = None + role: Optional[str] sourceApps: List[str] updatedAt: datetime visibility: ProjectVisibility - workspaceId: Optional[str] = None + workspaceId: Optional[str] class ProjectWithModels(Project): diff --git a/src/specklepy/core/api/resources/model.py b/src/specklepy/core/api/resources/model.py new file mode 100644 index 00000000..7024cd7b --- /dev/null +++ b/src/specklepy/core/api/resources/model.py @@ -0,0 +1,271 @@ +from typing import Optional + +from gql import gql + +from specklepy.core.api.inputs.model_inputs import ( + CreateModelInput, + DeleteModelInput, + ModelVersionsFilter, + UpdateModelInput, +) +from specklepy.core.api.inputs.project_inputs import ( + ProjectModelsFilter, +) +from specklepy.core.api.models import Project +from specklepy.core.api.new_models import ( + Model, + ModelWithVersions, +) +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.responses import DataResponse, ResourceCollection + +NAME = "model" + + +class ModelResource(ResourceBase): + """API Access class for models""" + + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + name=NAME, + server_version=server_version, + ) + + def get(self, model_id: str, project_id: str) -> Model: + QUERY = gql( + """ + query ModelGet($modelId: String!, $projectId: String!) { + data:project(id: $projectId) { + data:model(id: $modelId) { + id + name + previewUrl + updatedAt + description + displayName + createdAt + author { + avatar + bio + company + id + name + role + verified + } + } + } + } + """ + ) + + variables = {"modelId": model_id, "projectId": project_id} + return self.make_request_and_parse_response( + DataResponse[DataResponse[Model]], QUERY, variables + ).data.data + + def get_with_versions( + self, + model_id: str, + project_id: str, + versions_limit: int = 25, + versions_cursor: Optional[str] = None, + versions_filter: Optional[ModelVersionsFilter] = None, + ) -> ModelWithVersions: + QUERY = gql( + """ + query ModelGetWithVersions($modelId: String!, $projectId: String!, $versionsLimit: Int!, $versionsCursor: String, $versionsFilter: ModelVersionsFilter) { + data:project(id: $projectId) { + data:model(id: $modelId) { + id + name + previewUrl + updatedAt + versions(limit: $versionsLimit, cursor: $versionsCursor, filter: $versionsFilter) { + items { + id + referencedObject + message + sourceApplication + createdAt + previewUrl + authorUser { + id + name + bio + company + verified + role + } + } + totalCount + cursor + } + description + displayName + createdAt + author { + avatar + bio + company + id + name + role + verified + } + } + } + } + """ + ) + + variables = { + "projectId": project_id, + "modelId": model_id, + "versionsLimit": versions_limit, + "versionsCursor": versions_cursor, + "versionsFilter": versions_filter.model_dump(warnings="error") + if versions_filter + else None, + } + + return self.make_request_and_parse_response( + DataResponse[DataResponse[ModelWithVersions]], QUERY, variables + ).data.data + + def get_models( + self, + project_id: str, + models_limit: int = 25, + models_cursor: Optional[str] = None, + models_filter: Optional[ProjectModelsFilter] = None, + ) -> ResourceCollection[Model]: + QUERY = gql( + """ + query ProjectGetWithModels($projectId: String!, $modelsLimit: Int!, $modelsCursor: String, $modelsFilter: ProjectModelsFilter) { + data:project(id: $projectId) { + data:models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) { + items { + id + name + previewUrl + updatedAt + displayName + description + createdAt + author { + avatar + bio + company + id + name + role + verified + } + } + totalCount + cursor + } + } + } + """ + ) + + variables = { + "projectId": project_id, + "modelsLimit": models_limit, + "modelsCursor": models_cursor, + "modelsFilter": models_filter, + } + + return self.make_request_and_parse_response( + DataResponse[DataResponse[ResourceCollection[Model]]], QUERY, variables + ).data.data + + def create(self, input: CreateModelInput) -> Model: + QUERY = gql( + """ + mutation ModelCreate($input: CreateModelInput!) { + data:modelMutations { + data:create(input: $input) { + id + displayName + name + description + createdAt + updatedAt + previewUrl + author { + avatar + bio + company + id + name + role + verified + } + } + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[Model]], QUERY, variables + ).data.data + + def delete(self, input: DeleteModelInput) -> bool: + QUERY = gql( + """ + mutation ModelDelete($input: DeleteModelInput!) { + data:modelMutations { + data:delete(input: $input) + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[bool]], QUERY, variables + ).data.data + + def update(self, input: UpdateModelInput) -> Model: + QUERY = gql( + """ + mutation ModelUpdate($input: UpdateModelInput!) { + data:modelMutations { + data:update(input: $input) { + id + name + displayName + description + createdAt + updatedAt + previewUrl + author { + avatar + bio + company + id + name + role + verified + } + } + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[Model]], QUERY, variables + ).data.data diff --git a/src/specklepy/core/api/resources/project.py b/src/specklepy/core/api/resources/project.py index 1f5f5a87..194fbfc2 100644 --- a/src/specklepy/core/api/resources/project.py +++ b/src/specklepy/core/api/resources/project.py @@ -73,6 +73,7 @@ def get_with_models( createdAt updatedAt sourceApps + workspaceId models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) { items { id @@ -82,6 +83,15 @@ def get_with_models( displayName description createdAt + author { + avatar + bio + company + id + name + role + verified + } } cursor totalCount diff --git a/tests/integration/client/test_model.py b/tests/integration/client/test_model.py new file mode 100644 index 00000000..2eb952cc --- /dev/null +++ b/tests/integration/client/test_model.py @@ -0,0 +1,113 @@ +import pytest +from specklepy.api.client import SpeckleClient +from specklepy.core.api.inputs.model_inputs import ( + CreateModelInput, + UpdateModelInput, + DeleteModelInput, +) +from specklepy.core.api.inputs.project_inputs import ProjectCreateInput +from specklepy.core.api.models import Model, Project +from specklepy.logging.exceptions import GraphQLException + + +@pytest.mark.run(order=1) +class TestModel: + @pytest.fixture() + def test_project(self, client: SpeckleClient) -> Project: + project = client.project.create( + ProjectCreateInput(name="Test project", description="", visibility=None) + ) + return project + + @pytest.fixture() + def test_model(self, client: SpeckleClient, test_project: Project) -> Model: + model = client.model.create( + CreateModelInput( + name="Test Model", description="", projectId=test_project.id + ) + ) + return model + + @pytest.mark.parametrize( + "name, description", + [ + ("My Model", "My model description"), + ("my/nested/model", None), + ], + ) + def test_model_create( + self, client: SpeckleClient, test_project: Project, name: str, description: str + ): + input_data = CreateModelInput( + name=name, description=description, projectId=test_project.id + ) + result = client.model.create(input_data) + + assert result is not None + assert result.id is not None + assert result.name.lower() == name.lower() + assert result.description == description + + def test_model_get( + self, client: SpeckleClient, test_model: Model, test_project: Project + ): + result = client.model.get(test_model.id, test_project.id) + + assert result.id == test_model.id + assert result.name == test_model.name + assert result.description == test_model.description + assert result.createdAt == test_model.createdAt + assert result.updatedAt == test_model.updatedAt + + def test_get_models( + self, client: SpeckleClient, test_project: Project, test_model: Model + ): + result = client.model.get_models(test_project.id) + + assert len(result.items) == 1 + assert result.totalCount == 1 + assert result.items[0].id == test_model.id + + def test_project_get_models( + self, client: SpeckleClient, test_project: Project, test_model: Model + ): + result = client.project.get_with_models(test_project.id) + + assert result.id == test_project.id + assert len(result.models.items) == 1 + assert result.models.totalCount == 1 + assert result.models.items[0].id == test_model.id + + def test_model_update( + self, client: SpeckleClient, test_model: Model, test_project: Project + ): + new_name = "MY new name" + new_description = "MY new desc" + + update_data = UpdateModelInput( + id=test_model.id, + name=new_name, + description=new_description, + projectId=test_project.id, + ) + + updated_model = client.model.update(update_data) + + assert updated_model.id == test_model.id + assert updated_model.name.lower() == new_name.lower() + assert updated_model.description == new_description + assert updated_model.updatedAt >= test_model.updatedAt + + def test_model_delete( + self, client: SpeckleClient, test_model: Model, test_project: Project + ): + delete_data = DeleteModelInput(id=test_model.id, projectId=test_project.id) + + response = client.model.delete(delete_data) + assert response is True + + with pytest.raises(GraphQLException): + client.model.get(test_model.id, test_project.id) + + with pytest.raises(GraphQLException): + client.model.delete(delete_data) diff --git a/tests/integration/client/test_project.py b/tests/integration/client/test_project.py index db9512c8..07bb6a2c 100644 --- a/tests/integration/client/test_project.py +++ b/tests/integration/client/test_project.py @@ -10,7 +10,7 @@ from specklepy.logging.exceptions import GraphQLException -@pytest.mark.run(order=3) +@pytest.mark.run() class TestProject: @pytest.fixture(scope="session") def test_project(self, client: SpeckleClient) -> Project: @@ -89,89 +89,3 @@ def test_project_delete(self, client: SpeckleClient): with pytest.raises(GraphQLException): client.project.get(project_to_delete.id) - - # @pytest.fixture(scope="session") - # def (self) -> Project: - # return Project( - # id= "TO BE SET LATER", - # name="a wonderful project", - # description="a project created for testing", - # visibility=ProjectVisibility.PUBLIC, - # allowPublicComments=false, - # createdAt=None, - # sourceApps=[] - - # ) - - # @pytest.fixture(scope="module") - # def updated_project( - # self, - # ) -> Project: - # return Project( - # name="a wonderful updated project", - # description="an updated project description for testing", - # visibility=ProjectVisibility.PRIVATE, - # ) - - # def test_project_create( - # self, client: SpeckleClient, project: Project, updated_project: Project - # ): - # result = client.project.create( - # ProjectCreateInput( - # name=project.name, - # description=project.description, - # visibility=project.visibility, - # ) - # ) - - # assert isinstance(result.id, str) - # assert result.name == project.name - # assert result.description == project.description - # assert result.visibility == project.visibility - - # project = updated_project = result - - # _ = updated_project # check if this actually is needed, are we mutating the right instance - - # def test_project_create_short_name( - # self, client: SpeckleClient, project: Project, updated_project: Project - # ): - # new_project_id = client.project.create( - # ProjectCreateInput( - # name="x", - # description=project.description, - # visibility=project.visibility, - # ) - # ) - # assert isinstance(new_project_id, SpeckleException) - - # def test_project_get(self, client: SpeckleClient, project: Project): - # result = client.project.get(project.id) - - # assert result.id == project.id - # assert result.name == project.name - # assert result.description == project.description - # assert result.visibility == project.visibility - # assert result.createdAt == project.createdAt - - # def test_stream_update(self, client, updated_stream): - # updated = client.stream.update( - # id=updated_stream.id, - # name=updated_stream.name, - # description=updated_stream.description, - # is_public=updated_stream.isPublic, - # ) - # fetched_stream = client.stream.get(updated_stream.id) - - # assert updated is True - # assert fetched_stream.name == updated_stream.name - # assert fetched_stream.description == updated_stream.description - # assert fetched_stream.isPublic == updated_stream.isPublic - - # def test_stream_delete(self, client: SpeckleClient, project: Project): - # deleted = client.project.delete(project.id) - - # stream_get = client.project.get(project.id) - - # assert deleted is True - # assert isinstance(stream_get, GraphQLException) From a10b2594d359d0963c0d95235423574945dcc6e0 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:34:46 +0100 Subject: [PATCH 06/26] version resource --- src/specklepy/api/client.py | 7 + src/specklepy/core/api/models.py | 2 - src/specklepy/core/api/new_models.py | 4 +- src/specklepy/core/api/resources/model.py | 10 +- src/specklepy/core/api/resources/version.py | 228 ++++++++++++++++++++ tests/integration/client/test_model.py | 5 +- tests/integration/client/test_version.py | 148 +++++++++++++ 7 files changed, 390 insertions(+), 14 deletions(-) create mode 100644 src/specklepy/core/api/resources/version.py create mode 100644 tests/integration/client/test_version.py diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index e62c0321..bd169d8c 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -15,6 +15,7 @@ from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient from specklepy.core.api.resources.model import ModelResource from specklepy.core.api.resources.project import ProjectResource +from specklepy.core.api.resources.version import VersionResource from specklepy.logging import metrics @@ -100,6 +101,12 @@ def _init_resources(self) -> None: client=self.httpclient, server_version=server_version, ) + self.version = VersionResource( + account=self.account, + basepath=self.url, + client=self.httpclient, + server_version=server_version, + ) # Deprecated Resources self.user = user.Resource( account=self.account, diff --git a/src/specklepy/core/api/models.py b/src/specklepy/core/api/models.py index 9f67db93..5d0f7412 100644 --- a/src/specklepy/core/api/models.py +++ b/src/specklepy/core/api/models.py @@ -4,8 +4,6 @@ from deprecated import deprecated from pydantic import BaseModel, Field -from specklepy.core.api.responses import ResourceCollection - FE1_DEPRECATION_REASON = "Stream/Branch/Commit API is now deprecated, Use the new Project/Model/Version API functions in Client}" FE1_DEPRECATION_VERSION = "2.20" diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/new_models.py index c1241318..6e556f72 100644 --- a/src/specklepy/core/api/new_models.py +++ b/src/specklepy/core/api/new_models.py @@ -126,11 +126,9 @@ class Comment(BaseModel): class Version(BaseModel): authorUser: Optional[LimitedUser] - commentThreads: List[Comment] createdAt: datetime id: str message: Optional[str] - model: "Model" previewUrl: str referencedObject: str sourceApplication: Optional[str] @@ -175,7 +173,7 @@ class Model(BaseModel): updatedAt: datetime -class ModelWithVersions(BaseModel): +class ModelWithVersions(Model): versions: ResourceCollection[Version] diff --git a/src/specklepy/core/api/resources/model.py b/src/specklepy/core/api/resources/model.py index 7024cd7b..7f2c5e52 100644 --- a/src/specklepy/core/api/resources/model.py +++ b/src/specklepy/core/api/resources/model.py @@ -8,14 +8,9 @@ ModelVersionsFilter, UpdateModelInput, ) -from specklepy.core.api.inputs.project_inputs import ( - ProjectModelsFilter, -) +from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter from specklepy.core.api.models import Project -from specklepy.core.api.new_models import ( - Model, - ModelWithVersions, -) +from specklepy.core.api.new_models import Model, ModelWithVersions from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse, ResourceCollection @@ -93,6 +88,7 @@ def get_with_versions( createdAt previewUrl authorUser { + avatar id name bio diff --git a/src/specklepy/core/api/resources/version.py b/src/specklepy/core/api/resources/version.py new file mode 100644 index 00000000..d8ae6b62 --- /dev/null +++ b/src/specklepy/core/api/resources/version.py @@ -0,0 +1,228 @@ +from typing import Optional + +from gql import gql + +from specklepy.core.api.inputs.model_inputs import ( + CreateModelInput, + DeleteModelInput, + ModelVersionsFilter, + UpdateModelInput, +) +from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter +from specklepy.core.api.inputs.version_inputs import ( + CreateVersionInput, + DeleteVersionsInput, + MarkReceivedVersionInput, + MoveVersionsInput, + UpdateVersionInput, +) +from specklepy.core.api.models import Project +from specklepy.core.api.new_models import Model, ModelWithVersions, Version +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.responses import DataResponse, ResourceCollection + +NAME = "model" + + +class VersionResource(ResourceBase): + """API Access class for model versions""" + + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + name=NAME, + server_version=server_version, + ) + + def get(self, version_id: str, project_id: str) -> Version: + QUERY = gql( + """ + query VersionGet($projectId: String!, $versionId: String!) { + data:project(id: $projectId) { + data:version(id: $versionId) { + id + referencedObject + message + sourceApplication + createdAt + previewUrl + authorUser { + id + name + bio + company + verified + role + avatar + } + } + } + } + """ + ) + + variables = {"projectId": project_id, "versionId": version_id} + return self.make_request_and_parse_response( + DataResponse[DataResponse[Version]], QUERY, variables + ).data.data + + def get_versions( + self, + model_id: str, + project_id: str, + limit: int = 25, + cursor: Optional[str] = None, + filter: Optional[ModelVersionsFilter] = None, + ) -> ResourceCollection[Version]: + QUERY = gql( + """ + query VersionGetVersions($projectId: String!, $modelId: String!, $limit: Int!, $cursor: String, $filter: ModelVersionsFilter) { + data:project(id: $projectId) { + data:model(id: $modelId) { + data:versions(limit: $limit, cursor: $cursor, filter: $filter) { + items { + id + referencedObject + message + sourceApplication + createdAt + previewUrl + authorUser { + id + name + bio + company + verified + role + avatar + } + } + cursor + totalCount + } + } + } + } + """ + ) + + variables = { + "projectId": project_id, + "modelId": model_id, + "limit": limit, + "cursor": cursor, + "filter": filter.model_dump(warnings="error") if filter else None, + } + + return self.make_request_and_parse_response( + DataResponse[DataResponse[DataResponse[ResourceCollection[Version]]]], + QUERY, + variables, + ).data.data.data + + def create(self, input: CreateVersionInput) -> str: + QUERY = gql( + """ + mutation Create($input: CreateVersionInput!) { + data:versionMutations { + data:create(input: $input) { + data:id + } + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[DataResponse[str]]], QUERY, variables + ).data.data.data + + def update(self, input: UpdateVersionInput) -> Version: + QUERY = gql( + """ + mutation VersionUpdate($input: UpdateVersionInput!) { + data:versionMutations { + data:update(input: $input) { + id + referencedObject + message + sourceApplication + createdAt + previewUrl + authorUser { + id + name + bio + company + verified + role + avatar + } + } + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[Version]], QUERY, variables + ).data.data + + def move_to_model(self, input: MoveVersionsInput) -> str: + QUERY = gql( + """ + mutation VersionMoveToModel($input: MoveVersionsInput!) { + data:versionMutations { + data:moveToModel(input: $input) { + data:id + } + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[DataResponse[str]]], QUERY, variables + ).data.data.data + + def delete(self, input: DeleteVersionsInput) -> bool: + QUERY = gql( + """ + mutation VersionDelete($input: DeleteVersionsInput!) { + data:versionMutations { + data:delete(input: $input) + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[bool]], QUERY, variables + ).data.data + + def received(self, input: MarkReceivedVersionInput) -> bool: + QUERY = gql( + """ + mutation MarkReceived($input: MarkReceivedVersionInput!) { + data:versionMutations { + data:markReceived(input: $input) + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[bool]], QUERY, variables + ).data.data diff --git a/tests/integration/client/test_model.py b/tests/integration/client/test_model.py index 2eb952cc..2e4d7e6d 100644 --- a/tests/integration/client/test_model.py +++ b/tests/integration/client/test_model.py @@ -1,16 +1,17 @@ import pytest + from specklepy.api.client import SpeckleClient from specklepy.core.api.inputs.model_inputs import ( CreateModelInput, - UpdateModelInput, DeleteModelInput, + UpdateModelInput, ) from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.models import Model, Project from specklepy.logging.exceptions import GraphQLException -@pytest.mark.run(order=1) +@pytest.mark.run() class TestModel: @pytest.fixture() def test_project(self, client: SpeckleClient) -> Project: diff --git a/tests/integration/client/test_version.py b/tests/integration/client/test_version.py new file mode 100644 index 00000000..a3b91cfe --- /dev/null +++ b/tests/integration/client/test_version.py @@ -0,0 +1,148 @@ +import pytest + +from specklepy.api.client import SpeckleClient +from specklepy.core.api import operations +from specklepy.core.api.inputs.model_inputs import CreateModelInput +from specklepy.core.api.inputs.project_inputs import ProjectCreateInput +from specklepy.core.api.inputs.version_inputs import ( + CreateVersionInput, + DeleteVersionsInput, + MarkReceivedVersionInput, + MoveVersionsInput, + UpdateVersionInput, +) +from specklepy.core.api.models import Model, Project, Version +from specklepy.logging.exceptions import GraphQLException +from specklepy.objects.base import Base +from specklepy.transports.server.server import ServerTransport + + +@pytest.mark.run(order=4) +class TestVersion: + @pytest.fixture + def test_project(self, client: SpeckleClient) -> Project: + project = client.project.create( + ProjectCreateInput(name="Test project", description="", visibility=None) + ) + return project + + @pytest.fixture + def test_model_1(self, client: SpeckleClient, test_project: Project) -> Model: + model1 = client.model.create( + CreateModelInput( + name="Test Model 1", description="", projectId=test_project.id + ) + ) + return model1 + + @pytest.fixture + def test_model_2(self, client: SpeckleClient, test_project: Project) -> Model: + model2 = client.model.create( + CreateModelInput( + name="Test Model 2", description="", projectId=test_project.id + ) + ) + return model2 + + @pytest.fixture + def test_version( + self, client: SpeckleClient, test_project: Project, test_model_1: Model + ) -> Version: + remote = ServerTransport(test_project.id, client) + objectId = operations.send( + Base(applicationId="ASDF"), [remote], use_default_cache=False + ) + input = CreateVersionInput( + objectId=objectId, modelId=test_model_1.id, projectId=test_project.id + ) + version_id = client.version.create(input) + return client.version.get(version_id, test_project.id) + + def test_version_get( + self, client: SpeckleClient, test_version: Version, test_project: Project + ): + result = client.version.get(test_version.id, test_project.id) + + assert result.id == test_version.id + assert result.message == test_version.message + + def test_versions_get( + self, + client: SpeckleClient, + test_model_1: Model, + test_project: Project, + test_version: Version, + ): + result = client.version.get_versions(test_model_1.id, test_project.id) + + assert len(result.items) == 1 + assert result.totalCount == 1 + assert result.items[0].id == test_version.id + + def test_version_received( + self, client: SpeckleClient, test_version: Version, test_project: Project + ): + input_data = MarkReceivedVersionInput( + versionId=test_version.id, + projectId=test_project.id, + sourceApplication="Integration test", + ) + result = client.version.received(input_data) + + assert result is True + + def test_model_get_with_versions( + self, + client: SpeckleClient, + test_model_1: Model, + test_project: Project, + test_version: Version, + ): + result = client.model.get_with_versions(test_model_1.id, test_project.id) + + assert result.id == test_model_1.id + assert len(result.versions.items) == 1 + assert result.versions.totalCount == 1 + assert result.versions.items[0].id == test_version.id + + def test_version_update(self, client: SpeckleClient, test_version: Version): + new_message = "MY new version message" + input_data = UpdateVersionInput(versionId=test_version.id, message=new_message) + updated_version = client.version.update(input_data) + + assert updated_version.id == test_version.id + assert updated_version.message == new_message + assert updated_version.previewUrl == test_version.previewUrl + + def test_version_move_to_model( + self, + client: SpeckleClient, + test_project: Project, + test_version: Version, + test_model_2: Model, + ): + input_data = MoveVersionsInput( + targetModelName=test_model_2.name, versionIds=[test_version.id] + ) + moved_model_id = client.version.move_to_model(input_data) + + assert moved_model_id == test_model_2.id + moved_version = client.version.get(test_version.id, test_project.id) + + assert moved_version.id == test_version.id + assert moved_version.message == test_version.message + assert moved_version.previewUrl == test_version.previewUrl + + def test_version_delete( + self, client: SpeckleClient, test_version: Version, test_project: Project + ): + input_data = DeleteVersionsInput(versionIds=[test_version.id]) + + response = client.version.delete(input_data) + assert response is True + + with pytest.raises(GraphQLException): + client.version.get(test_version.id, test_project.id) + + with pytest.raises(GraphQLException): + client.version.delete(input_data) From cc004c8e6b81a525db75a2efc819e93b2d6b9a1b Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:13:19 +0100 Subject: [PATCH 07/26] active user --- src/specklepy/api/client.py | 12 +- src/specklepy/api/resources/active_user.py | 4 +- src/specklepy/core/api/client.py | 32 +- src/specklepy/core/api/credentials.py | 3 +- src/specklepy/core/api/inputs/user_inputs.py | 9 + .../core/api/resources/active_user.py | 285 +------------ .../api/resources/active_user_resource.py | 373 ++++++++++++++++++ .../resources/{model.py => model_resource.py} | 2 + .../{project.py => project_resource.py} | 1 + .../{version.py => version_resource.py} | 8 +- tests/integration/client/test_active_user.py | 18 +- .../client/test_active_user_resource.py | 44 +++ tests/integration/client/test_branch.py | 2 + tests/integration/client/test_commit.py | 2 + .../{test_model.py => test_model_resource.py} | 11 +- tests/integration/client/test_objects.py | 2 + ...st_project.py => test_project_resource.py} | 8 +- tests/integration/client/test_stream.py | 2 + tests/integration/client/test_user.py | 2 + ...st_version.py => test_version_resource.py} | 12 +- 20 files changed, 524 insertions(+), 308 deletions(-) create mode 100644 src/specklepy/core/api/inputs/user_inputs.py create mode 100644 src/specklepy/core/api/resources/active_user_resource.py rename src/specklepy/core/api/resources/{model.py => model_resource.py} (99%) rename src/specklepy/core/api/resources/{project.py => project_resource.py} (99%) rename src/specklepy/core/api/resources/{version.py => version_resource.py} (96%) create mode 100644 tests/integration/client/test_active_user_resource.py rename tests/integration/client/{test_model.py => test_model_resource.py} (90%) rename tests/integration/client/{test_project.py => test_project_resource.py} (94%) rename tests/integration/client/{test_version.py => test_version_resource.py} (91%) diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index bd169d8c..aa76069e 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -2,7 +2,6 @@ from specklepy.api.credentials import Account from specklepy.api.resources import ( - active_user, branch, commit, object, @@ -13,9 +12,12 @@ user, ) from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient -from specklepy.core.api.resources.model import ModelResource -from specklepy.core.api.resources.project import ProjectResource -from specklepy.core.api.resources.version import VersionResource + +# TODO: re-reference core.api resources +from specklepy.core.api.resources.active_user_resource import ActiveUserResource +from specklepy.core.api.resources.model_resource import ModelResource +from specklepy.core.api.resources.project_resource import ProjectResource +from specklepy.core.api.resources.version_resource import VersionResource from specklepy.logging import metrics @@ -83,7 +85,7 @@ def _init_resources(self) -> None: client=self.httpclient, server_version=server_version, ) - self.active_user = active_user.Resource( + self.active_user = ActiveUserResource( account=self.account, basepath=self.url, client=self.httpclient, diff --git a/src/specklepy/api/resources/active_user.py b/src/specklepy/api/resources/active_user.py index 26016e68..aaf0261a 100644 --- a/src/specklepy/api/resources/active_user.py +++ b/src/specklepy/api/resources/active_user.py @@ -2,7 +2,9 @@ from typing import List, Optional from specklepy.api.models import PendingStreamCollaborator, User -from specklepy.core.api.resources.active_user import Resource as CoreResource +from specklepy.core.api.resources.active_user_resource import ( + ActiveUserResource as CoreResource, +) from specklepy.logging import metrics diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index ed9c7768..bc14fc5d 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -11,7 +11,6 @@ from specklepy.core.api import resources from specklepy.core.api.credentials import Account, get_account_from_token from specklepy.core.api.resources import ( - active_user, branch, commit, object, @@ -21,6 +20,10 @@ subscriptions, user, ) +from specklepy.core.api.resources.active_user_resource import ActiveUserResource +from specklepy.core.api.resources.model_resource import ModelResource +from specklepy.core.api.resources.project_resource import ProjectResource +from specklepy.core.api.resources.version_resource import VersionResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException, SpeckleWarning @@ -200,24 +203,45 @@ 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 Exception: pass - self.user = user.Resource( + + self.other_user = other_user.Resource( account=self.account, basepath=self.url, client=self.httpclient, server_version=server_version, ) - self.other_user = other_user.Resource( + self.active_user = ActiveUserResource( account=self.account, basepath=self.url, client=self.httpclient, server_version=server_version, ) - self.active_user = active_user.Resource( + self.project = ProjectResource( + account=self.account, + basepath=self.url, + client=self.httpclient, + server_version=server_version, + ) + self.model = ModelResource( + account=self.account, + basepath=self.url, + client=self.httpclient, + server_version=server_version, + ) + self.version = VersionResource( + account=self.account, + basepath=self.url, + client=self.httpclient, + server_version=server_version, + ) + # Deprecated Resources + self.user = user.Resource( account=self.account, basepath=self.url, client=self.httpclient, diff --git a/src/specklepy/core/api/credentials.py b/src/specklepy/core/api/credentials.py index 46531b71..9999ce22 100644 --- a/src/specklepy/core/api/credentials.py +++ b/src/specklepy/core/api/credentials.py @@ -12,10 +12,11 @@ class UserInfo(BaseModel): + id: Optional[str] = None name: Optional[str] = None email: Optional[str] = None company: Optional[str] = None - id: Optional[str] = None + avatar: Optional[str] = None class Account(BaseModel): diff --git a/src/specklepy/core/api/inputs/user_inputs.py b/src/specklepy/core/api/inputs/user_inputs.py new file mode 100644 index 00000000..de6c112b --- /dev/null +++ b/src/specklepy/core/api/inputs/user_inputs.py @@ -0,0 +1,9 @@ +from typing import Optional +from pydantic import BaseModel + + +class UserUpdateInput(BaseModel): + avatar: Optional[str] = None + bio: Optional[str] = None + company: Optional[str] = None + name: Optional[str] = None diff --git a/src/specklepy/core/api/resources/active_user.py b/src/specklepy/core/api/resources/active_user.py index aeb190bd..91647065 100644 --- a/src/specklepy/core/api/resources/active_user.py +++ b/src/specklepy/core/api/resources/active_user.py @@ -1,279 +1,14 @@ -from datetime import datetime, timezone -from typing import List, Optional +from deprecated import deprecated +from specklepy.core.api.models import FE1_DEPRECATION_VERSION +from specklepy.core.api.resources.active_user_resource import ActiveUserResource -from gql import gql -from specklepy.core.api.models import ( - ActivityCollection, - PendingStreamCollaborator, - User, +@deprecated( + reason="Class renamed to ActiveUserResource", version=FE1_DEPRECATION_VERSION ) -from specklepy.core.api.resource import ResourceBase -from specklepy.logging.exceptions import SpeckleException +class Resource(ActiveUserResource): + """ + Class renamed to ActiveUserResource + """ -NAME = "active_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) -> 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 { - activeUser { - id - email - name - bio - company - avatar - verified - profiles - role - } - } - """ - ) - - params = {} - - return self.make_request(query=query, params=params, return_type="activeUser") - - def update( - self, - name: Optional[str] = None, - company: Optional[str] = None, - bio: Optional[str] = None, - avatar: Optional[str] = None, - ) -> bool: - """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 @deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT): - bool -- True if your profile was updated successfully - """ - 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, - limit: int = 20, - action_type: Optional[str] = None, - before: Optional[datetime] = None, - after: Optional[datetime] = None, - cursor: Optional[datetime] = None, - ) -> ActivityCollection: - """ - 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( - $action_type: String, - $before:DateTime, - $after: DateTime, - $cursor: DateTime, - $limit: Int - ){ - activeUser { - activity( - actionType: $action_type, - before: $before, - after: $after, - cursor: $cursor, - limit: $limit - ) { - totalCount - cursor - items { - actionType - info - userId - streamId - resourceId - resourceType - message - time - } - } - } - } - """ - ) - - params = { - "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=["activeUser", "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 - """ - self._check_invites_supported() - - query = gql( - """ - query StreamInvites { - streamInvites{ - id - token - inviteId - streamId - streamName - projectId - projectName - title - role - invitedBy { - id - name - bio - company - avatar - verified - role - } - } - } - """ - ) - - return self.make_request( - query=query, - return_type="streamInvites", - schema=PendingStreamCollaborator, - ) - - def get_pending_invite( - self, stream_id: str, token: Optional[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) - """ - self._check_invites_supported() - - query = gql( - """ - query StreamInvite($streamId: String!, $token: String) { - streamInvite(streamId: $streamId, token: $token) { - id - token - inviteId - streamId - streamName - projectId - projectName - title - role - invitedBy { - id - name - bio - company - avatar - verified - role - } - } - } - """ - ) - - params = {"streamId": stream_id} - if token: - params["token"] = token - - return self.make_request( - query=query, - params=params, - return_type="streamInvite", - schema=PendingStreamCollaborator, - ) + pass diff --git a/src/specklepy/core/api/resources/active_user_resource.py b/src/specklepy/core/api/resources/active_user_resource.py new file mode 100644 index 00000000..d5d784d8 --- /dev/null +++ b/src/specklepy/core/api/resources/active_user_resource.py @@ -0,0 +1,373 @@ +from datetime import datetime, timezone +from typing import List, Optional + +from deprecated import deprecated +from gql import gql + +from specklepy.core.api.inputs.project_inputs import UserProjectsFilter +from specklepy.core.api.inputs.user_inputs import UserUpdateInput +from specklepy.core.api.models import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, + ActivityCollection, + PendingStreamCollaborator, + User, +) +from specklepy.core.api.new_models import Project +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.responses import DataResponse, ResourceCollection +from specklepy.logging.exceptions import GraphQLException + +NAME = "active_user" + + +class ActiveUserResource(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) -> Optional[User]: + """Gets the currently active user profile (as extracted from the authorization header) + + Returns: + User -- the requested user, or none if no authentication token is provided to the Client + """ + QUERY = gql( + """ + query User { + data:activeUser { + id + email + name + bio + company + avatar + verified + role + } + } + """ + ) + + variables = {} + + return self.make_request_and_parse_response( + DataResponse[Optional[User]], QUERY, variables + ).data + + # todo: breaking change, can we make this not? + def update(self, input: UserUpdateInput) -> User: + QUERY = gql( + """ + mutation ActiveUserMutations($input: UserUpdateInput!) { + data:activeUserMutations { + data:update(user: $input) { + id + email + name + bio + company + avatar + verified + role + } + } + } + """ + ) + + variables = {"input": input.model_dump(warnings="error")} + + return self.make_request_and_parse_response( + DataResponse[DataResponse[User]], QUERY, variables + ).data.data + + def get_projects( + self, + *, + limit: int = 25, + cursor: Optional[str] = None, + filter: Optional[UserProjectsFilter] = None, + ) -> ResourceCollection[Project]: + QUERY = gql( + """ + query User($limit : Int!, $cursor: String, $filter: UserProjectsFilter) { + data:activeUser { + data:projects(limit: $limit, cursor: $cursor, filter: $filter) { + totalCount + items { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + workspaceId + } + } + } + } + """ + ) + + variables = { + "limit": limit, + "cursor": cursor, + "filter": filter.model_dump(warnings="error") if filter else None, + } + + response = self.make_request_and_parse_response( + DataResponse[Optional[DataResponse[ResourceCollection[Project]]]], + QUERY, + variables, + ) + + if response.data is None: + raise GraphQLException( + "GraphQL response indicated that the ActiveUser could not be found" + ) + + return response.data.data + + def get_project_invites(self) -> List[PendingStreamCollaborator]: + QUERY = gql( + """ + query ProjectInvites { + data:activeUser { + data:projectInvites { + id + inviteId + invitedBy { + avatar + bio + company + id + name + role + verified + } + projectId + projectName + role + title + token + user { + id, + name, + bio, + company, + verified, + role, + } + } + } + } + """ + ) + + variables = {} + + response = self.make_request_and_parse_response( + DataResponse[Optional[DataResponse[List[PendingStreamCollaborator]]]], + QUERY, + variables, + ) + + if response.data is None: + raise GraphQLException( + "GraphQL response indicated that the ActiveUser could not be found" + ) + + return response.data.data + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + def activity( + self, + limit: int = 20, + action_type: Optional[str] = None, + before: Optional[datetime] = None, + after: Optional[datetime] = None, + cursor: Optional[datetime] = None, + ) -> ActivityCollection: + """ + 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( + $action_type: String, + $before:DateTime, + $after: DateTime, + $cursor: DateTime, + $limit: Int + ){ + activeUser { + activity( + actionType: $action_type, + before: $before, + after: $after, + cursor: $cursor, + limit: $limit + ) { + totalCount + cursor + items { + actionType + info + userId + streamId + resourceId + resourceType + message + time + } + } + } + } + """ + ) + + params = { + "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=["activeUser", "activity"], + schema=ActivityCollection, + ) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + 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 + """ + self._check_invites_supported() + + query = gql( + """ + query StreamInvites { + streamInvites{ + id + token + inviteId + streamId + streamName + projectId + projectName + title + role + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + } + """ + ) + + return self.make_request( + query=query, + return_type="streamInvites", + schema=PendingStreamCollaborator, + ) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + def get_pending_invite( + self, stream_id: str, token: Optional[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) + """ + self._check_invites_supported() + + query = gql( + """ + query StreamInvite($streamId: String!, $token: String) { + streamInvite(streamId: $streamId, token: $token) { + id + token + inviteId + streamId + streamName + projectId + projectName + title + role + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + } + """ + ) + + params = {"streamId": stream_id} + if token: + params["token"] = token + + return self.make_request( + query=query, + params=params, + return_type="streamInvite", + schema=PendingStreamCollaborator, + ) diff --git a/src/specklepy/core/api/resources/model.py b/src/specklepy/core/api/resources/model_resource.py similarity index 99% rename from src/specklepy/core/api/resources/model.py rename to src/specklepy/core/api/resources/model_resource.py index 7f2c5e52..279776fa 100644 --- a/src/specklepy/core/api/resources/model.py +++ b/src/specklepy/core/api/resources/model_resource.py @@ -66,6 +66,7 @@ def get_with_versions( self, model_id: str, project_id: str, + *, versions_limit: int = 25, versions_cursor: Optional[str] = None, versions_filter: Optional[ModelVersionsFilter] = None, @@ -135,6 +136,7 @@ def get_with_versions( def get_models( self, project_id: str, + *, models_limit: int = 25, models_cursor: Optional[str] = None, models_filter: Optional[ProjectModelsFilter] = None, diff --git a/src/specklepy/core/api/resources/project.py b/src/specklepy/core/api/resources/project_resource.py similarity index 99% rename from src/specklepy/core/api/resources/project.py rename to src/specklepy/core/api/resources/project_resource.py index 194fbfc2..cdd81bbc 100644 --- a/src/specklepy/core/api/resources/project.py +++ b/src/specklepy/core/api/resources/project_resource.py @@ -56,6 +56,7 @@ def get(self, project_id: str) -> Project: def get_with_models( self, project_id: str, + *, models_limit: int = 25, models_cursor: Optional[str] = None, models_filter: Optional[ProjectModelsFilter] = None, diff --git a/src/specklepy/core/api/resources/version.py b/src/specklepy/core/api/resources/version_resource.py similarity index 96% rename from src/specklepy/core/api/resources/version.py rename to src/specklepy/core/api/resources/version_resource.py index d8ae6b62..e98d03b1 100644 --- a/src/specklepy/core/api/resources/version.py +++ b/src/specklepy/core/api/resources/version_resource.py @@ -3,12 +3,8 @@ from gql import gql from specklepy.core.api.inputs.model_inputs import ( - CreateModelInput, - DeleteModelInput, ModelVersionsFilter, - UpdateModelInput, ) -from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter from specklepy.core.api.inputs.version_inputs import ( CreateVersionInput, DeleteVersionsInput, @@ -16,8 +12,7 @@ MoveVersionsInput, UpdateVersionInput, ) -from specklepy.core.api.models import Project -from specklepy.core.api.new_models import Model, ModelWithVersions, Version +from specklepy.core.api.new_models import Version from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse, ResourceCollection @@ -72,6 +67,7 @@ def get_versions( self, model_id: str, project_id: str, + *, limit: int = 25, cursor: Optional[str] = None, filter: Optional[ModelVersionsFilter] = None, diff --git a/tests/integration/client/test_active_user.py b/tests/integration/client/test_active_user.py index 755a8cb3..79998436 100644 --- a/tests/integration/client/test_active_user.py +++ b/tests/integration/client/test_active_user.py @@ -1,10 +1,13 @@ +from deprecated import deprecated import pytest from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, User -from specklepy.logging.exceptions import SpeckleException +from specklepy.core.api.inputs.user_inputs import UserUpdateInput +from specklepy.logging.exceptions import GraphQLException +@deprecated @pytest.mark.run(order=2) class TestUser: def test_user_get_self(self, client: SpeckleClient, user_dict): @@ -19,15 +22,14 @@ def test_user_get_self(self, client: SpeckleClient, user_dict): def test_user_update(self, client: SpeckleClient): bio = "i am a ghost in the machine" - failed_update = client.active_user.update() - assert isinstance(failed_update, SpeckleException) + with pytest.raises(GraphQLException): + client.active_user.update(UserUpdateInput()) - updated = client.active_user.update(bio=bio) + updated = client.active_user.update(UserUpdateInput(bio=bio)) - me = client.active_user.get() - - assert updated is True - assert me.bio == bio + assert isinstance(updated, User) + assert isinstance(updated, User) + assert updated.bio == bio def test_user_activity(self, client: SpeckleClient, second_user_dict): my_activity = client.active_user.activity(limit=10) diff --git a/tests/integration/client/test_active_user_resource.py b/tests/integration/client/test_active_user_resource.py new file mode 100644 index 00000000..84b15d8a --- /dev/null +++ b/tests/integration/client/test_active_user_resource.py @@ -0,0 +1,44 @@ +import pytest +from specklepy.core.api.client import SpeckleClient +from specklepy.core.api.inputs.user_inputs import UserUpdateInput +from specklepy.core.api.inputs.project_inputs import ProjectCreateInput +from specklepy.core.api.models import User +from specklepy.core.api.responses import ResourceCollection + + +@pytest.mark.run() +class TestActiveUserResource: + def test_active_user_get(self, client: SpeckleClient): + res = client.active_user.get() + + assert isinstance(res, User) + + def test_active_user_update(self, client: SpeckleClient): + NEW_NAME = "Ron" + NEW_BIO = "Now I have a bio, isn't that nice!" + NEW_COMPANY = "Limited Cooperation Organization Inc" + + input_data = UserUpdateInput(name=NEW_NAME, bio=NEW_BIO, company=NEW_COMPANY) + res = client.active_user.update(input_data) + + assert isinstance(res, User) + assert res.name == NEW_NAME + assert res.bio == NEW_BIO + assert res.company == NEW_COMPANY + + def test_active_user_get_projects(self, client: SpeckleClient): + existing = client.active_user.get_projects() + + p1 = client.project.create( + ProjectCreateInput(name="Project 1", description=None, visibility=None) + ) + p2 = client.project.create( + ProjectCreateInput(name="Project 2", description=None, visibility=None) + ) + + res = client.active_user.get_projects() + + assert isinstance(res, ResourceCollection) + assert len(res.items) == len(existing.items) + 2 + assert any(project.id == p1.id for project in res.items) + assert any(project.id == p2.id for project in res.items) diff --git a/tests/integration/client/test_branch.py b/tests/integration/client/test_branch.py index 27bf8ba4..f067c5c1 100644 --- a/tests/integration/client/test_branch.py +++ b/tests/integration/client/test_branch.py @@ -1,3 +1,4 @@ +from deprecated import deprecated import pytest from specklepy.api import operations @@ -5,6 +6,7 @@ from specklepy.transports.server import ServerTransport +@deprecated class TestBranch: @pytest.fixture(scope="module") def branch(self): diff --git a/tests/integration/client/test_commit.py b/tests/integration/client/test_commit.py index 9ce3c815..f609f092 100644 --- a/tests/integration/client/test_commit.py +++ b/tests/integration/client/test_commit.py @@ -1,3 +1,4 @@ +from deprecated import deprecated import pytest from specklepy.api import operations @@ -5,6 +6,7 @@ from specklepy.transports.server.server import ServerTransport +@deprecated @pytest.mark.run(order=6) class TestCommit: @pytest.fixture(scope="module") diff --git a/tests/integration/client/test_model.py b/tests/integration/client/test_model_resource.py similarity index 90% rename from tests/integration/client/test_model.py rename to tests/integration/client/test_model_resource.py index 2e4d7e6d..908e9108 100644 --- a/tests/integration/client/test_model.py +++ b/tests/integration/client/test_model_resource.py @@ -8,11 +8,13 @@ ) from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.models import Model, Project +from specklepy.core.api.new_models import ProjectWithModels +from specklepy.core.api.responses import ResourceCollection from specklepy.logging.exceptions import GraphQLException @pytest.mark.run() -class TestModel: +class TestModelResource: @pytest.fixture() def test_project(self, client: SpeckleClient) -> Project: project = client.project.create( @@ -44,8 +46,7 @@ def test_model_create( ) result = client.model.create(input_data) - assert result is not None - assert result.id is not None + assert isinstance(result, Model) assert result.name.lower() == name.lower() assert result.description == description @@ -54,6 +55,7 @@ def test_model_get( ): result = client.model.get(test_model.id, test_project.id) + assert isinstance(result, Model) assert result.id == test_model.id assert result.name == test_model.name assert result.description == test_model.description @@ -65,6 +67,7 @@ def test_get_models( ): result = client.model.get_models(test_project.id) + assert isinstance(result, ResourceCollection) assert len(result.items) == 1 assert result.totalCount == 1 assert result.items[0].id == test_model.id @@ -74,6 +77,7 @@ def test_project_get_models( ): result = client.project.get_with_models(test_project.id) + assert isinstance(result, ProjectWithModels) assert result.id == test_project.id assert len(result.models.items) == 1 assert result.models.totalCount == 1 @@ -94,6 +98,7 @@ def test_model_update( updated_model = client.model.update(update_data) + assert isinstance(updated_model, Model) assert updated_model.id == test_model.id assert updated_model.name.lower() == new_name.lower() assert updated_model.description == new_description diff --git a/tests/integration/client/test_objects.py b/tests/integration/client/test_objects.py index f921ee66..d753e3fb 100644 --- a/tests/integration/client/test_objects.py +++ b/tests/integration/client/test_objects.py @@ -1,3 +1,4 @@ +from deprecated import deprecated import pytest from specklepy.api.models import Stream @@ -7,6 +8,7 @@ from specklepy.transports.sqlite import SQLiteTransport +@deprecated class TestObject: @pytest.fixture(scope="module") def stream(self, client): diff --git a/tests/integration/client/test_project.py b/tests/integration/client/test_project_resource.py similarity index 94% rename from tests/integration/client/test_project.py rename to tests/integration/client/test_project_resource.py index 07bb6a2c..f1c805d6 100644 --- a/tests/integration/client/test_project.py +++ b/tests/integration/client/test_project_resource.py @@ -11,8 +11,8 @@ @pytest.mark.run() -class TestProject: - @pytest.fixture(scope="session") +class TestProjectResource: + @pytest.fixture() def test_project(self, client: SpeckleClient) -> Project: project = client.project.create( ProjectCreateInput( @@ -44,7 +44,7 @@ def test_project_create( ) result = client.project.create(input_data) - assert result is not None + assert isinstance(result, Project) assert result.id is not None assert result.name == name assert result.description == (description or "") @@ -53,6 +53,7 @@ def test_project_create( def test_project_get(self, client: SpeckleClient, test_project: Project): result = client.project.get(test_project.id) + assert isinstance(result, Project) assert result.id == test_project.id assert result.name == test_project.name assert result.description == test_project.description @@ -73,6 +74,7 @@ def test_project_update(self, client: SpeckleClient, test_project: Project): updated_project = client.project.update(update_data) + assert isinstance(updated_project, Project) assert updated_project.id == test_project.id assert updated_project.name == new_name assert updated_project.description == new_description diff --git a/tests/integration/client/test_stream.py b/tests/integration/client/test_stream.py index 84ce5f7e..7dc10b91 100644 --- a/tests/integration/client/test_stream.py +++ b/tests/integration/client/test_stream.py @@ -1,3 +1,4 @@ +from deprecated import deprecated import pytest from specklepy.api.client import SpeckleClient @@ -11,6 +12,7 @@ from specklepy.logging.exceptions import GraphQLException, SpeckleException +@deprecated @pytest.mark.run(order=3) class TestStream: @pytest.fixture(scope="session") diff --git a/tests/integration/client/test_user.py b/tests/integration/client/test_user.py index 58859852..8547b5cf 100644 --- a/tests/integration/client/test_user.py +++ b/tests/integration/client/test_user.py @@ -1,3 +1,4 @@ +from deprecated import deprecated import pytest from specklepy.api.client import SpeckleClient @@ -5,6 +6,7 @@ from specklepy.logging.exceptions import SpeckleException +@deprecated @pytest.mark.run(order=1) class TestUser: def test_user_get_self(self, client: SpeckleClient, user_dict): diff --git a/tests/integration/client/test_version.py b/tests/integration/client/test_version_resource.py similarity index 91% rename from tests/integration/client/test_version.py rename to tests/integration/client/test_version_resource.py index a3b91cfe..f4c78597 100644 --- a/tests/integration/client/test_version.py +++ b/tests/integration/client/test_version_resource.py @@ -12,13 +12,15 @@ UpdateVersionInput, ) from specklepy.core.api.models import Model, Project, Version +from specklepy.core.api.new_models import ModelWithVersions +from specklepy.core.api.responses import ResourceCollection from specklepy.logging.exceptions import GraphQLException from specklepy.objects.base import Base from specklepy.transports.server.server import ServerTransport -@pytest.mark.run(order=4) -class TestVersion: +@pytest.mark.run() +class TestVersionResource: @pytest.fixture def test_project(self, client: SpeckleClient) -> Project: project = client.project.create( @@ -63,6 +65,7 @@ def test_version_get( ): result = client.version.get(test_version.id, test_project.id) + assert isinstance(result, Version) assert result.id == test_version.id assert result.message == test_version.message @@ -75,6 +78,7 @@ def test_versions_get( ): result = client.version.get_versions(test_model_1.id, test_project.id) + assert isinstance(result, ResourceCollection) assert len(result.items) == 1 assert result.totalCount == 1 assert result.items[0].id == test_version.id @@ -100,6 +104,7 @@ def test_model_get_with_versions( ): result = client.model.get_with_versions(test_model_1.id, test_project.id) + assert isinstance(result, ModelWithVersions) assert result.id == test_model_1.id assert len(result.versions.items) == 1 assert result.versions.totalCount == 1 @@ -110,6 +115,7 @@ def test_version_update(self, client: SpeckleClient, test_version: Version): input_data = UpdateVersionInput(versionId=test_version.id, message=new_message) updated_version = client.version.update(input_data) + assert isinstance(updated_version, Version) assert updated_version.id == test_version.id assert updated_version.message == new_message assert updated_version.previewUrl == test_version.previewUrl @@ -126,9 +132,11 @@ def test_version_move_to_model( ) moved_model_id = client.version.move_to_model(input_data) + assert isinstance(moved_model_id, str) assert moved_model_id == test_model_2.id moved_version = client.version.get(test_version.id, test_project.id) + assert isinstance(moved_version, Version) assert moved_version.id == test_version.id assert moved_version.message == test_version.message assert moved_version.previewUrl == test_version.previewUrl From 6096cd25f6dad81d8a07ea85a73870e5d1590ef6 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:30:37 +0100 Subject: [PATCH 08/26] project_invites --- src/specklepy/api/client.py | 7 + .../api/resources/active_user_resource.py | 13 +- .../core/api/resources/model_resource.py | 18 +- .../api/resources/project_invite_resource.py | 258 ++++++++++++++++++ .../core/api/resources/project_resource.py | 32 ++- .../core/api/resources/version_resource.py | 22 +- .../client/test_project_invite_resource.py | 170 ++++++++++++ tests/integration/conftest.py | 14 + 8 files changed, 512 insertions(+), 22 deletions(-) create mode 100644 src/specklepy/core/api/resources/project_invite_resource.py create mode 100644 tests/integration/client/test_project_invite_resource.py diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index aa76069e..cedb3442 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -16,6 +16,7 @@ # TODO: re-reference core.api resources from specklepy.core.api.resources.active_user_resource import ActiveUserResource from specklepy.core.api.resources.model_resource import ModelResource +from specklepy.core.api.resources.project_invite_resource import ProjectInviteResource from specklepy.core.api.resources.project_resource import ProjectResource from specklepy.core.api.resources.version_resource import VersionResource from specklepy.logging import metrics @@ -97,6 +98,12 @@ def _init_resources(self) -> None: client=self.httpclient, server_version=server_version, ) + self.project_invite = ProjectInviteResource( + account=self.account, + basepath=self.url, + client=self.httpclient, + server_version=server_version, + ) self.model = ModelResource( account=self.account, basepath=self.url, diff --git a/src/specklepy/core/api/resources/active_user_resource.py b/src/specklepy/core/api/resources/active_user_resource.py index d5d784d8..6a71202a 100644 --- a/src/specklepy/core/api/resources/active_user_resource.py +++ b/src/specklepy/core/api/resources/active_user_resource.py @@ -163,12 +163,13 @@ def get_project_invites(self) -> List[PendingStreamCollaborator]: title token user { - id, - name, - bio, - company, - verified, - role, + id + name + bio + company + verified + avatar + role } } } diff --git a/src/specklepy/core/api/resources/model_resource.py b/src/specklepy/core/api/resources/model_resource.py index 279776fa..d31f9140 100644 --- a/src/specklepy/core/api/resources/model_resource.py +++ b/src/specklepy/core/api/resources/model_resource.py @@ -57,7 +57,11 @@ def get(self, model_id: str, project_id: str) -> Model: """ ) - variables = {"modelId": model_id, "projectId": project_id} + variables = { + "modelId": model_id, + "projectId": project_id, + } + return self.make_request_and_parse_response( DataResponse[DataResponse[Model]], QUERY, variables ).data.data @@ -176,7 +180,9 @@ def get_models( "projectId": project_id, "modelsLimit": models_limit, "modelsCursor": models_cursor, - "modelsFilter": models_filter, + "modelsFilter": models_filter.model_dump(warnings="error") + if models_filter + else None, } return self.make_request_and_parse_response( @@ -211,7 +217,9 @@ def create(self, input: CreateModelInput) -> Model: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[Model]], QUERY, variables @@ -262,7 +270,9 @@ def update(self, input: UpdateModelInput) -> Model: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[Model]], QUERY, variables diff --git a/src/specklepy/core/api/resources/project_invite_resource.py b/src/specklepy/core/api/resources/project_invite_resource.py new file mode 100644 index 00000000..2193891b --- /dev/null +++ b/src/specklepy/core/api/resources/project_invite_resource.py @@ -0,0 +1,258 @@ +from typing import Any, Optional, Tuple + +from gql import Client, gql + +from specklepy.core.api.client import SpeckleClient +from specklepy.core.api.credentials import Account +from specklepy.core.api.inputs.project_inputs import ( + ProjectInviteCreateInput, + ProjectInviteUseInput, +) +from specklepy.core.api.new_models import ( + PendingStreamCollaborator, + ProjectWithTeam, +) +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.responses import DataResponse + +NAME = "project_invite" + + +class ProjectInviteResource(ResourceBase): + """API Access class for project invites""" + + def __init__( + self, + account: Account, + basepath: str, + client: Client, + server_version: Optional[Tuple[Any, ...]], + ) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + name=NAME, + server_version=server_version, + ) + + def create( + self, project_id: str, input: ProjectInviteCreateInput + ) -> ProjectWithTeam: + QUERY = gql( + """ + mutation ProjectInviteCreate($projectId: ID!, $input: ProjectInviteCreateInput!) { + data:projectMutations { + data:invites { + data:create(projectId: $projectId, input: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + workspaceId + sourceApps + team { + id + role + user { + id + name + bio + company + avatar + verified + role + } + } + invitedTeam { + id + inviteId + projectId + projectName + title + role + token + user { + id + name + bio + company + avatar + verified + role + } + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + } + } + } + } + """ + ) + + variables = { + "projectId": project_id, + "input": input.model_dump(warnings="error"), + } + + return self.make_request_and_parse_response( + DataResponse[DataResponse[DataResponse[ProjectWithTeam]]], QUERY, variables + ).data.data.data + + def use(self, input: ProjectInviteUseInput) -> bool: + QUERY = gql( + """ + mutation ProjectInviteUse($input: ProjectInviteUseInput!) { + data:projectMutations { + data:invites { + data:use(input: $input) + } + } + } + """ + ) + + variables = { + "input": input.model_dump(warnings="error"), + } + + return self.make_request_and_parse_response( + DataResponse[DataResponse[DataResponse[bool]]], QUERY, variables + ).data.data.data + + def get( + self, project_id: str, token: Optional[str] + ) -> Optional[PendingStreamCollaborator]: + """Returns: The invite, or None if no invite exists""" + + QUERY = gql( + """ + query ProjectInvite($projectId: String!, $token: String) { + data:projectInvite(projectId: $projectId, token: $token) { + id + inviteId + invitedBy { + avatar + bio + company + id + name + role + verified + } + projectId + projectName + role + title + token + user { + avatar + bio + company + id + name + role + verified + } + } + } + """ + ) + + variables = { + "projectId": project_id, + "token": token, + } + + return self.make_request_and_parse_response( + DataResponse[Optional[PendingStreamCollaborator]], QUERY, variables + ).data + + def cancel( + self, + project_id: str, + invite_id: str, + ) -> ProjectWithTeam: + QUERY = gql( + """ + mutation ProjectInviteCancel($projectId: ID!, $inviteId: String!) { + data:projectMutations { + data:invites { + data:cancel(projectId: $projectId, inviteId: $inviteId) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + workspaceId + team { + id + role + user { + id + name + bio + company + avatar + verified + role + } + } + invitedTeam { + id + inviteId + projectId + projectName + title + role + token + user { + id + name + bio + company + avatar + verified + role + } + invitedBy { + id + name + bio + company + avatar + verified + role + } + } + } + } + } + } + """ + ) + + variables = { + "projectId": project_id, + "inviteId": invite_id, + } + + return self.make_request_and_parse_response( + DataResponse[DataResponse[DataResponse[ProjectWithTeam]]], QUERY, variables + ).data.data.data diff --git a/src/specklepy/core/api/resources/project_resource.py b/src/specklepy/core/api/resources/project_resource.py index cdd81bbc..e4975cd9 100644 --- a/src/specklepy/core/api/resources/project_resource.py +++ b/src/specklepy/core/api/resources/project_resource.py @@ -48,7 +48,10 @@ def get(self, project_id: str) -> Project: """ ) - variables = {"projectId": project_id} + variables = { + "projectId": project_id, + } + return self.make_request_and_parse_response( DataResponse[Project], QUERY, variables ).data @@ -128,7 +131,10 @@ def get_with_team(self, project_id: str) -> ProjectWithTeam: role createdAt updatedAt + workspaceId + sourceApps team { + id role user { id @@ -173,7 +179,9 @@ def get_with_team(self, project_id: str) -> ProjectWithTeam: """ ) - variables = {"projectId": project_id} + variables = { + "projectId": project_id, + } return self.make_request_and_parse_response( DataResponse[ProjectWithTeam], QUERY, variables @@ -201,7 +209,9 @@ def create(self, input: ProjectCreateInput) -> Project: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[Project]], QUERY, variables @@ -229,7 +239,9 @@ def update(self, input: ProjectUpdateInput) -> Project: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[Project]], QUERY, variables @@ -246,7 +258,9 @@ def delete(self, project_id: str) -> bool: """ ) - variables = {"projectId": project_id} + variables = { + "projectId": project_id, + } return self.make_request_and_parse_response( DataResponse[DataResponse[bool]], QUERY, variables @@ -266,7 +280,10 @@ def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam: role createdAt updatedAt + sourceApps + workspaceId team { + id role user { id @@ -305,14 +322,15 @@ def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam: role } } - workspaceId } } } """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[ProjectWithTeam]], QUERY, variables diff --git a/src/specklepy/core/api/resources/version_resource.py b/src/specklepy/core/api/resources/version_resource.py index e98d03b1..758f0ade 100644 --- a/src/specklepy/core/api/resources/version_resource.py +++ b/src/specklepy/core/api/resources/version_resource.py @@ -58,7 +58,11 @@ def get(self, version_id: str, project_id: str) -> Version: """ ) - variables = {"projectId": project_id, "versionId": version_id} + variables = { + "projectId": project_id, + "versionId": version_id, + } + return self.make_request_and_parse_response( DataResponse[DataResponse[Version]], QUERY, variables ).data.data @@ -131,7 +135,9 @@ def create(self, input: CreateVersionInput) -> str: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[DataResponse[str]]], QUERY, variables @@ -183,7 +189,9 @@ def move_to_model(self, input: MoveVersionsInput) -> str: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[DataResponse[str]]], QUERY, variables @@ -200,7 +208,9 @@ def delete(self, input: DeleteVersionsInput) -> bool: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[bool]], QUERY, variables @@ -217,7 +227,9 @@ def received(self, input: MarkReceivedVersionInput) -> bool: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = { + "input": input.model_dump(warnings="error"), + } return self.make_request_and_parse_response( DataResponse[DataResponse[bool]], QUERY, variables diff --git a/tests/integration/client/test_project_invite_resource.py b/tests/integration/client/test_project_invite_resource.py new file mode 100644 index 00000000..6ac1ac09 --- /dev/null +++ b/tests/integration/client/test_project_invite_resource.py @@ -0,0 +1,170 @@ +from typing import Optional +import pytest +from specklepy.api.client import SpeckleClient +from specklepy.core.api.inputs.project_inputs import ( + ProjectCreateInput, + ProjectInviteCreateInput, + ProjectInviteUseInput, + ProjectUpdateRoleInput, +) +from specklepy.core.api.models import PendingStreamCollaborator, Project +from specklepy.core.api.new_models import LimitedUser, ProjectWithTeam + + +@pytest.mark.run(order=1) +class TestProjectInviteResource: + @pytest.fixture + def project(self, client: SpeckleClient): + return client.project.create( + ProjectCreateInput(name="test", description=None, visibility=None) + ) + + @pytest.fixture + def created_invite( + self, client: SpeckleClient, second_client: SpeckleClient, project: Project + ): + input = ProjectInviteCreateInput( + email=second_client.account.userInfo.email, + role=None, + serverRole=None, + userId=None, + ) + res = client.project_invite.create(project.id, input) + invites = second_client.active_user.get_project_invites() + return next(i for i in invites if i.projectId == res.id) + + def test_project_invite_create_by_email( + self, client: SpeckleClient, second_client: SpeckleClient, project: Project + ): + input = ProjectInviteCreateInput( + email=second_client.account.userInfo.email, + role=None, + serverRole=None, + userId=None, + ) + res = client.project_invite.create(project.id, input) + + invites = second_client.active_user.get_project_invites() + invite = next(i for i in invites if i.projectId == res.id) + + assert isinstance(res, ProjectWithTeam) + assert res.id == project.id + assert len(res.invitedTeam) == 1 + + assert isinstance(invite.user, LimitedUser) + assert invite.user.id == second_client.account.userInfo.id + assert invite.token + + def test_project_invite_create_by_user_id( + self, client: SpeckleClient, second_client: SpeckleClient, project: Project + ): + input = ProjectInviteCreateInput( + email=None, + role=None, + serverRole=None, + userId=second_client.account.userInfo.id, + ) + res = client.project_invite.create(project.id, input) + + assert isinstance(res, ProjectWithTeam) + assert res.id == project.id + assert len(res.invitedTeam) == 1 + invited_team_member = res.invitedTeam[0].user + assert isinstance(invited_team_member, LimitedUser) + assert invited_team_member.id == second_client.account.userInfo.id + + def test_project_invite_get( + self, + second_client: SpeckleClient, + project: Project, + created_invite: PendingStreamCollaborator, + ): + collaborator = second_client.project_invite.get( + project.id, created_invite.token + ) + assert isinstance(collaborator, PendingStreamCollaborator) + assert collaborator.inviteId == created_invite.inviteId + + assert isinstance(collaborator.user, LimitedUser) + assert isinstance(created_invite.user, LimitedUser) + + assert collaborator.user.id == created_invite.user.id + + def test_project_invite_get_non_existing( + self, second_client: SpeckleClient, project: Project + ): + collaborator = second_client.project_invite.get( + project.id, "this is not a real token" + ) + + assert collaborator is None + + def test_project_invite_use_member_added( + self, + client: SpeckleClient, + second_client: SpeckleClient, + project: Project, + created_invite: PendingStreamCollaborator, + ): + assert created_invite.token + + input = ProjectInviteUseInput( + accept=True, projectId=created_invite.projectId, token=created_invite.token + ) + res = second_client.project_invite.use(input) + + assert res is True + + project = client.project.get_with_team(project.id) + assert isinstance(project, ProjectWithTeam) + + team_members = [c.user.id for c in project.team] + expected_team_members = [ + client.account.userInfo.id, + second_client.account.userInfo.id, + ] + + assert set(team_members) == set(expected_team_members) + + def test_project_invite_cancel_member_not_added( + self, client: SpeckleClient, created_invite: PendingStreamCollaborator + ): + res = client.project_invite.cancel( + created_invite.projectId, created_invite.inviteId + ) + + assert isinstance(res, ProjectWithTeam) + assert len(res.invitedTeam) == 0 + + @pytest.mark.parametrize( + "new_role", ["stream:owner", "stream:contributor", "stream:reviewer", None] + ) + def test_project_update_role( + self, + client: SpeckleClient, + second_client: SpeckleClient, + project: Project, + new_role: Optional[str], + created_invite: PendingStreamCollaborator, + ): + assert created_invite.token + + input = ProjectInviteUseInput( + accept=True, projectId=created_invite.projectId, token=created_invite.token + ) + res = second_client.project_invite.use(input) + + invitee_id = second_client.account.userInfo.id + assert invitee_id + input = ProjectUpdateRoleInput( + userId=invitee_id, + projectId=project.id, + role=new_role, + ) + res = client.project.update_role(input) + + assert isinstance(res, ProjectWithTeam) + final_project = second_client.project.get(project.id) + + assert isinstance(res, Project) + assert final_project.role == new_role diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 319a2367..b61d8e21 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -71,6 +71,13 @@ def second_user_dict(host): def client(host, user_dict): client = SpeckleClient(host=host, use_ssl=False) client.authenticate_with_token(user_dict["token"]) + user = client.active_user.get() + assert user + client.account.userInfo.id = user.id + client.account.userInfo.email = user.email + client.account.userInfo.name = user.name + client.account.userInfo.company = user.company + client.account.userInfo.avatar = user.avatar return client @@ -78,6 +85,13 @@ def client(host, user_dict): def second_client(host, second_user_dict): client = SpeckleClient(host=host, use_ssl=False) client.authenticate_with_token(second_user_dict["token"]) + user = client.active_user.get() + assert user + client.account.userInfo.id = user.id + client.account.userInfo.email = user.email + client.account.userInfo.name = user.name + client.account.userInfo.company = user.company + client.account.userInfo.avatar = user.avatar return client From 280927b7209e6d7d2f26a7c379afdd0c0e75650e Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:09:26 +0100 Subject: [PATCH 09/26] active user update overloads --- src/specklepy/api/resources/project.py | 6 +-- src/specklepy/core/api/inputs/user_inputs.py | 1 + .../core/api/resources/active_user.py | 1 + .../api/resources/active_user_resource.py | 41 +++++++++++++++++-- .../core/api/resources/model_resource.py | 1 - .../api/resources/project_invite_resource.py | 6 +-- .../core/api/resources/version_resource.py | 4 +- tests/integration/client/test_active_user.py | 2 +- .../client/test_active_user_resource.py | 3 +- tests/integration/client/test_branch.py | 2 +- tests/integration/client/test_commit.py | 2 +- tests/integration/client/test_objects.py | 2 +- .../client/test_project_invite_resource.py | 2 + tests/integration/client/test_stream.py | 2 +- tests/integration/client/test_user.py | 2 +- 15 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/specklepy/api/resources/project.py b/src/specklepy/api/resources/project.py index b232dbd3..3bfba0a4 100644 --- a/src/specklepy/api/resources/project.py +++ b/src/specklepy/api/resources/project.py @@ -1,9 +1,5 @@ -from datetime import datetime -from typing import List, Optional - -from specklepy.api.models import PendingStreamCollaborator, Stream +from specklepy.api.models import Stream from specklepy.core.api.resources.stream import Resource as CoreResource -from specklepy.logging import metrics class Resource(CoreResource): diff --git a/src/specklepy/core/api/inputs/user_inputs.py b/src/specklepy/core/api/inputs/user_inputs.py index de6c112b..18b1efea 100644 --- a/src/specklepy/core/api/inputs/user_inputs.py +++ b/src/specklepy/core/api/inputs/user_inputs.py @@ -1,4 +1,5 @@ from typing import Optional + from pydantic import BaseModel diff --git a/src/specklepy/core/api/resources/active_user.py b/src/specklepy/core/api/resources/active_user.py index 91647065..1d4aa3f7 100644 --- a/src/specklepy/core/api/resources/active_user.py +++ b/src/specklepy/core/api/resources/active_user.py @@ -1,4 +1,5 @@ from deprecated import deprecated + from specklepy.core.api.models import FE1_DEPRECATION_VERSION from specklepy.core.api.resources.active_user_resource import ActiveUserResource diff --git a/src/specklepy/core/api/resources/active_user_resource.py b/src/specklepy/core/api/resources/active_user_resource.py index 6a71202a..7c1b97f2 100644 --- a/src/specklepy/core/api/resources/active_user_resource.py +++ b/src/specklepy/core/api/resources/active_user_resource.py @@ -1,5 +1,5 @@ from datetime import datetime, timezone -from typing import List, Optional +from typing import List, Optional, overload from deprecated import deprecated from gql import gql @@ -63,8 +63,7 @@ def get(self) -> Optional[User]: DataResponse[Optional[User]], QUERY, variables ).data - # todo: breaking change, can we make this not? - def update(self, input: UserUpdateInput) -> User: + def _update(self, input: UserUpdateInput) -> User: QUERY = gql( """ mutation ActiveUserMutations($input: UserUpdateInput!) { @@ -90,6 +89,42 @@ def update(self, input: UserUpdateInput) -> User: DataResponse[DataResponse[User]], QUERY, variables ).data.data + @deprecated("Use UserUpdateInput overload", version=FE1_DEPRECATION_VERSION) + @overload + def update( + self, + name: Optional[str] = None, + company: Optional[str] = None, + bio: Optional[str] = None, + avatar: Optional[str] = None, + ) -> User: + ... + + @overload + def update(self, *, input: UserUpdateInput) -> User: + ... + + def update( + self, + name: Optional[str] = None, + company: Optional[str] = None, + bio: Optional[str] = None, + avatar: Optional[str] = None, + *, + input: Optional[UserUpdateInput] = None, + ) -> User: + if isinstance(input, UserUpdateInput): + return self._update(input=input) + else: + return self._update( + input=UserUpdateInput( + name=name, + company=company, + bio=bio, + avatar=avatar, + ) + ) + def get_projects( self, *, diff --git a/src/specklepy/core/api/resources/model_resource.py b/src/specklepy/core/api/resources/model_resource.py index d31f9140..c85cecb4 100644 --- a/src/specklepy/core/api/resources/model_resource.py +++ b/src/specklepy/core/api/resources/model_resource.py @@ -9,7 +9,6 @@ UpdateModelInput, ) from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter -from specklepy.core.api.models import Project from specklepy.core.api.new_models import Model, ModelWithVersions from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse, ResourceCollection diff --git a/src/specklepy/core/api/resources/project_invite_resource.py b/src/specklepy/core/api/resources/project_invite_resource.py index 2193891b..fea5ca69 100644 --- a/src/specklepy/core/api/resources/project_invite_resource.py +++ b/src/specklepy/core/api/resources/project_invite_resource.py @@ -2,16 +2,12 @@ from gql import Client, gql -from specklepy.core.api.client import SpeckleClient from specklepy.core.api.credentials import Account from specklepy.core.api.inputs.project_inputs import ( ProjectInviteCreateInput, ProjectInviteUseInput, ) -from specklepy.core.api.new_models import ( - PendingStreamCollaborator, - ProjectWithTeam, -) +from specklepy.core.api.new_models import PendingStreamCollaborator, ProjectWithTeam from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse diff --git a/src/specklepy/core/api/resources/version_resource.py b/src/specklepy/core/api/resources/version_resource.py index 758f0ade..2c7fdeb1 100644 --- a/src/specklepy/core/api/resources/version_resource.py +++ b/src/specklepy/core/api/resources/version_resource.py @@ -2,9 +2,7 @@ from gql import gql -from specklepy.core.api.inputs.model_inputs import ( - ModelVersionsFilter, -) +from specklepy.core.api.inputs.model_inputs import ModelVersionsFilter from specklepy.core.api.inputs.version_inputs import ( CreateVersionInput, DeleteVersionsInput, diff --git a/tests/integration/client/test_active_user.py b/tests/integration/client/test_active_user.py index 79998436..508881f0 100644 --- a/tests/integration/client/test_active_user.py +++ b/tests/integration/client/test_active_user.py @@ -1,5 +1,5 @@ -from deprecated import deprecated import pytest +from deprecated import deprecated from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, User diff --git a/tests/integration/client/test_active_user_resource.py b/tests/integration/client/test_active_user_resource.py index 84b15d8a..8dbb8ef7 100644 --- a/tests/integration/client/test_active_user_resource.py +++ b/tests/integration/client/test_active_user_resource.py @@ -1,7 +1,8 @@ import pytest + from specklepy.core.api.client import SpeckleClient -from specklepy.core.api.inputs.user_inputs import UserUpdateInput from specklepy.core.api.inputs.project_inputs import ProjectCreateInput +from specklepy.core.api.inputs.user_inputs import UserUpdateInput from specklepy.core.api.models import User from specklepy.core.api.responses import ResourceCollection diff --git a/tests/integration/client/test_branch.py b/tests/integration/client/test_branch.py index f067c5c1..cd6d388d 100644 --- a/tests/integration/client/test_branch.py +++ b/tests/integration/client/test_branch.py @@ -1,5 +1,5 @@ -from deprecated import deprecated import pytest +from deprecated import deprecated from specklepy.api import operations from specklepy.api.models import Branch, Commit, Stream diff --git a/tests/integration/client/test_commit.py b/tests/integration/client/test_commit.py index f609f092..94a97386 100644 --- a/tests/integration/client/test_commit.py +++ b/tests/integration/client/test_commit.py @@ -1,5 +1,5 @@ -from deprecated import deprecated import pytest +from deprecated import deprecated from specklepy.api import operations from specklepy.api.models import Commit, Stream diff --git a/tests/integration/client/test_objects.py b/tests/integration/client/test_objects.py index d753e3fb..2631a538 100644 --- a/tests/integration/client/test_objects.py +++ b/tests/integration/client/test_objects.py @@ -1,5 +1,5 @@ -from deprecated import deprecated import pytest +from deprecated import deprecated from specklepy.api.models import Stream from specklepy.objects import Base diff --git a/tests/integration/client/test_project_invite_resource.py b/tests/integration/client/test_project_invite_resource.py index 6ac1ac09..94893340 100644 --- a/tests/integration/client/test_project_invite_resource.py +++ b/tests/integration/client/test_project_invite_resource.py @@ -1,5 +1,7 @@ from typing import Optional + import pytest + from specklepy.api.client import SpeckleClient from specklepy.core.api.inputs.project_inputs import ( ProjectCreateInput, diff --git a/tests/integration/client/test_stream.py b/tests/integration/client/test_stream.py index 7dc10b91..b894bf99 100644 --- a/tests/integration/client/test_stream.py +++ b/tests/integration/client/test_stream.py @@ -1,5 +1,5 @@ -from deprecated import deprecated import pytest +from deprecated import deprecated from specklepy.api.client import SpeckleClient from specklepy.api.models import ( diff --git a/tests/integration/client/test_user.py b/tests/integration/client/test_user.py index 8547b5cf..0eb141c8 100644 --- a/tests/integration/client/test_user.py +++ b/tests/integration/client/test_user.py @@ -1,5 +1,5 @@ -from deprecated import deprecated import pytest +from deprecated import deprecated from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, User From 0f1f00db00ec0287d5b8b81d3e26710c7e15a431 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:44:12 +0100 Subject: [PATCH 10/26] Other user resource --- src/specklepy/api/client.py | 3 +- src/specklepy/api/resources/other_user.py | 6 +- src/specklepy/core/api/client.py | 4 +- .../api/resources/active_user_resource.py | 7 +- .../core/api/resources/other_user.py | 179 +------------ .../core/api/resources/other_user_resource.py | 235 ++++++++++++++++++ src/specklepy/core/api/responses.py | 7 + tests/integration/client/test_active_user.py | 4 +- .../client/test_active_user_resource.py | 4 +- .../integration/client/test_model_resource.py | 4 +- tests/integration/client/test_other_user.py | 3 +- .../client/test_other_user_resource.py | 32 +++ .../client/test_project_resource.py | 4 +- .../client/test_version_resource.py | 18 +- 14 files changed, 315 insertions(+), 195 deletions(-) create mode 100644 src/specklepy/core/api/resources/other_user_resource.py create mode 100644 tests/integration/client/test_other_user_resource.py diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index cedb3442..39949c03 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -16,6 +16,7 @@ # TODO: re-reference core.api resources from specklepy.core.api.resources.active_user_resource import ActiveUserResource from specklepy.core.api.resources.model_resource import ModelResource +from specklepy.core.api.resources.other_user_resource import OtherUserResource from specklepy.core.api.resources.project_invite_resource import ProjectInviteResource from specklepy.core.api.resources.project_resource import ProjectResource from specklepy.core.api.resources.version_resource import VersionResource @@ -80,7 +81,7 @@ def _init_resources(self) -> None: except Exception: pass - self.other_user = other_user.Resource( + self.other_user = OtherUserResource( account=self.account, basepath=self.url, client=self.httpclient, diff --git a/src/specklepy/api/resources/other_user.py b/src/specklepy/api/resources/other_user.py index b70c7fc0..6c903b20 100644 --- a/src/specklepy/api/resources/other_user.py +++ b/src/specklepy/api/resources/other_user.py @@ -2,7 +2,9 @@ from typing import List, Optional, Union from specklepy.api.models import ActivityCollection, LimitedUser -from specklepy.core.api.resources.other_user import Resource as CoreResource +from specklepy.core.api.resources.other_user_resource import ( + OtherUserResource as CoreResource, +) from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException @@ -23,7 +25,7 @@ def __init__(self, account, basepath, client, server_version) -> None: ) self.schema = LimitedUser - def get(self, id: str) -> LimitedUser: + def get(self, id: str) -> Optional[LimitedUser]: """ Retrieves the profile of a user specified by their user ID. diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index bc14fc5d..6dcd5659 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -14,7 +14,7 @@ branch, commit, object, - other_user, + other_user_resource, server, stream, subscriptions, @@ -210,7 +210,7 @@ def _init_resources(self) -> None: except Exception: pass - self.other_user = other_user.Resource( + self.other_user = other_user_resource.Resource( account=self.account, basepath=self.url, client=self.httpclient, diff --git a/src/specklepy/core/api/resources/active_user_resource.py b/src/specklepy/core/api/resources/active_user_resource.py index 7c1b97f2..cc7e47ab 100644 --- a/src/specklepy/core/api/resources/active_user_resource.py +++ b/src/specklepy/core/api/resources/active_user_resource.py @@ -97,12 +97,10 @@ def update( company: Optional[str] = None, bio: Optional[str] = None, avatar: Optional[str] = None, - ) -> User: - ... + ) -> User: ... @overload - def update(self, *, input: UserUpdateInput) -> User: - ... + def update(self, *, input: UserUpdateInput) -> User: ... def update( self, @@ -138,6 +136,7 @@ def get_projects( data:activeUser { data:projects(limit: $limit, cursor: $cursor, filter: $filter) { totalCount + cursor items { id name diff --git a/src/specklepy/core/api/resources/other_user.py b/src/specklepy/core/api/resources/other_user.py index d39187dd..0db1b600 100644 --- a/src/specklepy/core/api/resources/other_user.py +++ b/src/specklepy/core/api/resources/other_user.py @@ -1,172 +1,15 @@ -from datetime import datetime, timezone -from typing import List, Optional, Union +from deprecated import deprecated -from gql import gql +from specklepy.core.api.models import FE1_DEPRECATION_VERSION +from specklepy.core.api.resources.other_user_resource import OtherUserResource -from specklepy.core.api.models import ActivityCollection, LimitedUser -from specklepy.core.api.resource import ResourceBase -from specklepy.logging.exceptions import SpeckleException -NAME = "other_user" +@deprecated( + reason="Class renamed to OtherUserResource", version=FE1_DEPRECATION_VERSION +) +class Resource(OtherUserResource): + """ + Class renamed to OtherUserResource + """ - -class Resource(ResourceBase): - """API Access class for other users, that are not the currently active user.""" - - 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 = LimitedUser - - def get(self, id: str) -> LimitedUser: - """ - Gets the profile of another user. - - Arguments: - id {str} -- the user id - - Returns: - LimitedUser -- the retrieved profile of another user - """ - query = gql( - """ - query OtherUser($id: String!) { - otherUser(id: $id) { - id - name - bio - company - avatar - verified - role - } - } - """ - ) - - params = {"id": id} - - return self.make_request(query=query, params=params, return_type="otherUser") - - def search( - self, search_query: str, limit: int = 25 - ) -> Union[List[LimitedUser], 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[LimitedUser] -- 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"] - ) - - def activity( - self, - user_id: str, - limit: int = 20, - action_type: Optional[str] = None, - before: Optional[datetime] = None, - after: Optional[datetime] = None, - cursor: Optional[datetime] = None, - ) -> ActivityCollection: - """ - 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 - - 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 - ){ - otherUser(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=["otherUser", "activity"], - schema=ActivityCollection, - ) + pass diff --git a/src/specklepy/core/api/resources/other_user_resource.py b/src/specklepy/core/api/resources/other_user_resource.py new file mode 100644 index 00000000..35888dd0 --- /dev/null +++ b/src/specklepy/core/api/resources/other_user_resource.py @@ -0,0 +1,235 @@ +from datetime import datetime, timezone +from typing import List, Optional, Union + +from deprecated import deprecated +from gql import gql + +from specklepy.core.api.models import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, + ActivityCollection, + LimitedUser, +) +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.responses import DataResponse, ResourceCollection +from specklepy.logging.exceptions import SpeckleException + +NAME = "other_user" + + +class OtherUserResource(ResourceBase): + """API Access class for other users, that are not the currently active user.""" + + 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 = LimitedUser + + def get(self, id: str) -> Optional[LimitedUser]: + """ + Gets the profile of another user. + + Arguments: + id {str} -- the user id + + Returns: + LimitedUser -- the retrieved profile of another user + """ + QUERY = gql( + """ + query LimitedUser($id: String!) { + data:otherUser(id: $id){ + id + name + bio + company + avatar + verified + role + } + } + """ + ) + + variables = {"id": id} + + return self.make_request_and_parse_response( + DataResponse[Optional[LimitedUser]], QUERY, variables + ).data + + def user_search( + self, + query: str, + *, + limit: int = 25, + cursor: Optional[str] = None, + archived: bool = False, + emailOnly: bool = False, + ) -> ResourceCollection[LimitedUser]: + """Searches for a user on the server, 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 + cursor {Optional[str]} -- + archived {bool} -- + emailOnly {bool} -- + Returns: + ResourceCollection[LimitedUser] -- User objects that match the search query + """ + + QUERY = gql( + """ + query UserSearch($query: String!, $limit: Int!, $cursor: String, $archived: Boolean, $emailOnly: Boolean) { + data:userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived, emailOnly: $emailOnly) { + cursor + items { + id + name + bio + company + avatar + verified + role + } + } + } + """ + ) + variables = { + "query": query, + "limit": limit, + "cursor": cursor, + "archived": archived, + "emailOnly": emailOnly, + } + + return self.make_request_and_parse_response( + DataResponse[ResourceCollection[LimitedUser]], QUERY, variables + ).data + + @deprecated(reason="Use user_search instead", version=FE1_DEPRECATION_VERSION) + def search( + self, search_query: str, limit: int = 25 + ) -> Union[List[LimitedUser], 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[LimitedUser] -- 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"] + ) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + def activity( + self, + user_id: str, + limit: int = 20, + action_type: Optional[str] = None, + before: Optional[datetime] = None, + after: Optional[datetime] = None, + cursor: Optional[datetime] = None, + ) -> ActivityCollection: + """ + 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 + + 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 + ){ + otherUser(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=["otherUser", "activity"], + schema=ActivityCollection, + ) diff --git a/src/specklepy/core/api/responses.py b/src/specklepy/core/api/responses.py index 1fd331ae..d99df1db 100644 --- a/src/specklepy/core/api/responses.py +++ b/src/specklepy/core/api/responses.py @@ -2,6 +2,8 @@ from pydantic import BaseModel +from specklepy.core.api.new_models import LimitedUser + T = TypeVar("T") @@ -15,5 +17,10 @@ class ProjectCommentCollection(ResourceCollection[T], Generic[T]): totalArchivedCount: int +class UserSearchResultCollection(BaseModel): + items: List[LimitedUser] + cursor: Optional[str] = None + + class DataResponse(BaseModel, Generic[T]): data: T diff --git a/tests/integration/client/test_active_user.py b/tests/integration/client/test_active_user.py index 508881f0..ffc3917e 100644 --- a/tests/integration/client/test_active_user.py +++ b/tests/integration/client/test_active_user.py @@ -23,9 +23,9 @@ def test_user_update(self, client: SpeckleClient): bio = "i am a ghost in the machine" with pytest.raises(GraphQLException): - client.active_user.update(UserUpdateInput()) + client.active_user.update(input=UserUpdateInput()) - updated = client.active_user.update(UserUpdateInput(bio=bio)) + updated = client.active_user.update(input=UserUpdateInput(bio=bio)) assert isinstance(updated, User) assert isinstance(updated, User) diff --git a/tests/integration/client/test_active_user_resource.py b/tests/integration/client/test_active_user_resource.py index 8dbb8ef7..dc110569 100644 --- a/tests/integration/client/test_active_user_resource.py +++ b/tests/integration/client/test_active_user_resource.py @@ -19,8 +19,8 @@ def test_active_user_update(self, client: SpeckleClient): NEW_BIO = "Now I have a bio, isn't that nice!" NEW_COMPANY = "Limited Cooperation Organization Inc" - input_data = UserUpdateInput(name=NEW_NAME, bio=NEW_BIO, company=NEW_COMPANY) - res = client.active_user.update(input_data) + input = UserUpdateInput(name=NEW_NAME, bio=NEW_BIO, company=NEW_COMPANY) + res = client.active_user.update(input=input) assert isinstance(res, User) assert res.name == NEW_NAME diff --git a/tests/integration/client/test_model_resource.py b/tests/integration/client/test_model_resource.py index 908e9108..72b64ce3 100644 --- a/tests/integration/client/test_model_resource.py +++ b/tests/integration/client/test_model_resource.py @@ -41,10 +41,10 @@ def test_model(self, client: SpeckleClient, test_project: Project) -> Model: def test_model_create( self, client: SpeckleClient, test_project: Project, name: str, description: str ): - input_data = CreateModelInput( + input = CreateModelInput( name=name, description=description, projectId=test_project.id ) - result = client.model.create(input_data) + result = client.model.create(input) assert isinstance(result, Model) assert result.name.lower() == name.lower() diff --git a/tests/integration/client/test_other_user.py b/tests/integration/client/test_other_user.py index 106e9641..3458198f 100644 --- a/tests/integration/client/test_other_user.py +++ b/tests/integration/client/test_other_user.py @@ -1,9 +1,10 @@ +from deprecated import deprecated import pytest from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, LimitedUser - +@deprecated() @pytest.mark.run(order=4) class TestOtherUser: def test_user_get_self(self, client): diff --git a/tests/integration/client/test_other_user_resource.py b/tests/integration/client/test_other_user_resource.py new file mode 100644 index 00000000..97fe451d --- /dev/null +++ b/tests/integration/client/test_other_user_resource.py @@ -0,0 +1,32 @@ +import pytest + +from specklepy.api.client import SpeckleClient +from specklepy.core.api.models import User + + +@pytest.mark.run() +class TestOtherUserResource: + @pytest.fixture(scope="class") + def test_data(self, second_client: SpeckleClient) -> User: + user_info = second_client.active_user.get() + assert user_info + return user_info + + def test_other_user_get(self, client: SpeckleClient, test_data: User): + res = client.other_user.get(test_data.id) + assert res is not None + assert res.name == test_data.name + + def test_other_user_get_non_existent_user(self, client: SpeckleClient): + result = client.other_user.get("AnIdThatDoesntExist") + assert result is None + + def test_user_search(self, client: SpeckleClient, test_data: User): + assert test_data.email + res = client.other_user.user_search(test_data.email, limit=25) + assert len(res.items) == 1 + assert res.items[0].id == test_data.id + + def test_user_search_non_existent_user(self, client: SpeckleClient): + res = client.other_user.user_search("idontexist@example.com", limit=25) + assert len(res.items) == 0 diff --git a/tests/integration/client/test_project_resource.py b/tests/integration/client/test_project_resource.py index f1c805d6..05e25d5c 100644 --- a/tests/integration/client/test_project_resource.py +++ b/tests/integration/client/test_project_resource.py @@ -37,12 +37,12 @@ def test_project_create( description: str, visibility: ProjectVisibility, ): - input_data = ProjectCreateInput( + input = ProjectCreateInput( name=name, description=description, visibility=visibility, ) - result = client.project.create(input_data) + result = client.project.create(input) assert isinstance(result, Project) assert result.id is not None diff --git a/tests/integration/client/test_version_resource.py b/tests/integration/client/test_version_resource.py index f4c78597..ce7fdbd1 100644 --- a/tests/integration/client/test_version_resource.py +++ b/tests/integration/client/test_version_resource.py @@ -86,12 +86,12 @@ def test_versions_get( def test_version_received( self, client: SpeckleClient, test_version: Version, test_project: Project ): - input_data = MarkReceivedVersionInput( + input = MarkReceivedVersionInput( versionId=test_version.id, projectId=test_project.id, sourceApplication="Integration test", ) - result = client.version.received(input_data) + result = client.version.received(input) assert result is True @@ -112,8 +112,8 @@ def test_model_get_with_versions( def test_version_update(self, client: SpeckleClient, test_version: Version): new_message = "MY new version message" - input_data = UpdateVersionInput(versionId=test_version.id, message=new_message) - updated_version = client.version.update(input_data) + input = UpdateVersionInput(versionId=test_version.id, message=new_message) + updated_version = client.version.update(input) assert isinstance(updated_version, Version) assert updated_version.id == test_version.id @@ -127,10 +127,10 @@ def test_version_move_to_model( test_version: Version, test_model_2: Model, ): - input_data = MoveVersionsInput( + input = MoveVersionsInput( targetModelName=test_model_2.name, versionIds=[test_version.id] ) - moved_model_id = client.version.move_to_model(input_data) + moved_model_id = client.version.move_to_model(input) assert isinstance(moved_model_id, str) assert moved_model_id == test_model_2.id @@ -144,13 +144,13 @@ def test_version_move_to_model( def test_version_delete( self, client: SpeckleClient, test_version: Version, test_project: Project ): - input_data = DeleteVersionsInput(versionIds=[test_version.id]) + input = DeleteVersionsInput(versionIds=[test_version.id]) - response = client.version.delete(input_data) + response = client.version.delete(input) assert response is True with pytest.raises(GraphQLException): client.version.get(test_version.id, test_project.id) with pytest.raises(GraphQLException): - client.version.delete(input_data) + client.version.delete(input) From 6b6ff80bf29a5bdfb4078f3edd02b1bea665874d Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:02:08 +0000 Subject: [PATCH 11/26] Fixed issues --- src/specklepy/api/client.py | 1 - src/specklepy/api/resources/active_user.py | 4 ++-- src/specklepy/core/api/client.py | 4 ++-- src/specklepy/core/api/models.py | 4 ++-- src/specklepy/core/api/new_models.py | 20 +++++++++++++++++-- .../api/resources/active_user_resource.py | 9 ++++++--- .../core/api/resources/model_resource.py | 4 ++-- .../core/api/resources/other_user_resource.py | 7 ++++--- .../core/api/resources/version_resource.py | 4 ++-- src/specklepy/core/api/responses.py | 19 +----------------- tests/integration/client/test_other_user.py | 3 ++- 11 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index 39949c03..f2cfe3cc 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -5,7 +5,6 @@ branch, commit, object, - other_user, server, stream, subscriptions, diff --git a/src/specklepy/api/resources/active_user.py b/src/specklepy/api/resources/active_user.py index aaf0261a..a7d15317 100644 --- a/src/specklepy/api/resources/active_user.py +++ b/src/specklepy/api/resources/active_user.py @@ -21,7 +21,7 @@ def __init__(self, account, basepath, client, server_version) -> None: ) self.schema = User - def get(self) -> User: + def get(self) -> Optional[User]: """Gets the profile of the current authenticated user's profile (as extracted from the authorization header). @@ -37,7 +37,7 @@ def update( company: Optional[str] = None, bio: Optional[str] = None, avatar: Optional[str] = None, - ) -> bool: + ) -> User: """Updates your user profile. All arguments are optional. Args: diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index 6dcd5659..5b486035 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -14,7 +14,6 @@ branch, commit, object, - other_user_resource, server, stream, subscriptions, @@ -22,6 +21,7 @@ ) from specklepy.core.api.resources.active_user_resource import ActiveUserResource from specklepy.core.api.resources.model_resource import ModelResource +from specklepy.core.api.resources.other_user_resource import OtherUserResource from specklepy.core.api.resources.project_resource import ProjectResource from specklepy.core.api.resources.version_resource import VersionResource from specklepy.logging import metrics @@ -210,7 +210,7 @@ def _init_resources(self) -> None: except Exception: pass - self.other_user = other_user_resource.Resource( + self.other_user = OtherUserResource( account=self.account, basepath=self.url, client=self.httpclient, diff --git a/src/specklepy/core/api/models.py b/src/specklepy/core/api/models.py index 5d0f7412..741bf66e 100644 --- a/src/specklepy/core/api/models.py +++ b/src/specklepy/core/api/models.py @@ -4,11 +4,11 @@ from deprecated import deprecated from pydantic import BaseModel, Field +from specklepy.core.api.new_models import * # noqa: F403 + FE1_DEPRECATION_REASON = "Stream/Branch/Commit API is now deprecated, Use the new Project/Model/Version API functions in Client}" FE1_DEPRECATION_VERSION = "2.20" -from specklepy.core.api.new_models import * - class User(BaseModel): id: str diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/new_models.py index 6e556f72..91110b37 100644 --- a/src/specklepy/core/api/new_models.py +++ b/src/specklepy/core/api/new_models.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import List, Optional +from typing import Generic, List, Optional, TypeVar from pydantic import BaseModel @@ -8,7 +8,14 @@ ProjectVisibility, ResourceType, ) -from specklepy.core.api.responses import ResourceCollection + +T = TypeVar("T") + + +class ResourceCollection(BaseModel, Generic[T]): + totalCount: int + items: List[T] + cursor: Optional[str] = None class ServerMigration(BaseModel): @@ -197,3 +204,12 @@ class ProjectWithModels(Project): class ProjectWithTeam(Project): invitedTeam: List[PendingStreamCollaborator] team: List[ProjectCollaborator] + + +class ProjectCommentCollection(ResourceCollection[T], Generic[T]): + totalArchivedCount: int + + +class UserSearchResultCollection(BaseModel): + items: List[LimitedUser] + cursor: Optional[str] = None diff --git a/src/specklepy/core/api/resources/active_user_resource.py b/src/specklepy/core/api/resources/active_user_resource.py index cc7e47ab..ca47c71a 100644 --- a/src/specklepy/core/api/resources/active_user_resource.py +++ b/src/specklepy/core/api/resources/active_user_resource.py @@ -11,11 +11,12 @@ FE1_DEPRECATION_VERSION, ActivityCollection, PendingStreamCollaborator, + ResourceCollection, User, ) from specklepy.core.api.new_models import Project from specklepy.core.api.resource import ResourceBase -from specklepy.core.api.responses import DataResponse, ResourceCollection +from specklepy.core.api.responses import DataResponse from specklepy.logging.exceptions import GraphQLException NAME = "active_user" @@ -97,10 +98,12 @@ def update( company: Optional[str] = None, bio: Optional[str] = None, avatar: Optional[str] = None, - ) -> User: ... + ) -> User: + ... @overload - def update(self, *, input: UserUpdateInput) -> User: ... + def update(self, *, input: UserUpdateInput) -> User: + ... def update( self, diff --git a/src/specklepy/core/api/resources/model_resource.py b/src/specklepy/core/api/resources/model_resource.py index c85cecb4..87c2f608 100644 --- a/src/specklepy/core/api/resources/model_resource.py +++ b/src/specklepy/core/api/resources/model_resource.py @@ -9,9 +9,9 @@ UpdateModelInput, ) from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter -from specklepy.core.api.new_models import Model, ModelWithVersions +from specklepy.core.api.new_models import Model, ModelWithVersions, ResourceCollection from specklepy.core.api.resource import ResourceBase -from specklepy.core.api.responses import DataResponse, ResourceCollection +from specklepy.core.api.responses import DataResponse NAME = "model" diff --git a/src/specklepy/core/api/resources/other_user_resource.py b/src/specklepy/core/api/resources/other_user_resource.py index 35888dd0..d82513b7 100644 --- a/src/specklepy/core/api/resources/other_user_resource.py +++ b/src/specklepy/core/api/resources/other_user_resource.py @@ -10,8 +10,9 @@ ActivityCollection, LimitedUser, ) +from specklepy.core.api.new_models import UserSearchResultCollection from specklepy.core.api.resource import ResourceBase -from specklepy.core.api.responses import DataResponse, ResourceCollection +from specklepy.core.api.responses import DataResponse from specklepy.logging.exceptions import SpeckleException NAME = "other_user" @@ -70,7 +71,7 @@ def user_search( cursor: Optional[str] = None, archived: bool = False, emailOnly: bool = False, - ) -> ResourceCollection[LimitedUser]: + ) -> UserSearchResultCollection: """Searches for a user on the server, by name or email. The search query must be at least 3 characters long @@ -111,7 +112,7 @@ def user_search( } return self.make_request_and_parse_response( - DataResponse[ResourceCollection[LimitedUser]], QUERY, variables + DataResponse[UserSearchResultCollection], QUERY, variables ).data @deprecated(reason="Use user_search instead", version=FE1_DEPRECATION_VERSION) diff --git a/src/specklepy/core/api/resources/version_resource.py b/src/specklepy/core/api/resources/version_resource.py index 2c7fdeb1..de9405a3 100644 --- a/src/specklepy/core/api/resources/version_resource.py +++ b/src/specklepy/core/api/resources/version_resource.py @@ -10,9 +10,9 @@ MoveVersionsInput, UpdateVersionInput, ) -from specklepy.core.api.new_models import Version +from specklepy.core.api.new_models import ResourceCollection, Version from specklepy.core.api.resource import ResourceBase -from specklepy.core.api.responses import DataResponse, ResourceCollection +from specklepy.core.api.responses import DataResponse NAME = "model" diff --git a/src/specklepy/core/api/responses.py b/src/specklepy/core/api/responses.py index d99df1db..24646705 100644 --- a/src/specklepy/core/api/responses.py +++ b/src/specklepy/core/api/responses.py @@ -1,26 +1,9 @@ -from typing import Generic, List, Optional, TypeVar +from typing import Generic, TypeVar from pydantic import BaseModel -from specklepy.core.api.new_models import LimitedUser - T = TypeVar("T") -class ResourceCollection(BaseModel, Generic[T]): - totalCount: int - items: List[T] - cursor: Optional[str] = None - - -class ProjectCommentCollection(ResourceCollection[T], Generic[T]): - totalArchivedCount: int - - -class UserSearchResultCollection(BaseModel): - items: List[LimitedUser] - cursor: Optional[str] = None - - class DataResponse(BaseModel, Generic[T]): data: T diff --git a/tests/integration/client/test_other_user.py b/tests/integration/client/test_other_user.py index 3458198f..5e79a57b 100644 --- a/tests/integration/client/test_other_user.py +++ b/tests/integration/client/test_other_user.py @@ -1,9 +1,10 @@ -from deprecated import deprecated import pytest +from deprecated import deprecated from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, LimitedUser + @deprecated() @pytest.mark.run(order=4) class TestOtherUser: From 2acf4c41c7154338db8e2c3a1ce97006c6d77cd5 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:24:46 +0000 Subject: [PATCH 12/26] fixed tests --- .../core/api/resources/other_user_resource.py | 13 +++++++------ tests/integration/client/test_active_user.py | 1 - .../integration/client/test_active_user_resource.py | 2 +- tests/integration/client/test_branch.py | 1 - tests/integration/client/test_commit.py | 1 - tests/integration/client/test_model_resource.py | 3 +-- tests/integration/client/test_objects.py | 1 - tests/integration/client/test_other_user.py | 5 ++--- tests/integration/client/test_stream.py | 1 - tests/integration/client/test_user.py | 1 - 10 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/specklepy/core/api/resources/other_user_resource.py b/src/specklepy/core/api/resources/other_user_resource.py index d82513b7..90ab1751 100644 --- a/src/specklepy/core/api/resources/other_user_resource.py +++ b/src/specklepy/core/api/resources/other_user_resource.py @@ -138,12 +138,13 @@ def search( query UserSearch($search_query: String!, $limit: Int!) { userSearch(query: $search_query, limit: $limit) { items { - id - name - bio - company - avatar - verified + id + name + bio + company + avatar + verified + role } } } diff --git a/tests/integration/client/test_active_user.py b/tests/integration/client/test_active_user.py index ffc3917e..3e946528 100644 --- a/tests/integration/client/test_active_user.py +++ b/tests/integration/client/test_active_user.py @@ -7,7 +7,6 @@ from specklepy.logging.exceptions import GraphQLException -@deprecated @pytest.mark.run(order=2) class TestUser: def test_user_get_self(self, client: SpeckleClient, user_dict): diff --git a/tests/integration/client/test_active_user_resource.py b/tests/integration/client/test_active_user_resource.py index dc110569..4478ad91 100644 --- a/tests/integration/client/test_active_user_resource.py +++ b/tests/integration/client/test_active_user_resource.py @@ -4,7 +4,7 @@ from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.inputs.user_inputs import UserUpdateInput from specklepy.core.api.models import User -from specklepy.core.api.responses import ResourceCollection +from specklepy.core.api.new_models import ResourceCollection @pytest.mark.run() diff --git a/tests/integration/client/test_branch.py b/tests/integration/client/test_branch.py index cd6d388d..075ae9f7 100644 --- a/tests/integration/client/test_branch.py +++ b/tests/integration/client/test_branch.py @@ -6,7 +6,6 @@ from specklepy.transports.server import ServerTransport -@deprecated class TestBranch: @pytest.fixture(scope="module") def branch(self): diff --git a/tests/integration/client/test_commit.py b/tests/integration/client/test_commit.py index 94a97386..4a664d40 100644 --- a/tests/integration/client/test_commit.py +++ b/tests/integration/client/test_commit.py @@ -6,7 +6,6 @@ from specklepy.transports.server.server import ServerTransport -@deprecated @pytest.mark.run(order=6) class TestCommit: @pytest.fixture(scope="module") diff --git a/tests/integration/client/test_model_resource.py b/tests/integration/client/test_model_resource.py index 72b64ce3..6295a96c 100644 --- a/tests/integration/client/test_model_resource.py +++ b/tests/integration/client/test_model_resource.py @@ -8,8 +8,7 @@ ) from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.models import Model, Project -from specklepy.core.api.new_models import ProjectWithModels -from specklepy.core.api.responses import ResourceCollection +from specklepy.core.api.new_models import ProjectWithModels, ResourceCollection from specklepy.logging.exceptions import GraphQLException diff --git a/tests/integration/client/test_objects.py b/tests/integration/client/test_objects.py index 2631a538..7c07b149 100644 --- a/tests/integration/client/test_objects.py +++ b/tests/integration/client/test_objects.py @@ -8,7 +8,6 @@ from specklepy.transports.sqlite import SQLiteTransport -@deprecated class TestObject: @pytest.fixture(scope="module") def stream(self, client): diff --git a/tests/integration/client/test_other_user.py b/tests/integration/client/test_other_user.py index 5e79a57b..9394e197 100644 --- a/tests/integration/client/test_other_user.py +++ b/tests/integration/client/test_other_user.py @@ -5,7 +5,6 @@ from specklepy.api.models import Activity, ActivityCollection, LimitedUser -@deprecated() @pytest.mark.run(order=4) class TestOtherUser: def test_user_get_self(self, client): @@ -15,7 +14,7 @@ def test_user_get_self(self, client): with pytest.raises(TypeError): client.other_user.get() - def test_user_search(self, client, second_user_dict): + def test_user_search(self, client: SpeckleClient, second_user_dict): search_results = client.other_user.search( search_query=second_user_dict["name"][:5] ) @@ -29,7 +28,7 @@ def test_user_search(self, client, second_user_dict): second_user_dict["id"] = result_user.id assert getattr(result_user, "email", None) is None - def test_user_get(self, client, second_user_dict): + def test_user_get(self, client: SpeckleClient, second_user_dict): fetched_user = client.other_user.get(id=second_user_dict["id"]) assert isinstance(fetched_user, LimitedUser) diff --git a/tests/integration/client/test_stream.py b/tests/integration/client/test_stream.py index b894bf99..87f5b729 100644 --- a/tests/integration/client/test_stream.py +++ b/tests/integration/client/test_stream.py @@ -12,7 +12,6 @@ from specklepy.logging.exceptions import GraphQLException, SpeckleException -@deprecated @pytest.mark.run(order=3) class TestStream: @pytest.fixture(scope="session") diff --git a/tests/integration/client/test_user.py b/tests/integration/client/test_user.py index 0eb141c8..a87ae6de 100644 --- a/tests/integration/client/test_user.py +++ b/tests/integration/client/test_user.py @@ -6,7 +6,6 @@ from specklepy.logging.exceptions import SpeckleException -@deprecated @pytest.mark.run(order=1) class TestUser: def test_user_get_self(self, client: SpeckleClient, user_dict): From 3bd849c815766d107c50d03bc3e3567b8e4227d6 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:28:30 +0000 Subject: [PATCH 13/26] imports --- tests/integration/client/test_active_user.py | 1 - tests/integration/client/test_branch.py | 1 - tests/integration/client/test_commit.py | 1 - tests/integration/client/test_objects.py | 1 - tests/integration/client/test_other_user.py | 1 - tests/integration/client/test_stream.py | 1 - tests/integration/client/test_user.py | 1 - .../client/test_version_resource.py | 3 +-- tests/integration/conftest.py | 21 ++++++++++--------- 9 files changed, 12 insertions(+), 19 deletions(-) diff --git a/tests/integration/client/test_active_user.py b/tests/integration/client/test_active_user.py index 3e946528..7f35139c 100644 --- a/tests/integration/client/test_active_user.py +++ b/tests/integration/client/test_active_user.py @@ -1,5 +1,4 @@ import pytest -from deprecated import deprecated from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, User diff --git a/tests/integration/client/test_branch.py b/tests/integration/client/test_branch.py index 075ae9f7..27bf8ba4 100644 --- a/tests/integration/client/test_branch.py +++ b/tests/integration/client/test_branch.py @@ -1,5 +1,4 @@ import pytest -from deprecated import deprecated from specklepy.api import operations from specklepy.api.models import Branch, Commit, Stream diff --git a/tests/integration/client/test_commit.py b/tests/integration/client/test_commit.py index 4a664d40..9ce3c815 100644 --- a/tests/integration/client/test_commit.py +++ b/tests/integration/client/test_commit.py @@ -1,5 +1,4 @@ import pytest -from deprecated import deprecated from specklepy.api import operations from specklepy.api.models import Commit, Stream diff --git a/tests/integration/client/test_objects.py b/tests/integration/client/test_objects.py index 7c07b149..f921ee66 100644 --- a/tests/integration/client/test_objects.py +++ b/tests/integration/client/test_objects.py @@ -1,5 +1,4 @@ import pytest -from deprecated import deprecated from specklepy.api.models import Stream from specklepy.objects import Base diff --git a/tests/integration/client/test_other_user.py b/tests/integration/client/test_other_user.py index 9394e197..9061b11f 100644 --- a/tests/integration/client/test_other_user.py +++ b/tests/integration/client/test_other_user.py @@ -1,5 +1,4 @@ import pytest -from deprecated import deprecated from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, LimitedUser diff --git a/tests/integration/client/test_stream.py b/tests/integration/client/test_stream.py index 87f5b729..84ce5f7e 100644 --- a/tests/integration/client/test_stream.py +++ b/tests/integration/client/test_stream.py @@ -1,5 +1,4 @@ import pytest -from deprecated import deprecated from specklepy.api.client import SpeckleClient from specklepy.api.models import ( diff --git a/tests/integration/client/test_user.py b/tests/integration/client/test_user.py index a87ae6de..58859852 100644 --- a/tests/integration/client/test_user.py +++ b/tests/integration/client/test_user.py @@ -1,5 +1,4 @@ import pytest -from deprecated import deprecated from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, User diff --git a/tests/integration/client/test_version_resource.py b/tests/integration/client/test_version_resource.py index ce7fdbd1..e0ec7b52 100644 --- a/tests/integration/client/test_version_resource.py +++ b/tests/integration/client/test_version_resource.py @@ -12,8 +12,7 @@ UpdateVersionInput, ) from specklepy.core.api.models import Model, Project, Version -from specklepy.core.api.new_models import ModelWithVersions -from specklepy.core.api.responses import ResourceCollection +from specklepy.core.api.new_models import ModelWithVersions, ResourceCollection from specklepy.logging.exceptions import GraphQLException from specklepy.objects.base import Base from specklepy.transports.server.server import ServerTransport diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index b61d8e21..a9d2f922 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,12 +1,13 @@ import random import uuid +from typing import Dict from urllib.parse import parse_qs, urlparse import pytest import requests -from specklepy.api.client import SpeckleClient -from specklepy.api.models import Stream +from specklepy.core.api.client import SpeckleClient +from specklepy.core.api.models import Stream from specklepy.logging import metrics from specklepy.objects.base import Base from specklepy.objects.fakemesh import FakeDirection, FakeMesh @@ -16,11 +17,11 @@ @pytest.fixture(scope="session") -def host(): +def host() -> str: return "localhost:3000" -def seed_user(host): +def seed_user(host: str) -> Dict[str, str]: seed = uuid.uuid4().hex user_dict = { "email": f"{seed[0:7]}@example.org", @@ -58,17 +59,17 @@ def seed_user(host): @pytest.fixture(scope="session") -def user_dict(host): +def user_dict(host: str) -> Dict[str, str]: return seed_user(host) @pytest.fixture(scope="session") -def second_user_dict(host): +def second_user_dict(host: str) -> Dict[str, str]: return seed_user(host) @pytest.fixture(scope="session") -def client(host, user_dict): +def client(host: str, user_dict: Dict[str, str]) -> SpeckleClient: client = SpeckleClient(host=host, use_ssl=False) client.authenticate_with_token(user_dict["token"]) user = client.active_user.get() @@ -82,7 +83,7 @@ def client(host, user_dict): @pytest.fixture(scope="session") -def second_client(host, second_user_dict): +def second_client(host: str, second_user_dict: Dict[str, str]): client = SpeckleClient(host=host, use_ssl=False) client.authenticate_with_token(second_user_dict["token"]) user = client.active_user.get() @@ -96,7 +97,7 @@ def second_client(host, second_user_dict): @pytest.fixture(scope="session") -def sample_stream(client): +def sample_stream(client: SpeckleClient) -> Stream: stream = Stream( name="a sample stream for testing", description="a stream created for testing", @@ -107,7 +108,7 @@ def sample_stream(client): @pytest.fixture(scope="session") -def mesh(): +def mesh() -> FakeMesh: mesh = FakeMesh() mesh.name = "my_mesh" mesh.vertices = [random.uniform(0, 10) for _ in range(1, 210)] From 3642731f37bb6df6139ead0e525403923f64f49c Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:43:40 +0000 Subject: [PATCH 14/26] Wrapping up tests --- poetry.lock | 26 +- pyproject.toml | 1 + src/specklepy/api/client.py | 36 +- .../api/resources/active_user_resource.py | 138 ++++++++ src/specklepy/api/resources/model.py | 0 src/specklepy/api/resources/model_resource.py | 65 ++++ .../api/resources/other_user_resource.py | 98 ++++++ src/specklepy/api/resources/project.py | 16 - .../api/resources/project_invite_resource.py | 54 +++ .../api/resources/project_resource.py | 56 +++ .../api/resources/subscription_resource.py | 128 +++++++ src/specklepy/api/resources/version.py | 0 .../api/resources/version_resource.py | 63 ++++ src/specklepy/core/api/client.py | 29 +- src/specklepy/core/api/enums.py | 28 ++ src/specklepy/core/api/new_models.py | 36 ++ src/specklepy/core/api/resources/branch.py | 11 +- src/specklepy/core/api/resources/commit.py | 12 +- src/specklepy/core/api/resources/stream.py | 21 +- .../api/resources/subscription_resource.py | 329 ++++++++++++++++++ .../core/api/resources/subscriptions.py | 143 +------- src/specklepy/logging/metrics.py | 3 + .../client/test_active_user_resource.py | 2 +- .../client/test_subscription_resource.py | 167 +++++++++ .../client/test_version_resource.py | 15 +- tests/integration/conftest.py | 16 + 26 files changed, 1283 insertions(+), 210 deletions(-) create mode 100644 src/specklepy/api/resources/active_user_resource.py delete mode 100644 src/specklepy/api/resources/model.py create mode 100644 src/specklepy/api/resources/model_resource.py create mode 100644 src/specklepy/api/resources/other_user_resource.py delete mode 100644 src/specklepy/api/resources/project.py create mode 100644 src/specklepy/api/resources/project_invite_resource.py create mode 100644 src/specklepy/api/resources/project_resource.py create mode 100644 src/specklepy/api/resources/subscription_resource.py delete mode 100644 src/specklepy/api/resources/version.py create mode 100644 src/specklepy/api/resources/version_resource.py create mode 100644 src/specklepy/core/api/resources/subscription_resource.py create mode 100644 tests/integration/client/test_subscription_resource.py diff --git a/poetry.lock b/poetry.lock index fe771809..a42eb48a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1309,6 +1309,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.23.8" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "pytest-cov" version = "3.0.0" @@ -1717,13 +1735,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.27.0" +version = "20.27.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, - {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, + {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"}, + {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, ] [package.dependencies] @@ -2019,4 +2037,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = ">=3.8.0, <4.0" -content-hash = "3c1df591124eff9dab25ffc2f5678ded27aed3e68e58bf3c66ceb0edafe35e4b" +content-hash = "4c914ee1a14f24b46dfdd0718e340a021c50f88a4628cec91f62cafddb2240d5" diff --git a/pyproject.toml b/pyproject.toml index 10b4ff59..1eea152f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ httpx = "^0.25.0" black = "23.11.0" isort = "^5.7.0" pytest = "^7.1.3" +pytest-asyncio = "^0.23.0" pytest-ordering = "^0.6" pytest-cov = "^3.0.0" devtools = "^0.8.0" diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index f2cfe3cc..7167f42b 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -1,24 +1,17 @@ from deprecated import deprecated from specklepy.api.credentials import Account -from specklepy.api.resources import ( - branch, - commit, - object, - server, - stream, - subscriptions, - user, -) -from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient +from specklepy.api.resources import branch, commit, object, server, stream, user # TODO: re-reference core.api resources -from specklepy.core.api.resources.active_user_resource import ActiveUserResource -from specklepy.core.api.resources.model_resource import ModelResource -from specklepy.core.api.resources.other_user_resource import OtherUserResource -from specklepy.core.api.resources.project_invite_resource import ProjectInviteResource -from specklepy.core.api.resources.project_resource import ProjectResource -from specklepy.core.api.resources.version_resource import VersionResource +from specklepy.api.resources.active_user_resource import ActiveUserResource +from specklepy.api.resources.model_resource import ModelResource +from specklepy.api.resources.other_user_resource import OtherUserResource +from specklepy.api.resources.project_invite_resource import ProjectInviteResource +from specklepy.api.resources.project_resource import ProjectResource +from specklepy.api.resources.subscription_resource import SubscriptionResource +from specklepy.api.resources.version_resource import VersionResource +from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient from specklepy.logging import metrics @@ -116,6 +109,11 @@ def _init_resources(self) -> None: client=self.httpclient, server_version=server_version, ) + self.subscription = SubscriptionResource( + account=self.account, + basepath=self.ws_url, + client=self.wsclient, + ) # Deprecated Resources self.user = user.Resource( account=self.account, @@ -138,11 +136,7 @@ def _init_resources(self) -> None: 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, - ) + self.subscribe = self.subscription @deprecated( version="2.6.0", diff --git a/src/specklepy/api/resources/active_user_resource.py b/src/specklepy/api/resources/active_user_resource.py new file mode 100644 index 00000000..55241753 --- /dev/null +++ b/src/specklepy/api/resources/active_user_resource.py @@ -0,0 +1,138 @@ +from datetime import datetime +from typing import List, Optional, overload + +from deprecated import deprecated + +from specklepy.core.api.inputs.project_inputs import UserProjectsFilter +from specklepy.core.api.inputs.user_inputs import UserUpdateInput +from specklepy.core.api.models import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, + PendingStreamCollaborator, + ResourceCollection, + User, +) +from specklepy.core.api.new_models import Project +from specklepy.core.api.resources.active_user_resource import ( + ActiveUserResource as CoreResource, +) +from specklepy.logging import metrics + + +class ActiveUserResource(CoreResource): + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + server_version=server_version, + ) + self.schema = User + + def get(self) -> Optional[User]: + return super().get() + + @deprecated("Use UserUpdateInput overload", version=FE1_DEPRECATION_VERSION) + @overload + def update( + self, + name: Optional[str] = None, + company: Optional[str] = None, + bio: Optional[str] = None, + avatar: Optional[str] = None, + ) -> User: + ... + + @overload + def update(self, *, input: UserUpdateInput) -> User: + ... + + def update( + self, + name: Optional[str] = None, + company: Optional[str] = None, + bio: Optional[str] = None, + avatar: Optional[str] = None, + *, + input: Optional[UserUpdateInput] = None, + ) -> User: + if isinstance(input, UserUpdateInput): + return super()._update(input=input) + else: + return super()._update( + input=UserUpdateInput( + name=name, + company=company, + bio=bio, + avatar=avatar, + ) + ) + + def get_projects( + self, + *, + limit: int = 25, + cursor: Optional[str] = None, + filter: Optional[UserProjectsFilter] = None, + ) -> ResourceCollection[Project]: + return super().get_projects(limit=limit, cursor=cursor, filter=filter) + + def get_project_invites(self) -> List[PendingStreamCollaborator]: + return super().get_project_invites() + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + def activity( + self, + limit: int = 20, + action_type: Optional[str] = None, + before: Optional[datetime] = None, + after: Optional[datetime] = None, + cursor: Optional[datetime] = None, + ): + """ + Fetches collection the current authenticated user's activity + as filtered by given parameters + + Note: all timestamps arguments should be `datetime` of any tz as they will be + converted to UTC ISO format strings + + Args: + limit (int): The maximum number of activity items to return. + action_type (Optional[str]): Filter results to a single action type. + before (Optional[datetime]): Latest cutoff for activity to include. + after (Optional[datetime]): Oldest cutoff for an activity to include. + cursor (Optional[datetime]): Timestamp cursor for pagination. + + Returns: + Activity collection, filtered according to the provided parameters. + """ + metrics.track(metrics.SDK, self.account, {"name": "User Active Activity"}) + return super().activity(limit, action_type, before, after, cursor) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + def get_all_pending_invites(self) -> List[PendingStreamCollaborator]: + """Fetches all of the current user's pending stream invitations. + + Returns: + List[PendingStreamCollaborator]: A list of pending stream invitations. + """ + metrics.track( + metrics.SDK, self.account, {"name": "User Active Invites All Get"} + ) + return super().get_all_pending_invites() + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + def get_pending_invite( + self, stream_id: str, token: Optional[str] = None + ) -> Optional[PendingStreamCollaborator]: + """Fetches a specific pending invite for the current user on a given stream. + + Args: + stream_id (str): The ID of the stream to look for invites on. + token (Optional[str]): The token of the invite to look for (optional). + + Returns: + Optional[PendingStreamCollaborator]: The invite for the given stream, or None if not found. + """ + metrics.track(metrics.SDK, self.account, {"name": "User Active Invite Get"}) + return super().get_pending_invite(stream_id, token) diff --git a/src/specklepy/api/resources/model.py b/src/specklepy/api/resources/model.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/specklepy/api/resources/model_resource.py b/src/specklepy/api/resources/model_resource.py new file mode 100644 index 00000000..cc3df5d2 --- /dev/null +++ b/src/specklepy/api/resources/model_resource.py @@ -0,0 +1,65 @@ +from typing import Optional + +from specklepy.core.api.inputs.model_inputs import ( + CreateModelInput, + DeleteModelInput, + ModelVersionsFilter, + UpdateModelInput, +) +from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter +from specklepy.core.api.new_models import Model, ModelWithVersions, ResourceCollection +from specklepy.core.api.resources.model_resource import ModelResource as CoreResource + + +class ModelResource(CoreResource): + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + server_version=server_version, + ) + + def get(self, model_id: str, project_id: str) -> Model: + return super().get(model_id, project_id) + + def get_with_versions( + self, + model_id: str, + project_id: str, + *, + versions_limit: int = 25, + versions_cursor: Optional[str] = None, + versions_filter: Optional[ModelVersionsFilter] = None, + ) -> ModelWithVersions: + return super().get_with_versions( + model_id, + project_id, + versions_limit=versions_limit, + versions_cursor=versions_cursor, + versions_filter=versions_filter, + ) + + def get_models( + self, + project_id: str, + *, + models_limit: int = 25, + models_cursor: Optional[str] = None, + models_filter: Optional[ProjectModelsFilter] = None, + ) -> ResourceCollection[Model]: + return super().get_models( + project_id, + models_limit=models_limit, + models_cursor=models_cursor, + models_filter=models_filter, + ) + + def create(self, input: CreateModelInput) -> Model: + return super().create(input) + + def delete(self, input: DeleteModelInput) -> bool: + return super().delete(input) + + def update(self, input: UpdateModelInput) -> Model: + return super().update(input) diff --git a/src/specklepy/api/resources/other_user_resource.py b/src/specklepy/api/resources/other_user_resource.py new file mode 100644 index 00000000..28ab4c34 --- /dev/null +++ b/src/specklepy/api/resources/other_user_resource.py @@ -0,0 +1,98 @@ +from datetime import datetime +from typing import List, Optional, Union + +from deprecated import deprecated + +from specklepy.core.api.models import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, + ActivityCollection, + LimitedUser, +) +from specklepy.core.api.new_models import UserSearchResultCollection +from specklepy.core.api.resources.other_user_resource import ( + OtherUserResource as CoreResource, +) +from specklepy.logging import metrics +from specklepy.logging.exceptions import SpeckleException + + +class OtherUserResource(CoreResource): + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + server_version=(server_version,), + ) + self.schema = LimitedUser + + def get(self, id: str) -> Optional[LimitedUser]: + metrics.track(metrics.SDK, self.account, {"name": "Other User Get"}) + return super().get(id) + + def user_search( + self, + query: str, + *, + limit: int = 25, + cursor: Optional[str] = None, + archived: bool = False, + emailOnly: bool = False, + ) -> UserSearchResultCollection: + metrics.track(metrics.SDK, self.account, {"name": "Other User Search"}) + return super().user_search( + query, limit=limit, cursor=cursor, archived=archived, emailOnly=emailOnly + ) + + @deprecated(reason="Use user_search instead", version=FE1_DEPRECATION_VERSION) + def search( + self, search_query: str, limit: int = 25 + ) -> Union[List[LimitedUser], SpeckleException]: + """ + Searches for users by name or email. + The search requires a minimum query length of 3 characters. + + Args: + search_query (str): The search string. + limit (int): Maximum number of search results to return. + + Returns: + Union[List[LimitedUser], SpeckleException]: A list of users matching the search + query or an exception if the query is too short. + """ + if len(search_query) < 3: + return SpeckleException( + message="User search query must be at least 3 characters." + ) + + metrics.track(metrics.SDK, self.account, {"name": "Other User Search"}) + return super().search(search_query, limit) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + def activity( + self, + user_id: str, + limit: int = 20, + action_type: Optional[str] = None, + before: Optional[datetime] = None, + after: Optional[datetime] = None, + cursor: Optional[datetime] = None, + ) -> ActivityCollection: + """ + Retrieves a collection of activities for a specified user, with optional filters for activity type, + time frame, and pagination. + + Args: + user_id (str): The ID of the user whose activities are being requested. + limit (int): The maximum number of activity items to return. + action_type (Optional[str]): A specific type of activity to filter. + before (Optional[datetime]): Latest timestamp to include activities before. + after (Optional[datetime]): Earliest timestamp to include activities after. + cursor (Optional[datetime]): Timestamp for pagination cursor. + + Returns: + ActivityCollection: A collection of user activities filtered according to specified criteria. + """ + metrics.track(metrics.SDK, self.account, {"name": "Other User Activity"}) + return super().activity(user_id, limit, action_type, before, after, cursor) diff --git a/src/specklepy/api/resources/project.py b/src/specklepy/api/resources/project.py deleted file mode 100644 index 3bfba0a4..00000000 --- a/src/specklepy/api/resources/project.py +++ /dev/null @@ -1,16 +0,0 @@ -from specklepy.api.models import Stream -from specklepy.core.api.resources.stream import Resource as CoreResource - - -class Resource(CoreResource): - """API Access class for projects""" - - def __init__(self, account, basepath, client, server_version) -> None: - super().__init__( - account=account, - basepath=basepath, - client=client, - server_version=server_version, - ) - - self.schema = Stream diff --git a/src/specklepy/api/resources/project_invite_resource.py b/src/specklepy/api/resources/project_invite_resource.py new file mode 100644 index 00000000..1a1cccac --- /dev/null +++ b/src/specklepy/api/resources/project_invite_resource.py @@ -0,0 +1,54 @@ +from typing import Any, Optional, Tuple + +from gql import Client + +from specklepy.core.api.credentials import Account +from specklepy.core.api.inputs.project_inputs import ( + ProjectInviteCreateInput, + ProjectInviteUseInput, +) +from specklepy.core.api.new_models import PendingStreamCollaborator, ProjectWithTeam +from specklepy.core.api.resources.project_invite_resource import ( + ProjectInviteResource as CoreResource, +) +from specklepy.logging import metrics + + +class ProjectInviteResource(CoreResource): + def __init__( + self, + account: Account, + basepath: str, + client: Client, + server_version: Optional[Tuple[Any, ...]], + ) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + server_version=server_version, + ) + + def create( + self, project_id: str, input: ProjectInviteCreateInput + ) -> ProjectWithTeam: + metrics.track(metrics.SDK, self.account, {"name": "Project Invite Create"}) + return super().create(project_id, input) + + def use(self, input: ProjectInviteUseInput) -> bool: + metrics.track(metrics.SDK, self.account, {"name": "Project Invite Use"}) + return super().use(input) + + def get( + self, project_id: str, token: Optional[str] + ) -> Optional[PendingStreamCollaborator]: + metrics.track(metrics.SDK, self.account, {"name": "Project Invite Get"}) + return super().get(project_id, token) + + def cancel( + self, + project_id: str, + invite_id: str, + ) -> ProjectWithTeam: + metrics.track(metrics.SDK, self.account, {"name": "Project Invite Cancel"}) + return super().cancel(project_id, invite_id) diff --git a/src/specklepy/api/resources/project_resource.py b/src/specklepy/api/resources/project_resource.py new file mode 100644 index 00000000..ad40fec1 --- /dev/null +++ b/src/specklepy/api/resources/project_resource.py @@ -0,0 +1,56 @@ +from typing import Optional + +from specklepy.core.api.inputs.project_inputs import ( + ProjectCreateInput, + ProjectModelsFilter, + ProjectUpdateInput, + ProjectUpdateRoleInput, +) +from specklepy.core.api.models import Project +from specklepy.core.api.new_models import ProjectWithModels, ProjectWithTeam +from specklepy.core.api.resources.project_resource import ( + ProjectResource as CoreResource, +) + + +class ProjectResource(CoreResource): + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + server_version=server_version, + ) + + def get(self, project_id: str) -> Project: + return super().get(project_id) + + def get_with_models( + self, + project_id: str, + *, + models_limit: int = 25, + models_cursor: Optional[str] = None, + models_filter: Optional[ProjectModelsFilter] = None, + ) -> ProjectWithModels: + return super().get_with_models( + project_id, + models_limit=models_limit, + models_cursor=models_cursor, + models_filter=models_filter, + ) + + def get_with_team(self, project_id: str) -> ProjectWithTeam: + return super().get_with_team(project_id) + + def create(self, input: ProjectCreateInput) -> Project: + return super().create(input) + + def update(self, input: ProjectUpdateInput) -> Project: + return super().update(input) + + def delete(self, project_id: str) -> bool: + return super().delete(project_id) + + def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam: + return super().update_role(input) diff --git a/src/specklepy/api/resources/subscription_resource.py b/src/specklepy/api/resources/subscription_resource.py new file mode 100644 index 00000000..a0641aca --- /dev/null +++ b/src/specklepy/api/resources/subscription_resource.py @@ -0,0 +1,128 @@ +from typing import Callable, Optional, Sequence + +from deprecated import deprecated +from pydantic import BaseModel +from typing_extensions import TypeVar + +from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION +from specklepy.core.api.new_models import ( + ProjectModelsUpdatedMessage, + ProjectUpdatedMessage, + ProjectVersionsUpdatedMessage, + UserProjectsUpdatedMessage, +) +from specklepy.core.api.resources.subscription_resource import ( + SubscriptionResource as CoreResource, +) +from specklepy.core.api.resources.subscription_resource import check_wsclient +from specklepy.logging import metrics + +TEventArgs = TypeVar("TEventArgs", bound=BaseModel) + + +class SubscriptionResource(CoreResource): + def __init__(self, account, basepath, client) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + ) + + async def user_projects_updated( + self, callback: Callable[[UserProjectsUpdatedMessage], None] + ) -> None: + metrics.track( + metrics.SDK, self.account, {"name": "Subscription Project Models Updated"} + ) + return await super().user_projects_updated(callback) + + async def project_models_updated( + self, + callback: Callable[[ProjectModelsUpdatedMessage], None], + id: str, + *, + model_ids: Optional[Sequence[str]] = None, + ) -> None: + metrics.track( + metrics.SDK, self.account, {"name": "Subscription Project Models Updated"} + ) + return await super().project_models_updated(callback, id, model_ids=model_ids) + + async def project_updated( + self, + callback: Callable[[ProjectUpdatedMessage], None], + id: str, + ) -> None: + metrics.track( + metrics.SDK, self.account, {"name": "Subscription Project Updated"} + ) + return await super().project_updated(callback, id) + + async def project_versions_updated( + self, + callback: Callable[[ProjectVersionsUpdatedMessage], None], + id: str, + ) -> None: + metrics.track( + metrics.SDK, self.account, {"name": "Subscription Project Versions Updated"} + ) + return await super().project_versions_updated(callback, id) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + @check_wsclient + async def stream_added(self, callback: Optional[Callable] = None): + """Subscribes to new stream added event for your profile. + Use this to display an up-to-date list of streams. + + Arguments: + callback {Callable[Stream]} -- a function that takes the updated stream + as an argument and executes each time a stream is added + + Returns: + Stream -- the update stream + """ + metrics.track(metrics.SDK, self.account, {"name": "Subscription Stream Added"}) + return super().stream_added(callback) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + @check_wsclient + async def stream_updated(self, id: str, callback: Optional[Callable] = None): + """ + Subscribes to stream updated event. + Use this in clients/components that pertain only to this stream. + + Arguments: + id {str} -- the stream id of the stream to subscribe to + callback {Callable[Stream]} + -- a function that takes the updated stream + as an argument and executes each time the stream is updated + + Returns: + Stream -- the update stream + """ + metrics.track( + metrics.SDK, self.account, {"name": "Subscription Stream Updated"} + ) + return super().stream_updated(id, callback) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + @check_wsclient + async def stream_removed(self, callback: Optional[Callable] = None): + """Subscribes to stream removed event for your profile. + Use this to display an up-to-date list of streams for your profile. + NOTE: If someone revokes your permissions on a stream, + this subscription will be triggered with an extra value of revokedBy + in the payload. + + Arguments: + callback {Callable[Dict]} + -- a function that takes the returned dict as an argument + and executes each time a stream is removed + + Returns: + dict -- dict containing 'id' of stream removed and optionally 'revokedBy' + """ + metrics.track( + metrics.SDK, self.account, {"name": "Subscription Stream Removed"} + ) + return super().stream_removed(callback) diff --git a/src/specklepy/api/resources/version.py b/src/specklepy/api/resources/version.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/specklepy/api/resources/version_resource.py b/src/specklepy/api/resources/version_resource.py new file mode 100644 index 00000000..52e8120d --- /dev/null +++ b/src/specklepy/api/resources/version_resource.py @@ -0,0 +1,63 @@ +from typing import Optional + +from specklepy.core.api.inputs.model_inputs import ModelVersionsFilter +from specklepy.core.api.inputs.version_inputs import ( + CreateVersionInput, + DeleteVersionsInput, + MarkReceivedVersionInput, + MoveVersionsInput, + UpdateVersionInput, +) +from specklepy.core.api.new_models import ResourceCollection, Version +from specklepy.core.api.resources.version_resource import ( + VersionResource as CoreResource, +) +from specklepy.logging import metrics + + +class VersionResource(CoreResource): + def __init__(self, account, basepath, client, server_version) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + server_version=server_version, + ) + + def get(self, version_id: str, project_id: str) -> Version: + metrics.track(metrics.SDK, self.account, {"name": "Version Get"}) + return super().get(version_id, project_id) + + def get_versions( + self, + model_id: str, + project_id: str, + *, + limit: int = 25, + cursor: Optional[str] = None, + filter: Optional[ModelVersionsFilter] = None, + ) -> ResourceCollection[Version]: + metrics.track(metrics.SDK, self.account, {"name": "Version Get Versions"}) + return super().get_versions( + model_id, project_id, limit=limit, cursor=cursor, filter=filter + ) + + def create(self, input: CreateVersionInput) -> str: + metrics.track(metrics.SDK, self.account, {"name": "Version Create"}) + return super().create(input) + + def update(self, input: UpdateVersionInput) -> Version: + metrics.track(metrics.SDK, self.account, {"name": "Version Update"}) + return super().update(input) + + def move_to_model(self, input: MoveVersionsInput) -> str: + metrics.track(metrics.SDK, self.account, {"name": "Version Move To Model"}) + return super().move_to_model(input) + + def delete(self, input: DeleteVersionsInput) -> bool: + metrics.track(metrics.SDK, self.account, {"name": "Version Delete"}) + return super().delete(input) + + def received(self, input: MarkReceivedVersionInput) -> bool: + metrics.track(metrics.SDK, self.account, {"name": "Version Received"}) + return super().received(input) diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index 5b486035..283c51af 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -8,21 +8,15 @@ from gql.transport.requests import RequestsHTTPTransport from gql.transport.websockets import WebsocketsTransport +from specklepy.api.resources.project_invite_resource import ProjectInviteResource from specklepy.core.api import resources from specklepy.core.api.credentials import Account, get_account_from_token -from specklepy.core.api.resources import ( - branch, - commit, - object, - server, - stream, - subscriptions, - user, -) +from specklepy.core.api.resources import branch, commit, object, server, stream, user from specklepy.core.api.resources.active_user_resource import ActiveUserResource from specklepy.core.api.resources.model_resource import ModelResource from specklepy.core.api.resources.other_user_resource import OtherUserResource from specklepy.core.api.resources.project_resource import ProjectResource +from specklepy.core.api.resources.subscription_resource import SubscriptionResource from specklepy.core.api.resources.version_resource import VersionResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException, SpeckleWarning @@ -228,6 +222,12 @@ def _init_resources(self) -> None: client=self.httpclient, server_version=server_version, ) + self.project_invite = ProjectInviteResource( + account=self.account, + basepath=self.url, + client=self.httpclient, + server_version=server_version, + ) self.model = ModelResource( account=self.account, basepath=self.url, @@ -240,6 +240,11 @@ def _init_resources(self) -> None: client=self.httpclient, server_version=server_version, ) + self.subscription = SubscriptionResource( + account=self.account, + basepath=self.ws_url, + client=self.wsclient, + ) # Deprecated Resources self.user = user.Resource( account=self.account, @@ -262,11 +267,7 @@ def _init_resources(self) -> None: 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, - ) + self.subscribe = self.subscription def __getattr__(self, name): try: diff --git a/src/specklepy/core/api/enums.py b/src/specklepy/core/api/enums.py index e43c8c15..46cebfb8 100644 --- a/src/specklepy/core/api/enums.py +++ b/src/specklepy/core/api/enums.py @@ -19,3 +19,31 @@ class ResourceType(str, Enum): STREAM = "STREAM" OBJECT = "OBJECT" COMMENT = "COMMENT" + + +class UserProjectsUpdatedMessageType(str, Enum): + ADDED = "ADDED" + REMOVED = "REMOVED" + + +class ProjectCommentsUpdatedMessageType(str, Enum): + ARCHIVED = "ARCHIVED" + CREATED = "CREATED" + UPDATED = "UPDATED" + + +class ProjectModelsUpdatedMessageType(str, Enum): + CREATED = "CREATED" + DELETED = "DELETED" + UPDATED = "UPDATED" + + +class ProjectUpdatedMessageType(str, Enum): + DELETED = "DELETED" + UPDATED = "UPDATED" + + +class ProjectVersionsUpdatedMessageType(str, Enum): + CREATED = "CREATED" + DELETED = "DELETED" + UPDATED = "UPDATED" diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/new_models.py index 91110b37..b9fe92b1 100644 --- a/src/specklepy/core/api/new_models.py +++ b/src/specklepy/core/api/new_models.py @@ -5,8 +5,13 @@ from specklepy.core.api.enums import ( FileUploadConversionStatus, + ProjectCommentsUpdatedMessageType, + ProjectModelsUpdatedMessageType, + ProjectUpdatedMessageType, + ProjectVersionsUpdatedMessageType, ProjectVisibility, ResourceType, + UserProjectsUpdatedMessageType, ) T = TypeVar("T") @@ -213,3 +218,34 @@ class ProjectCommentCollection(ResourceCollection[T], Generic[T]): class UserSearchResultCollection(BaseModel): items: List[LimitedUser] cursor: Optional[str] = None + + +class UserProjectsUpdatedMessage(BaseModel): + id: str + type: UserProjectsUpdatedMessageType + project: Optional[Project] + + +class ProjectCommentsUpdatedMessage(BaseModel): + id: str + type: ProjectCommentsUpdatedMessageType + comment: Optional[Comment] + + +class ProjectModelsUpdatedMessage(BaseModel): + id: str + type: ProjectModelsUpdatedMessageType + model: Optional[Model] + + +class ProjectUpdatedMessage(BaseModel): + id: str + type: ProjectUpdatedMessageType + project: Optional[Project] + + +class ProjectVersionsUpdatedMessage(BaseModel): + id: str + type: ProjectVersionsUpdatedMessageType + modelId: Optional[str] + version: Optional[Version] diff --git a/src/specklepy/core/api/resources/branch.py b/src/specklepy/core/api/resources/branch.py index 478a881f..710f674a 100644 --- a/src/specklepy/core/api/resources/branch.py +++ b/src/specklepy/core/api/resources/branch.py @@ -14,9 +14,11 @@ NAME = "branch" -@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Resource(ResourceBase): - """API Access class for branches""" + """ + API Access class for branches + Branch resource is deprecated, please use model resource instead + """ def __init__(self, account, basepath, client) -> None: super().__init__( @@ -27,6 +29,7 @@ def __init__(self, account, basepath, client) -> None: ) self.schema = Branch + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def create( self, stream_id: str, name: str, description: str = "No description provided" ) -> str: @@ -60,6 +63,7 @@ def create( query=query, params=params, return_type="branchCreate", parse_response=False ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get(self, stream_id: str, name: str, commits_limit: int = 10): """Get a branch by name from a stream @@ -107,6 +111,7 @@ def get(self, stream_id: str, name: str, commits_limit: int = 10): query=query, params=params, return_type=["stream", "branch"] ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def list(self, stream_id: str, branches_limit: int = 10, commits_limit: int = 10): """Get a list of branches from a given stream @@ -162,6 +167,7 @@ def list(self, stream_id: str, branches_limit: int = 10, commits_limit: int = 10 query=query, params=params, return_type=["stream", "branches", "items"] ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def update( self, stream_id: str, @@ -203,6 +209,7 @@ def update( query=query, params=params, return_type="branchUpdate", parse_response=False ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def delete(self, stream_id: str, branch_id: str): """Delete a branch diff --git a/src/specklepy/core/api/resources/commit.py b/src/specklepy/core/api/resources/commit.py index 3917b615..cfd58ddf 100644 --- a/src/specklepy/core/api/resources/commit.py +++ b/src/specklepy/core/api/resources/commit.py @@ -14,9 +14,11 @@ NAME = "commit" -@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Resource(ResourceBase): - """API Access class for commits""" + """ + API Access class for commits + Commit resource is deprecated, please use version resource instead + """ def __init__(self, account, basepath, client) -> None: super().__init__( @@ -27,6 +29,7 @@ def __init__(self, account, basepath, client) -> None: ) self.schema = Commit + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get(self, stream_id: str, commit_id: str) -> Commit: """ Gets a commit given a stream and the commit id @@ -65,6 +68,7 @@ def get(self, stream_id: str, commit_id: str) -> Commit: query=query, params=params, return_type=["stream", "commit"] ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def list(self, stream_id: str, limit: int = 10) -> List[Commit]: """ Get a list of commits on a given stream @@ -106,6 +110,7 @@ def list(self, stream_id: str, limit: int = 10) -> List[Commit]: query=query, params=params, return_type=["stream", "commits", "items"] ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def create( self, stream_id: str, @@ -155,6 +160,7 @@ def create( query=query, params=params, return_type="commitCreate", parse_response=False ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def update(self, stream_id: str, commit_id: str, message: str) -> bool: """ Update a commit @@ -182,6 +188,7 @@ def update(self, stream_id: str, commit_id: str, message: str) -> bool: query=query, params=params, return_type="commitUpdate", parse_response=False ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def delete(self, stream_id: str, commit_id: str) -> bool: """ Delete a commit @@ -206,6 +213,7 @@ def delete(self, stream_id: str, commit_id: str) -> bool: query=query, params=params, return_type="commitDelete", parse_response=False ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def received( self, stream_id: str, diff --git a/src/specklepy/core/api/resources/stream.py b/src/specklepy/core/api/resources/stream.py index 812b2c31..9db1368e 100644 --- a/src/specklepy/core/api/resources/stream.py +++ b/src/specklepy/core/api/resources/stream.py @@ -17,9 +17,11 @@ NAME = "stream" -@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Resource(ResourceBase): - """API Access class for streams""" + """ + API Access class for streams + Stream resource is deprecated, please use project resource instead + """ def __init__(self, account, basepath, client, server_version) -> None: super().__init__( @@ -32,6 +34,7 @@ def __init__(self, account, basepath, client, server_version) -> None: self.schema = Stream + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream: """Get the specified stream from the server @@ -93,6 +96,7 @@ def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream return self.make_request(query=query, params=params, return_type="stream") + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def list(self, stream_limit: int = 10) -> List[Stream]: """Get a list of the user's streams @@ -146,6 +150,7 @@ def list(self, stream_limit: int = 10) -> List[Stream]: query=query, params=params, return_type=["user", "streams", "items"] ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def create( self, name: str = "Anonymous Python Stream", @@ -180,6 +185,7 @@ def create( query=query, params=params, return_type="streamCreate", parse_response=False ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def update( self, id: str, @@ -220,6 +226,7 @@ def update( query=query, params=params, return_type="streamUpdate", parse_response=False ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def delete(self, id: str) -> bool: """Delete a stream given its id @@ -243,6 +250,7 @@ def delete(self, id: str) -> bool: query=query, params=params, return_type="streamDelete", parse_response=False ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def search( self, search_query: str, @@ -322,6 +330,7 @@ def search( query=query, params=params, return_type=["streams", "items"] ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def favorite(self, stream_id: str, favorited: bool = True): """Favorite or unfavorite the given stream. @@ -355,6 +364,7 @@ def favorite(self, stream_id: str, favorited: bool = True): query=query, params=params, return_type=["streamFavorite"] ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get_all_pending_invites( self, stream_id: str ) -> List[PendingStreamCollaborator]: @@ -418,6 +428,7 @@ def get_all_pending_invites( schema=PendingStreamCollaborator, ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def invite( self, stream_id: str, @@ -474,6 +485,7 @@ def invite( parse_response=False, ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def invite_batch( self, stream_id: str, @@ -533,6 +545,7 @@ def invite_batch( parse_response=False, ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def invite_cancel(self, stream_id: str, invite_id: str) -> bool: """Cancel an existing stream invite @@ -564,6 +577,7 @@ def invite_cancel(self, stream_id: str, invite_id: str) -> bool: parse_response=False, ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def invite_use(self, stream_id: str, token: str, accept: bool = True) -> bool: """Accept or decline a stream invite @@ -601,6 +615,7 @@ def invite_use(self, stream_id: str, token: str, accept: bool = True) -> bool: parse_response=False, ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def update_permission(self, stream_id: str, user_id: str, role: str): """Updates permissions for a user on a given stream @@ -647,6 +662,7 @@ def update_permission(self, stream_id: str, user_id: str, role: str): parse_response=False, ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def revoke_permission(self, stream_id: str, user_id: str): """Revoke permissions from a user on a given stream @@ -676,6 +692,7 @@ def revoke_permission(self, stream_id: str, user_id: str): parse_response=False, ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def activity( self, stream_id: str, diff --git a/src/specklepy/core/api/resources/subscription_resource.py b/src/specklepy/core/api/resources/subscription_resource.py new file mode 100644 index 00000000..611d283d --- /dev/null +++ b/src/specklepy/core/api/resources/subscription_resource.py @@ -0,0 +1,329 @@ +from functools import wraps +from typing import Any, Callable, Dict, List, Optional, Sequence, Type, Union + +from deprecated import deprecated +from gql import gql +from graphql import DocumentNode +from pydantic import BaseModel +from typing_extensions import TypeVar + +from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION +from specklepy.core.api.new_models import ( + ProjectModelsUpdatedMessage, + ProjectUpdatedMessage, + ProjectVersionsUpdatedMessage, + UserProjectsUpdatedMessage, +) +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.resources.stream import Stream +from specklepy.core.api.responses import DataResponse +from specklepy.logging.exceptions import SpeckleException + +NAME = "subscribe" + +TEventArgs = TypeVar("TEventArgs", bound=BaseModel) + + +def check_wsclient(function): + @wraps(function) + async def check_wsclient_wrapper(self, *args, **kwargs): + if self.client is None: + raise SpeckleException( + "You must authenticate before you can subscribe to events" + ) + else: + return await function(self, *args, **kwargs) + + return check_wsclient_wrapper + + +class SubscriptionResource(ResourceBase): + """API Access class for subscriptions""" + + def __init__(self, account, basepath, client) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + name=NAME, + ) + + @check_wsclient + async def user_projects_updated( + self, callback: Callable[[UserProjectsUpdatedMessage], None] + ) -> None: + QUERY = gql( + """ + subscription UserProjectsUpdated { + data:userProjectsUpdated { + id + project { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + workspaceId + } + type + } + } + """ + ) + + await self.subscribe_2( + DataResponse[UserProjectsUpdatedMessage], + QUERY, + None, + callback=lambda d: callback(d.data), + ) + + @check_wsclient + async def project_models_updated( + self, + callback: Callable[[ProjectModelsUpdatedMessage], None], + id: str, + model_ids: Optional[Sequence[str]] = None, + ) -> None: + QUERY = gql( + """ + subscription ProjectModelsUpdated($id: String!, $modelIds: [String!]) { + data:projectModelsUpdated(id: $id, modelIds: $modelIds) { + id + model { + id + name + previewUrl + updatedAt + description + displayName + createdAt + author { + avatar + bio + company + id + name + role + verified + } + } + type + } + } + """ + ) + + variables = {"id": id, "modelIds": model_ids} + + await self.subscribe_2( + DataResponse[ProjectModelsUpdatedMessage], + QUERY, + variables, + callback=lambda d: callback(d.data), + ) + + @check_wsclient + async def project_updated( + self, + callback: Callable[[ProjectUpdatedMessage], None], + id: str, + ) -> None: + QUERY = gql( + """ + subscription ProjectUpdated($id: String!) { + data:projectUpdated(id: $id) { + id + project { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + } + type + } + } + """ + ) + + variables = {"id": id} + + await self.subscribe_2( + DataResponse[ProjectUpdatedMessage], + QUERY, + variables, + callback=lambda d: callback(d.data), + ) + + @check_wsclient + async def project_versions_updated( + self, + callback: Callable[[ProjectVersionsUpdatedMessage], None], + id: str, + ) -> None: + QUERY = gql( + """ + subscription ProjectVersionsUpdated($id: String!) { + data:projectVersionsUpdated(id: $id) { + id + modelId + type + version { + id + referencedObject + message + sourceApplication + createdAt + previewUrl + authorUser { + id + name + bio + company + verified + role + avatar + } + } + } + } + """ + ) + + variables = {"id": id} + + await self.subscribe_2( + DataResponse[ProjectVersionsUpdatedMessage], + QUERY, + variables, + callback=lambda d: callback(d.data), + ) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + @check_wsclient + async def stream_added(self, callback: Optional[Callable] = None): + """Subscribes to new stream added event for your profile. + Use this to display an up-to-date list of streams. + + Arguments: + callback {Callable[Stream]} -- a function that takes the updated stream + as an argument and executes each time a stream is added + + Returns: + Stream -- the update stream + """ + query = gql( + """ + subscription { userStreamAdded } + """ + ) + return await self.subscribe( + query=query, callback=callback, return_type="userStreamAdded", schema=Stream + ) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + @check_wsclient + async def stream_updated(self, id: str, callback: Optional[Callable] = None): + """ + Subscribes to stream updated event. + Use this in clients/components that pertain only to this stream. + + Arguments: + id {str} -- the stream id of the stream to subscribe to + callback {Callable[Stream]} + -- a function that takes the updated stream + as an argument and executes each time the stream is updated + + Returns: + Stream -- the update stream + """ + query = gql( + """ + subscription Update($id: String!) { streamUpdated(streamId: $id) } + """ + ) + params = {"id": id} + + return await self.subscribe( + query=query, + params=params, + callback=callback, + return_type="streamUpdated", + schema=Stream, + ) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + @check_wsclient + async def stream_removed(self, callback: Optional[Callable] = None): + """Subscribes to stream removed event for your profile. + Use this to display an up-to-date list of streams for your profile. + NOTE: If someone revokes your permissions on a stream, + this subscription will be triggered with an extra value of revokedBy + in the payload. + + Arguments: + callback {Callable[Dict]} + -- a function that takes the returned dict as an argument + and executes each time a stream is removed + + Returns: + dict -- dict containing 'id' of stream removed and optionally 'revokedBy' + """ + query = gql( + """ + subscription { userStreamRemoved } + """ + ) + + return await self.subscribe( + query=query, + callback=callback, + return_type="userStreamRemoved", + parse_response=False, + ) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + @check_wsclient + async def subscribe( + self, + query: DocumentNode, + params: Optional[Dict] = None, + callback: Optional[Callable] = None, + return_type: Optional[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 + 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) + if parse_response: + res = self._parse_response(response=res, schema=schema) + if callback is not None: + callback(res) + else: + return res + + async def subscribe_2( + self, + response_type: Type[TEventArgs], + query: DocumentNode, + variables: Optional[Dict[str, Any]], + callback: Callable[[TEventArgs], None], + ) -> None: + async with self.client as session: + self.session = session + gen = session.subscribe(query, variable_values=variables) + async for res in gen: + event_arg = response_type.model_validate(res) + callback(event_arg) diff --git a/src/specklepy/core/api/resources/subscriptions.py b/src/specklepy/core/api/resources/subscriptions.py index 445ef4e8..bc9f1a29 100644 --- a/src/specklepy/core/api/resources/subscriptions.py +++ b/src/specklepy/core/api/resources/subscriptions.py @@ -1,138 +1,11 @@ -from functools import wraps -from typing import Callable, Dict, List, Optional, Union +from deprecated import deprecated -from gql import gql -from graphql import DocumentNode +from specklepy.core.api.models import FE1_DEPRECATION_VERSION +from specklepy.core.api.resources.subscription_resource import SubscriptionResource -from specklepy.core.api.resource import ResourceBase -from specklepy.core.api.resources.stream import Stream -from specklepy.logging.exceptions import SpeckleException -NAME = "subscribe" - - -def check_wsclient(function): - @wraps(function) - async def check_wsclient_wrapper(self, *args, **kwargs): - if self.client is None: - raise SpeckleException( - "You must authenticate before you can subscribe to events" - ) - else: - return await function(self, *args, **kwargs) - - return check_wsclient_wrapper - - -class Resource(ResourceBase): - """API Access class for subscriptions""" - - def __init__(self, account, basepath, client) -> None: - super().__init__( - account=account, - basepath=basepath, - client=client, - name=NAME, - ) - - @check_wsclient - async def stream_added(self, callback: Optional[Callable] = None): - """Subscribes to new stream added event for your profile. - Use this to display an up-to-date list of streams. - - Arguments: - callback {Callable[Stream]} -- a function that takes the updated stream - as an argument and executes each time a stream is added - - Returns: - Stream -- the update stream - """ - query = gql( - """ - subscription { userStreamAdded } - """ - ) - return await self.subscribe( - query=query, callback=callback, return_type="userStreamAdded", schema=Stream - ) - - @check_wsclient - async def stream_updated(self, id: str, callback: Optional[Callable] = None): - """ - Subscribes to stream updated event. - Use this in clients/components that pertain only to this stream. - - Arguments: - id {str} -- the stream id of the stream to subscribe to - callback {Callable[Stream]} - -- a function that takes the updated stream - as an argument and executes each time the stream is updated - - Returns: - Stream -- the update stream - """ - query = gql( - """ - subscription Update($id: String!) { streamUpdated(streamId: $id) } - """ - ) - params = {"id": id} - - return await self.subscribe( - query=query, - params=params, - callback=callback, - return_type="streamUpdated", - schema=Stream, - ) - - @check_wsclient - async def stream_removed(self, callback: Optional[Callable] = None): - """Subscribes to stream removed event for your profile. - Use this to display an up-to-date list of streams for your profile. - NOTE: If someone revokes your permissions on a stream, - this subscription will be triggered with an extra value of revokedBy - in the payload. - - Arguments: - callback {Callable[Dict]} - -- a function that takes the returned dict as an argument - and executes each time a stream is removed - - Returns: - dict -- dict containing 'id' of stream removed and optionally 'revokedBy' - """ - query = gql( - """ - subscription { userStreamRemoved } - """ - ) - - return await self.subscribe( - query=query, - callback=callback, - return_type="userStreamRemoved", - parse_response=False, - ) - - @check_wsclient - async def subscribe( - self, - query: DocumentNode, - params: Optional[Dict] = None, - callback: Optional[Callable] = None, - return_type: Optional[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 - 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) - if parse_response: - res = self._parse_response(response=res, schema=schema) - if callback is not None: - callback(res) - else: - return res +@deprecated( + reason="Class renamed to SubscriptionResource", version=FE1_DEPRECATION_VERSION +) +class Resource(SubscriptionResource): + pass diff --git a/src/specklepy/logging/metrics.py b/src/specklepy/logging/metrics.py index 3177ee75..273db2ab 100644 --- a/src/specklepy/logging/metrics.py +++ b/src/specklepy/logging/metrics.py @@ -15,6 +15,7 @@ This really helps us to deliver a better open source project and product! """ TRACK = True +TRACK_SDK = True HOST_APP = "python" HOST_APP_VERSION = f"python {'.'.join(map(str, sys.version_info[:2]))}" PLATFORMS = {"win32": "Windows", "cygwin": "Windows", "darwin": "Mac OS X"} @@ -67,6 +68,8 @@ def track( ): if not TRACK: return + if not TRACK_SDK and action == SDK: + return try: initialise_tracker(account) event_params = { diff --git a/tests/integration/client/test_active_user_resource.py b/tests/integration/client/test_active_user_resource.py index 4478ad91..ac01cd5f 100644 --- a/tests/integration/client/test_active_user_resource.py +++ b/tests/integration/client/test_active_user_resource.py @@ -1,6 +1,6 @@ import pytest -from specklepy.core.api.client import SpeckleClient +from specklepy.api.client import SpeckleClient from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.inputs.user_inputs import UserUpdateInput from specklepy.core.api.models import User diff --git a/tests/integration/client/test_subscription_resource.py b/tests/integration/client/test_subscription_resource.py new file mode 100644 index 00000000..aee7feaf --- /dev/null +++ b/tests/integration/client/test_subscription_resource.py @@ -0,0 +1,167 @@ +import asyncio +from typing import Optional + +import pytest + +from specklepy.api.client import SpeckleClient +from specklepy.core.api.enums import ( + ProjectModelsUpdatedMessageType, + ProjectUpdatedMessageType, + ProjectVersionsUpdatedMessageType, + UserProjectsUpdatedMessageType, +) +from specklepy.core.api.inputs.model_inputs import CreateModelInput +from specklepy.core.api.inputs.project_inputs import ( + ProjectCreateInput, + ProjectUpdateInput, +) +from specklepy.core.api.new_models import ( + Model, + Project, + ProjectModelsUpdatedMessage, + ProjectUpdatedMessage, + ProjectVersionsUpdatedMessage, + UserProjectsUpdatedMessage, + Version, +) +from tests.integration.conftest import create_version + +WAIT_PERIOD = 5 # time in seconds + + +@pytest.mark.run() +class TestSubscriptionResource: + @pytest.fixture + def test_project(self, client: SpeckleClient) -> Project: + project = client.project.create( + ProjectCreateInput(name="Test project", description="", visibility=None) + ) + return project + + @pytest.fixture + def test_model(self, client: SpeckleClient, test_project: Project) -> Model: + model1 = client.model.create( + CreateModelInput( + name="Test Model 1", description="", projectId=test_project.id + ) + ) + return model1 + + @pytest.mark.asyncio + async def test_user_projects_updated( + self, + client: SpeckleClient, + ) -> None: + message: Optional[UserProjectsUpdatedMessage] = None + + task = None + + def callback(d: UserProjectsUpdatedMessage): + nonlocal message + message = d + + task = asyncio.create_task(client.subscribe.user_projects_updated(callback)) + + await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup + + input = ProjectCreateInput(name=None, description=None, visibility=None) + created = client.project.create(input) + + await asyncio.sleep(WAIT_PERIOD) # Give time for subscription to be triggered + + assert isinstance(message, UserProjectsUpdatedMessage) + assert message.id == created.id + assert message.type == UserProjectsUpdatedMessageType.ADDED + assert isinstance(message.project, Project) + task.cancel() + + @pytest.mark.asyncio + async def test_project_models_updated( + self, client: SpeckleClient, test_project: Project + ) -> None: + message: Optional[ProjectModelsUpdatedMessage] = None + + task = None + + def callback(d: ProjectModelsUpdatedMessage): + nonlocal message + message = d + + task = asyncio.create_task( + client.subscribe.project_models_updated(callback, test_project.id) + ) + + await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup + + input = CreateModelInput( + name="my model", description="myDescription", projectId=test_project.id + ) + created = client.model.create(input) + + await asyncio.sleep(WAIT_PERIOD) # Give time for subscription to be triggered + + assert isinstance(message, ProjectModelsUpdatedMessage) + assert message.id == created.id + assert message.type == ProjectModelsUpdatedMessageType.CREATED + assert isinstance(message.model, Model) + task.cancel() + + @pytest.mark.asyncio + async def test_project_updated( + self, client: SpeckleClient, test_project: Project + ) -> None: + message: Optional[ProjectUpdatedMessage] = None + + task = None + + def callback(d: ProjectUpdatedMessage): + nonlocal message + message = d + + task = asyncio.create_task( + client.subscribe.project_updated(callback, test_project.id) + ) + + await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup + + input = ProjectUpdateInput(id=test_project.id, name="This is my new name") + created = client.project.update(input) + + await asyncio.sleep(WAIT_PERIOD) # Give time for subscription to be triggered + + assert isinstance(message, ProjectUpdatedMessage) + assert message.id == created.id + assert message.type == ProjectUpdatedMessageType.UPDATED + assert isinstance(message.project, Project) + task.cancel() + + @pytest.mark.asyncio + async def test_project_versions_updated( + self, + client: SpeckleClient, + test_project: Project, + test_model: Model, + ) -> None: + message: Optional[ProjectVersionsUpdatedMessage] = None + + task = None + + def callback(d: ProjectVersionsUpdatedMessage): + nonlocal message + message = d + + task = asyncio.create_task( + client.subscribe.project_versions_updated(callback, test_project.id) + ) + + await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup + + created = create_version(client, test_project.id, test_model.id) + + await asyncio.sleep(WAIT_PERIOD) # Give time for subscription to be triggered + + assert isinstance(message, ProjectVersionsUpdatedMessage) + assert message.id == created.id + assert message.type == ProjectVersionsUpdatedMessageType.UPDATED + assert isinstance(message.version, Version) + task.cancel() diff --git a/tests/integration/client/test_version_resource.py b/tests/integration/client/test_version_resource.py index e0ec7b52..ff7d89dd 100644 --- a/tests/integration/client/test_version_resource.py +++ b/tests/integration/client/test_version_resource.py @@ -1,11 +1,9 @@ import pytest from specklepy.api.client import SpeckleClient -from specklepy.core.api import operations from specklepy.core.api.inputs.model_inputs import CreateModelInput from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.inputs.version_inputs import ( - CreateVersionInput, DeleteVersionsInput, MarkReceivedVersionInput, MoveVersionsInput, @@ -14,8 +12,7 @@ from specklepy.core.api.models import Model, Project, Version from specklepy.core.api.new_models import ModelWithVersions, ResourceCollection from specklepy.logging.exceptions import GraphQLException -from specklepy.objects.base import Base -from specklepy.transports.server.server import ServerTransport +from tests.integration.conftest import create_version @pytest.mark.run() @@ -49,15 +46,7 @@ def test_model_2(self, client: SpeckleClient, test_project: Project) -> Model: def test_version( self, client: SpeckleClient, test_project: Project, test_model_1: Model ) -> Version: - remote = ServerTransport(test_project.id, client) - objectId = operations.send( - Base(applicationId="ASDF"), [remote], use_default_cache=False - ) - input = CreateVersionInput( - objectId=objectId, modelId=test_model_1.id, projectId=test_project.id - ) - version_id = client.version.create(input) - return client.version.get(version_id, test_project.id) + return create_version(client, test_project.id, test_model_1.id) def test_version_get( self, client: SpeckleClient, test_version: Version, test_project: Project diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index a9d2f922..2afb413c 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -6,12 +6,16 @@ import pytest import requests +from specklepy.core.api import operations from specklepy.core.api.client import SpeckleClient +from specklepy.core.api.inputs.version_inputs import CreateVersionInput from specklepy.core.api.models import Stream +from specklepy.core.api.new_models import Version from specklepy.logging import metrics from specklepy.objects.base import Base from specklepy.objects.fakemesh import FakeDirection, FakeMesh from specklepy.objects.geometry import Point +from specklepy.transports.server.server import ServerTransport metrics.disable() @@ -58,6 +62,18 @@ def seed_user(host: str) -> Dict[str, str]: return user_dict +def create_version(client: SpeckleClient, project_id: str, model_id: str) -> Version: + remote = ServerTransport(project_id, client) + objectId = operations.send( + Base(applicationId="ASDF"), [remote], use_default_cache=False + ) + input = CreateVersionInput( + objectId=objectId, modelId=model_id, projectId=project_id + ) + version_id = client.version.create(input) + return client.version.get(version_id, project_id) + + @pytest.fixture(scope="session") def user_dict(host: str) -> Dict[str, str]: return seed_user(host) From bb6210933234573b07bd4767d23fc33332389651 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:51:55 +0000 Subject: [PATCH 15/26] Fixed subscription tests --- src/specklepy/api/resources/active_user.py | 110 +----------------- .../api/resources/active_user_resource.py | 9 ++ src/specklepy/api/resources/branch.py | 8 ++ src/specklepy/api/resources/comment.py | 0 src/specklepy/api/resources/commit.py | 9 ++ src/specklepy/api/resources/model_resource.py | 9 ++ src/specklepy/api/resources/object.py | 5 + src/specklepy/api/resources/other_user.py | 90 +------------- .../api/resources/other_user_resource.py | 6 + .../api/resources/project_invite_resource.py | 2 + .../api/resources/project_resource.py | 10 ++ src/specklepy/api/resources/stream.py | 18 +++ .../api/resources/subscription_resource.py | 62 ---------- src/specklepy/api/resources/subscriptions.py | 22 ++-- .../api/resources/version_resource.py | 2 + .../api/resources/active_user_resource.py | 2 +- .../api/resources/subscription_resource.py | 1 + .../client/test_subscription_resource.py | 58 +++++---- tests/integration/conftest.py | 23 ++-- 19 files changed, 146 insertions(+), 300 deletions(-) delete mode 100644 src/specklepy/api/resources/comment.py diff --git a/src/specklepy/api/resources/active_user.py b/src/specklepy/api/resources/active_user.py index a7d15317..d4d854a7 100644 --- a/src/specklepy/api/resources/active_user.py +++ b/src/specklepy/api/resources/active_user.py @@ -1,107 +1,9 @@ -from datetime import datetime -from typing import List, Optional +from deprecated import deprecated -from specklepy.api.models import PendingStreamCollaborator, User -from specklepy.core.api.resources.active_user_resource import ( - ActiveUserResource as CoreResource, -) -from specklepy.logging import metrics +from specklepy.api.resources.active_user_resource import ActiveUserResource +from specklepy.core.api.models import FE1_DEPRECATION_VERSION -class Resource(CoreResource): - """API Access class for users. This class provides methods to get and update - the user profile, fetch user activity, and manage pending stream invitations.""" - - def __init__(self, account, basepath, client, server_version) -> None: - super().__init__( - account=account, - basepath=basepath, - client=client, - server_version=server_version, - ) - self.schema = User - - def get(self) -> Optional[User]: - """Gets the profile of the current authenticated user's profile - (as extracted from the authorization header). - - Returns: - User -- the retrieved user - """ - metrics.track(metrics.SDK, custom_props={"name": "User Active Get"}) - return super().get() - - def update( - self, - name: Optional[str] = None, - company: Optional[str] = None, - bio: Optional[str] = None, - avatar: Optional[str] = None, - ) -> User: - """Updates your user profile. All arguments are optional. - - Args: - name (Optional[str]): The user's name. - company (Optional[str]): The company the user works for. - bio (Optional[str]): A brief user biography. - avatar (Optional[str]): A URL to an avatar image for the user. - - Returns @deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT): - bool -- True if your profile was updated successfully - """ - metrics.track(metrics.SDK, self.account, {"name": "User Active Update"}) - return super().update(name, company, bio, avatar) - - def activity( - self, - limit: int = 20, - action_type: Optional[str] = None, - before: Optional[datetime] = None, - after: Optional[datetime] = None, - cursor: Optional[datetime] = None, - ): - """ - Fetches collection the current authenticated user's activity - as filtered by given parameters - - Note: all timestamps arguments should be `datetime` of any tz as they will be - converted to UTC ISO format strings - - Args: - limit (int): The maximum number of activity items to return. - action_type (Optional[str]): Filter results to a single action type. - before (Optional[datetime]): Latest cutoff for activity to include. - after (Optional[datetime]): Oldest cutoff for an activity to include. - cursor (Optional[datetime]): Timestamp cursor for pagination. - - Returns: - Activity collection, filtered according to the provided parameters. - """ - metrics.track(metrics.SDK, self.account, {"name": "User Active Activity"}) - return super().activity(limit, action_type, before, after, cursor) - - def get_all_pending_invites(self) -> List[PendingStreamCollaborator]: - """Fetches all of the current user's pending stream invitations. - - Returns: - List[PendingStreamCollaborator]: A list of pending stream invitations. - """ - metrics.track( - metrics.SDK, self.account, {"name": "User Active Invites All Get"} - ) - return super().get_all_pending_invites() - - def get_pending_invite( - self, stream_id: str, token: Optional[str] = None - ) -> Optional[PendingStreamCollaborator]: - """Fetches a specific pending invite for the current user on a given stream. - - Args: - stream_id (str): The ID of the stream to look for invites on. - token (Optional[str]): The token of the invite to look for (optional). - - Returns: - Optional[PendingStreamCollaborator]: The invite for the given stream, or None if not found. - """ - metrics.track(metrics.SDK, self.account, {"name": "User Active Invite Get"}) - return super().get_pending_invite(stream_id, token) +@deprecated(reason="Renamed to ActiveUserResource", version=FE1_DEPRECATION_VERSION) +class Resource(ActiveUserResource): + """Renamed to ActiveUserResource""" diff --git a/src/specklepy/api/resources/active_user_resource.py b/src/specklepy/api/resources/active_user_resource.py index 55241753..2d2d8493 100644 --- a/src/specklepy/api/resources/active_user_resource.py +++ b/src/specklepy/api/resources/active_user_resource.py @@ -20,6 +20,9 @@ class ActiveUserResource(CoreResource): + """API Access class for users. This class provides methods to get and update + the user profile, fetch user activity, and manage pending stream invitations.""" + def __init__(self, account, basepath, client, server_version) -> None: super().__init__( account=account, @@ -30,6 +33,7 @@ def __init__(self, account, basepath, client, server_version) -> None: self.schema = User def get(self) -> Optional[User]: + metrics.track(metrics.SDK, self.account, {"name": "Active User Get"}) return super().get() @deprecated("Use UserUpdateInput overload", version=FE1_DEPRECATION_VERSION) @@ -56,6 +60,7 @@ def update( *, input: Optional[UserUpdateInput] = None, ) -> User: + metrics.track(metrics.SDK, self.account, {"name": "Active User Update"}) if isinstance(input, UserUpdateInput): return super()._update(input=input) else: @@ -75,9 +80,13 @@ def get_projects( cursor: Optional[str] = None, filter: Optional[UserProjectsFilter] = None, ) -> ResourceCollection[Project]: + metrics.track(metrics.SDK, self.account, {"name": "Active User Get Projects"}) return super().get_projects(limit=limit, cursor=cursor, filter=filter) def get_project_invites(self) -> List[PendingStreamCollaborator]: + metrics.track( + metrics.SDK, self.account, {"name": "Active User Get Project Invites"} + ) return super().get_project_invites() @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) diff --git a/src/specklepy/api/resources/branch.py b/src/specklepy/api/resources/branch.py index 6a64bd40..6cc46ceb 100644 --- a/src/specklepy/api/resources/branch.py +++ b/src/specklepy/api/resources/branch.py @@ -1,6 +1,9 @@ from typing import Optional, Union +from deprecated import deprecated + from specklepy.api.models import Branch +from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION from specklepy.core.api.resources.branch import Resource as CoreResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException @@ -17,6 +20,7 @@ def __init__(self, account, basepath, client) -> None: ) self.schema = Branch + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def create( self, stream_id: str, name: str, description: str = "No description provided" ) -> str: @@ -32,6 +36,7 @@ def create( metrics.track(metrics.SDK, self.account, {"name": "Branch Create"}) return super().create(stream_id, name, description) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get( self, stream_id: str, name: str, commits_limit: int = 10 ) -> Union[Branch, None, SpeckleException]: @@ -48,6 +53,7 @@ def get( metrics.track(metrics.SDK, self.account, {"name": "Branch Get"}) return super().get(stream_id, name, commits_limit) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def list(self, stream_id: str, branches_limit: int = 10, commits_limit: int = 10): """Get a list of branches from a given stream @@ -62,6 +68,7 @@ def list(self, stream_id: str, branches_limit: int = 10, commits_limit: int = 10 metrics.track(metrics.SDK, self.account, {"name": "Branch List"}) return super().list(stream_id, branches_limit, commits_limit) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def update( self, stream_id: str, @@ -83,6 +90,7 @@ def update( metrics.track(metrics.SDK, self.account, {"name": "Branch Update"}) return super().update(stream_id, branch_id, name, description) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def delete(self, stream_id: str, branch_id: str): """Delete a branch diff --git a/src/specklepy/api/resources/comment.py b/src/specklepy/api/resources/comment.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/specklepy/api/resources/commit.py b/src/specklepy/api/resources/commit.py index 367e5ad3..de008826 100644 --- a/src/specklepy/api/resources/commit.py +++ b/src/specklepy/api/resources/commit.py @@ -1,6 +1,9 @@ from typing import List, Optional, Union +from deprecated import deprecated + from specklepy.api.models import Commit +from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION from specklepy.core.api.resources.commit import Resource as CoreResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException @@ -17,6 +20,7 @@ def __init__(self, account, basepath, client) -> None: ) self.schema = Commit + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get(self, stream_id: str, commit_id: str) -> Commit: """ Gets a commit given a stream and the commit id @@ -31,6 +35,7 @@ def get(self, stream_id: str, commit_id: str) -> Commit: metrics.track(metrics.SDK, self.account, {"name": "Commit Get"}) return super().get(stream_id, commit_id) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def list(self, stream_id: str, limit: int = 10) -> List[Commit]: """ Get a list of commits on a given stream @@ -45,6 +50,7 @@ def list(self, stream_id: str, limit: int = 10) -> List[Commit]: metrics.track(metrics.SDK, self.account, {"name": "Commit List"}) return super().list(stream_id, limit) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def create( self, stream_id: str, @@ -77,6 +83,7 @@ def create( stream_id, object_id, branch_name, message, source_application, parents ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def update(self, stream_id: str, commit_id: str, message: str) -> bool: """ Update a commit @@ -93,6 +100,7 @@ def update(self, stream_id: str, commit_id: str, message: str) -> bool: metrics.track(metrics.SDK, self.account, {"name": "Commit Update"}) return super().update(stream_id, commit_id, message) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def delete(self, stream_id: str, commit_id: str) -> bool: """ Delete a commit @@ -108,6 +116,7 @@ def delete(self, stream_id: str, commit_id: str) -> bool: metrics.track(metrics.SDK, self.account, {"name": "Commit Delete"}) return super().delete(stream_id, commit_id) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def received( self, stream_id: str, diff --git a/src/specklepy/api/resources/model_resource.py b/src/specklepy/api/resources/model_resource.py index cc3df5d2..cfd71d08 100644 --- a/src/specklepy/api/resources/model_resource.py +++ b/src/specklepy/api/resources/model_resource.py @@ -9,9 +9,12 @@ from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter from specklepy.core.api.new_models import Model, ModelWithVersions, ResourceCollection from specklepy.core.api.resources.model_resource import ModelResource as CoreResource +from specklepy.logging import metrics class ModelResource(CoreResource): + """API Access class for models""" + def __init__(self, account, basepath, client, server_version) -> None: super().__init__( account=account, @@ -21,6 +24,7 @@ def __init__(self, account, basepath, client, server_version) -> None: ) def get(self, model_id: str, project_id: str) -> Model: + metrics.track(metrics.SDK, self.account, {"name": "Model Get"}) return super().get(model_id, project_id) def get_with_versions( @@ -32,6 +36,7 @@ def get_with_versions( versions_cursor: Optional[str] = None, versions_filter: Optional[ModelVersionsFilter] = None, ) -> ModelWithVersions: + metrics.track(metrics.SDK, self.account, {"name": "Model Get With Versions"}) return super().get_with_versions( model_id, project_id, @@ -48,6 +53,7 @@ def get_models( models_cursor: Optional[str] = None, models_filter: Optional[ProjectModelsFilter] = None, ) -> ResourceCollection[Model]: + metrics.track(metrics.SDK, self.account, {"name": "Model Get Models"}) return super().get_models( project_id, models_limit=models_limit, @@ -56,10 +62,13 @@ def get_models( ) def create(self, input: CreateModelInput) -> Model: + metrics.track(metrics.SDK, self.account, {"name": "Model Create"}) return super().create(input) def delete(self, input: DeleteModelInput) -> bool: + metrics.track(metrics.SDK, self.account, {"name": "Model Delete"}) return super().delete(input) def update(self, input: UpdateModelInput) -> Model: + metrics.track(metrics.SDK, self.account, {"name": "Model Update"}) return super().update(input) diff --git a/src/specklepy/api/resources/object.py b/src/specklepy/api/resources/object.py index b7ea96c9..4188e401 100644 --- a/src/specklepy/api/resources/object.py +++ b/src/specklepy/api/resources/object.py @@ -1,5 +1,8 @@ from typing import Dict, List +from deprecated import deprecated + +from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION from specklepy.core.api.resources.object import Resource as CoreResource from specklepy.logging import metrics from specklepy.objects.base import Base @@ -16,6 +19,7 @@ def __init__(self, account, basepath, client) -> None: ) self.schema = Base + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get(self, stream_id: str, object_id: str) -> Base: """ Get a stream object @@ -30,6 +34,7 @@ def get(self, stream_id: str, object_id: str) -> Base: metrics.track(metrics.SDK, self.account, {"name": "Object Get"}) return super().get(stream_id, object_id) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def create(self, stream_id: str, objects: List[Dict]) -> str: """ Not advised - generally, you want to use `operations.send()`. diff --git a/src/specklepy/api/resources/other_user.py b/src/specklepy/api/resources/other_user.py index 6c903b20..52f683f5 100644 --- a/src/specklepy/api/resources/other_user.py +++ b/src/specklepy/api/resources/other_user.py @@ -1,89 +1,11 @@ -from datetime import datetime -from typing import List, Optional, Union +from deprecated import deprecated -from specklepy.api.models import ActivityCollection, LimitedUser -from specklepy.core.api.resources.other_user_resource import ( - OtherUserResource as CoreResource, -) -from specklepy.logging import metrics -from specklepy.logging.exceptions import SpeckleException +from specklepy.api.resources.other_user_resource import OtherUserResource +from specklepy.core.api.models import FE1_DEPRECATION_VERSION -class Resource(CoreResource): +@deprecated(reason="Renamed to OtherUserResource", version=FE1_DEPRECATION_VERSION) +class Resource(OtherUserResource): """ - Provides API access to other users' profiles and activities on the platform. - This class enables fetching limited information about users, searching for users by name or email, - and accessing user activity logs with appropriate privacy and access control measures in place. + Renamed to OtherUserResource """ - - def __init__(self, account, basepath, client, server_version) -> None: - super().__init__( - account=account, - basepath=basepath, - client=client, - server_version=server_version, - ) - self.schema = LimitedUser - - def get(self, id: str) -> Optional[LimitedUser]: - """ - Retrieves the profile of a user specified by their user ID. - - Args: - id (str): The unique identifier of the user. - - Returns: - LimitedUser: The profile of the user with limited information. - """ - metrics.track(metrics.SDK, self.account, {"name": "Other User Get"}) - return super().get(id) - - def search( - self, search_query: str, limit: int = 25 - ) -> Union[List[LimitedUser], SpeckleException]: - """ - Searches for users by name or email. - The search requires a minimum query length of 3 characters. - - Args: - search_query (str): The search string. - limit (int): Maximum number of search results to return. - - Returns: - Union[List[LimitedUser], SpeckleException]: A list of users matching the search - query or an exception if the query is too short. - """ - if len(search_query) < 3: - return SpeckleException( - message="User search query must be at least 3 characters." - ) - - metrics.track(metrics.SDK, self.account, {"name": "Other User Search"}) - return super().search(search_query, limit) - - def activity( - self, - user_id: str, - limit: int = 20, - action_type: Optional[str] = None, - before: Optional[datetime] = None, - after: Optional[datetime] = None, - cursor: Optional[datetime] = None, - ) -> ActivityCollection: - """ - Retrieves a collection of activities for a specified user, with optional filters for activity type, - time frame, and pagination. - - Args: - user_id (str): The ID of the user whose activities are being requested. - limit (int): The maximum number of activity items to return. - action_type (Optional[str]): A specific type of activity to filter. - before (Optional[datetime]): Latest timestamp to include activities before. - after (Optional[datetime]): Earliest timestamp to include activities after. - cursor (Optional[datetime]): Timestamp for pagination cursor. - - Returns: - ActivityCollection: A collection of user activities filtered according to specified criteria. - """ - metrics.track(metrics.SDK, self.account, {"name": "Other User Activity"}) - return super().activity(user_id, limit, action_type, before, after, cursor) diff --git a/src/specklepy/api/resources/other_user_resource.py b/src/specklepy/api/resources/other_user_resource.py index 28ab4c34..163ed097 100644 --- a/src/specklepy/api/resources/other_user_resource.py +++ b/src/specklepy/api/resources/other_user_resource.py @@ -18,6 +18,12 @@ class OtherUserResource(CoreResource): + """ + Provides API access to other users' profiles and activities on the platform. + This class enables fetching limited information about users, searching for users by name or email, + and accessing user activity logs with appropriate privacy and access control measures in place. + """ + def __init__(self, account, basepath, client, server_version) -> None: super().__init__( account=account, diff --git a/src/specklepy/api/resources/project_invite_resource.py b/src/specklepy/api/resources/project_invite_resource.py index 1a1cccac..114b00b3 100644 --- a/src/specklepy/api/resources/project_invite_resource.py +++ b/src/specklepy/api/resources/project_invite_resource.py @@ -15,6 +15,8 @@ class ProjectInviteResource(CoreResource): + """API Access class for project invites""" + def __init__( self, account: Account, diff --git a/src/specklepy/api/resources/project_resource.py b/src/specklepy/api/resources/project_resource.py index ad40fec1..dbfa3825 100644 --- a/src/specklepy/api/resources/project_resource.py +++ b/src/specklepy/api/resources/project_resource.py @@ -11,9 +11,12 @@ from specklepy.core.api.resources.project_resource import ( ProjectResource as CoreResource, ) +from specklepy.logging import metrics class ProjectResource(CoreResource): + """API Access class for projects""" + def __init__(self, account, basepath, client, server_version) -> None: super().__init__( account=account, @@ -23,6 +26,7 @@ def __init__(self, account, basepath, client, server_version) -> None: ) def get(self, project_id: str) -> Project: + metrics.track(metrics.SDK, self.account, {"name": "Project Get "}) return super().get(project_id) def get_with_models( @@ -33,6 +37,7 @@ def get_with_models( models_cursor: Optional[str] = None, models_filter: Optional[ProjectModelsFilter] = None, ) -> ProjectWithModels: + metrics.track(metrics.SDK, self.account, {"name": "Project Get With Models"}) return super().get_with_models( project_id, models_limit=models_limit, @@ -41,16 +46,21 @@ def get_with_models( ) def get_with_team(self, project_id: str) -> ProjectWithTeam: + metrics.track(metrics.SDK, self.account, {"name": "Project Get With Team"}) return super().get_with_team(project_id) def create(self, input: ProjectCreateInput) -> Project: + metrics.track(metrics.SDK, self.account, {"name": "Project Create"}) return super().create(input) def update(self, input: ProjectUpdateInput) -> Project: + metrics.track(metrics.SDK, self.account, {"name": "Project Update"}) return super().update(input) def delete(self, project_id: str) -> bool: + metrics.track(metrics.SDK, self.account, {"name": "Project Delete"}) return super().delete(project_id) def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam: + metrics.track(metrics.SDK, self.account, {"name": "Project Update Role"}) return super().update_role(input) diff --git a/src/specklepy/api/resources/stream.py b/src/specklepy/api/resources/stream.py index 330677df..65ccc0ae 100644 --- a/src/specklepy/api/resources/stream.py +++ b/src/specklepy/api/resources/stream.py @@ -1,7 +1,10 @@ from datetime import datetime from typing import List, Optional +from deprecated import deprecated + from specklepy.api.models import PendingStreamCollaborator, Stream +from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION from specklepy.core.api.resources.stream import Resource as CoreResource from specklepy.logging import metrics @@ -19,6 +22,7 @@ def __init__(self, account, basepath, client, server_version) -> None: self.schema = Stream + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream: """Get the specified stream from the server @@ -33,6 +37,7 @@ def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream metrics.track(metrics.SDK, self.account, {"name": "Stream Get"}) return super().get(id, branch_limit, commit_limit) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def list(self, stream_limit: int = 10) -> List[Stream]: """Get a list of the user's streams @@ -45,6 +50,7 @@ def list(self, stream_limit: int = 10) -> List[Stream]: metrics.track(metrics.SDK, self.account, {"name": "Stream List"}) return super().list(stream_limit) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def create( self, name: str = "Anonymous Python Stream", @@ -65,6 +71,7 @@ def create( metrics.track(metrics.SDK, self.account, {"name": "Stream Create"}) return super().create(name, description, is_public) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def update( self, id: str, @@ -87,6 +94,7 @@ def update( metrics.track(metrics.SDK, self.account, {"name": "Stream Update"}) return super().update(id, name, description, is_public) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def delete(self, id: str) -> bool: """Delete a stream given its id @@ -99,6 +107,7 @@ def delete(self, id: str) -> bool: metrics.track(metrics.SDK, self.account, {"name": "Stream Delete"}) return super().delete(id) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def search( self, search_query: str, @@ -120,6 +129,7 @@ def search( metrics.track(metrics.SDK, self.account, {"name": "Stream Search"}) return super().search(search_query, limit, branch_limit, commit_limit) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def favorite(self, stream_id: str, favorited: bool = True): """Favorite or unfavorite the given stream. @@ -134,6 +144,7 @@ def favorite(self, stream_id: str, favorited: bool = True): metrics.track(metrics.SDK, self.account, {"name": "Stream Favorite"}) return super().favorite(stream_id, favorited) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def get_all_pending_invites( self, stream_id: str ) -> List[PendingStreamCollaborator]: @@ -152,6 +163,7 @@ def get_all_pending_invites( metrics.track(metrics.SDK, self.account, {"name": "Stream Invite Get"}) return super().get_all_pending_invites(stream_id) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def invite( self, stream_id: str, @@ -179,6 +191,7 @@ def invite( metrics.track(metrics.SDK, self.account, {"name": "Stream Invite Create"}) return super().invite(stream_id, email, user_id, role, message) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def invite_batch( self, stream_id: str, @@ -205,6 +218,7 @@ def invite_batch( metrics.track(metrics.SDK, self.account, {"name": "Stream Invite Batch Create"}) return super().invite_batch(stream_id, emails, user_ids, message) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def invite_cancel(self, stream_id: str, invite_id: str) -> bool: """Cancel an existing stream invite @@ -220,6 +234,7 @@ def invite_cancel(self, stream_id: str, invite_id: str) -> bool: metrics.track(metrics.SDK, self.account, {"name": "Stream Invite Cancel"}) return super().invite_cancel(stream_id, invite_id) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def invite_use(self, stream_id: str, token: str, accept: bool = True) -> bool: """Accept or decline a stream invite @@ -237,6 +252,7 @@ def invite_use(self, stream_id: str, token: str, accept: bool = True) -> bool: metrics.track(metrics.SDK, self.account, {"name": "Invite Use"}) return super().invite_use(stream_id, token, accept) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def update_permission(self, stream_id: str, user_id: str, role: str): """Updates permissions for a user on a given stream @@ -257,6 +273,7 @@ def update_permission(self, stream_id: str, user_id: str, role: str): ) return super().update_permission(stream_id, user_id, role) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def revoke_permission(self, stream_id: str, user_id: str): """Revoke permissions from a user on a given stream @@ -270,6 +287,7 @@ def revoke_permission(self, stream_id: str, user_id: str): metrics.track(metrics.SDK, self.account, {"name": "Stream Permission Revoke"}) return super().revoke_permission(stream_id, user_id) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) def activity( self, stream_id: str, diff --git a/src/specklepy/api/resources/subscription_resource.py b/src/specklepy/api/resources/subscription_resource.py index a0641aca..a102ad21 100644 --- a/src/specklepy/api/resources/subscription_resource.py +++ b/src/specklepy/api/resources/subscription_resource.py @@ -1,10 +1,8 @@ from typing import Callable, Optional, Sequence -from deprecated import deprecated from pydantic import BaseModel from typing_extensions import TypeVar -from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION from specklepy.core.api.new_models import ( ProjectModelsUpdatedMessage, ProjectUpdatedMessage, @@ -14,7 +12,6 @@ from specklepy.core.api.resources.subscription_resource import ( SubscriptionResource as CoreResource, ) -from specklepy.core.api.resources.subscription_resource import check_wsclient from specklepy.logging import metrics TEventArgs = TypeVar("TEventArgs", bound=BaseModel) @@ -67,62 +64,3 @@ async def project_versions_updated( metrics.SDK, self.account, {"name": "Subscription Project Versions Updated"} ) return await super().project_versions_updated(callback, id) - - @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) - @check_wsclient - async def stream_added(self, callback: Optional[Callable] = None): - """Subscribes to new stream added event for your profile. - Use this to display an up-to-date list of streams. - - Arguments: - callback {Callable[Stream]} -- a function that takes the updated stream - as an argument and executes each time a stream is added - - Returns: - Stream -- the update stream - """ - metrics.track(metrics.SDK, self.account, {"name": "Subscription Stream Added"}) - return super().stream_added(callback) - - @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) - @check_wsclient - async def stream_updated(self, id: str, callback: Optional[Callable] = None): - """ - Subscribes to stream updated event. - Use this in clients/components that pertain only to this stream. - - Arguments: - id {str} -- the stream id of the stream to subscribe to - callback {Callable[Stream]} - -- a function that takes the updated stream - as an argument and executes each time the stream is updated - - Returns: - Stream -- the update stream - """ - metrics.track( - metrics.SDK, self.account, {"name": "Subscription Stream Updated"} - ) - return super().stream_updated(id, callback) - - @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) - @check_wsclient - async def stream_removed(self, callback: Optional[Callable] = None): - """Subscribes to stream removed event for your profile. - Use this to display an up-to-date list of streams for your profile. - NOTE: If someone revokes your permissions on a stream, - this subscription will be triggered with an extra value of revokedBy - in the payload. - - Arguments: - callback {Callable[Dict]} - -- a function that takes the returned dict as an argument - and executes each time a stream is removed - - Returns: - dict -- dict containing 'id' of stream removed and optionally 'revokedBy' - """ - metrics.track( - metrics.SDK, self.account, {"name": "Subscription Stream Removed"} - ) - return super().stream_removed(callback) diff --git a/src/specklepy/api/resources/subscriptions.py b/src/specklepy/api/resources/subscriptions.py index 3e015fb6..1cc77836 100644 --- a/src/specklepy/api/resources/subscriptions.py +++ b/src/specklepy/api/resources/subscriptions.py @@ -1,24 +1,12 @@ -from functools import wraps from typing import Callable, Dict, List, Optional, Union +from deprecated import deprecated from graphql import DocumentNode +from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION +from specklepy.core.api.resources.subscription_resource import check_wsclient from specklepy.core.api.resources.subscriptions import Resource as CoreResource from specklepy.logging import metrics -from specklepy.logging.exceptions import SpeckleException - - -def check_wsclient(function): - @wraps(function) - async def check_wsclient_wrapper(self, *args, **kwargs): - if self.client is None: - raise SpeckleException( - "You must authenticate before you can subscribe to events" - ) - else: - return await function(self, *args, **kwargs) - - return check_wsclient_wrapper class Resource(CoreResource): @@ -31,6 +19,7 @@ def __init__(self, account, basepath, client) -> None: client=client, ) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) @check_wsclient async def stream_added(self, callback: Optional[Callable] = None): """Subscribes to new stream added event for your profile. @@ -46,6 +35,7 @@ async def stream_added(self, callback: Optional[Callable] = None): metrics.track(metrics.SDK, self.account, {"name": "Subscription Stream Added"}) return super().stream_added(callback) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) @check_wsclient async def stream_updated(self, id: str, callback: Optional[Callable] = None): """ @@ -66,6 +56,7 @@ async def stream_updated(self, id: str, callback: Optional[Callable] = None): ) return super().stream_updated(id, callback) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) @check_wsclient async def stream_removed(self, callback: Optional[Callable] = None): """Subscribes to stream removed event for your profile. @@ -87,6 +78,7 @@ async def stream_removed(self, callback: Optional[Callable] = None): ) return super().stream_removed(callback) + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) @check_wsclient async def subscribe( self, diff --git a/src/specklepy/api/resources/version_resource.py b/src/specklepy/api/resources/version_resource.py index 52e8120d..f2087518 100644 --- a/src/specklepy/api/resources/version_resource.py +++ b/src/specklepy/api/resources/version_resource.py @@ -16,6 +16,8 @@ class VersionResource(CoreResource): + """API Access class for model versions""" + def __init__(self, account, basepath, client, server_version) -> None: super().__init__( account=account, diff --git a/src/specklepy/core/api/resources/active_user_resource.py b/src/specklepy/core/api/resources/active_user_resource.py index ca47c71a..74bf4969 100644 --- a/src/specklepy/core/api/resources/active_user_resource.py +++ b/src/specklepy/core/api/resources/active_user_resource.py @@ -23,7 +23,7 @@ class ActiveUserResource(ResourceBase): - """API Access class for users""" + """API Access class for the active user""" def __init__(self, account, basepath, client, server_version) -> None: super().__init__( diff --git a/src/specklepy/core/api/resources/subscription_resource.py b/src/specklepy/core/api/resources/subscription_resource.py index 611d283d..119a1245 100644 --- a/src/specklepy/core/api/resources/subscription_resource.py +++ b/src/specklepy/core/api/resources/subscription_resource.py @@ -148,6 +148,7 @@ async def project_updated( createdAt updatedAt sourceApps + workspaceId } type } diff --git a/tests/integration/client/test_subscription_resource.py b/tests/integration/client/test_subscription_resource.py index aee7feaf..dd72c359 100644 --- a/tests/integration/client/test_subscription_resource.py +++ b/tests/integration/client/test_subscription_resource.py @@ -1,5 +1,5 @@ import asyncio -from typing import Optional +from typing import Dict, Optional import pytest @@ -24,23 +24,31 @@ UserProjectsUpdatedMessage, Version, ) -from tests.integration.conftest import create_version +from tests.integration.conftest import create_client, create_version -WAIT_PERIOD = 5 # time in seconds +WAIT_PERIOD = 0.4 # time in seconds @pytest.mark.run() class TestSubscriptionResource: @pytest.fixture - def test_project(self, client: SpeckleClient) -> Project: - project = client.project.create( + def subscription_client( + self, host: str, user_dict: Dict[str, str] + ) -> SpeckleClient: + return create_client(host, user_dict["token"]) + + @pytest.fixture + def test_project(self, subscription_client: SpeckleClient) -> Project: + project = subscription_client.project.create( ProjectCreateInput(name="Test project", description="", visibility=None) ) return project @pytest.fixture - def test_model(self, client: SpeckleClient, test_project: Project) -> Model: - model1 = client.model.create( + def test_model( + self, subscription_client: SpeckleClient, test_project: Project + ) -> Model: + model1 = subscription_client.model.create( CreateModelInput( name="Test Model 1", description="", projectId=test_project.id ) @@ -50,7 +58,7 @@ def test_model(self, client: SpeckleClient, test_project: Project) -> Model: @pytest.mark.asyncio async def test_user_projects_updated( self, - client: SpeckleClient, + subscription_client: SpeckleClient, ) -> None: message: Optional[UserProjectsUpdatedMessage] = None @@ -60,12 +68,14 @@ def callback(d: UserProjectsUpdatedMessage): nonlocal message message = d - task = asyncio.create_task(client.subscribe.user_projects_updated(callback)) + task = asyncio.create_task( + subscription_client.subscribe.user_projects_updated(callback) + ) await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup input = ProjectCreateInput(name=None, description=None, visibility=None) - created = client.project.create(input) + created = subscription_client.project.create(input) await asyncio.sleep(WAIT_PERIOD) # Give time for subscription to be triggered @@ -74,10 +84,11 @@ def callback(d: UserProjectsUpdatedMessage): assert message.type == UserProjectsUpdatedMessageType.ADDED assert isinstance(message.project, Project) task.cancel() + await task @pytest.mark.asyncio async def test_project_models_updated( - self, client: SpeckleClient, test_project: Project + self, subscription_client: SpeckleClient, test_project: Project ) -> None: message: Optional[ProjectModelsUpdatedMessage] = None @@ -88,7 +99,9 @@ def callback(d: ProjectModelsUpdatedMessage): message = d task = asyncio.create_task( - client.subscribe.project_models_updated(callback, test_project.id) + subscription_client.subscribe.project_models_updated( + callback, test_project.id + ) ) await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup @@ -96,7 +109,7 @@ def callback(d: ProjectModelsUpdatedMessage): input = CreateModelInput( name="my model", description="myDescription", projectId=test_project.id ) - created = client.model.create(input) + created = subscription_client.model.create(input) await asyncio.sleep(WAIT_PERIOD) # Give time for subscription to be triggered @@ -105,10 +118,11 @@ def callback(d: ProjectModelsUpdatedMessage): assert message.type == ProjectModelsUpdatedMessageType.CREATED assert isinstance(message.model, Model) task.cancel() + await task @pytest.mark.asyncio async def test_project_updated( - self, client: SpeckleClient, test_project: Project + self, subscription_client: SpeckleClient, test_project: Project ) -> None: message: Optional[ProjectUpdatedMessage] = None @@ -119,13 +133,13 @@ def callback(d: ProjectUpdatedMessage): message = d task = asyncio.create_task( - client.subscribe.project_updated(callback, test_project.id) + subscription_client.subscribe.project_updated(callback, test_project.id) ) await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup input = ProjectUpdateInput(id=test_project.id, name="This is my new name") - created = client.project.update(input) + created = subscription_client.project.update(input) await asyncio.sleep(WAIT_PERIOD) # Give time for subscription to be triggered @@ -134,11 +148,12 @@ def callback(d: ProjectUpdatedMessage): assert message.type == ProjectUpdatedMessageType.UPDATED assert isinstance(message.project, Project) task.cancel() + await task @pytest.mark.asyncio async def test_project_versions_updated( self, - client: SpeckleClient, + subscription_client: SpeckleClient, test_project: Project, test_model: Model, ) -> None: @@ -151,17 +166,20 @@ def callback(d: ProjectVersionsUpdatedMessage): message = d task = asyncio.create_task( - client.subscribe.project_versions_updated(callback, test_project.id) + subscription_client.subscribe.project_versions_updated( + callback, test_project.id + ) ) await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup - created = create_version(client, test_project.id, test_model.id) + created = create_version(subscription_client, test_project.id, test_model.id) await asyncio.sleep(WAIT_PERIOD) # Give time for subscription to be triggered assert isinstance(message, ProjectVersionsUpdatedMessage) assert message.id == created.id - assert message.type == ProjectVersionsUpdatedMessageType.UPDATED + assert message.type == ProjectVersionsUpdatedMessageType.CREATED assert isinstance(message.version, Version) task.cancel() + await task diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 2afb413c..31910c01 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -6,8 +6,8 @@ import pytest import requests +from specklepy.api.client import SpeckleClient from specklepy.core.api import operations -from specklepy.core.api.client import SpeckleClient from specklepy.core.api.inputs.version_inputs import CreateVersionInput from specklepy.core.api.models import Stream from specklepy.core.api.new_models import Version @@ -84,10 +84,9 @@ def second_user_dict(host: str) -> Dict[str, str]: return seed_user(host) -@pytest.fixture(scope="session") -def client(host: str, user_dict: Dict[str, str]) -> SpeckleClient: +def create_client(host: str, token: str) -> SpeckleClient: client = SpeckleClient(host=host, use_ssl=False) - client.authenticate_with_token(user_dict["token"]) + client.authenticate_with_token(token) user = client.active_user.get() assert user client.account.userInfo.id = user.id @@ -98,18 +97,14 @@ def client(host: str, user_dict: Dict[str, str]) -> SpeckleClient: return client +@pytest.fixture(scope="session") +def client(host: str, user_dict: Dict[str, str]) -> SpeckleClient: + return create_client(host, user_dict["token"]) + + @pytest.fixture(scope="session") def second_client(host: str, second_user_dict: Dict[str, str]): - client = SpeckleClient(host=host, use_ssl=False) - client.authenticate_with_token(second_user_dict["token"]) - user = client.active_user.get() - assert user - client.account.userInfo.id = user.id - client.account.userInfo.email = user.email - client.account.userInfo.name = user.name - client.account.userInfo.company = user.company - client.account.userInfo.avatar = user.avatar - return client + return create_client(host, second_user_dict["token"]) @pytest.fixture(scope="session") From 7e09d4f4cec6aabbb2750adfa3dc08f01433c93e Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:10:55 +0000 Subject: [PATCH 16/26] pr clean up --- src/specklepy/api/client.py | 9 ++- src/specklepy/core/api/client.py | 18 ++++- src/specklepy/core/api/enums.py | 18 ----- .../core/api/inputs/comment_inputs.py | 26 ------- src/specklepy/core/api/inputs/model_inputs.py | 8 +-- .../core/api/inputs/project_inputs.py | 24 ++----- .../core/api/inputs/subscription_inputs.py | 9 --- src/specklepy/core/api/new_models.py | 71 ------------------- src/specklepy/core/api/resources/branch.py | 2 +- src/specklepy/core/api/resources/commit.py | 2 +- src/specklepy/logging/metrics.py | 3 - 11 files changed, 33 insertions(+), 157 deletions(-) delete mode 100644 src/specklepy/core/api/inputs/comment_inputs.py delete mode 100644 src/specklepy/core/api/inputs/subscription_inputs.py diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index 7167f42b..ec704adf 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -1,9 +1,8 @@ from deprecated import deprecated from specklepy.api.credentials import Account -from specklepy.api.resources import branch, commit, object, server, stream, user +from specklepy.api.resources import branch, commit, object, server, stream, subscriptions, user -# TODO: re-reference core.api resources from specklepy.api.resources.active_user_resource import ActiveUserResource from specklepy.api.resources.model_resource import ModelResource from specklepy.api.resources.other_user_resource import OtherUserResource @@ -136,7 +135,11 @@ def _init_resources(self) -> None: self.object = object.Resource( account=self.account, basepath=self.url, client=self.httpclient ) - self.subscribe = self.subscription + self.subscribe = subscriptions.Resource( + account=self.account, + basepath=self.ws_url, + client=self.wsclient, + ) @deprecated( version="2.6.0", diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index 283c51af..830b1563 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -8,14 +8,22 @@ from gql.transport.requests import RequestsHTTPTransport from gql.transport.websockets import WebsocketsTransport -from specklepy.api.resources.project_invite_resource import ProjectInviteResource from specklepy.core.api import resources from specklepy.core.api.credentials import Account, get_account_from_token -from specklepy.core.api.resources import branch, commit, object, server, stream, user +from specklepy.core.api.resources import ( + branch, + commit, + object, + server, + stream, + subscriptions, + user, +) from specklepy.core.api.resources.active_user_resource import ActiveUserResource from specklepy.core.api.resources.model_resource import ModelResource from specklepy.core.api.resources.other_user_resource import OtherUserResource from specklepy.core.api.resources.project_resource import ProjectResource +from specklepy.core.api.resources.project_invite_resource import ProjectInviteResource from specklepy.core.api.resources.subscription_resource import SubscriptionResource from specklepy.core.api.resources.version_resource import VersionResource from specklepy.logging import metrics @@ -267,7 +275,11 @@ def _init_resources(self) -> None: self.object = object.Resource( account=self.account, basepath=self.url, client=self.httpclient ) - self.subscribe = self.subscription + self.subscribe = subscriptions.Resource( + account=self.account, + basepath=self.ws_url, + client=self.wsclient, + ) def __getattr__(self, name): try: diff --git a/src/specklepy/core/api/enums.py b/src/specklepy/core/api/enums.py index 46cebfb8..3548d1de 100644 --- a/src/specklepy/core/api/enums.py +++ b/src/specklepy/core/api/enums.py @@ -1,12 +1,6 @@ from enum import Enum -class FileUploadConversionStatus(Enum): - QUEUED = 0 - PROCESSING = 1 - SUCCESS = 2 - ERROR = 3 - class ProjectVisibility(str, Enum): PRIVATE = "PRIVATE" @@ -14,23 +8,11 @@ class ProjectVisibility(str, Enum): UNLISTEd = "UNLISTED" -class ResourceType(str, Enum): - COMMIT = "COMMIT" - STREAM = "STREAM" - OBJECT = "OBJECT" - COMMENT = "COMMENT" - - class UserProjectsUpdatedMessageType(str, Enum): ADDED = "ADDED" REMOVED = "REMOVED" -class ProjectCommentsUpdatedMessageType(str, Enum): - ARCHIVED = "ARCHIVED" - CREATED = "CREATED" - UPDATED = "UPDATED" - class ProjectModelsUpdatedMessageType(str, Enum): CREATED = "CREATED" diff --git a/src/specklepy/core/api/inputs/comment_inputs.py b/src/specklepy/core/api/inputs/comment_inputs.py deleted file mode 100644 index 0406d3ab..00000000 --- a/src/specklepy/core/api/inputs/comment_inputs.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Any, Optional, Sequence - -from pydantic import BaseModel - - -class CommentContentInput(BaseModel): - blobIds: Optional[Sequence[str]] - doc: Any - - -class CreateCommentInput(BaseModel): - content: CommentContentInput - projectId: str - resourceIdString: str - screenshot: Optional[str] - viewerState: Optional[Any] - - -class EditCommentInput(BaseModel): - content: CommentContentInput - commentId: str - - -class CreateCommentReplyInput(BaseModel): - content: CommentContentInput - threadId: str diff --git a/src/specklepy/core/api/inputs/model_inputs.py b/src/specklepy/core/api/inputs/model_inputs.py index 90b8b072..0121fc02 100644 --- a/src/specklepy/core/api/inputs/model_inputs.py +++ b/src/specklepy/core/api/inputs/model_inputs.py @@ -5,7 +5,7 @@ class CreateModelInput(BaseModel): name: str - description: Optional[str] + description: Optional[str] = None projectId: str @@ -16,11 +16,11 @@ class DeleteModelInput(BaseModel): class UpdateModelInput(BaseModel): id: str - name: Optional[str] - description: Optional[str] + name: Optional[str] = None + description: Optional[str] = None projectId: str class ModelVersionsFilter(BaseModel): priorityIds: Sequence[str] - priorityIdsOnly: Optional[bool] + priorityIdsOnly: Optional[bool] = None diff --git a/src/specklepy/core/api/inputs/project_inputs.py b/src/specklepy/core/api/inputs/project_inputs.py index db30acb1..3cb19eab 100644 --- a/src/specklepy/core/api/inputs/project_inputs.py +++ b/src/specklepy/core/api/inputs/project_inputs.py @@ -5,12 +5,6 @@ from specklepy.core.api.models import ProjectVisibility -class ProjectCommentsFilter(BaseModel): - includeArchived: Optional[bool] - loadedVersionsOnly: Optional[bool] - resourceIdString: Optional[str] - - class ProjectCreateInput(BaseModel): name: Optional[str] description: Optional[str] @@ -31,18 +25,12 @@ class ProjectInviteUseInput(BaseModel): class ProjectModelsFilter(BaseModel): - contributors: Optional[Sequence[str]] - excludeIds: Optional[Sequence[str]] - ids: Optional[Sequence[str]] - onlyWithVersions: Optional[bool] - search: Optional[str] - sourceApps: Sequence[str] - - -class ProjectModelsTreeFilter(BaseModel): - contributors: Optional[Sequence[str]] - search: Optional[str] - sourceApps: Sequence[str] + contributors: Optional[Sequence[str]] = None + excludeIds: Optional[Sequence[str]] = None + ids: Optional[Sequence[str]] = None + onlyWithVersions: Optional[bool] = None + search: Optional[str] = None + sourceApps: Optional[Sequence[str]] = None class ProjectUpdateInput(BaseModel): diff --git a/src/specklepy/core/api/inputs/subscription_inputs.py b/src/specklepy/core/api/inputs/subscription_inputs.py deleted file mode 100644 index 99a40427..00000000 --- a/src/specklepy/core/api/inputs/subscription_inputs.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel - - -class ViewerUpdateTrackingTarget(BaseModel): - projectId: str - resourceIdString: str - loadedVersionsOnly: Optional[bool] = None diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/new_models.py index b9fe92b1..f1663860 100644 --- a/src/specklepy/core/api/new_models.py +++ b/src/specklepy/core/api/new_models.py @@ -4,13 +4,10 @@ from pydantic import BaseModel from specklepy.core.api.enums import ( - FileUploadConversionStatus, - ProjectCommentsUpdatedMessageType, ProjectModelsUpdatedMessageType, ProjectUpdatedMessageType, ProjectVersionsUpdatedMessageType, ProjectVisibility, - ResourceType, UserProjectsUpdatedMessageType, ) @@ -102,40 +99,6 @@ class ProjectCollaborator(BaseModel): user: LimitedUser -class ResourceIdentifier(BaseModel): - resourceId: str - resourceType: ResourceType - - -class ViewerResourceItem(BaseModel): - modelId: Optional[str] - objectId: str - versionId: Optional[str] - - -class ViewerResourceGroup(BaseModel): - identifier: str - items: List[ViewerResourceItem] - - -class Comment(BaseModel): - archived: bool - author: LimitedUser - authorId: str - createdAt: datetime - hasParent: bool - id: str - parent: Optional["Comment"] = None - rawText: str - replies: ResourceCollection["Comment"] - replyAuthors: ResourceCollection[LimitedUser] - resources: List[ResourceIdentifier] - screenshot: Optional[str] - updatedAt: datetime - viewedAt: Optional[datetime] - viewerResources: List[ViewerResourceItem] - - class Version(BaseModel): authorUser: Optional[LimitedUser] createdAt: datetime @@ -146,34 +109,6 @@ class Version(BaseModel): sourceApplication: Optional[str] -class ModelsTreeItem(BaseModel): - children: List["ModelsTreeItem"] - fullName: str - hasChildren: bool - id: str - model: Optional["Model"] = None - name: str - updatedAt: datetime - - -class FileUpload(BaseModel): - convertedCommitId: Optional[str] - convertedLastUpdate: datetime - convertedMessage: Optional[str] - convertedStatus: FileUploadConversionStatus - convertedVersionId: Optional[str] - fileName: str - fileSize: int - fileType: str - id: str - model: Optional["Model"] = None - modelName: str - projectId: str - uploadComplete: bool - uploadDate: datetime - userId: str - - class Model(BaseModel): author: LimitedUser createdAt: datetime @@ -226,12 +161,6 @@ class UserProjectsUpdatedMessage(BaseModel): project: Optional[Project] -class ProjectCommentsUpdatedMessage(BaseModel): - id: str - type: ProjectCommentsUpdatedMessageType - comment: Optional[Comment] - - class ProjectModelsUpdatedMessage(BaseModel): id: str type: ProjectModelsUpdatedMessageType diff --git a/src/specklepy/core/api/resources/branch.py b/src/specklepy/core/api/resources/branch.py index 710f674a..df633df2 100644 --- a/src/specklepy/core/api/resources/branch.py +++ b/src/specklepy/core/api/resources/branch.py @@ -50,7 +50,7 @@ def create( """ ) if len(name) < 3: - raise SpeckleException(message="Branch Name must be at least 3 characters") + return SpeckleException(message="Branch Name must be at least 3 characters") params = { "branch": { "streamId": stream_id, diff --git a/src/specklepy/core/api/resources/commit.py b/src/specklepy/core/api/resources/commit.py index cfd58ddf..a17ced47 100644 --- a/src/specklepy/core/api/resources/commit.py +++ b/src/specklepy/core/api/resources/commit.py @@ -144,7 +144,7 @@ def create( { commitCreate(commit: $commit)} """ ) - params: dict[str, dict[str, Any]] = { + params = { "commit": { "streamId": stream_id, "branchName": branch_name, diff --git a/src/specklepy/logging/metrics.py b/src/specklepy/logging/metrics.py index 273db2ab..3177ee75 100644 --- a/src/specklepy/logging/metrics.py +++ b/src/specklepy/logging/metrics.py @@ -15,7 +15,6 @@ This really helps us to deliver a better open source project and product! """ TRACK = True -TRACK_SDK = True HOST_APP = "python" HOST_APP_VERSION = f"python {'.'.join(map(str, sys.version_info[:2]))}" PLATFORMS = {"win32": "Windows", "cygwin": "Windows", "darwin": "Mac OS X"} @@ -68,8 +67,6 @@ def track( ): if not TRACK: return - if not TRACK_SDK and action == SDK: - return try: initialise_tracker(account) event_params = { From b7933e00880abd0b7046d38b3a473d92461d257c Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:11:36 +0000 Subject: [PATCH 17/26] pre-commit stuff --- src/specklepy/api/client.py | 11 +++++++++-- src/specklepy/core/api/client.py | 2 +- src/specklepy/core/api/enums.py | 2 -- src/specklepy/core/api/resources/commit.py | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index ec704adf..f227a3fb 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -1,8 +1,15 @@ from deprecated import deprecated from specklepy.api.credentials import Account -from specklepy.api.resources import branch, commit, object, server, stream, subscriptions, user - +from specklepy.api.resources import ( + branch, + commit, + object, + server, + stream, + subscriptions, + user, +) from specklepy.api.resources.active_user_resource import ActiveUserResource from specklepy.api.resources.model_resource import ModelResource from specklepy.api.resources.other_user_resource import OtherUserResource diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index 830b1563..4ca9d173 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -22,8 +22,8 @@ from specklepy.core.api.resources.active_user_resource import ActiveUserResource from specklepy.core.api.resources.model_resource import ModelResource from specklepy.core.api.resources.other_user_resource import OtherUserResource -from specklepy.core.api.resources.project_resource import ProjectResource from specklepy.core.api.resources.project_invite_resource import ProjectInviteResource +from specklepy.core.api.resources.project_resource import ProjectResource from specklepy.core.api.resources.subscription_resource import SubscriptionResource from specklepy.core.api.resources.version_resource import VersionResource from specklepy.logging import metrics diff --git a/src/specklepy/core/api/enums.py b/src/specklepy/core/api/enums.py index 3548d1de..1455b415 100644 --- a/src/specklepy/core/api/enums.py +++ b/src/specklepy/core/api/enums.py @@ -1,7 +1,6 @@ from enum import Enum - class ProjectVisibility(str, Enum): PRIVATE = "PRIVATE" PUBLIC = "PUBLIC" @@ -13,7 +12,6 @@ class UserProjectsUpdatedMessageType(str, Enum): REMOVED = "REMOVED" - class ProjectModelsUpdatedMessageType(str, Enum): CREATED = "CREATED" DELETED = "DELETED" diff --git a/src/specklepy/core/api/resources/commit.py b/src/specklepy/core/api/resources/commit.py index a17ced47..b2da0e79 100644 --- a/src/specklepy/core/api/resources/commit.py +++ b/src/specklepy/core/api/resources/commit.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, Union +from typing import List, Optional, Union from deprecated import deprecated from gql import gql From f843bb0c89a39537d9f446a8a03437cb0e23f988 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:32:30 +0000 Subject: [PATCH 18/26] Fixed up client auth error handling --- src/specklepy/core/api/client.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index 4ca9d173..d15358f4 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -187,16 +187,17 @@ def _set_up_client(self) -> None: raise user_or_error.exception else: raise user_or_error - except TransportServerError as ex: - if ex.code == 403: - warn( - SpeckleWarning( - "Possibly invalid token - could not authenticate Speckle Client" - f" for server {self.url}" + except SpeckleException as ex: + if isinstance(ex.exception, TransportServerError): + if ex.exception.code == 403: + warn( + SpeckleWarning( + "Possibly invalid token - could not authenticate Speckle Client" + f" for server {self.url}" + ) ) - ) - else: - raise ex + else: + raise ex def execute_query(self, query: str) -> Dict: return self.httpclient.execute(query) From ab41d3cbe0b0ae05a424588c5acd87383083acef Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:41:37 +0000 Subject: [PATCH 19/26] last fixes --- src/specklepy/core/api/resource.py | 2 ++ tests/integration/client/test_project_invite_resource.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/specklepy/core/api/resource.py b/src/specklepy/core/api/resource.py index bf15b5a5..ad992af2 100644 --- a/src/specklepy/core/api/resource.py +++ b/src/specklepy/core/api/resource.py @@ -101,6 +101,8 @@ def make_request( parse_response: bool = True, ) -> Any: """Executes the GraphQL query""" + # This method has quite complex and ambiguous typing, and counter-intuitive error handling + # We are going to phase it out in favour of `make_request_and_parse_response` try: with self.__lock: response = self.client.execute(query, variable_values=params) diff --git a/tests/integration/client/test_project_invite_resource.py b/tests/integration/client/test_project_invite_resource.py index 94893340..f0e6878c 100644 --- a/tests/integration/client/test_project_invite_resource.py +++ b/tests/integration/client/test_project_invite_resource.py @@ -13,7 +13,7 @@ from specklepy.core.api.new_models import LimitedUser, ProjectWithTeam -@pytest.mark.run(order=1) +@pytest.mark.run() class TestProjectInviteResource: @pytest.fixture def project(self, client: SpeckleClient): From be8fae3b1ccf1457ca1fc5243b41e081a442e558 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:55:15 +0000 Subject: [PATCH 20/26] removed unused subscription functions --- .../api/resources/subscription_resource.py | 109 ------------------ 1 file changed, 109 deletions(-) diff --git a/src/specklepy/core/api/resources/subscription_resource.py b/src/specklepy/core/api/resources/subscription_resource.py index 119a1245..63a593c1 100644 --- a/src/specklepy/core/api/resources/subscription_resource.py +++ b/src/specklepy/core/api/resources/subscription_resource.py @@ -48,7 +48,6 @@ def __init__(self, account, basepath, client) -> None: name=NAME, ) - @check_wsclient async def user_projects_updated( self, callback: Callable[[UserProjectsUpdatedMessage], None] ) -> None: @@ -82,7 +81,6 @@ async def user_projects_updated( callback=lambda d: callback(d.data), ) - @check_wsclient async def project_models_updated( self, callback: Callable[[ProjectModelsUpdatedMessage], None], @@ -127,7 +125,6 @@ async def project_models_updated( callback=lambda d: callback(d.data), ) - @check_wsclient async def project_updated( self, callback: Callable[[ProjectUpdatedMessage], None], @@ -165,7 +162,6 @@ async def project_updated( callback=lambda d: callback(d.data), ) - @check_wsclient async def project_versions_updated( self, callback: Callable[[ProjectVersionsUpdatedMessage], None], @@ -209,112 +205,7 @@ async def project_versions_updated( callback=lambda d: callback(d.data), ) - @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) - @check_wsclient - async def stream_added(self, callback: Optional[Callable] = None): - """Subscribes to new stream added event for your profile. - Use this to display an up-to-date list of streams. - - Arguments: - callback {Callable[Stream]} -- a function that takes the updated stream - as an argument and executes each time a stream is added - - Returns: - Stream -- the update stream - """ - query = gql( - """ - subscription { userStreamAdded } - """ - ) - return await self.subscribe( - query=query, callback=callback, return_type="userStreamAdded", schema=Stream - ) - - @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) - @check_wsclient - async def stream_updated(self, id: str, callback: Optional[Callable] = None): - """ - Subscribes to stream updated event. - Use this in clients/components that pertain only to this stream. - - Arguments: - id {str} -- the stream id of the stream to subscribe to - callback {Callable[Stream]} - -- a function that takes the updated stream - as an argument and executes each time the stream is updated - - Returns: - Stream -- the update stream - """ - query = gql( - """ - subscription Update($id: String!) { streamUpdated(streamId: $id) } - """ - ) - params = {"id": id} - - return await self.subscribe( - query=query, - params=params, - callback=callback, - return_type="streamUpdated", - schema=Stream, - ) - - @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) @check_wsclient - async def stream_removed(self, callback: Optional[Callable] = None): - """Subscribes to stream removed event for your profile. - Use this to display an up-to-date list of streams for your profile. - NOTE: If someone revokes your permissions on a stream, - this subscription will be triggered with an extra value of revokedBy - in the payload. - - Arguments: - callback {Callable[Dict]} - -- a function that takes the returned dict as an argument - and executes each time a stream is removed - - Returns: - dict -- dict containing 'id' of stream removed and optionally 'revokedBy' - """ - query = gql( - """ - subscription { userStreamRemoved } - """ - ) - - return await self.subscribe( - query=query, - callback=callback, - return_type="userStreamRemoved", - parse_response=False, - ) - - @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) - @check_wsclient - async def subscribe( - self, - query: DocumentNode, - params: Optional[Dict] = None, - callback: Optional[Callable] = None, - return_type: Optional[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 - 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) - if parse_response: - res = self._parse_response(response=res, schema=schema) - if callback is not None: - callback(res) - else: - return res - async def subscribe_2( self, response_type: Type[TEventArgs], From 1b45f5069731a4e4f45b2f921f4492813e05254c Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:02:38 +0000 Subject: [PATCH 21/26] removed dead code in client --- src/specklepy/core/api/client.py | 7 +------ src/specklepy/core/api/resources/subscription_resource.py | 5 +---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index d15358f4..8bc8ca99 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -181,12 +181,7 @@ def _set_up_client(self) -> None: self._init_resources() try: - user_or_error = self.active_user.get() - if isinstance(user_or_error, SpeckleException): - if isinstance(user_or_error.exception, TransportServerError): - raise user_or_error.exception - else: - raise user_or_error + _ = self.active_user.get() except SpeckleException as ex: if isinstance(ex.exception, TransportServerError): if ex.exception.code == 403: diff --git a/src/specklepy/core/api/resources/subscription_resource.py b/src/specklepy/core/api/resources/subscription_resource.py index 63a593c1..447dd2ef 100644 --- a/src/specklepy/core/api/resources/subscription_resource.py +++ b/src/specklepy/core/api/resources/subscription_resource.py @@ -1,13 +1,11 @@ from functools import wraps -from typing import Any, Callable, Dict, List, Optional, Sequence, Type, Union +from typing import Any, Callable, Dict, Optional, Sequence, Type -from deprecated import deprecated from gql import gql from graphql import DocumentNode from pydantic import BaseModel from typing_extensions import TypeVar -from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION from specklepy.core.api.new_models import ( ProjectModelsUpdatedMessage, ProjectUpdatedMessage, @@ -15,7 +13,6 @@ UserProjectsUpdatedMessage, ) from specklepy.core.api.resource import ResourceBase -from specklepy.core.api.resources.stream import Stream from specklepy.core.api.responses import DataResponse from specklepy.logging.exceptions import SpeckleException From c78a780e85ed271b297657f462d37499ee86a86a Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:42:43 +0000 Subject: [PATCH 22/26] Use the correct subscription resource in integration tests --- tests/integration/client/test_subscription_resource.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/client/test_subscription_resource.py b/tests/integration/client/test_subscription_resource.py index dd72c359..0a4af5e2 100644 --- a/tests/integration/client/test_subscription_resource.py +++ b/tests/integration/client/test_subscription_resource.py @@ -69,7 +69,7 @@ def callback(d: UserProjectsUpdatedMessage): message = d task = asyncio.create_task( - subscription_client.subscribe.user_projects_updated(callback) + subscription_client.subscription.user_projects_updated(callback) ) await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup @@ -99,7 +99,7 @@ def callback(d: ProjectModelsUpdatedMessage): message = d task = asyncio.create_task( - subscription_client.subscribe.project_models_updated( + subscription_client.subscription.project_models_updated( callback, test_project.id ) ) @@ -133,7 +133,7 @@ def callback(d: ProjectUpdatedMessage): message = d task = asyncio.create_task( - subscription_client.subscribe.project_updated(callback, test_project.id) + subscription_client.subscription.project_updated(callback, test_project.id) ) await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup @@ -166,7 +166,7 @@ def callback(d: ProjectVersionsUpdatedMessage): message = d task = asyncio.create_task( - subscription_client.subscribe.project_versions_updated( + subscription_client.subscription.project_versions_updated( callback, test_project.id ) ) From eae60160a117e88a361f21286b4230133986a824 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:45:30 +0000 Subject: [PATCH 23/26] reverted changes to old subscription resource --- .../core/api/resources/subscriptions.py | 144 +++++++++++++++++- 1 file changed, 137 insertions(+), 7 deletions(-) diff --git a/src/specklepy/core/api/resources/subscriptions.py b/src/specklepy/core/api/resources/subscriptions.py index bc9f1a29..055effdb 100644 --- a/src/specklepy/core/api/resources/subscriptions.py +++ b/src/specklepy/core/api/resources/subscriptions.py @@ -1,11 +1,141 @@ +from functools import wraps +from typing import Callable, Dict, List, Optional, Union + from deprecated import deprecated +from gql import gql +from graphql import DocumentNode + +from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION +from specklepy.core.api.resource import ResourceBase +from specklepy.core.api.resources.stream import Stream +from specklepy.logging.exceptions import SpeckleException + +NAME = "subscribe" + + +def check_wsclient(function): + @wraps(function) + async def check_wsclient_wrapper(self, *args, **kwargs): + if self.client is None: + raise SpeckleException( + "You must authenticate before you can subscribe to events" + ) + else: + return await function(self, *args, **kwargs) + + return check_wsclient_wrapper + + +class Resource(ResourceBase): + """API Access class for subscriptions""" + + def __init__(self, account, basepath, client) -> None: + super().__init__( + account=account, + basepath=basepath, + client=client, + name=NAME, + ) + + @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) + @check_wsclient + async def stream_added(self, callback: Optional[Callable] = None): + """Subscribes to new stream added event for your profile. + Use this to display an up-to-date list of streams. + + Arguments: + callback {Callable[Stream]} -- a function that takes the updated stream + as an argument and executes each time a stream is added + + Returns: + Stream -- the update stream + """ + query = gql( + """ + subscription { userStreamAdded } + """ + ) + return await self.subscribe( + query=query, callback=callback, return_type="userStreamAdded", schema=Stream + ) + + @check_wsclient + async def stream_updated(self, id: str, callback: Optional[Callable] = None): + """ + Subscribes to stream updated event. + Use this in clients/components that pertain only to this stream. + + Arguments: + id {str} -- the stream id of the stream to subscribe to + callback {Callable[Stream]} + -- a function that takes the updated stream + as an argument and executes each time the stream is updated + + Returns: + Stream -- the update stream + """ + query = gql( + """ + subscription Update($id: String!) { streamUpdated(streamId: $id) } + """ + ) + params = {"id": id} + + return await self.subscribe( + query=query, + params=params, + callback=callback, + return_type="streamUpdated", + schema=Stream, + ) + + @check_wsclient + async def stream_removed(self, callback: Optional[Callable] = None): + """Subscribes to stream removed event for your profile. + Use this to display an up-to-date list of streams for your profile. + NOTE: If someone revokes your permissions on a stream, + this subscription will be triggered with an extra value of revokedBy + in the payload. + + Arguments: + callback {Callable[Dict]} + -- a function that takes the returned dict as an argument + and executes each time a stream is removed -from specklepy.core.api.models import FE1_DEPRECATION_VERSION -from specklepy.core.api.resources.subscription_resource import SubscriptionResource + Returns: + dict -- dict containing 'id' of stream removed and optionally 'revokedBy' + """ + query = gql( + """ + subscription { userStreamRemoved } + """ + ) + return await self.subscribe( + query=query, + callback=callback, + return_type="userStreamRemoved", + parse_response=False, + ) -@deprecated( - reason="Class renamed to SubscriptionResource", version=FE1_DEPRECATION_VERSION -) -class Resource(SubscriptionResource): - pass + @check_wsclient + async def subscribe( + self, + query: DocumentNode, + params: Optional[Dict] = None, + callback: Optional[Callable] = None, + return_type: Optional[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 + 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) + if parse_response: + res = self._parse_response(response=res, schema=schema) + if callback is not None: + callback(res) + else: + return res From e978e4f632cbb7cd007d32d139eded8874d30cc6 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:58:44 +0000 Subject: [PATCH 24/26] Re-export deprecated resources and models --- src/specklepy/api/client.py | 15 ++-- .../api/{models.py => models/__init__.py} | 0 src/specklepy/api/resources/__init__.py | 48 ++++++++++--- .../{ => current}/active_user_resource.py | 10 +-- .../resources/{ => current}/model_resource.py | 4 +- .../{ => current}/other_user_resource.py | 10 +-- .../{ => current}/project_invite_resource.py | 6 +- .../{ => current}/project_resource.py | 7 +- .../{server.py => current/server_resource.py} | 4 +- .../{ => current}/subscription_resource.py | 6 +- .../{ => current}/version_resource.py | 6 +- .../resources/{ => deprecated}/active_user.py | 4 +- .../api/resources/{ => deprecated}/branch.py | 7 +- .../api/resources/{ => deprecated}/commit.py | 7 +- .../api/resources/{ => deprecated}/object.py | 7 +- .../resources/{ => deprecated}/other_user.py | 4 +- .../api/resources/deprecated/server.py | 9 +++ .../api/resources/{ => deprecated}/stream.py | 7 +- .../{ => deprecated}/subscriptions.py | 11 ++- .../api/resources/{ => deprecated}/user.py | 2 +- src/specklepy/core/api/client.py | 18 ++--- src/specklepy/core/api/credentials.py | 2 +- .../core/api/inputs/project_inputs.py | 2 +- src/specklepy/core/api/models/__init__.py | 71 +++++++++++++++++++ .../api/{new_models.py => models/current.py} | 55 ++++++-------- .../api/{models.py => models/deprecated.py} | 23 ------ .../core/api/models/subscription_messages.py | 36 ++++++++++ src/specklepy/core/api/resources/__init__.py | 43 +++++++++++ .../{ => current}/active_user_resource.py | 8 ++- .../resources/{ => current}/model_resource.py | 2 +- .../{ => current}/other_user_resource.py | 8 ++- .../{ => current}/project_invite_resource.py | 2 +- .../{ => current}/project_resource.py | 3 +- .../{server.py => current/server_resource.py} | 2 +- .../{ => current}/subscription_resource.py | 2 +- .../{ => current}/version_resource.py | 2 +- .../resources/{ => deprecated}/active_user.py | 4 +- .../api/resources/{ => deprecated}/branch.py | 2 +- .../api/resources/{ => deprecated}/commit.py | 2 +- .../api/resources/{ => deprecated}/object.py | 0 .../resources/{ => deprecated}/other_user.py | 4 +- .../core/api/resources/deprecated/server.py | 11 +++ .../api/resources/{ => deprecated}/stream.py | 6 +- .../{ => deprecated}/subscriptions.py | 7 +- .../api/resources/{ => deprecated}/user.py | 0 tests/integration/client/test_active_user.py | 5 +- .../client/test_active_user_resource.py | 4 +- .../integration/client/test_model_resource.py | 4 +- .../client/test_other_user_resource.py | 2 +- .../client/test_project_invite_resource.py | 4 +- .../client/test_project_resource.py | 2 +- .../client/test_subscription_resource.py | 2 +- .../client/test_version_resource.py | 4 +- tests/integration/conftest.py | 4 +- tests/unit/test_account_server_migration.py | 2 +- 55 files changed, 354 insertions(+), 168 deletions(-) rename src/specklepy/api/{models.py => models/__init__.py} (100%) rename src/specklepy/api/resources/{ => current}/active_user_resource.py (97%) rename src/specklepy/api/resources/{ => current}/model_resource.py (93%) rename src/specklepy/api/resources/{ => current}/other_user_resource.py (95%) rename src/specklepy/api/resources/{ => current}/project_invite_resource.py (88%) rename src/specklepy/api/resources/{ => current}/project_resource.py (90%) rename src/specklepy/api/resources/{server.py => current/server_resource.py} (95%) rename src/specklepy/api/resources/{ => current}/subscription_resource.py (92%) rename src/specklepy/api/resources/{ => current}/version_resource.py (92%) rename src/specklepy/api/resources/{ => deprecated}/active_user.py (58%) rename src/specklepy/api/resources/{ => deprecated}/branch.py (95%) rename src/specklepy/api/resources/{ => deprecated}/commit.py (96%) rename src/specklepy/api/resources/{ => deprecated}/object.py (91%) rename src/specklepy/api/resources/{ => deprecated}/other_user.py (60%) create mode 100644 src/specklepy/api/resources/deprecated/server.py rename src/specklepy/api/resources/{ => deprecated}/stream.py (98%) rename src/specklepy/api/resources/{ => deprecated}/subscriptions.py (92%) rename src/specklepy/api/resources/{ => deprecated}/user.py (98%) create mode 100644 src/specklepy/core/api/models/__init__.py rename src/specklepy/core/api/{new_models.py => models/current.py} (82%) rename src/specklepy/core/api/{models.py => models/deprecated.py} (89%) create mode 100644 src/specklepy/core/api/models/subscription_messages.py rename src/specklepy/core/api/resources/{ => current}/active_user_resource.py (99%) rename src/specklepy/core/api/resources/{ => current}/model_resource.py (98%) rename src/specklepy/core/api/resources/{ => current}/other_user_resource.py (98%) rename src/specklepy/core/api/resources/{ => current}/project_invite_resource.py (98%) rename src/specklepy/core/api/resources/{ => current}/project_resource.py (98%) rename src/specklepy/core/api/resources/{server.py => current/server_resource.py} (99%) rename src/specklepy/core/api/resources/{ => current}/subscription_resource.py (99%) rename src/specklepy/core/api/resources/{ => current}/version_resource.py (98%) rename src/specklepy/core/api/resources/{ => deprecated}/active_user.py (62%) rename src/specklepy/core/api/resources/{ => deprecated}/branch.py (99%) rename src/specklepy/core/api/resources/{ => deprecated}/commit.py (99%) rename src/specklepy/core/api/resources/{ => deprecated}/object.py (100%) rename src/specklepy/core/api/resources/{ => deprecated}/other_user.py (62%) create mode 100644 src/specklepy/core/api/resources/deprecated/server.py rename src/specklepy/core/api/resources/{ => deprecated}/stream.py (99%) rename src/specklepy/core/api/resources/{ => deprecated}/subscriptions.py (96%) rename src/specklepy/core/api/resources/{ => deprecated}/user.py (100%) diff --git a/src/specklepy/api/client.py b/src/specklepy/api/client.py index f227a3fb..5755cc5c 100644 --- a/src/specklepy/api/client.py +++ b/src/specklepy/api/client.py @@ -2,6 +2,13 @@ from specklepy.api.credentials import Account from specklepy.api.resources import ( + ActiveUserResource, + ModelResource, + OtherUserResource, + ProjectInviteResource, + ProjectResource, + SubscriptionResource, + VersionResource, branch, commit, object, @@ -10,13 +17,6 @@ subscriptions, user, ) -from specklepy.api.resources.active_user_resource import ActiveUserResource -from specklepy.api.resources.model_resource import ModelResource -from specklepy.api.resources.other_user_resource import OtherUserResource -from specklepy.api.resources.project_invite_resource import ProjectInviteResource -from specklepy.api.resources.project_resource import ProjectResource -from specklepy.api.resources.subscription_resource import SubscriptionResource -from specklepy.api.resources.version_resource import VersionResource from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient from specklepy.logging import metrics @@ -119,6 +119,7 @@ def _init_resources(self) -> None: account=self.account, basepath=self.ws_url, client=self.wsclient, + # todo: why doesn't this take a server version ) # Deprecated Resources self.user = user.Resource( diff --git a/src/specklepy/api/models.py b/src/specklepy/api/models/__init__.py similarity index 100% rename from src/specklepy/api/models.py rename to src/specklepy/api/models/__init__.py diff --git a/src/specklepy/api/resources/__init__.py b/src/specklepy/api/resources/__init__.py index 13beff43..c154ff80 100644 --- a/src/specklepy/api/resources/__init__.py +++ b/src/specklepy/api/resources/__init__.py @@ -1,9 +1,41 @@ -import pkgutil -import sys -from importlib import import_module +from specklepy.api.resources.current.active_user_resource import ActiveUserResource +from specklepy.api.resources.current.model_resource import ModelResource +from specklepy.api.resources.current.other_user_resource import OtherUserResource +from specklepy.api.resources.current.project_invite_resource import ( + ProjectInviteResource, +) +from specklepy.api.resources.current.project_resource import ProjectResource +from specklepy.api.resources.current.server_resource import ServerResource +from specklepy.api.resources.current.subscription_resource import SubscriptionResource +from specklepy.api.resources.current.version_resource import VersionResource +from specklepy.api.resources.deprecated import ( + active_user, + branch, + commit, + object, + other_user, + server, + stream, + subscriptions, + user, +) -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) +__all__ = [ + "ActiveUserResource", + "ModelResource", + "OtherUserResource", + "ProjectInviteResource", + "ProjectResource", + "ServerResource", + "SubscriptionResource", + "VersionResource", + "active_user", + "branch", + "commit", + "object", + "other_user", + "server", + "stream", + "subscriptions", + "user", +] diff --git a/src/specklepy/api/resources/active_user_resource.py b/src/specklepy/api/resources/current/active_user_resource.py similarity index 97% rename from src/specklepy/api/resources/active_user_resource.py rename to src/specklepy/api/resources/current/active_user_resource.py index 2d2d8493..9716b3f5 100644 --- a/src/specklepy/api/resources/active_user_resource.py +++ b/src/specklepy/api/resources/current/active_user_resource.py @@ -6,16 +6,16 @@ from specklepy.core.api.inputs.project_inputs import UserProjectsFilter from specklepy.core.api.inputs.user_inputs import UserUpdateInput from specklepy.core.api.models import ( - FE1_DEPRECATION_REASON, - FE1_DEPRECATION_VERSION, PendingStreamCollaborator, + Project, ResourceCollection, User, ) -from specklepy.core.api.new_models import Project -from specklepy.core.api.resources.active_user_resource import ( - ActiveUserResource as CoreResource, +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, ) +from specklepy.core.api.resources import ActiveUserResource as CoreResource from specklepy.logging import metrics diff --git a/src/specklepy/api/resources/model_resource.py b/src/specklepy/api/resources/current/model_resource.py similarity index 93% rename from src/specklepy/api/resources/model_resource.py rename to src/specklepy/api/resources/current/model_resource.py index cfd71d08..e97d9f64 100644 --- a/src/specklepy/api/resources/model_resource.py +++ b/src/specklepy/api/resources/current/model_resource.py @@ -7,8 +7,8 @@ UpdateModelInput, ) from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter -from specklepy.core.api.new_models import Model, ModelWithVersions, ResourceCollection -from specklepy.core.api.resources.model_resource import ModelResource as CoreResource +from specklepy.core.api.models import Model, ModelWithVersions, ResourceCollection +from specklepy.core.api.resources import ModelResource as CoreResource from specklepy.logging import metrics diff --git a/src/specklepy/api/resources/other_user_resource.py b/src/specklepy/api/resources/current/other_user_resource.py similarity index 95% rename from src/specklepy/api/resources/other_user_resource.py rename to src/specklepy/api/resources/current/other_user_resource.py index 163ed097..9ab82486 100644 --- a/src/specklepy/api/resources/other_user_resource.py +++ b/src/specklepy/api/resources/current/other_user_resource.py @@ -4,15 +4,15 @@ from deprecated import deprecated from specklepy.core.api.models import ( - FE1_DEPRECATION_REASON, - FE1_DEPRECATION_VERSION, ActivityCollection, LimitedUser, + UserSearchResultCollection, ) -from specklepy.core.api.new_models import UserSearchResultCollection -from specklepy.core.api.resources.other_user_resource import ( - OtherUserResource as CoreResource, +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, ) +from specklepy.core.api.resources import OtherUserResource as CoreResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException diff --git a/src/specklepy/api/resources/project_invite_resource.py b/src/specklepy/api/resources/current/project_invite_resource.py similarity index 88% rename from src/specklepy/api/resources/project_invite_resource.py rename to src/specklepy/api/resources/current/project_invite_resource.py index 114b00b3..9181085f 100644 --- a/src/specklepy/api/resources/project_invite_resource.py +++ b/src/specklepy/api/resources/current/project_invite_resource.py @@ -7,10 +7,8 @@ ProjectInviteCreateInput, ProjectInviteUseInput, ) -from specklepy.core.api.new_models import PendingStreamCollaborator, ProjectWithTeam -from specklepy.core.api.resources.project_invite_resource import ( - ProjectInviteResource as CoreResource, -) +from specklepy.core.api.models import PendingStreamCollaborator, ProjectWithTeam +from specklepy.core.api.resources import ProjectInviteResource as CoreResource from specklepy.logging import metrics diff --git a/src/specklepy/api/resources/project_resource.py b/src/specklepy/api/resources/current/project_resource.py similarity index 90% rename from src/specklepy/api/resources/project_resource.py rename to src/specklepy/api/resources/current/project_resource.py index dbfa3825..502aef8d 100644 --- a/src/specklepy/api/resources/project_resource.py +++ b/src/specklepy/api/resources/current/project_resource.py @@ -6,11 +6,8 @@ ProjectUpdateInput, ProjectUpdateRoleInput, ) -from specklepy.core.api.models import Project -from specklepy.core.api.new_models import ProjectWithModels, ProjectWithTeam -from specklepy.core.api.resources.project_resource import ( - ProjectResource as CoreResource, -) +from specklepy.core.api.models import Project, ProjectWithModels, ProjectWithTeam +from specklepy.core.api.resources import ProjectResource as CoreResource from specklepy.logging import metrics diff --git a/src/specklepy/api/resources/server.py b/src/specklepy/api/resources/current/server_resource.py similarity index 95% rename from src/specklepy/api/resources/server.py rename to src/specklepy/api/resources/current/server_resource.py index 1e3bb132..85ee78ec 100644 --- a/src/specklepy/api/resources/server.py +++ b/src/specklepy/api/resources/current/server_resource.py @@ -1,11 +1,11 @@ from typing import Any, Dict, List, Tuple from specklepy.api.models import ServerInfo -from specklepy.core.api.resources.server import Resource as CoreResource +from specklepy.core.api.resources import ServerResource as CoreResource from specklepy.logging import metrics -class Resource(CoreResource): +class ServerResource(CoreResource): """API Access class for the server""" def __init__(self, account, basepath, client) -> None: diff --git a/src/specklepy/api/resources/subscription_resource.py b/src/specklepy/api/resources/current/subscription_resource.py similarity index 92% rename from src/specklepy/api/resources/subscription_resource.py rename to src/specklepy/api/resources/current/subscription_resource.py index a102ad21..51f84366 100644 --- a/src/specklepy/api/resources/subscription_resource.py +++ b/src/specklepy/api/resources/current/subscription_resource.py @@ -3,15 +3,13 @@ from pydantic import BaseModel from typing_extensions import TypeVar -from specklepy.core.api.new_models import ( +from specklepy.core.api.models import ( ProjectModelsUpdatedMessage, ProjectUpdatedMessage, ProjectVersionsUpdatedMessage, UserProjectsUpdatedMessage, ) -from specklepy.core.api.resources.subscription_resource import ( - SubscriptionResource as CoreResource, -) +from specklepy.core.api.resources import SubscriptionResource as CoreResource from specklepy.logging import metrics TEventArgs = TypeVar("TEventArgs", bound=BaseModel) diff --git a/src/specklepy/api/resources/version_resource.py b/src/specklepy/api/resources/current/version_resource.py similarity index 92% rename from src/specklepy/api/resources/version_resource.py rename to src/specklepy/api/resources/current/version_resource.py index f2087518..f3d48da1 100644 --- a/src/specklepy/api/resources/version_resource.py +++ b/src/specklepy/api/resources/current/version_resource.py @@ -8,10 +8,8 @@ MoveVersionsInput, UpdateVersionInput, ) -from specklepy.core.api.new_models import ResourceCollection, Version -from specklepy.core.api.resources.version_resource import ( - VersionResource as CoreResource, -) +from specklepy.core.api.models import ResourceCollection, Version +from specklepy.core.api.resources import VersionResource as CoreResource from specklepy.logging import metrics diff --git a/src/specklepy/api/resources/active_user.py b/src/specklepy/api/resources/deprecated/active_user.py similarity index 58% rename from src/specklepy/api/resources/active_user.py rename to src/specklepy/api/resources/deprecated/active_user.py index d4d854a7..1fc40394 100644 --- a/src/specklepy/api/resources/active_user.py +++ b/src/specklepy/api/resources/deprecated/active_user.py @@ -1,7 +1,7 @@ from deprecated import deprecated -from specklepy.api.resources.active_user_resource import ActiveUserResource -from specklepy.core.api.models import FE1_DEPRECATION_VERSION +from specklepy.api.resources import ActiveUserResource +from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION @deprecated(reason="Renamed to ActiveUserResource", version=FE1_DEPRECATION_VERSION) diff --git a/src/specklepy/api/resources/branch.py b/src/specklepy/api/resources/deprecated/branch.py similarity index 95% rename from src/specklepy/api/resources/branch.py rename to src/specklepy/api/resources/deprecated/branch.py index 6cc46ceb..a96ff138 100644 --- a/src/specklepy/api/resources/branch.py +++ b/src/specklepy/api/resources/deprecated/branch.py @@ -3,8 +3,11 @@ from deprecated import deprecated from specklepy.api.models import Branch -from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION -from specklepy.core.api.resources.branch import Resource as CoreResource +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, +) +from specklepy.core.api.resources.deprecated.branch import Resource as CoreResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException diff --git a/src/specklepy/api/resources/commit.py b/src/specklepy/api/resources/deprecated/commit.py similarity index 96% rename from src/specklepy/api/resources/commit.py rename to src/specklepy/api/resources/deprecated/commit.py index de008826..91ebee44 100644 --- a/src/specklepy/api/resources/commit.py +++ b/src/specklepy/api/resources/deprecated/commit.py @@ -3,8 +3,11 @@ from deprecated import deprecated from specklepy.api.models import Commit -from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION -from specklepy.core.api.resources.commit import Resource as CoreResource +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, +) +from specklepy.core.api.resources.deprecated.commit import Resource as CoreResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException diff --git a/src/specklepy/api/resources/object.py b/src/specklepy/api/resources/deprecated/object.py similarity index 91% rename from src/specklepy/api/resources/object.py rename to src/specklepy/api/resources/deprecated/object.py index 4188e401..573e6f5a 100644 --- a/src/specklepy/api/resources/object.py +++ b/src/specklepy/api/resources/deprecated/object.py @@ -2,8 +2,11 @@ from deprecated import deprecated -from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION -from specklepy.core.api.resources.object import Resource as CoreResource +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, +) +from specklepy.core.api.resources.deprecated.object import Resource as CoreResource from specklepy.logging import metrics from specklepy.objects.base import Base diff --git a/src/specklepy/api/resources/other_user.py b/src/specklepy/api/resources/deprecated/other_user.py similarity index 60% rename from src/specklepy/api/resources/other_user.py rename to src/specklepy/api/resources/deprecated/other_user.py index 52f683f5..2bdc575a 100644 --- a/src/specklepy/api/resources/other_user.py +++ b/src/specklepy/api/resources/deprecated/other_user.py @@ -1,7 +1,7 @@ from deprecated import deprecated -from specklepy.api.resources.other_user_resource import OtherUserResource -from specklepy.core.api.models import FE1_DEPRECATION_VERSION +from specklepy.api.resources import OtherUserResource +from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION @deprecated(reason="Renamed to OtherUserResource", version=FE1_DEPRECATION_VERSION) diff --git a/src/specklepy/api/resources/deprecated/server.py b/src/specklepy/api/resources/deprecated/server.py new file mode 100644 index 00000000..f44203c5 --- /dev/null +++ b/src/specklepy/api/resources/deprecated/server.py @@ -0,0 +1,9 @@ +from deprecated import deprecated + +from specklepy.api.resources import ServerResource +from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION + + +@deprecated(reason="Renamed to ActiveUserResource", version=FE1_DEPRECATION_VERSION) +class Resource(ServerResource): + """Renamed to ServerResource""" diff --git a/src/specklepy/api/resources/stream.py b/src/specklepy/api/resources/deprecated/stream.py similarity index 98% rename from src/specklepy/api/resources/stream.py rename to src/specklepy/api/resources/deprecated/stream.py index 65ccc0ae..688290a6 100644 --- a/src/specklepy/api/resources/stream.py +++ b/src/specklepy/api/resources/deprecated/stream.py @@ -4,8 +4,11 @@ from deprecated import deprecated from specklepy.api.models import PendingStreamCollaborator, Stream -from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION -from specklepy.core.api.resources.stream import Resource as CoreResource +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, +) +from specklepy.core.api.resources.deprecated.stream import Resource as CoreResource from specklepy.logging import metrics diff --git a/src/specklepy/api/resources/subscriptions.py b/src/specklepy/api/resources/deprecated/subscriptions.py similarity index 92% rename from src/specklepy/api/resources/subscriptions.py rename to src/specklepy/api/resources/deprecated/subscriptions.py index 1cc77836..3530e744 100644 --- a/src/specklepy/api/resources/subscriptions.py +++ b/src/specklepy/api/resources/deprecated/subscriptions.py @@ -3,9 +3,14 @@ from deprecated import deprecated from graphql import DocumentNode -from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION -from specklepy.core.api.resources.subscription_resource import check_wsclient -from specklepy.core.api.resources.subscriptions import Resource as CoreResource +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, +) +from specklepy.core.api.resources.current.subscription_resource import check_wsclient +from specklepy.core.api.resources.deprecated.subscriptions import ( + Resource as CoreResource, +) from specklepy.logging import metrics diff --git a/src/specklepy/api/resources/user.py b/src/specklepy/api/resources/deprecated/user.py similarity index 98% rename from src/specklepy/api/resources/user.py rename to src/specklepy/api/resources/deprecated/user.py index 3f45768e..6d45033c 100644 --- a/src/specklepy/api/resources/user.py +++ b/src/specklepy/api/resources/deprecated/user.py @@ -4,7 +4,7 @@ from deprecated import deprecated from specklepy.api.models import PendingStreamCollaborator, User -from specklepy.core.api.resources.user import Resource as CoreResource +from specklepy.core.api.resources.deprecated.user import Resource as CoreResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException diff --git a/src/specklepy/core/api/client.py b/src/specklepy/core/api/client.py index 8bc8ca99..3c1511d2 100644 --- a/src/specklepy/core/api/client.py +++ b/src/specklepy/core/api/client.py @@ -11,21 +11,21 @@ from specklepy.core.api import resources from specklepy.core.api.credentials import Account, get_account_from_token from specklepy.core.api.resources import ( + ActiveUserResource, + ModelResource, + OtherUserResource, + ProjectInviteResource, + ProjectResource, + ServerResource, + SubscriptionResource, + VersionResource, branch, commit, object, - server, stream, subscriptions, user, ) -from specklepy.core.api.resources.active_user_resource import ActiveUserResource -from specklepy.core.api.resources.model_resource import ModelResource -from specklepy.core.api.resources.other_user_resource import OtherUserResource -from specklepy.core.api.resources.project_invite_resource import ProjectInviteResource -from specklepy.core.api.resources.project_resource import ProjectResource -from specklepy.core.api.resources.subscription_resource import SubscriptionResource -from specklepy.core.api.resources.version_resource import VersionResource from specklepy.logging import metrics from specklepy.logging.exceptions import SpeckleException, SpeckleWarning @@ -198,7 +198,7 @@ def execute_query(self, query: str) -> Dict: return self.httpclient.execute(query) def _init_resources(self) -> None: - self.server = server.Resource( + self.server = ServerResource( account=self.account, basepath=self.url, client=self.httpclient ) diff --git a/src/specklepy/core/api/credentials.py b/src/specklepy/core/api/credentials.py index 9999ce22..5b0eeaab 100644 --- a/src/specklepy/core/api/credentials.py +++ b/src/specklepy/core/api/credentials.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, Field # pylint: disable=no-name-in-module -from specklepy.core.api.models import ServerInfo +from specklepy.core.api.models.deprecated import ServerInfo from specklepy.core.helpers import speckle_path_provider from specklepy.logging.exceptions import SpeckleException from specklepy.transports.sqlite import SQLiteTransport diff --git a/src/specklepy/core/api/inputs/project_inputs.py b/src/specklepy/core/api/inputs/project_inputs.py index 3cb19eab..fa7261de 100644 --- a/src/specklepy/core/api/inputs/project_inputs.py +++ b/src/specklepy/core/api/inputs/project_inputs.py @@ -2,7 +2,7 @@ from pydantic import BaseModel -from specklepy.core.api.models import ProjectVisibility +from specklepy.core.api.enums import ProjectVisibility class ProjectCreateInput(BaseModel): diff --git a/src/specklepy/core/api/models/__init__.py b/src/specklepy/core/api/models/__init__.py new file mode 100644 index 00000000..92c72477 --- /dev/null +++ b/src/specklepy/core/api/models/__init__.py @@ -0,0 +1,71 @@ +from specklepy.core.api.models.current import ( + AuthStrategy, + LimitedUser, + Model, + ModelWithVersions, + PendingStreamCollaborator, + Project, + ProjectCollaborator, + ProjectCommentCollection, + ProjectWithModels, + ProjectWithTeam, + ResourceCollection, + ServerConfiguration, + ServerInfo, + ServerMigration, + User, + UserSearchResultCollection, + Version, +) +from specklepy.core.api.models.deprecated import ( + Activity, + ActivityCollection, + Branch, + Branches, + Collaborator, + Commit, + Commits, + Object, + Stream, + Streams, +) +from specklepy.core.api.models.subscription_messages import ( + ProjectModelsUpdatedMessage, + ProjectUpdatedMessage, + ProjectVersionsUpdatedMessage, + UserProjectsUpdatedMessage, +) + +__all__ = [ + "User", + "ResourceCollection", + "ServerMigration", + "AuthStrategy", + "ServerConfiguration", + "ServerInfo", + "LimitedUser", + "PendingStreamCollaborator", + "ProjectCollaborator", + "Version", + "Model", + "ModelWithVersions", + "Project", + "ProjectWithModels", + "ProjectWithTeam", + "ProjectCommentCollection", + "UserSearchResultCollection", + "UserProjectsUpdatedMessage", + "ProjectModelsUpdatedMessage", + "ProjectUpdatedMessage", + "ProjectVersionsUpdatedMessage", + "Collaborator", + "Commit", + "Commits", + "Object", + "Branch", + "Branches", + "Stream", + "Streams", + "Activity", + "ActivityCollection", +] diff --git a/src/specklepy/core/api/new_models.py b/src/specklepy/core/api/models/current.py similarity index 82% rename from src/specklepy/core/api/new_models.py rename to src/specklepy/core/api/models/current.py index f1663860..549c4c49 100644 --- a/src/specklepy/core/api/new_models.py +++ b/src/specklepy/core/api/models/current.py @@ -3,17 +3,33 @@ from pydantic import BaseModel -from specklepy.core.api.enums import ( - ProjectModelsUpdatedMessageType, - ProjectUpdatedMessageType, - ProjectVersionsUpdatedMessageType, - ProjectVisibility, - UserProjectsUpdatedMessageType, -) +from specklepy.core.api.enums import ProjectVisibility +from specklepy.core.api.models.deprecated import Streams T = TypeVar("T") +class User(BaseModel): + id: str + email: Optional[str] = None + name: str + bio: Optional[str] = None + company: Optional[str] = None + avatar: Optional[str] = None + verified: Optional[bool] = None + role: Optional[str] = None + streams: Optional["Streams"] = None + + def __repr__(self): + return ( + f"User( id: {self.id}, name: {self.name}, email: {self.email}, company:" + f" {self.company} )" + ) + + def __str__(self) -> str: + return self.__repr__() + + class ResourceCollection(BaseModel, Generic[T]): totalCount: int items: List[T] @@ -153,28 +169,3 @@ class ProjectCommentCollection(ResourceCollection[T], Generic[T]): class UserSearchResultCollection(BaseModel): items: List[LimitedUser] cursor: Optional[str] = None - - -class UserProjectsUpdatedMessage(BaseModel): - id: str - type: UserProjectsUpdatedMessageType - project: Optional[Project] - - -class ProjectModelsUpdatedMessage(BaseModel): - id: str - type: ProjectModelsUpdatedMessageType - model: Optional[Model] - - -class ProjectUpdatedMessage(BaseModel): - id: str - type: ProjectUpdatedMessageType - project: Optional[Project] - - -class ProjectVersionsUpdatedMessage(BaseModel): - id: str - type: ProjectVersionsUpdatedMessageType - modelId: Optional[str] - version: Optional[Version] diff --git a/src/specklepy/core/api/models.py b/src/specklepy/core/api/models/deprecated.py similarity index 89% rename from src/specklepy/core/api/models.py rename to src/specklepy/core/api/models/deprecated.py index 741bf66e..1ea17fc0 100644 --- a/src/specklepy/core/api/models.py +++ b/src/specklepy/core/api/models/deprecated.py @@ -4,33 +4,10 @@ from deprecated import deprecated from pydantic import BaseModel, Field -from specklepy.core.api.new_models import * # noqa: F403 - FE1_DEPRECATION_REASON = "Stream/Branch/Commit API is now deprecated, Use the new Project/Model/Version API functions in Client}" FE1_DEPRECATION_VERSION = "2.20" -class User(BaseModel): - id: str - email: Optional[str] = None - name: str - bio: Optional[str] = None - company: Optional[str] = None - avatar: Optional[str] = None - verified: Optional[bool] = None - role: Optional[str] = None - streams: Optional["Streams"] = None - - def __repr__(self): - return ( - f"User( id: {self.id}, name: {self.name}, email: {self.email}, company:" - f" {self.company} )" - ) - - def __str__(self) -> str: - return self.__repr__() - - @deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION) class Collaborator(BaseModel): id: Optional[str] = None diff --git a/src/specklepy/core/api/models/subscription_messages.py b/src/specklepy/core/api/models/subscription_messages.py new file mode 100644 index 00000000..35e4ed70 --- /dev/null +++ b/src/specklepy/core/api/models/subscription_messages.py @@ -0,0 +1,36 @@ +from typing import Optional + +from pydantic import BaseModel + +from specklepy.core.api.enums import ( + ProjectModelsUpdatedMessageType, + ProjectUpdatedMessageType, + ProjectVersionsUpdatedMessageType, + UserProjectsUpdatedMessageType, +) +from specklepy.core.api.models.current import Model, Project, Version + + +class UserProjectsUpdatedMessage(BaseModel): + id: str + type: UserProjectsUpdatedMessageType + project: Optional[Project] + + +class ProjectModelsUpdatedMessage(BaseModel): + id: str + type: ProjectModelsUpdatedMessageType + model: Optional[Model] + + +class ProjectUpdatedMessage(BaseModel): + id: str + type: ProjectUpdatedMessageType + project: Optional[Project] + + +class ProjectVersionsUpdatedMessage(BaseModel): + id: str + type: ProjectVersionsUpdatedMessageType + modelId: Optional[str] + version: Optional[Version] diff --git a/src/specklepy/core/api/resources/__init__.py b/src/specklepy/core/api/resources/__init__.py index e69de29b..5321e247 100644 --- a/src/specklepy/core/api/resources/__init__.py +++ b/src/specklepy/core/api/resources/__init__.py @@ -0,0 +1,43 @@ +from specklepy.core.api.resources.current.active_user_resource import ActiveUserResource +from specklepy.core.api.resources.current.model_resource import ModelResource +from specklepy.core.api.resources.current.other_user_resource import OtherUserResource +from specklepy.core.api.resources.current.project_invite_resource import ( + ProjectInviteResource, +) +from specklepy.core.api.resources.current.project_resource import ProjectResource +from specklepy.core.api.resources.current.server_resource import ServerResource +from specklepy.core.api.resources.current.subscription_resource import ( + SubscriptionResource, +) +from specklepy.core.api.resources.current.version_resource import VersionResource +from specklepy.core.api.resources.deprecated import ( + active_user, + branch, + commit, + object, + other_user, + server, + stream, + subscriptions, + user, +) + +__all__ = [ + "ActiveUserResource", + "ModelResource", + "OtherUserResource", + "ProjectInviteResource", + "ProjectResource", + "ServerResource", + "SubscriptionResource", + "VersionResource", + "active_user", + "branch", + "commit", + "object", + "other_user", + "server", + "stream", + "subscriptions", + "user", +] diff --git a/src/specklepy/core/api/resources/active_user_resource.py b/src/specklepy/core/api/resources/current/active_user_resource.py similarity index 99% rename from src/specklepy/core/api/resources/active_user_resource.py rename to src/specklepy/core/api/resources/current/active_user_resource.py index 74bf4969..90bee1c8 100644 --- a/src/specklepy/core/api/resources/active_user_resource.py +++ b/src/specklepy/core/api/resources/current/active_user_resource.py @@ -7,14 +7,16 @@ from specklepy.core.api.inputs.project_inputs import UserProjectsFilter from specklepy.core.api.inputs.user_inputs import UserUpdateInput from specklepy.core.api.models import ( - FE1_DEPRECATION_REASON, - FE1_DEPRECATION_VERSION, ActivityCollection, PendingStreamCollaborator, + Project, ResourceCollection, User, ) -from specklepy.core.api.new_models import Project +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, +) from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse from specklepy.logging.exceptions import GraphQLException diff --git a/src/specklepy/core/api/resources/model_resource.py b/src/specklepy/core/api/resources/current/model_resource.py similarity index 98% rename from src/specklepy/core/api/resources/model_resource.py rename to src/specklepy/core/api/resources/current/model_resource.py index 87c2f608..6945bbd0 100644 --- a/src/specklepy/core/api/resources/model_resource.py +++ b/src/specklepy/core/api/resources/current/model_resource.py @@ -9,7 +9,7 @@ UpdateModelInput, ) from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter -from specklepy.core.api.new_models import Model, ModelWithVersions, ResourceCollection +from specklepy.core.api.models import Model, ModelWithVersions, ResourceCollection from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse diff --git a/src/specklepy/core/api/resources/other_user_resource.py b/src/specklepy/core/api/resources/current/other_user_resource.py similarity index 98% rename from src/specklepy/core/api/resources/other_user_resource.py rename to src/specklepy/core/api/resources/current/other_user_resource.py index 90ab1751..c1c8acf1 100644 --- a/src/specklepy/core/api/resources/other_user_resource.py +++ b/src/specklepy/core/api/resources/current/other_user_resource.py @@ -5,12 +5,14 @@ from gql import gql from specklepy.core.api.models import ( - FE1_DEPRECATION_REASON, - FE1_DEPRECATION_VERSION, ActivityCollection, LimitedUser, + UserSearchResultCollection, +) +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, ) -from specklepy.core.api.new_models import UserSearchResultCollection from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse from specklepy.logging.exceptions import SpeckleException diff --git a/src/specklepy/core/api/resources/project_invite_resource.py b/src/specklepy/core/api/resources/current/project_invite_resource.py similarity index 98% rename from src/specklepy/core/api/resources/project_invite_resource.py rename to src/specklepy/core/api/resources/current/project_invite_resource.py index fea5ca69..c9315058 100644 --- a/src/specklepy/core/api/resources/project_invite_resource.py +++ b/src/specklepy/core/api/resources/current/project_invite_resource.py @@ -7,7 +7,7 @@ ProjectInviteCreateInput, ProjectInviteUseInput, ) -from specklepy.core.api.new_models import PendingStreamCollaborator, ProjectWithTeam +from specklepy.core.api.models import PendingStreamCollaborator, ProjectWithTeam from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse diff --git a/src/specklepy/core/api/resources/project_resource.py b/src/specklepy/core/api/resources/current/project_resource.py similarity index 98% rename from src/specklepy/core/api/resources/project_resource.py rename to src/specklepy/core/api/resources/current/project_resource.py index e4975cd9..90290019 100644 --- a/src/specklepy/core/api/resources/project_resource.py +++ b/src/specklepy/core/api/resources/current/project_resource.py @@ -8,8 +8,7 @@ ProjectUpdateInput, ProjectUpdateRoleInput, ) -from specklepy.core.api.models import Project -from specklepy.core.api.new_models import ProjectWithModels, ProjectWithTeam +from specklepy.core.api.models import Project, ProjectWithModels, ProjectWithTeam from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse diff --git a/src/specklepy/core/api/resources/server.py b/src/specklepy/core/api/resources/current/server_resource.py similarity index 99% rename from src/specklepy/core/api/resources/server.py rename to src/specklepy/core/api/resources/current/server_resource.py index 4f869cec..b4bd494a 100644 --- a/src/specklepy/core/api/resources/server.py +++ b/src/specklepy/core/api/resources/current/server_resource.py @@ -11,7 +11,7 @@ NAME = "server" -class Resource(ResourceBase): +class ServerResource(ResourceBase): """API Access class for the server""" def __init__(self, account, basepath, client) -> None: diff --git a/src/specklepy/core/api/resources/subscription_resource.py b/src/specklepy/core/api/resources/current/subscription_resource.py similarity index 99% rename from src/specklepy/core/api/resources/subscription_resource.py rename to src/specklepy/core/api/resources/current/subscription_resource.py index 447dd2ef..da80b1d7 100644 --- a/src/specklepy/core/api/resources/subscription_resource.py +++ b/src/specklepy/core/api/resources/current/subscription_resource.py @@ -6,7 +6,7 @@ from pydantic import BaseModel from typing_extensions import TypeVar -from specklepy.core.api.new_models import ( +from specklepy.core.api.models import ( ProjectModelsUpdatedMessage, ProjectUpdatedMessage, ProjectVersionsUpdatedMessage, diff --git a/src/specklepy/core/api/resources/version_resource.py b/src/specklepy/core/api/resources/current/version_resource.py similarity index 98% rename from src/specklepy/core/api/resources/version_resource.py rename to src/specklepy/core/api/resources/current/version_resource.py index de9405a3..f72ebe56 100644 --- a/src/specklepy/core/api/resources/version_resource.py +++ b/src/specklepy/core/api/resources/current/version_resource.py @@ -10,7 +10,7 @@ MoveVersionsInput, UpdateVersionInput, ) -from specklepy.core.api.new_models import ResourceCollection, Version +from specklepy.core.api.models import ResourceCollection, Version from specklepy.core.api.resource import ResourceBase from specklepy.core.api.responses import DataResponse diff --git a/src/specklepy/core/api/resources/active_user.py b/src/specklepy/core/api/resources/deprecated/active_user.py similarity index 62% rename from src/specklepy/core/api/resources/active_user.py rename to src/specklepy/core/api/resources/deprecated/active_user.py index 1d4aa3f7..411c06d1 100644 --- a/src/specklepy/core/api/resources/active_user.py +++ b/src/specklepy/core/api/resources/deprecated/active_user.py @@ -1,7 +1,7 @@ from deprecated import deprecated -from specklepy.core.api.models import FE1_DEPRECATION_VERSION -from specklepy.core.api.resources.active_user_resource import ActiveUserResource +from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION +from specklepy.core.api.resources import ActiveUserResource @deprecated( diff --git a/src/specklepy/core/api/resources/branch.py b/src/specklepy/core/api/resources/deprecated/branch.py similarity index 99% rename from src/specklepy/core/api/resources/branch.py rename to src/specklepy/core/api/resources/deprecated/branch.py index df633df2..7967a4ab 100644 --- a/src/specklepy/core/api/resources/branch.py +++ b/src/specklepy/core/api/resources/deprecated/branch.py @@ -3,7 +3,7 @@ from deprecated import deprecated from gql import gql -from specklepy.core.api.models import ( +from specklepy.core.api.models.deprecated import ( FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION, Branch, diff --git a/src/specklepy/core/api/resources/commit.py b/src/specklepy/core/api/resources/deprecated/commit.py similarity index 99% rename from src/specklepy/core/api/resources/commit.py rename to src/specklepy/core/api/resources/deprecated/commit.py index b2da0e79..6b8b3a13 100644 --- a/src/specklepy/core/api/resources/commit.py +++ b/src/specklepy/core/api/resources/deprecated/commit.py @@ -3,7 +3,7 @@ from deprecated import deprecated from gql import gql -from specklepy.core.api.models import ( +from specklepy.core.api.models.deprecated import ( FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION, Commit, diff --git a/src/specklepy/core/api/resources/object.py b/src/specklepy/core/api/resources/deprecated/object.py similarity index 100% rename from src/specklepy/core/api/resources/object.py rename to src/specklepy/core/api/resources/deprecated/object.py diff --git a/src/specklepy/core/api/resources/other_user.py b/src/specklepy/core/api/resources/deprecated/other_user.py similarity index 62% rename from src/specklepy/core/api/resources/other_user.py rename to src/specklepy/core/api/resources/deprecated/other_user.py index 0db1b600..9f63043b 100644 --- a/src/specklepy/core/api/resources/other_user.py +++ b/src/specklepy/core/api/resources/deprecated/other_user.py @@ -1,7 +1,7 @@ from deprecated import deprecated -from specklepy.core.api.models import FE1_DEPRECATION_VERSION -from specklepy.core.api.resources.other_user_resource import OtherUserResource +from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION +from specklepy.core.api.resources import OtherUserResource @deprecated( diff --git a/src/specklepy/core/api/resources/deprecated/server.py b/src/specklepy/core/api/resources/deprecated/server.py new file mode 100644 index 00000000..4abf4ad8 --- /dev/null +++ b/src/specklepy/core/api/resources/deprecated/server.py @@ -0,0 +1,11 @@ +from deprecated import deprecated + +from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION +from specklepy.core.api.resources import ServerResource + +NAME = "server" + + +@deprecated(reason="Renamed to ServerResource", version=FE1_DEPRECATION_VERSION) +class Resource(ServerResource): + """API Access class for the server""" diff --git a/src/specklepy/core/api/resources/stream.py b/src/specklepy/core/api/resources/deprecated/stream.py similarity index 99% rename from src/specklepy/core/api/resources/stream.py rename to src/specklepy/core/api/resources/deprecated/stream.py index 9db1368e..21edafc4 100644 --- a/src/specklepy/core/api/resources/stream.py +++ b/src/specklepy/core/api/resources/deprecated/stream.py @@ -5,12 +5,14 @@ from gql import gql from specklepy.core.api.models import ( - FE1_DEPRECATION_REASON, - FE1_DEPRECATION_VERSION, ActivityCollection, PendingStreamCollaborator, Stream, ) +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, +) from specklepy.core.api.resource import ResourceBase from specklepy.logging.exceptions import SpeckleException, UnsupportedException diff --git a/src/specklepy/core/api/resources/subscriptions.py b/src/specklepy/core/api/resources/deprecated/subscriptions.py similarity index 96% rename from src/specklepy/core/api/resources/subscriptions.py rename to src/specklepy/core/api/resources/deprecated/subscriptions.py index 055effdb..f9826f51 100644 --- a/src/specklepy/core/api/resources/subscriptions.py +++ b/src/specklepy/core/api/resources/deprecated/subscriptions.py @@ -5,9 +5,12 @@ from gql import gql from graphql import DocumentNode -from specklepy.core.api.models import FE1_DEPRECATION_REASON, FE1_DEPRECATION_VERSION +from specklepy.core.api.models.deprecated import ( + FE1_DEPRECATION_REASON, + FE1_DEPRECATION_VERSION, + Stream, +) from specklepy.core.api.resource import ResourceBase -from specklepy.core.api.resources.stream import Stream from specklepy.logging.exceptions import SpeckleException NAME = "subscribe" diff --git a/src/specklepy/core/api/resources/user.py b/src/specklepy/core/api/resources/deprecated/user.py similarity index 100% rename from src/specklepy/core/api/resources/user.py rename to src/specklepy/core/api/resources/deprecated/user.py diff --git a/tests/integration/client/test_active_user.py b/tests/integration/client/test_active_user.py index 7f35139c..89f2c831 100644 --- a/tests/integration/client/test_active_user.py +++ b/tests/integration/client/test_active_user.py @@ -2,7 +2,6 @@ from specklepy.api.client import SpeckleClient from specklepy.api.models import Activity, ActivityCollection, User -from specklepy.core.api.inputs.user_inputs import UserUpdateInput from specklepy.logging.exceptions import GraphQLException @@ -21,9 +20,9 @@ def test_user_update(self, client: SpeckleClient): bio = "i am a ghost in the machine" with pytest.raises(GraphQLException): - client.active_user.update(input=UserUpdateInput()) + client.active_user.update(bio=None) - updated = client.active_user.update(input=UserUpdateInput(bio=bio)) + updated = client.active_user.update(bio=bio) assert isinstance(updated, User) assert isinstance(updated, User) diff --git a/tests/integration/client/test_active_user_resource.py b/tests/integration/client/test_active_user_resource.py index ac01cd5f..ceba9f7f 100644 --- a/tests/integration/client/test_active_user_resource.py +++ b/tests/integration/client/test_active_user_resource.py @@ -3,8 +3,8 @@ from specklepy.api.client import SpeckleClient from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.inputs.user_inputs import UserUpdateInput -from specklepy.core.api.models import User -from specklepy.core.api.new_models import ResourceCollection +from specklepy.core.api.models.current import ResourceCollection +from specklepy.core.api.models.deprecated import User @pytest.mark.run() diff --git a/tests/integration/client/test_model_resource.py b/tests/integration/client/test_model_resource.py index 6295a96c..eddb1252 100644 --- a/tests/integration/client/test_model_resource.py +++ b/tests/integration/client/test_model_resource.py @@ -7,8 +7,8 @@ UpdateModelInput, ) from specklepy.core.api.inputs.project_inputs import ProjectCreateInput -from specklepy.core.api.models import Model, Project -from specklepy.core.api.new_models import ProjectWithModels, ResourceCollection +from specklepy.core.api.models.current import ProjectWithModels, ResourceCollection +from specklepy.core.api.models.deprecated import Model, Project from specklepy.logging.exceptions import GraphQLException diff --git a/tests/integration/client/test_other_user_resource.py b/tests/integration/client/test_other_user_resource.py index 97fe451d..2c39aaac 100644 --- a/tests/integration/client/test_other_user_resource.py +++ b/tests/integration/client/test_other_user_resource.py @@ -1,7 +1,7 @@ import pytest from specklepy.api.client import SpeckleClient -from specklepy.core.api.models import User +from specklepy.core.api.models.deprecated import User @pytest.mark.run() diff --git a/tests/integration/client/test_project_invite_resource.py b/tests/integration/client/test_project_invite_resource.py index f0e6878c..1c51bd23 100644 --- a/tests/integration/client/test_project_invite_resource.py +++ b/tests/integration/client/test_project_invite_resource.py @@ -9,8 +9,8 @@ ProjectInviteUseInput, ProjectUpdateRoleInput, ) -from specklepy.core.api.models import PendingStreamCollaborator, Project -from specklepy.core.api.new_models import LimitedUser, ProjectWithTeam +from specklepy.core.api.models.current import LimitedUser, ProjectWithTeam +from specklepy.core.api.models.deprecated import PendingStreamCollaborator, Project @pytest.mark.run() diff --git a/tests/integration/client/test_project_resource.py b/tests/integration/client/test_project_resource.py index 05e25d5c..261127c7 100644 --- a/tests/integration/client/test_project_resource.py +++ b/tests/integration/client/test_project_resource.py @@ -6,7 +6,7 @@ ProjectCreateInput, ProjectUpdateInput, ) -from specklepy.core.api.models import Project +from specklepy.core.api.models.deprecated import Project from specklepy.logging.exceptions import GraphQLException diff --git a/tests/integration/client/test_subscription_resource.py b/tests/integration/client/test_subscription_resource.py index 0a4af5e2..5ea5b720 100644 --- a/tests/integration/client/test_subscription_resource.py +++ b/tests/integration/client/test_subscription_resource.py @@ -15,7 +15,7 @@ ProjectCreateInput, ProjectUpdateInput, ) -from specklepy.core.api.new_models import ( +from specklepy.core.api.models.current import ( Model, Project, ProjectModelsUpdatedMessage, diff --git a/tests/integration/client/test_version_resource.py b/tests/integration/client/test_version_resource.py index ff7d89dd..b0ab7188 100644 --- a/tests/integration/client/test_version_resource.py +++ b/tests/integration/client/test_version_resource.py @@ -9,8 +9,8 @@ MoveVersionsInput, UpdateVersionInput, ) -from specklepy.core.api.models import Model, Project, Version -from specklepy.core.api.new_models import ModelWithVersions, ResourceCollection +from specklepy.core.api.models.current import ModelWithVersions, ResourceCollection +from specklepy.core.api.models.deprecated import Model, Project, Version from specklepy.logging.exceptions import GraphQLException from tests.integration.conftest import create_version diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 31910c01..1ce9c684 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -9,8 +9,8 @@ from specklepy.api.client import SpeckleClient from specklepy.core.api import operations from specklepy.core.api.inputs.version_inputs import CreateVersionInput -from specklepy.core.api.models import Stream -from specklepy.core.api.new_models import Version +from specklepy.core.api.models.current import Version +from specklepy.core.api.models.deprecated import Stream from specklepy.logging import metrics from specklepy.objects.base import Base from specklepy.objects.fakemesh import FakeDirection, FakeMesh diff --git a/tests/unit/test_account_server_migration.py b/tests/unit/test_account_server_migration.py index a0af9c69..e66225f6 100644 --- a/tests/unit/test_account_server_migration.py +++ b/tests/unit/test_account_server_migration.py @@ -6,7 +6,7 @@ import pytest from specklepy.core.api.credentials import Account, UserInfo, get_accounts_for_server -from specklepy.core.api.models import ServerInfo, ServerMigration +from specklepy.core.api.models.deprecated import ServerInfo, ServerMigration from specklepy.core.helpers import speckle_path_provider From 040a4e2553d2a5c9de6c50b2636034a53a95fe57 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:04:20 +0000 Subject: [PATCH 25/26] fixed tests --- src/specklepy/core/api/credentials.py | 2 +- .../client/{ => current}/test_active_user_resource.py | 3 +-- .../client/{ => current}/test_model_resource.py | 8 ++++++-- .../client/{ => current}/test_other_user_resource.py | 2 +- .../client/{ => current}/test_project_invite_resource.py | 8 ++++++-- .../client/{ => current}/test_project_resource.py | 2 +- .../client/{ => current}/test_subscription_resource.py | 2 +- .../client/{ => current}/test_version_resource.py | 9 +++++++-- .../client/{ => deprecated}/test_active_user.py | 0 tests/integration/client/{ => deprecated}/test_branch.py | 0 tests/integration/client/{ => deprecated}/test_commit.py | 0 .../integration/client/{ => deprecated}/test_objects.py | 0 .../client/{ => deprecated}/test_other_user.py | 0 tests/integration/client/{ => deprecated}/test_server.py | 0 tests/integration/client/{ => deprecated}/test_stream.py | 0 tests/integration/client/{ => deprecated}/test_user.py | 0 tests/integration/conftest.py | 3 +-- tests/unit/test_account_server_migration.py | 2 +- 18 files changed, 26 insertions(+), 15 deletions(-) rename tests/integration/client/{ => current}/test_active_user_resource.py (92%) rename tests/integration/client/{ => current}/test_model_resource.py (96%) rename tests/integration/client/{ => current}/test_other_user_resource.py (95%) rename tests/integration/client/{ => current}/test_project_invite_resource.py (97%) rename tests/integration/client/{ => current}/test_project_resource.py (98%) rename tests/integration/client/{ => current}/test_subscription_resource.py (99%) rename tests/integration/client/{ => current}/test_version_resource.py (96%) rename tests/integration/client/{ => deprecated}/test_active_user.py (100%) rename tests/integration/client/{ => deprecated}/test_branch.py (100%) rename tests/integration/client/{ => deprecated}/test_commit.py (100%) rename tests/integration/client/{ => deprecated}/test_objects.py (100%) rename tests/integration/client/{ => deprecated}/test_other_user.py (100%) rename tests/integration/client/{ => deprecated}/test_server.py (100%) rename tests/integration/client/{ => deprecated}/test_stream.py (100%) rename tests/integration/client/{ => deprecated}/test_user.py (100%) diff --git a/src/specklepy/core/api/credentials.py b/src/specklepy/core/api/credentials.py index 5b0eeaab..9999ce22 100644 --- a/src/specklepy/core/api/credentials.py +++ b/src/specklepy/core/api/credentials.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, Field # pylint: disable=no-name-in-module -from specklepy.core.api.models.deprecated import ServerInfo +from specklepy.core.api.models import ServerInfo from specklepy.core.helpers import speckle_path_provider from specklepy.logging.exceptions import SpeckleException from specklepy.transports.sqlite import SQLiteTransport diff --git a/tests/integration/client/test_active_user_resource.py b/tests/integration/client/current/test_active_user_resource.py similarity index 92% rename from tests/integration/client/test_active_user_resource.py rename to tests/integration/client/current/test_active_user_resource.py index ceba9f7f..fd5a7bbf 100644 --- a/tests/integration/client/test_active_user_resource.py +++ b/tests/integration/client/current/test_active_user_resource.py @@ -3,8 +3,7 @@ from specklepy.api.client import SpeckleClient from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.inputs.user_inputs import UserUpdateInput -from specklepy.core.api.models.current import ResourceCollection -from specklepy.core.api.models.deprecated import User +from specklepy.core.api.models import ResourceCollection, User @pytest.mark.run() diff --git a/tests/integration/client/test_model_resource.py b/tests/integration/client/current/test_model_resource.py similarity index 96% rename from tests/integration/client/test_model_resource.py rename to tests/integration/client/current/test_model_resource.py index eddb1252..a5c9f451 100644 --- a/tests/integration/client/test_model_resource.py +++ b/tests/integration/client/current/test_model_resource.py @@ -7,8 +7,12 @@ UpdateModelInput, ) from specklepy.core.api.inputs.project_inputs import ProjectCreateInput -from specklepy.core.api.models.current import ProjectWithModels, ResourceCollection -from specklepy.core.api.models.deprecated import Model, Project +from specklepy.core.api.models.current import ( + ProjectWithModels, + ResourceCollection, + Model, + Project, +) from specklepy.logging.exceptions import GraphQLException diff --git a/tests/integration/client/test_other_user_resource.py b/tests/integration/client/current/test_other_user_resource.py similarity index 95% rename from tests/integration/client/test_other_user_resource.py rename to tests/integration/client/current/test_other_user_resource.py index 2c39aaac..97fe451d 100644 --- a/tests/integration/client/test_other_user_resource.py +++ b/tests/integration/client/current/test_other_user_resource.py @@ -1,7 +1,7 @@ import pytest from specklepy.api.client import SpeckleClient -from specklepy.core.api.models.deprecated import User +from specklepy.core.api.models import User @pytest.mark.run() diff --git a/tests/integration/client/test_project_invite_resource.py b/tests/integration/client/current/test_project_invite_resource.py similarity index 97% rename from tests/integration/client/test_project_invite_resource.py rename to tests/integration/client/current/test_project_invite_resource.py index 1c51bd23..c72c65af 100644 --- a/tests/integration/client/test_project_invite_resource.py +++ b/tests/integration/client/current/test_project_invite_resource.py @@ -9,8 +9,12 @@ ProjectInviteUseInput, ProjectUpdateRoleInput, ) -from specklepy.core.api.models.current import LimitedUser, ProjectWithTeam -from specklepy.core.api.models.deprecated import PendingStreamCollaborator, Project +from specklepy.core.api.models import ( + LimitedUser, + ProjectWithTeam, + PendingStreamCollaborator, + Project, +) @pytest.mark.run() diff --git a/tests/integration/client/test_project_resource.py b/tests/integration/client/current/test_project_resource.py similarity index 98% rename from tests/integration/client/test_project_resource.py rename to tests/integration/client/current/test_project_resource.py index 261127c7..05e25d5c 100644 --- a/tests/integration/client/test_project_resource.py +++ b/tests/integration/client/current/test_project_resource.py @@ -6,7 +6,7 @@ ProjectCreateInput, ProjectUpdateInput, ) -from specklepy.core.api.models.deprecated import Project +from specklepy.core.api.models import Project from specklepy.logging.exceptions import GraphQLException diff --git a/tests/integration/client/test_subscription_resource.py b/tests/integration/client/current/test_subscription_resource.py similarity index 99% rename from tests/integration/client/test_subscription_resource.py rename to tests/integration/client/current/test_subscription_resource.py index 5ea5b720..cf2d5a1d 100644 --- a/tests/integration/client/test_subscription_resource.py +++ b/tests/integration/client/current/test_subscription_resource.py @@ -15,7 +15,7 @@ ProjectCreateInput, ProjectUpdateInput, ) -from specklepy.core.api.models.current import ( +from specklepy.core.api.models import ( Model, Project, ProjectModelsUpdatedMessage, diff --git a/tests/integration/client/test_version_resource.py b/tests/integration/client/current/test_version_resource.py similarity index 96% rename from tests/integration/client/test_version_resource.py rename to tests/integration/client/current/test_version_resource.py index b0ab7188..5b11737c 100644 --- a/tests/integration/client/test_version_resource.py +++ b/tests/integration/client/current/test_version_resource.py @@ -9,8 +9,13 @@ MoveVersionsInput, UpdateVersionInput, ) -from specklepy.core.api.models.current import ModelWithVersions, ResourceCollection -from specklepy.core.api.models.deprecated import Model, Project, Version +from specklepy.core.api.models import ( + ModelWithVersions, + ResourceCollection, + Model, + Project, + Version, +) from specklepy.logging.exceptions import GraphQLException from tests.integration.conftest import create_version diff --git a/tests/integration/client/test_active_user.py b/tests/integration/client/deprecated/test_active_user.py similarity index 100% rename from tests/integration/client/test_active_user.py rename to tests/integration/client/deprecated/test_active_user.py diff --git a/tests/integration/client/test_branch.py b/tests/integration/client/deprecated/test_branch.py similarity index 100% rename from tests/integration/client/test_branch.py rename to tests/integration/client/deprecated/test_branch.py diff --git a/tests/integration/client/test_commit.py b/tests/integration/client/deprecated/test_commit.py similarity index 100% rename from tests/integration/client/test_commit.py rename to tests/integration/client/deprecated/test_commit.py diff --git a/tests/integration/client/test_objects.py b/tests/integration/client/deprecated/test_objects.py similarity index 100% rename from tests/integration/client/test_objects.py rename to tests/integration/client/deprecated/test_objects.py diff --git a/tests/integration/client/test_other_user.py b/tests/integration/client/deprecated/test_other_user.py similarity index 100% rename from tests/integration/client/test_other_user.py rename to tests/integration/client/deprecated/test_other_user.py diff --git a/tests/integration/client/test_server.py b/tests/integration/client/deprecated/test_server.py similarity index 100% rename from tests/integration/client/test_server.py rename to tests/integration/client/deprecated/test_server.py diff --git a/tests/integration/client/test_stream.py b/tests/integration/client/deprecated/test_stream.py similarity index 100% rename from tests/integration/client/test_stream.py rename to tests/integration/client/deprecated/test_stream.py diff --git a/tests/integration/client/test_user.py b/tests/integration/client/deprecated/test_user.py similarity index 100% rename from tests/integration/client/test_user.py rename to tests/integration/client/deprecated/test_user.py diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 1ce9c684..2f155e07 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -9,8 +9,7 @@ from specklepy.api.client import SpeckleClient from specklepy.core.api import operations from specklepy.core.api.inputs.version_inputs import CreateVersionInput -from specklepy.core.api.models.current import Version -from specklepy.core.api.models.deprecated import Stream +from specklepy.core.api.models import Version, Stream from specklepy.logging import metrics from specklepy.objects.base import Base from specklepy.objects.fakemesh import FakeDirection, FakeMesh diff --git a/tests/unit/test_account_server_migration.py b/tests/unit/test_account_server_migration.py index e66225f6..a0af9c69 100644 --- a/tests/unit/test_account_server_migration.py +++ b/tests/unit/test_account_server_migration.py @@ -6,7 +6,7 @@ import pytest from specklepy.core.api.credentials import Account, UserInfo, get_accounts_for_server -from specklepy.core.api.models.deprecated import ServerInfo, ServerMigration +from specklepy.core.api.models import ServerInfo, ServerMigration from specklepy.core.helpers import speckle_path_provider From 170d2f04508ac26d0819f54db6c7866fdf011ab0 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:04:45 +0000 Subject: [PATCH 26/26] isort --- tests/integration/client/current/test_model_resource.py | 4 ++-- .../client/current/test_project_invite_resource.py | 2 +- tests/integration/client/current/test_version_resource.py | 4 ++-- tests/integration/conftest.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/client/current/test_model_resource.py b/tests/integration/client/current/test_model_resource.py index a5c9f451..34a4520b 100644 --- a/tests/integration/client/current/test_model_resource.py +++ b/tests/integration/client/current/test_model_resource.py @@ -8,10 +8,10 @@ ) from specklepy.core.api.inputs.project_inputs import ProjectCreateInput from specklepy.core.api.models.current import ( - ProjectWithModels, - ResourceCollection, Model, Project, + ProjectWithModels, + ResourceCollection, ) from specklepy.logging.exceptions import GraphQLException diff --git a/tests/integration/client/current/test_project_invite_resource.py b/tests/integration/client/current/test_project_invite_resource.py index c72c65af..e77d3141 100644 --- a/tests/integration/client/current/test_project_invite_resource.py +++ b/tests/integration/client/current/test_project_invite_resource.py @@ -11,9 +11,9 @@ ) from specklepy.core.api.models import ( LimitedUser, - ProjectWithTeam, PendingStreamCollaborator, Project, + ProjectWithTeam, ) diff --git a/tests/integration/client/current/test_version_resource.py b/tests/integration/client/current/test_version_resource.py index 5b11737c..6ef95ff8 100644 --- a/tests/integration/client/current/test_version_resource.py +++ b/tests/integration/client/current/test_version_resource.py @@ -10,10 +10,10 @@ UpdateVersionInput, ) from specklepy.core.api.models import ( - ModelWithVersions, - ResourceCollection, Model, + ModelWithVersions, Project, + ResourceCollection, Version, ) from specklepy.logging.exceptions import GraphQLException diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 2f155e07..10a24360 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -9,7 +9,7 @@ from specklepy.api.client import SpeckleClient from specklepy.core.api import operations from specklepy.core.api.inputs.version_inputs import CreateVersionInput -from specklepy.core.api.models import Version, Stream +from specklepy.core.api.models import Stream, Version from specklepy.logging import metrics from specklepy.objects.base import Base from specklepy.objects.fakemesh import FakeDirection, FakeMesh