From c4901472795d414c5fcaad1bc03fb2d86c181ba4 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Fri, 15 Aug 2025 12:55:00 +0300 Subject: [PATCH 01/31] pydantic 2 all tests passed --- .github/workflows/actions.yml | 12 +- .github/workflows/publish-pypi.yml | 2 +- poetry.lock | 1321 +++++++++++-------- pybotx_smartapp_rpc/models/method.py | 2 +- pybotx_smartapp_rpc/openapi_utils.py | 89 +- pybotx_smartapp_rpc/pydantic_v1_override.py | 88 ++ pybotx_smartapp_rpc/router.py | 40 +- pyproject.toml | 27 +- tests/test_openapi.py | 29 +- tests/test_rpc_calls.py | 47 +- 10 files changed, 973 insertions(+), 684 deletions(-) create mode 100644 pybotx_smartapp_rpc/pydantic_v1_override.py diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 23043f1..5b4639d 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -4,17 +4,17 @@ jobs: test: name: Test - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: - python-version: [ "3.8", "3.9", "3.10", "3.11" ] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - name: Setup dependencies uses: ExpressApp/github-actions-poetry@v0.3 with: python-version: ${{ matrix.python-version }} - poetry-version: "1.3.2" + poetry-version: "2.1.4" - name: Run tests run: | @@ -23,17 +23,17 @@ jobs: lint: name: Lint - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: - python-version: [ "3.8", "3.9", "3.10", "3.11" ] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - name: Setup dependencies uses: ExpressApp/github-actions-poetry@v0.3 with: python-version: ${{ matrix.python-version }} - poetry-version: "1.3.2" + poetry-version: "2.1.4" - name: Run lint run: | diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index ed534ee..da5dd6d 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -5,7 +5,7 @@ on: - "*.*.*" jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v1 - name: Assert tag is from master diff --git a/poetry.lock b/poetry.lock index 4debe0a..102d6cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,14 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "add-trailing-comma" -version = "2.5.1" +version = "3.2.0" description = "Automatically add trailing commas to calls and literals" -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "add_trailing_comma-2.5.1-py2.py3-none-any.whl", hash = "sha256:603b4b55f76f85237f4c848e93e56a4c84ebf8c9e329daf44ce3e8111d68557b"}, - {file = "add_trailing_comma-2.5.1.tar.gz", hash = "sha256:f2a4afd41b96d6d34840455752c320c608942757ec6df3aa13b0b3c7effdd49e"}, + {file = "add_trailing_comma-3.2.0-py2.py3-none-any.whl", hash = "sha256:e88c66d0172754cc31998440a040d8c67a47d9ec488a0b83681b28a257fa88ab"}, + {file = "add_trailing_comma-3.2.0.tar.gz", hash = "sha256:b3bb8b7184983a15199ce282b849f9db0612d067936f2c2610f2af1e1a53b97d"}, ] [package.dependencies] @@ -19,29 +18,38 @@ tokenize-rt = ">=3.0.1" name = "aiocsv" version = "1.3.2" description = "" -category = "main" optional = false python-versions = ">=3.8" files = [ {file = "aiocsv-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1996ac960c196aecc7d22e701c273a2676d13bf25575af78d4e515fc724ef20"}, {file = "aiocsv-1.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd688dbc1723f2b3a433e42041ceb9c9a8fe70f547d35b2da4ea31e4c78efc5"}, {file = "aiocsv-1.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f921828e386bb6945ed7d268e1524349ea506974ae35b9772542714f0ef3efd"}, + {file = "aiocsv-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c17dba00ac5a0ba0a3962902ebd60ed529a59440c957343175e815947ac7f114"}, {file = "aiocsv-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:198c905ec29897c347bf9b18eb410af13d7ac94a03d4b673e64eaa5f4557c913"}, {file = "aiocsv-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7c25ad8afbf79d28ec3320e608c7f38d3eff93e96ebbbd2430ae8fa0f6e7631b"}, {file = "aiocsv-1.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4004569bff39cb839a335b8f673a6496fd5b0b6e074c7adb7aee4a0c8379ea22"}, {file = "aiocsv-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e9c98f8d760add0b52274523baa4b81dde4a3c96f79222d3d4d6965bac9cdcbd"}, + {file = "aiocsv-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3dce5e3b18e24b2e06d93cbd8186eac2e6a385cac40bbdfa09d6110a7f48d40"}, {file = "aiocsv-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9edb342b0d7dba94d8976f46ba5814b8d8704d67a45e1b8a6579ab0ba04309e7"}, + {file = "aiocsv-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5aa586564800df49280e0aa108acc855062ac5b9486bb052f0dd0c0051ea4f18"}, {file = "aiocsv-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:db943a463cb6828ba81bd7c083c6dd4c96edac4880b8638af81798d694405e26"}, {file = "aiocsv-1.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10780033a1ed3da825f2256449d177b7106b3c5a2d64bd683eab37f1fdee1e36"}, {file = "aiocsv-1.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8c7aee34ceff4eaa654f01acbdba648297f5f9532dc7a23fac62defec28e0fe5"}, + {file = "aiocsv-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f848e1cca7d22d8bd6480fa4c7338dc8be2abdd02e0b99f677b8a7af27e15767"}, {file = "aiocsv-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:59b0ea2d9e73539d4c1276467c4457acafa995717ea1b5340f3737f2cde2f71a"}, + {file = "aiocsv-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039dcf7bd684a98bf7c2218b8e7dc4abc951e1045dadd8813e992a1ba829ff"}, + {file = "aiocsv-1.3.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d8612392b7da7bff545b69202fb03a8e09381fff2d5c4d9594246d7375cd603"}, + {file = "aiocsv-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cee1577a381a44a18bcaed97c41f39b4400655de1a873f4e90b64af68e19dcd9"}, + {file = "aiocsv-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:0f0437f34ab7d1da86b30407653d635cf7de330681e746859b8c54aaac2c4574"}, {file = "aiocsv-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1c7d1700b8de16f25b24bfcebfc2b0817b29ce413f6961f08d5aa95bf00a6862"}, {file = "aiocsv-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa9629c8a1c07e9d02c7d80d84f021f7994fe30d021f13ac963e251b54724ef"}, {file = "aiocsv-1.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d125286f971e0038e8872f31b6f1cd6184b9c508445e6633f075d8b543b444bc"}, + {file = "aiocsv-1.3.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5fbfab48919aef505e2de38309f4808aa742dd23b834da6c670988e89b8b8577"}, {file = "aiocsv-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:b7220b4a6545abbbb6ab8fe7d4880aa8334f156b872b83641b898df2da9a6484"}, {file = "aiocsv-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd2ef214b6d7944991f62ac593ad45bdaf0ed9f5741c8441ee7de148e512fe7"}, {file = "aiocsv-1.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c3e5a817b3489283cc1fd80f8ba56431d552dc9ea4e539c0069d8d56bf0fba7"}, {file = "aiocsv-1.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ef14fa0839394ecc52274ea538b12b7b2e756eb0f514902a8fb391612161079"}, + {file = "aiocsv-1.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:188fc074ee8f72f1bab61c4838c36a354b15abd9c224285e7c60265c590fc87b"}, {file = "aiocsv-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:17341fa3b90414adda6cd8c79efc3c1a3f58a4dc72c2053c4532e82b61ef9f5e"}, {file = "aiocsv-1.3.2.tar.gz", hash = "sha256:806d93465c7808d58d3ff0d2bba270fb4d04b934be6a1e95d0834c50a510910e"}, ] @@ -51,44 +59,50 @@ typing_extensions = "*" [[package]] name = "aiofiles" -version = "23.2.1" +version = "24.1.0" description = "File support for asyncio." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, - {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, ] [[package]] -name = "anyio" -version = "4.4.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" 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 = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.10.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" +optional = false +python-versions = ">=3.9" +files = [ + {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [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)"] +trio = ["trio (>=0.26.1)"] [[package]] name = "astor" version = "0.8.1" description = "Read/rewrite/write Python ASTs" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ @@ -98,50 +112,58 @@ files = [ [[package]] name = "attrs" -version = "23.2.0" +version = "25.3.0" description = "Classes Without Boilerplate" -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "autoflake" -version = "1.7.8" +version = "2.3.1" description = "Removes unused imports and unused variables" -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "autoflake-1.7.8-py3-none-any.whl", hash = "sha256:46373ef69b6714f5064c923bb28bd797c4f8a9497f557d87fc36665c6d956b39"}, - {file = "autoflake-1.7.8.tar.gz", hash = "sha256:e7e46372dee46fa1c97acf310d99d922b63d369718a270809d7c278d34a194cf"}, + {file = "autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840"}, + {file = "autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e"}, ] [package.dependencies] -pyflakes = ">=1.1.0,<3" +pyflakes = ">=3.0.0" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + [[package]] name = "bandit" -version = "1.7.9" +version = "1.8.6" description = "Security oriented static analyser for python code." -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, - {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, + {file = "bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0"}, + {file = "bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b"}, ] [package.dependencies] @@ -159,73 +181,70 @@ yaml = ["PyYAML"] [[package]] name = "black" -version = "22.3.0" +version = "25.1.0" description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.6.2" -files = [ - {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, - {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, - {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, - {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, - {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, - {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, - {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, - {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, - {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, - {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, - {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, - {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, - {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, - {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, - {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, - {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, - {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, - {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, - {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, - {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, - {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, - {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, - {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, +optional = false +python-versions = ">=3.9" +files = [ + {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, + {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, + {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, + {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, + {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, + {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, + {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, + {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, + {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, + {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, + {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, + {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, + {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, + {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, + {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, + {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, + {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, + {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, + {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, + {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, + {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, + {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" +packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2024.7.4" +version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -235,7 +254,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -245,64 +263,99 @@ files = [ [[package]] name = "coverage" -version = "7.5.4" +version = "7.10.3" description = "Code coverage measurement for Python" -category = "dev" optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, - {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, - {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, - {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, - {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, - {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, - {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, - {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, - {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, - {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, - {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, - {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, - {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, - {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, +python-versions = ">=3.9" +files = [ + {file = "coverage-7.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53808194afdf948c462215e9403cca27a81cf150d2f9b386aee4dab614ae2ffe"}, + {file = "coverage-7.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f4d1b837d1abf72187a61645dbf799e0d7705aa9232924946e1f57eb09a3bf00"}, + {file = "coverage-7.10.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2a90dd4505d3cc68b847ab10c5ee81822a968b5191664e8a0801778fa60459fa"}, + {file = "coverage-7.10.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d52989685ff5bf909c430e6d7f6550937bc6d6f3e6ecb303c97a86100efd4596"}, + {file = "coverage-7.10.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdb558a1d97345bde3a9f4d3e8d11c9e5611f748646e9bb61d7d612a796671b5"}, + {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c9e6331a8f09cb1fc8bda032752af03c366870b48cce908875ba2620d20d0ad4"}, + {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:992f48bf35b720e174e7fae916d943599f1a66501a2710d06c5f8104e0756ee1"}, + {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c5595fc4ad6a39312c786ec3326d7322d0cf10e3ac6a6df70809910026d67cfb"}, + {file = "coverage-7.10.3-cp310-cp310-win32.whl", hash = "sha256:9e92fa1f2bd5a57df9d00cf9ce1eb4ef6fccca4ceabec1c984837de55329db34"}, + {file = "coverage-7.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b96524d6e4a3ce6a75c56bb15dbd08023b0ae2289c254e15b9fbdddf0c577416"}, + {file = "coverage-7.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2ff2e2afdf0d51b9b8301e542d9c21a8d084fd23d4c8ea2b3a1b3c96f5f7397"}, + {file = "coverage-7.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:18ecc5d1b9a8c570f6c9b808fa9a2b16836b3dd5414a6d467ae942208b095f85"}, + {file = "coverage-7.10.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1af4461b25fe92889590d438905e1fc79a95680ec2a1ff69a591bb3fdb6c7157"}, + {file = "coverage-7.10.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3966bc9a76b09a40dc6063c8b10375e827ea5dfcaffae402dd65953bef4cba54"}, + {file = "coverage-7.10.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:205a95b87ef4eb303b7bc5118b47b6b6604a644bcbdb33c336a41cfc0a08c06a"}, + {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b3801b79fb2ad61e3c7e2554bab754fc5f105626056980a2b9cf3aef4f13f84"}, + {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0dc69c60224cda33d384572da945759756e3f06b9cdac27f302f53961e63160"}, + {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a83d4f134bab2c7ff758e6bb1541dd72b54ba295ced6a63d93efc2e20cb9b124"}, + {file = "coverage-7.10.3-cp311-cp311-win32.whl", hash = "sha256:54e409dd64e5302b2a8fdf44ec1c26f47abd1f45a2dcf67bd161873ee05a59b8"}, + {file = "coverage-7.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:30c601610a9b23807c5e9e2e442054b795953ab85d525c3de1b1b27cebeb2117"}, + {file = "coverage-7.10.3-cp311-cp311-win_arm64.whl", hash = "sha256:dabe662312a97958e932dee056f2659051d822552c0b866823e8ba1c2fe64770"}, + {file = "coverage-7.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:449c1e2d3a84d18bd204258a897a87bc57380072eb2aded6a5b5226046207b42"}, + {file = "coverage-7.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d4f9ce50b9261ad196dc2b2e9f1fbbee21651b54c3097a25ad783679fd18294"}, + {file = "coverage-7.10.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4dd4564207b160d0d45c36a10bc0a3d12563028e8b48cd6459ea322302a156d7"}, + {file = "coverage-7.10.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ca3c9530ee072b7cb6a6ea7b640bcdff0ad3b334ae9687e521e59f79b1d0437"}, + {file = "coverage-7.10.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6df359e59fa243c9925ae6507e27f29c46698359f45e568fd51b9315dbbe587"}, + {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a181e4c2c896c2ff64c6312db3bda38e9ade2e1aa67f86a5628ae85873786cea"}, + {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a374d4e923814e8b72b205ef6b3d3a647bb50e66f3558582eda074c976923613"}, + {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:daeefff05993e5e8c6e7499a8508e7bd94502b6b9a9159c84fd1fe6bce3151cb"}, + {file = "coverage-7.10.3-cp312-cp312-win32.whl", hash = "sha256:187ecdcac21f9636d570e419773df7bd2fda2e7fa040f812e7f95d0bddf5f79a"}, + {file = "coverage-7.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:4a50ad2524ee7e4c2a95e60d2b0b83283bdfc745fe82359d567e4f15d3823eb5"}, + {file = "coverage-7.10.3-cp312-cp312-win_arm64.whl", hash = "sha256:c112f04e075d3495fa3ed2200f71317da99608cbb2e9345bdb6de8819fc30571"}, + {file = "coverage-7.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b99e87304ffe0eb97c5308447328a584258951853807afdc58b16143a530518a"}, + {file = "coverage-7.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4af09c7574d09afbc1ea7da9dcea23665c01f3bc1b1feb061dac135f98ffc53a"}, + {file = "coverage-7.10.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:488e9b50dc5d2aa9521053cfa706209e5acf5289e81edc28291a24f4e4488f46"}, + {file = "coverage-7.10.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:913ceddb4289cbba3a310704a424e3fb7aac2bc0c3a23ea473193cb290cf17d4"}, + {file = "coverage-7.10.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b1f91cbc78c7112ab84ed2a8defbccd90f888fcae40a97ddd6466b0bec6ae8a"}, + {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0bac054d45af7cd938834b43a9878b36ea92781bcb009eab040a5b09e9927e3"}, + {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fe72cbdd12d9e0f4aca873fa6d755e103888a7f9085e4a62d282d9d5b9f7928c"}, + {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c1e2e927ab3eadd7c244023927d646e4c15c65bb2ac7ae3c3e9537c013700d21"}, + {file = "coverage-7.10.3-cp313-cp313-win32.whl", hash = "sha256:24d0c13de473b04920ddd6e5da3c08831b1170b8f3b17461d7429b61cad59ae0"}, + {file = "coverage-7.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:3564aae76bce4b96e2345cf53b4c87e938c4985424a9be6a66ee902626edec4c"}, + {file = "coverage-7.10.3-cp313-cp313-win_arm64.whl", hash = "sha256:f35580f19f297455f44afcd773c9c7a058e52eb6eb170aa31222e635f2e38b87"}, + {file = "coverage-7.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07009152f497a0464ffdf2634586787aea0e69ddd023eafb23fc38267db94b84"}, + {file = "coverage-7.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd2ba5f0c7e7e8cc418be2f0c14c4d9e3f08b8fb8e4c0f83c2fe87d03eb655e"}, + {file = "coverage-7.10.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1ae22b97003c74186e034a93e4f946c75fad8c0ce8d92fbbc168b5e15ee2841f"}, + {file = "coverage-7.10.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb329f1046888a36b1dc35504d3029e1dd5afe2196d94315d18c45ee380f67d5"}, + {file = "coverage-7.10.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce01048199a91f07f96ca3074b0c14021f4fe7ffd29a3e6a188ac60a5c3a4af8"}, + {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08b989a06eb9dfacf96d42b7fb4c9a22bafa370d245dc22fa839f2168c6f9fa1"}, + {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:669fe0d4e69c575c52148511029b722ba8d26e8a3129840c2ce0522e1452b256"}, + {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3262d19092771c83f3413831d9904b1ccc5f98da5de4ffa4ad67f5b20c7aaf7b"}, + {file = "coverage-7.10.3-cp313-cp313t-win32.whl", hash = "sha256:cc0ee4b2ccd42cab7ee6be46d8a67d230cb33a0a7cd47a58b587a7063b6c6b0e"}, + {file = "coverage-7.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:03db599f213341e2960430984e04cf35fb179724e052a3ee627a068653cf4a7c"}, + {file = "coverage-7.10.3-cp313-cp313t-win_arm64.whl", hash = "sha256:46eae7893ba65f53c71284585a262f083ef71594f05ec5c85baf79c402369098"}, + {file = "coverage-7.10.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:bce8b8180912914032785850d8f3aacb25ec1810f5f54afc4a8b114e7a9b55de"}, + {file = "coverage-7.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07790b4b37d56608536f7c1079bd1aa511567ac2966d33d5cec9cf520c50a7c8"}, + {file = "coverage-7.10.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e79367ef2cd9166acedcbf136a458dfe9a4a2dd4d1ee95738fb2ee581c56f667"}, + {file = "coverage-7.10.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:419d2a0f769f26cb1d05e9ccbc5eab4cb5d70231604d47150867c07822acbdf4"}, + {file = "coverage-7.10.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee221cf244757cdc2ac882e3062ab414b8464ad9c884c21e878517ea64b3fa26"}, + {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c2079d8cdd6f7373d628e14b3357f24d1db02c9dc22e6a007418ca7a2be0435a"}, + {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bd8df1f83c0703fa3ca781b02d36f9ec67ad9cb725b18d486405924f5e4270bd"}, + {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6b4e25e0fa335c8aa26e42a52053f3786a61cc7622b4d54ae2dad994aa754fec"}, + {file = "coverage-7.10.3-cp314-cp314-win32.whl", hash = "sha256:d7c3d02c2866deb217dce664c71787f4b25420ea3eaf87056f44fb364a3528f5"}, + {file = "coverage-7.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:9c8916d44d9e0fe6cdb2227dc6b0edd8bc6c8ef13438bbbf69af7482d9bb9833"}, + {file = "coverage-7.10.3-cp314-cp314-win_arm64.whl", hash = "sha256:1007d6a2b3cf197c57105cc1ba390d9ff7f0bee215ced4dea530181e49c65ab4"}, + {file = "coverage-7.10.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ebc8791d346410d096818788877d675ca55c91db87d60e8f477bd41c6970ffc6"}, + {file = "coverage-7.10.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f4e4d8e75f6fd3c6940ebeed29e3d9d632e1f18f6fb65d33086d99d4d073241"}, + {file = "coverage-7.10.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:24581ed69f132b6225a31b0228ae4885731cddc966f8a33fe5987288bdbbbd5e"}, + {file = "coverage-7.10.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec151569ddfccbf71bac8c422dce15e176167385a00cd86e887f9a80035ce8a5"}, + {file = "coverage-7.10.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ae8e7c56290b908ee817200c0b65929b8050bc28530b131fe7c6dfee3e7d86b"}, + {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fb742309766d7e48e9eb4dc34bc95a424707bc6140c0e7d9726e794f11b92a0"}, + {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c65e2a5b32fbe1e499f1036efa6eb9cb4ea2bf6f7168d0e7a5852f3024f471b1"}, + {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d48d2cb07d50f12f4f18d2bb75d9d19e3506c26d96fffabf56d22936e5ed8f7c"}, + {file = "coverage-7.10.3-cp314-cp314t-win32.whl", hash = "sha256:dec0d9bc15ee305e09fe2cd1911d3f0371262d3cfdae05d79515d8cb712b4869"}, + {file = "coverage-7.10.3-cp314-cp314t-win_amd64.whl", hash = "sha256:424ea93a323aa0f7f01174308ea78bde885c3089ec1bef7143a6d93c3e24ef64"}, + {file = "coverage-7.10.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f5983c132a62d93d71c9ef896a0b9bf6e6828d8d2ea32611f58684fba60bba35"}, + {file = "coverage-7.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da749daa7e141985487e1ff90a68315b0845930ed53dc397f4ae8f8bab25b551"}, + {file = "coverage-7.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3126fb6a47d287f461d9b1aa5d1a8c97034d1dffb4f452f2cf211289dae74ef"}, + {file = "coverage-7.10.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3da794db13cc27ca40e1ec8127945b97fab78ba548040047d54e7bfa6d442dca"}, + {file = "coverage-7.10.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4e27bebbd184ef8d1c1e092b74a2b7109dcbe2618dce6e96b1776d53b14b3fe8"}, + {file = "coverage-7.10.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fd4ee2580b9fefbd301b4f8f85b62ac90d1e848bea54f89a5748cf132782118"}, + {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6999920bdd73259ce11cabfc1307484f071ecc6abdb2ca58d98facbcefc70f16"}, + {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3623f929db885fab100cb88220a5b193321ed37e03af719efdbaf5d10b6e227"}, + {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:25b902c5e15dea056485d782e420bb84621cc08ee75d5131ecb3dbef8bd1365f"}, + {file = "coverage-7.10.3-cp39-cp39-win32.whl", hash = "sha256:f930a4d92b004b643183451fe9c8fe398ccf866ed37d172ebaccfd443a097f61"}, + {file = "coverage-7.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:08e638a93c8acba13c7842953f92a33d52d73e410329acd472280d2a21a6c0e1"}, + {file = "coverage-7.10.3-py3-none-any.whl", hash = "sha256:416a8d74dc0adfd33944ba2f405897bab87b7e9e84a391e09d241956bd953ce1"}, + {file = "coverage-7.10.3.tar.gz", hash = "sha256:812ba9250532e4a823b070b0420a36499859542335af3dca8f47fc6aa1a05619"}, ] [package.dependencies] @@ -315,7 +368,6 @@ toml = ["tomli"] name = "darglint" version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." -category = "dev" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -323,20 +375,44 @@ files = [ {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, ] +[[package]] +name = "deepdiff" +version = "8.6.0" +description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." +optional = false +python-versions = ">=3.9" +files = [ + {file = "deepdiff-8.6.0-py3-none-any.whl", hash = "sha256:db80677a434ac1f84147fd1598e93f1beb06d467e107af45fcf77cf8a681169f"}, + {file = "deepdiff-8.6.0.tar.gz", hash = "sha256:6197216c2d777c3106a9989055c230e25848e599b26dcbcdc66226bd8d7fe901"}, +] + +[package.dependencies] +orderly-set = ">=5.4.1,<6" + +[package.extras] +cli = ["click (>=8.1.0,<8.2.0)", "pyyaml (>=6.0.0,<6.1.0)"] +coverage = ["coverage (>=7.6.0,<7.7.0)"] +dev = ["bump2version (>=1.0.0,<1.1.0)", "ipdb (>=0.13.0,<0.14.0)", "jsonpickle (>=4.0.0,<4.1.0)", "nox (==2025.5.1)", "numpy (>=2.0,<3.0)", "numpy (>=2.2.0,<2.3.0)", "orjson (>=3.10.0,<3.11.0)", "pandas (>=2.2.0,<2.3.0)", "polars (>=1.21.0,<1.22.0)", "python-dateutil (>=2.9.0,<2.10.0)", "tomli (>=2.2.0,<2.3.0)", "tomli-w (>=1.2.0,<1.3.0)", "uuid6 (==2025.0.1)"] +docs = ["Sphinx (>=6.2.0,<6.3.0)", "sphinx-sitemap (>=2.6.0,<2.7.0)", "sphinxemoji (>=0.3.0,<0.4.0)"] +optimize = ["orjson"] +static = ["flake8 (>=7.1.0,<7.2.0)", "flake8-pyproject (>=1.2.3,<1.3.0)", "pydantic (>=2.10.0,<2.11.0)"] +test = ["pytest (>=8.3.0,<8.4.0)", "pytest-benchmark (>=5.1.0,<5.2.0)", "pytest-cov (>=6.0.0,<6.1.0)", "python-dotenv (>=1.0.0,<1.1.0)"] + [[package]] name = "docutils" -version = "0.20.1" +version = "0.22" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false -python-versions = "*" -files = [] +python-versions = ">=3.9" +files = [ + {file = "docutils-0.22-py3-none-any.whl", hash = "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e"}, + {file = "docutils-0.22.tar.gz", hash = "sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f"}, +] [[package]] name = "eradicate" version = "2.3.0" description = "Removes commented-out code." -category = "dev" optional = false python-versions = "*" files = [ @@ -346,93 +422,88 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" -category = "main" 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.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] [[package]] name = "flake8" -version = "4.0.1" +version = "7.3.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" files = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, + {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, + {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, ] [package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.14.0,<2.15.0" +pyflakes = ">=3.4.0,<3.5.0" [[package]] name = "flake8-bandit" -version = "3.0.0" +version = "4.1.1" description = "Automated security testing with bandit and flake8." -category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "flake8_bandit-3.0.0-py2.py3-none-any.whl", hash = "sha256:61b617f4f7cdaa0e2b1e6bf7b68afb2b619a227bb3e3ae00dd36c213bd17900a"}, - {file = "flake8_bandit-3.0.0.tar.gz", hash = "sha256:54d19427e6a8d50322a7b02e1841c0a7c22d856975f3459803320e0e18e2d6a1"}, + {file = "flake8_bandit-4.1.1-py3-none-any.whl", hash = "sha256:4c8a53eb48f23d4ef1e59293657181a3c989d0077c9952717e98a0eace43e06d"}, + {file = "flake8_bandit-4.1.1.tar.gz", hash = "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e"}, ] [package.dependencies] bandit = ">=1.7.3" -flake8 = "*" -flake8-polyfill = "*" -pycodestyle = "*" +flake8 = ">=5.0.0" [[package]] name = "flake8-broken-line" -version = "0.4.0" +version = "1.0.0" description = "Flake8 plugin to forbid backslashes for line breaks" -category = "dev" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.8,<4.0" files = [ - {file = "flake8-broken-line-0.4.0.tar.gz", hash = "sha256:771aab5aa0997666796fed249d0e48e6c01cdfeca8c95521eea28a38b7ced4c7"}, - {file = "flake8_broken_line-0.4.0-py3-none-any.whl", hash = "sha256:e9c522856862239a2c7ef2c1de0276fa598572aa864bd4e9c7efc2a827538515"}, + {file = "flake8_broken_line-1.0.0-py3-none-any.whl", hash = "sha256:96c964336024a5030dc536a9f6fb02aa679e2d2a6b35b80a558b5136c35832a9"}, + {file = "flake8_broken_line-1.0.0.tar.gz", hash = "sha256:e2c6a17f8d9a129e99c1320fce89b33843e2963871025c4c2bb7b8b8d8732a85"}, ] [package.dependencies] -flake8 = ">=3.5,<5" +flake8 = ">5" [[package]] name = "flake8-bugbear" -version = "22.12.6" +version = "23.12.2" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8.1" files = [ - {file = "flake8-bugbear-22.12.6.tar.gz", hash = "sha256:4cdb2c06e229971104443ae293e75e64c6107798229202fbe4f4091427a30ac0"}, - {file = "flake8_bugbear-22.12.6-py3-none-any.whl", hash = "sha256:b69a510634f8a9c298dfda2b18a8036455e6b19ecac4fe582e4d7a0abfa50a30"}, + {file = "flake8-bugbear-23.12.2.tar.gz", hash = "sha256:32b2903e22331ae04885dae25756a32a8c666c85142e933f43512a70f342052a"}, + {file = "flake8_bugbear-23.12.2-py3-none-any.whl", hash = "sha256:83324bad4d90fee4bf64dd69c61aff94debf8073fbd807c8b6a36eec7a2f0719"}, ] [package.dependencies] attrs = ">=19.2.0" -flake8 = ">=3.0.0" +flake8 = ">=6.0.0" [package.extras] -dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "tox"] +dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", "tox"] [[package]] name = "flake8-commas" version = "2.1.0" description = "Flake8 lint for trailing commas." -category = "dev" optional = false python-versions = "*" files = [ @@ -445,14 +516,13 @@ flake8 = ">=2" [[package]] name = "flake8-comprehensions" -version = "3.15.0" +version = "3.16.0" description = "A flake8 plugin to help you write better list/set/dict comprehensions." -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "flake8_comprehensions-3.15.0-py3-none-any.whl", hash = "sha256:b7e027bbb52be2ceb779ee12484cdeef52b0ad3c1fcb8846292bdb86d3034681"}, - {file = "flake8_comprehensions-3.15.0.tar.gz", hash = "sha256:923c22603e0310376a6b55b03efebdc09753c69f2d977755cba8bb73458a5d4d"}, + {file = "flake8_comprehensions-3.16.0-py3-none-any.whl", hash = "sha256:7c1eadc9d22e765f39857798febe7766b4d9c519793c6c149e3e13bf99693f70"}, + {file = "flake8_comprehensions-3.16.0.tar.gz", hash = "sha256:9cbf789905a8f03f9d350fb82b17b264d9a16c7ce3542b2a7b871ef568cafabe"}, ] [package.dependencies] @@ -462,7 +532,6 @@ flake8 = ">=3,<3.2 || >3.2" name = "flake8-debugger" version = "4.1.2" description = "ipdb/pdb statement checker plugin for flake8" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -478,7 +547,6 @@ pycodestyle = "*" name = "flake8-docstrings" version = "1.7.0" description = "Extension for flake8 which uses pydocstyle to check docstrings" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -492,60 +560,42 @@ pydocstyle = ">=2.1" [[package]] name = "flake8-eradicate" -version = "1.4.0" +version = "1.5.0" description = "Flake8 plugin to find commented out code" -category = "dev" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8,<4.0" files = [ - {file = "flake8-eradicate-1.4.0.tar.gz", hash = "sha256:3088cfd6717d1c9c6c3ac45ef2e5f5b6c7267f7504d5a74b781500e95cb9c7e1"}, - {file = "flake8_eradicate-1.4.0-py3-none-any.whl", hash = "sha256:e3bbd0871be358e908053c1ab728903c114f062ba596b4d40c852fd18f473d56"}, + {file = "flake8_eradicate-1.5.0-py3-none-any.whl", hash = "sha256:18acc922ad7de623f5247c7d5595da068525ec5437dd53b22ec2259b96ce9d22"}, + {file = "flake8_eradicate-1.5.0.tar.gz", hash = "sha256:aee636cb9ecb5594a7cd92d67ad73eb69909e5cc7bd81710cf9d00970f3983a6"}, ] [package.dependencies] attrs = "*" eradicate = ">=2.0,<3.0" -flake8 = ">=3.5,<6" +flake8 = ">5" [[package]] name = "flake8-isort" -version = "4.2.0" -description = "flake8 plugin that integrates isort ." -category = "dev" +version = "6.1.2" +description = "flake8 plugin that integrates isort" optional = false -python-versions = "*" +python-versions = ">=3.9" files = [ - {file = "flake8-isort-4.2.0.tar.gz", hash = "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0"}, - {file = "flake8_isort-4.2.0-py3-none-any.whl", hash = "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"}, + {file = "flake8_isort-6.1.2-py3-none-any.whl", hash = "sha256:549197dedf0273502fb74f04c080beed9e62a7eb70244610413d27052e78bd3b"}, + {file = "flake8_isort-6.1.2.tar.gz", hash = "sha256:9d0452acdf0e1cd6f2d6848e3605e66b54d920e73471fb4744eef0f93df62d5d"}, ] [package.dependencies] -flake8 = ">=3.2.1,<6" -isort = ">=4.3.5,<6" +flake8 = "*" +isort = ">=5.0.0,<7" [package.extras] -test = ["pytest-cov"] - -[[package]] -name = "flake8-polyfill" -version = "1.0.2" -description = "Polyfill package for Flake8 plugins" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, - {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, -] - -[package.dependencies] -flake8 = "*" +test = ["pytest"] [[package]] name = "flake8-quotes" version = "3.4.0" description = "Flake8 lint for quotes." -category = "dev" optional = false python-versions = "*" files = [ @@ -558,26 +608,27 @@ setuptools = "*" [[package]] name = "flake8-rst-docstrings" -version = "0.2.7" -description = "Python docstring reStructuredText (RST) validator" -category = "dev" +version = "0.3.1" +description = "Python docstring reStructuredText (RST) validator for flake8" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "flake8-rst-docstrings-0.2.7.tar.gz", hash = "sha256:2740067ab9237559dd45a3434d8c987792c7b259ca563621a3b95efe201f5382"}, - {file = "flake8_rst_docstrings-0.2.7-py3-none-any.whl", hash = "sha256:5d56075dce360bcc9c6775bfe7cb431aa395de600ca7e8d40580a28d50b2a803"}, + {file = "flake8_rst_docstrings-0.3.1-py3-none-any.whl", hash = "sha256:ed831afca7ee47851e2162d5fa726b823b446fd46085c2164d7979ae5d9a96d7"}, + {file = "flake8_rst_docstrings-0.3.1.tar.gz", hash = "sha256:26dcc1338caf985990677696a8a6a274f73a0c6845b85f567befd3b648db78e2"}, ] [package.dependencies] -flake8 = ">=3.0.0" +flake8 = ">=3" pygments = "*" -restructuredtext-lint = "*" +restructuredtext_lint = "*" + +[package.extras] +develop = ["build", "twine"] [[package]] name = "flake8-string-format" version = "0.3.0" description = "string format checker, plugin for flake8" -category = "dev" optional = false python-versions = "*" files = [ @@ -590,110 +641,104 @@ flake8 = "*" [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] [[package]] name = "httpcore" -version = "1.0.2" +version = "1.0.9" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, ] [package.dependencies] certifi = "*" -h11 = ">=0.13,<0.15" +h11 = ">=0.16" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" -version = "0.25.2" +version = "0.28.1" description = "The next generation HTTP client." -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.25.2-py3-none-any.whl", hash = "sha256:a05d3d052d9b2dfce0e3896636467f8a5342fb2b902c819428e1ac65413ca118"}, - {file = "httpx-0.25.2.tar.gz", hash = "sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8"}, + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, ] [package.dependencies] anyio = "*" certifi = "*" -httpcore = ">=1.0.0,<2.0.0" +httpcore = "==1.*" idna = "*" -sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" 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" +version = "2.1.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "isort" -version = "5.10.1" +version = "6.0.1" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false -python-versions = ">=3.6.1,<4.0" +python-versions = ">=3.9.0" files = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, + {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, + {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, ] [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +colors = ["colorama"] plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "loguru" version = "0.6.0" description = "Python logging made (stupidly) simple" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -712,7 +757,6 @@ dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -735,21 +779,19 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -759,74 +801,108 @@ files = [ [[package]] name = "mypy" -version = "0.910" +version = "1.17.1" description = "Optional static typing for Python" -category = "dev" optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, - {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, - {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, - {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, - {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, - {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, - {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, - {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, - {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, - {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, - {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, - {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, - {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, - {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, - {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, - {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, - {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, - {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, - {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, - {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, - {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, - {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, - {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, +python-versions = ">=3.9" +files = [ + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typing-extensions = ">=3.7.4" +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "0.4.4" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "main" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=2.7" +python-versions = ">=3.8" files = [ - {file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] +[[package]] +name = "orderly-set" +version = "5.5.0" +description = "Orderly set" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orderly_set-5.5.0-py3-none-any.whl", hash = "sha256:46f0b801948e98f427b412fcabb831677194c05c3b699b80de260374baa0b1e7"}, + {file = "orderly_set-5.5.0.tar.gz", hash = "sha256:e87185c8e4d8afa64e7f8160ee2c542a475b738bc891dc3f58102e654125e6ce"}, +] + +[package.extras] +coverage = ["coverage (>=7.6.0,<7.7.0)"] +dev = ["bump2version (>=1.0.0,<1.1.0)", "ipdb (>=0.13.0,<0.14.0)"] +optimize = ["orjson"] +static = ["flake8 (>=7.1.0,<7.2.0)", "flake8-pyproject (>=1.2.3,<1.3.0)"] +test = ["pytest (>=8.3.0,<8.4.0)", "pytest-benchmark (>=5.1.0,<5.2.0)", "pytest-cov (>=6.0.0,<6.1.0)", "python-dotenv (>=1.0.0,<1.1.0)"] + [[package]] name = "packaging" -version = "24.1" +version = "25.0" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -836,164 +912,231 @@ files = [ [[package]] name = "pbr" -version = "6.0.0" +version = "7.0.0" description = "Python Build Reasonableness" -category = "dev" optional = false python-versions = ">=2.6" files = [ - {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, - {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, + {file = "pbr-7.0.0-py2.py3-none-any.whl", hash = "sha256:b447e63a2bc04fd975fc0480b8d5ebf979179e2c0ae203bf1eff9ea20073bc38"}, + {file = "pbr-7.0.0.tar.gz", hash = "sha256:cf4127298723dafbce3afd13775ccf3885be5d3c8435751b867f9a6a10b71a39"}, ] +[package.dependencies] +setuptools = "*" + [[package]] name = "pep8-naming" -version = "0.12.1" +version = "0.13.3" description = "Check PEP-8 naming conventions, plugin for flake8" -category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "pep8-naming-0.12.1.tar.gz", hash = "sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841"}, - {file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"}, + {file = "pep8-naming-0.13.3.tar.gz", hash = "sha256:1705f046dfcd851378aac3be1cd1551c7c1e5ff363bacad707d43007877fa971"}, + {file = "pep8_naming-0.13.3-py3-none-any.whl", hash = "sha256:1a86b8c71a03337c97181917e2b472f0f5e4ccb06844a0d6f0a33522549e7a80"}, ] [package.dependencies] -flake8 = ">=3.9.1" -flake8-polyfill = ">=1.0.2,<2" +flake8 = ">=5.0.0" [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" 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.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, ] [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.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pybotx" -version = "0.69.0" +version = "0.76.0a1" description = "A python library for interacting with eXpress BotX API" -category = "main" optional = false -python-versions = "<3.13,>=3.8" +python-versions = "<3.14,>=3.9" files = [ - {file = "pybotx-0.69.0-py3-none-any.whl", hash = "sha256:993a8c3c176d7a18a9eadc1c5eda76abb3bcc2fbd7154d51fe84b678f122cf10"}, - {file = "pybotx-0.69.0.tar.gz", hash = "sha256:6aed8a4db7fef5a50e5e05db531c48b0dc7585dd844ab4df63b933b14528919f"}, + {file = "pybotx-0.76.0a1-py3-none-any.whl", hash = "sha256:2051da3c627e254458d995127335f4781a9e382338fb9f7fc4945085c15390fb"}, + {file = "pybotx-0.76.0a1.tar.gz", hash = "sha256:fd07877244eeea7926fbeb35276ff64817a776393349a6ee936eb25073e6ed70"}, ] [package.dependencies] -aiocsv = ">=1.2.3,<1.4.0" -aiofiles = ">=0.7.0,<24.0.0" -httpcore = ">=1.0.0,<1.0.3" -httpx = ">=0.25.0,<0.26.0" +aiocsv = ">=1.2.3,<=1.4.0" +aiofiles = ">=0.7.0,<=24.1.0" +httpcore = "1.0.9" +httpx = ">=0.28.0,<0.29.0" loguru = ">=0.6.0,<0.7.0" -mypy-extensions = ">=0.2.0,<0.5.0" -pydantic = ">=1.6.0,<1.11.0" +mypy-extensions = ">=0.2.0,<=1.1.0" +pydantic = ">=2.8.2,<3.0" pyjwt = ">=2.0.0,<3.0.0" [[package]] name = "pycodestyle" -version = "2.8.0" +version = "2.14.0" description = "Python style guide checker" -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.9" files = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, ] [[package]] name = "pydantic" -version = "1.10.17" -description = "Data validation and settings management using python type hints" -category = "main" +version = "2.11.7" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"}, - {file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"}, - {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"}, - {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"}, - {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"}, - {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"}, - {file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"}, - {file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"}, - {file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"}, - {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"}, - {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"}, - {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"}, - {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"}, - {file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"}, - {file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"}, - {file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"}, - {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"}, - {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"}, - {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"}, - {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"}, - {file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"}, - {file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"}, - {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"}, - {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"}, - {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"}, - {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"}, - {file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"}, - {file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"}, - {file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"}, - {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"}, - {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"}, - {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"}, - {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"}, - {file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"}, - {file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"}, - {file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"}, - {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"}, - {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"}, - {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"}, - {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"}, - {file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"}, - {file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"}, - {file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"}, + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.6.0" +pydantic-core = "2.33.2" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydocstyle" version = "6.3.0" description = "Python docstring style checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1009,26 +1152,24 @@ toml = ["tomli (>=1.2.3)"] [[package]] name = "pyflakes" -version = "2.4.0" +version = "3.4.0" description = "passive checker of Python programs" -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.9" files = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, + {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, + {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, ] [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -1036,148 +1177,149 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.8.0" +version = "2.10.1" description = "JSON Web Token implementation in Python" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.4.1" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", 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"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" -version = "0.18.3" +version = "1.1.0" description = "Pytest support for asyncio" -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pytest-asyncio-0.18.3.tar.gz", hash = "sha256:7659bdb0a9eb9c6e3ef992eef11a2b3e69697800ad02fb06374a210d85b29f91"}, - {file = "pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213"}, - {file = "pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84"}, + {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, + {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, ] [package.dependencies] -pytest = ">=6.1.0" +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<9" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.10\""} [package.extras] -testing = ["coverage (==6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (==0.931)", "pytest-trio (>=0.7.0)"] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" -version = "4.1.0" +version = "6.2.1" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, + {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" +coverage = {version = ">=7.5", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=6.2.5" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" -category = "dev" 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_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]] name = "restructuredtext-lint" version = "1.4.0" description = "reStructuredText linter" -category = "dev" optional = false python-versions = "*" files = [ @@ -1189,45 +1331,46 @@ docutils = ">=0.11,<1.0" [[package]] name = "rich" -version = "13.7.1" +version = "14.1.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "dev" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "setuptools" -version = "70.3.0" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"}, - {file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1237,122 +1380,148 @@ files = [ [[package]] name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" +version = "3.0.1" +description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, + {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, + {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, ] [[package]] name = "stevedore" -version = "5.2.0" +version = "5.4.1" description = "Manage dynamic plugins for Python applications" -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "stevedore-5.2.0-py3-none-any.whl", hash = "sha256:1c15d95766ca0569cad14cb6272d4d31dae66b011a929d7c18219c176ea1b5c9"}, - {file = "stevedore-5.2.0.tar.gz", hash = "sha256:46b93ca40e1114cea93d738a6c1e365396981bb6bb78c27045b7587c9473544d"}, + {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, + {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, ] [package.dependencies] -pbr = ">=2.0.0,<2.1.0 || >2.1.0" +pbr = ">=2.0.0" [[package]] name = "tokenize-rt" -version = "5.2.0" +version = "6.2.0" description = "A wrapper around the stdlib `tokenize` which roundtrips." -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "tokenize_rt-5.2.0-py2.py3-none-any.whl", hash = "sha256:b79d41a65cfec71285433511b50271b05da3584a1da144a0752e9c621a285289"}, - {file = "tokenize_rt-5.2.0.tar.gz", hash = "sha256:9fe80f8a5c1edad2d3ede0f37481cc0cc1538a2f442c9c2f9e4feacd2792d054"}, + {file = "tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44"}, + {file = "tokenize_rt-6.2.0.tar.gz", hash = "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6"}, ] [[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.8" files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" +name = "typing-inspection" +version = "0.4.1" +description = "Runtime typing introspection tools" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, ] +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "wemake-python-styleguide" -version = "0.16.1" +version = "0.18.0" description = "The strictest and most opinionated python linter ever" -category = "dev" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.8.1,<4.0" files = [ - {file = "wemake-python-styleguide-0.16.1.tar.gz", hash = "sha256:4fcd78dd55732679b5fc8bc37fd7e04bbaa5cdc1b1a829ad265e8f6b0d853cf6"}, - {file = "wemake_python_styleguide-0.16.1-py3-none-any.whl", hash = "sha256:202c22ecfee1f5caf0555048602cd52f2435cd57903e6b0cd46b5aaa3f652140"}, + {file = "wemake_python_styleguide-0.18.0-py3-none-any.whl", hash = "sha256:2219be145185edcd5e01f4ce49e3dea11acc34f2c377face0c175bb6ea6ac988"}, + {file = "wemake_python_styleguide-0.18.0.tar.gz", hash = "sha256:69139858cf5b2a9ba09dac136e2873a4685515768f68fdef2684ebefd7b1dafd"}, ] [package.dependencies] astor = ">=0.8,<0.9" attrs = "*" darglint = ">=1.2,<2.0" -flake8 = ">=3.7,<5" -flake8-bandit = ">=2.1,<4" -flake8-broken-line = ">=0.3,<0.5" -flake8-bugbear = ">=20.1,<23.0" +flake8 = ">5" +flake8-bandit = ">=4.1,<5.0" +flake8-broken-line = ">=1.0,<2.0" +flake8-bugbear = ">=23.5,<24.0" flake8-commas = ">=2.0,<3.0" flake8-comprehensions = ">=3.1,<4.0" flake8-debugger = ">=4.0,<5.0" flake8-docstrings = ">=1.3,<2.0" -flake8-eradicate = ">=1.0,<2.0" -flake8-isort = ">=4.0,<5.0" +flake8-eradicate = ">=1.5,<2.0" +flake8-isort = ">=6.0,<7.0" flake8-quotes = ">=3.0,<4.0" -flake8-rst-docstrings = ">=0.2,<0.3" +flake8-rst-docstrings = ">=0.3,<0.4" flake8-string-format = ">=0.3,<0.4" -pep8-naming = ">=0.11,<0.13" +pep8-naming = ">=0.13,<0.14" pygments = ">=2.4,<3.0" -typing_extensions = ">=3.6,<5.0" +setuptools = "*" +typing_extensions = ">=4.0,<5.0" [[package]] name = "win32-setctime" -version = "1.1.0" +version = "1.2.0" description = "A small Python utility to set file creation time on Windows" -category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, - {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, + {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, + {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, ] [package.extras] @@ -1360,5 +1529,5 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [metadata] lock-version = "2.0" -python-versions = ">=3.8,<3.12" -content-hash = "f45a38dbe2e2a9208427d76e48bb464d142aa2830bff9cd044343ca0c94d7e49" +python-versions = ">=3.9,<3.14" +content-hash = "750c55f6b350d16273e13711cc9a283968c2f24d45ea0cb0c7689d9115144dd8" diff --git a/pybotx_smartapp_rpc/models/method.py b/pybotx_smartapp_rpc/models/method.py index 26580bf..ef641c3 100644 --- a/pybotx_smartapp_rpc/models/method.py +++ b/pybotx_smartapp_rpc/models/method.py @@ -3,7 +3,7 @@ from functools import partial from typing import Dict, List, Optional, Union -from pydantic.fields import ModelField +from pydantic.v1.fields import ModelField from pybotx_smartapp_rpc.smartapp import SmartApp from pybotx_smartapp_rpc.typing import ( diff --git a/pybotx_smartapp_rpc/openapi_utils.py b/pybotx_smartapp_rpc/openapi_utils.py index 6abe73c..bf98807 100644 --- a/pybotx_smartapp_rpc/openapi_utils.py +++ b/pybotx_smartapp_rpc/openapi_utils.py @@ -1,16 +1,15 @@ from enum import Enum from typing import Any, Dict, List, Optional, Set, Type, Union -from pydantic import BaseModel -from pydantic.fields import ModelField -from pydantic.schema import ( - field_schema, - get_flat_models_from_fields, - model_process_schema, -) +from pydantic import BaseModel, TypeAdapter + +from pydantic.v1.fields import ModelField +from pydantic.v1.schema import model_process_schema, \ + field_schema from pybotx_smartapp_rpc import RPCRouter from pybotx_smartapp_rpc.models.method import RPCMethod +from pybotx_smartapp_rpc.pydantic_v1_override import get_flat_models_from_fields REF_PREFIX = "#/components/schemas/" @@ -63,42 +62,47 @@ def get_rpc_model_definitions( flat_models: Set[Union[Type[BaseModel], Type[Enum]]], model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], ) -> Dict[str, Any]: + definitions: Dict[str, Dict[str, Any]] = {} for model in flat_models: - m_schema, m_definitions, m_nested_models = model_process_schema( - model, - model_name_map=model_name_map, - ref_prefix=REF_PREFIX, - ) - definitions.update(m_definitions) + # Get the JSON schema of the model (including nested definitions under '$defs') + m_schema = model.model_json_schema(ref_template=REF_PREFIX + '{model}') + + # Extract nested model definitions from $defs (if present) + nested_defs = m_schema.pop('$defs', {}) + + # Update overall definitions with nested definitions + definitions.update(nested_defs) + model_name = model_name_map[model] + + # Optionally modify the description to remove form feed characters as before if "description" in m_schema: m_schema["description"] = m_schema["description"].split("\f")[0] + + # Add the model's own schema under its mapped name definitions[model_name] = m_schema - return definitions + return definitions def get_openapi_operation_rpc_args( *, - body_field: Optional[ModelField], + body_field: Optional[Any], model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], ) -> Optional[Dict[str, Any]]: if not body_field: return None - body_schema, _, _ = field_schema( - body_field, - model_name_map=model_name_map, - ref_prefix=REF_PREFIX, - ) + model_type = body_field.outer_type_ + body_schema = schema_or_ref(model_type, model_name_map) + request_media_type = "application/json" - required = body_field.required request_body_oai: Dict[str, Any] = {} - if required: - request_body_oai["required"] = required + if body_field.required: + request_body_oai["required"] = True + + request_body_oai["content"] = {request_media_type: {"schema": body_schema}} - request_media_content: Dict[str, Any] = {"schema": body_schema} - request_body_oai["content"] = {request_media_type: request_media_content} return request_body_oai @@ -117,6 +121,16 @@ def get_openapi_rpc_metadata(*, name: str, route: RPCMethod) -> Dict[str, Any]: return operation +def schema_or_ref(model_type, model_name_map): + """Return a $ref if model_type is a Pydantic model or Enum in model_name_map, else inline schema.""" + if ( + isinstance(model_type, type) + and (issubclass(model_type, BaseModel) or issubclass(model_type, Enum)) + and model_type in model_name_map + ): + return {"$ref": f"{REF_PREFIX}{model_name_map[model_type]}"} + + return TypeAdapter(model_type).json_schema(ref_template=REF_PREFIX + '{model}') def get_rpc_openapi_path( # noqa: WPS231 *, method_name: str, @@ -137,11 +151,9 @@ def get_rpc_openapi_path( # noqa: WPS231 operation["requestBody"] = request_body_oai # - Successful response - - response_schema, _, _ = field_schema( - route.response_field, - model_name_map=model_name_map, - ref_prefix=REF_PREFIX, - ) + response_model = route.response_field.outer_type_ + response_schema = schema_or_ref(response_model, model_name_map) + response_schema["title"] = route.response_field.name.replace('_', ' ').title() operation.setdefault("responses", {}).setdefault("ok", {}).update( { @@ -160,17 +172,12 @@ def get_rpc_openapi_path( # noqa: WPS231 if route.errors_models and ( field := route.errors_models[error_status_code] # noqa: WPS332 ): - error_field_schema, _, _ = field_schema( - field, - model_name_map=model_name_map, - ref_prefix=REF_PREFIX, - ) - error_schema = ( - process_response.setdefault("content", {}) - .setdefault("application/json", {}) - .setdefault("schema", {}) - ) - deep_dict_update(error_schema, error_field_schema) + error_model = field.outer_type_ + error_schema = schema_or_ref(error_model, model_name_map) + + process_response.setdefault("content", {}).setdefault( + "application/json", {} + )["schema"] = error_schema description = error_response["description"] or "Error" deep_dict_update(openapi_response, process_response) diff --git a/pybotx_smartapp_rpc/pydantic_v1_override.py b/pybotx_smartapp_rpc/pydantic_v1_override.py new file mode 100644 index 0000000..d917433 --- /dev/null +++ b/pybotx_smartapp_rpc/pydantic_v1_override.py @@ -0,0 +1,88 @@ +from enum import Enum +from typing import Union, Sequence, Type + +from pydantic.fields import FieldInfo +from pydantic.v1.fields import ModelField +from pydantic.v1.schema import TypeModelSet +from pydantic.v1.utils import lenient_issubclass + +from pydantic.v1 import BaseModel as BaseModelV1 +from pydantic import BaseModel as BaseModelV2 + +BaseModelTypes = (BaseModelV1, BaseModelV2) + + +def _get_field_type(field: Union[ModelField, FieldInfo]): + """ + Extract the declared type from either a Pydantic v1 ModelField + or a Pydantic v2 FieldInfo. + """ + if isinstance(field, ModelField): + return field.type_ + elif isinstance(field, FieldInfo): + return field.annotation + else: + raise TypeError(f"Unsupported field type: {type(field)}") + +def get_flat_models_from_model(model: Type[Union[BaseModelV1, BaseModelV2]], + known_models: TypeModelSet) -> TypeModelSet: + """ + Custom version of pydantic.v1.schema.get_flat_models_from_model + that supports v2 BaseModel and v2 FieldInfo. + """ + flat_models: TypeModelSet = set() + + if model in known_models: + return flat_models + + known_models.add(model) + flat_models.add(model) + + # v1 stores __fields__, v2 stores model_fields + fields = getattr(model, '__fields__', None) or getattr(model, 'model_fields', {}) + flat_models |= get_flat_models_from_fields(fields.values(), known_models=known_models) + + return flat_models + + + +def get_flat_models_from_fields( + fields: Sequence[Union[ModelField, FieldInfo]], + known_models: TypeModelSet, +) -> TypeModelSet: + """ + Take a list of fields (v1 ModelField or v2 FieldInfo) and return all BaseModel/Enum types used, + recursively. + """ + flat_models: TypeModelSet = set() + for field in fields: + flat_models |= get_flat_models_from_field(field, known_models=known_models) + return flat_models + + +def get_flat_models_from_field( + field: Union[ModelField, FieldInfo], + known_models: TypeModelSet, +) -> TypeModelSet: + """ + Handle one field (v1 ModelField or v2 FieldInfo) and return all models in its type tree. + """ + + flat_models: TypeModelSet = set() + + field_type = _get_field_type(field) + + # Support TypeAdapter-style wrappers + if lenient_issubclass(getattr(field_type, '__pydantic_model__', None), BaseModelTypes): + field_type = field_type.__pydantic_model__ + + sub_fields = getattr(field, "sub_fields", None) + + if sub_fields and not lenient_issubclass(field_type, BaseModelTypes): + flat_models |= get_flat_models_from_fields(sub_fields, known_models=known_models) + elif lenient_issubclass(field_type, BaseModelTypes) and field_type not in known_models: + flat_models |= get_flat_models_from_model(field_type, known_models=known_models) + elif lenient_issubclass(field_type, Enum): + flat_models.add(field_type) + + return flat_models \ No newline at end of file diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index 8d245d1..b851673 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -2,9 +2,9 @@ from enum import Enum from typing import Callable, Dict, List, Optional, Tuple, Type, Union -from pydantic import BaseConfig +from pydantic.v1 import BaseConfig from pydantic.error_wrappers import ValidationError -from pydantic.fields import ModelField +from pydantic.v1.fields import ModelField from pybotx_smartapp_rpc import RPCError from pybotx_smartapp_rpc.empty_args import EmptyArgs @@ -19,6 +19,10 @@ from pybotx_smartapp_rpc.smartapp import SmartApp from pybotx_smartapp_rpc.typing import Handler, Middleware, RPCResponse +# Create a config that allows arbitrary types +class FlexibleConfig(BaseConfig): + arbitrary_types_allowed = True + class RPCRouter: def __init__( @@ -130,21 +134,12 @@ def _get_args_and_return_field( if return_type: response_type = return_type - response_field = ModelField( - name=f"Response_{handler.__name__}", - type_=response_type, - model_config=BaseConfig, - class_validators={}, - ) + response_field = self._get_ModelField(name=f"Response_{handler.__name__}", + type_=response_type,) args_annotations = [arg[1].annotation for arg in signature.parameters.items()] if len(args_annotations) >= 2: - arg_field = ModelField( - name=str(args_annotations[1].__name__), - type_=args_annotations[1], - model_config=BaseConfig, - class_validators={}, - ) + arg_field = self._get_ModelField(name=str(args_annotations[1].__name__), type_=args_annotations[1]) else: arg_field = None # type: ignore @@ -167,14 +162,19 @@ def _get_error_fields_and_models( if error.__fields__["id"].default } errors_models = { - error.__fields__["id"].default: ModelField( - name=error.__name__, - type_=error, - class_validators=None, - model_config=BaseConfig, - ) + error.__fields__["id"].default: self._get_ModelField(name=error.__name__, type_=error) for error in errors if error.__fields__["id"].default } return errors_fields, errors_models + + def _get_ModelField(self, name:str, type_:type) -> ModelField: + model_field = ModelField( + name=name, + type_=type_, + model_config=FlexibleConfig, + class_validators={}, + ) + model_field.prepare() + return model_field \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index cee30e4..64fbb03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,23 +6,24 @@ authors = ["Arseniy Zhiltsov "] readme = "README.md" [tool.poetry.dependencies] -python = ">=3.8,<3.12" +python = ">=3.9,<3.14" -pydantic = ">=1.6.0,<1.11.0" -pybotx = ">=0.69.0" +pydantic = ">=2.8.2,<3.0" +pybotx = "~0.76.0a1" +deepdiff = "^8.6.0" [tool.poetry.dev-dependencies] -add-trailing-comma = "^2.2.1" -black = "22.3.0" -isort = "5.10.1" -autoflake = "^1.4" -mypy = "0.910" -wemake-python-styleguide = "0.16.1" +add-trailing-comma = "^3.2.0" +black = "25.1.0" +isort = "6.0.1" +autoflake = "^2.3.1" +mypy = "1.17.1" +wemake-python-styleguide = "0.18.0" -pytest = "^7.0.1" -pytest-asyncio = "^0.18.1" -pytest-cov = "^4.1.0" +pytest = "^8.4.1" +pytest-asyncio = "^1.1.0" +pytest-cov = "^6.2.1" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=2.1.4"] build-backend = "poetry.core.masonry.api" diff --git a/tests/test_openapi.py b/tests/test_openapi.py index f973717..d8d2fcc 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -1,5 +1,6 @@ +from deepdiff import DeepDiff from pydantic import BaseModel -from pydantic.schema import get_model_name_map +from pydantic.v1.schema import get_model_name_map from pybotx_smartapp_rpc import ( RPCError, @@ -31,18 +32,18 @@ class Meta(BaseModel): class UserNotFound(RPCError): """Error description.""" - id = "UserNotFound" - reason = "User not found in system" + id:str = "UserNotFound" + reason:str = "User not found in system" meta: Meta class OneUserNotFound(UserNotFound): - id = "OneUserNotFound" + id:str = "OneUserNotFound" class InvalidCredentialsError(RPCError): - id = "InvalidCredentialsError" - reason = "Invalid credentials" + id:str = "InvalidCredentialsError" + reason:str = "Invalid credentials" def test__deep_dict_update() -> None: @@ -101,7 +102,7 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: security_scheme={"auth": []}, ) - assert path == { + expected_path = { "post": { "description": None, "operationId": "rpc_get_api_version", @@ -123,6 +124,10 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: } } + diff = DeepDiff(expected_path, path) + + assert not diff, diff + async def test_collect_rpc_method_exists__with_errors() -> None: # - Arrange - @@ -146,7 +151,7 @@ async def get_api_version( model_name_map=rpc_model_name_map, ) - assert path == { + expected_path = { "post": { "summary": "Get Api Version", "tags": ["rpc", "user"], @@ -202,6 +207,9 @@ async def get_api_version( } } + diff = DeepDiff(expected_path, path, ignore_order=True) + assert not diff, diff + async def test_get_rpc_model_definition() -> None: # - Arrange - @@ -223,7 +231,7 @@ async def get_api_version( flat_models=flat_rpc_models, model_name_map=rpc_model_name_map ) - assert rpc_definitions == { + expected_definitions = { "Meta": { "properties": {"user_id": {"title": "User Id", "type": "integer"}}, "required": ["user_id"], @@ -252,3 +260,6 @@ async def get_api_version( "type": "object", }, } + + diff = DeepDiff(expected_definitions, rpc_definitions, ignore_order=True) + assert not diff, diff diff --git a/tests/test_rpc_calls.py b/tests/test_rpc_calls.py index 316b60d..8e6d764 100644 --- a/tests/test_rpc_calls.py +++ b/tests/test_rpc_calls.py @@ -2,6 +2,7 @@ from unittest.mock import AsyncMock from uuid import UUID +from deepdiff import DeepDiff from pybotx import ( BotAPISyncSmartAppEventErrorResponse, BotAPISyncSmartAppEventResultResponse, @@ -109,13 +110,13 @@ async def sum_handler(smartapp: SmartApp, args: SumArgs) -> RPCResultResponse[in "status": "error", "errors": [ { - "reason": "value is not a valid integer", - "id": "TYPE_ERROR", + "reason": "Input should be a valid integer, unable to parse string as an integer", + "id": "INT_PARSING", "meta": {"location": ("first",)}, }, { - "reason": "field required", - "id": "VALUE_ERROR", + "reason": "Field required", + "id": "MISSING", "meta": {"location": ("second",)}, }, ], @@ -204,8 +205,8 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: "status": "error", "errors": [ { - "reason": "Invalid RPC request: field required", - "id": "VALUE_ERROR", + "reason": "Invalid RPC request: Field required", + "id": "MISSING", "meta": {"field": "method"}, }, ], @@ -310,7 +311,7 @@ async def sum_handler( args: SumArgs, ) -> RPCResultResponse[SumResponse]: return RPCResultResponse( - SumResponse(call_result=args.first_arg + args.second_arg), + SumResponse(callResult=args.first_arg + args.second_arg), ) smartapp_rpc = SmartAppRPC(routers=[rpc]) @@ -392,10 +393,12 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: ) # - Assert - - assert response == BotAPISyncSmartAppEventResultResponse.from_domain( + expected_object = BotAPISyncSmartAppEventResultResponse.from_domain( data=1, files=Undefined, ) + diff = DeepDiff(response.model_dump(), expected_object.model_dump()) + assert not diff, diff async def test_handle_sync_smartapp_event_rpc_error_returned( @@ -425,7 +428,7 @@ async def get_api_version(smartapp: SmartApp) -> RPCErrorResponse: ) # - Assert - - assert response == BotAPISyncSmartAppEventErrorResponse.from_domain( + expected_response = BotAPISyncSmartAppEventErrorResponse.from_domain( errors=[ { "reason": "Api version undefined", @@ -435,6 +438,9 @@ async def get_api_version(smartapp: SmartApp) -> RPCErrorResponse: ] ) + diff = DeepDiff(response.model_dump(), expected_response.model_dump()) + assert not diff, diff + async def test_handle_sync_smartapp_event_with_wrong_args( smartapp_event_factory: Callable[..., SmartAppEvent], @@ -463,21 +469,24 @@ async def sum_handler(smartapp: SmartApp, args: SumArgs) -> RPCResultResponse[in ) # - Assert - - assert response == BotAPISyncSmartAppEventErrorResponse.from_domain( + expected_response = BotAPISyncSmartAppEventErrorResponse.from_domain( errors=[ { - "reason": "value is not a valid integer", - "id": "TYPE_ERROR", + "reason": "Input should be a valid integer, unable to parse string as an integer", + "id": "INT_PARSING", "meta": {"location": ("first",)}, }, { - "reason": "field required", - "id": "VALUE_ERROR", + "reason": "Field required", + "id": "MISSING", "meta": {"location": ("second",)}, }, ] ) + diff = DeepDiff(response.model_dump(), expected_response.model_dump()) + assert not diff, diff + async def test_handle_sync_smartapp_event_wrong_rpc_request( smartapp_event_factory: Callable[..., SmartAppEvent], @@ -504,12 +513,16 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: ) # - Assert - - assert response == BotAPISyncSmartAppEventErrorResponse.from_domain( + expected_response = BotAPISyncSmartAppEventErrorResponse.from_domain( errors=[ { - "reason": "Invalid RPC request: field required", - "id": "VALUE_ERROR", + "reason": "Invalid RPC request: Field required", + "id": "MISSING", "meta": {"field": "method"}, }, ] ) + + diff = DeepDiff(response.model_dump(), expected_response.model_dump()) + assert not diff, diff + From ccaf84a9546e05a7e72f03d0b868ad0bebc2bcf5 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Fri, 15 Aug 2025 13:28:45 +0300 Subject: [PATCH 02/31] fix poetry --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 64fbb03..beba216 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,5 +25,5 @@ pytest-asyncio = "^1.1.0" pytest-cov = "^6.2.1" [build-system] -requires = ["poetry-core>=2.1.4"] +requires = ["poetry-core >=1.6.0"] build-backend = "poetry.core.masonry.api" From 01cd220e6f81141f21f81c729c3c2434bf2ad3d4 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Mon, 18 Aug 2025 10:50:34 +0300 Subject: [PATCH 03/31] temp --- pybotx_smartapp_rpc/router.py | 2 +- tests/test_rpc_router.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index b851673..666c765 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -155,7 +155,7 @@ def _get_error_fields_and_models( return errors_fields, errors_models errors_fields = { - error.__fields__["id"].default: { + error.model_fields["id"].default: { "description": error.__doc__ or error.__fields__["reason"].default, } for error in errors diff --git a/tests/test_rpc_router.py b/tests/test_rpc_router.py index 82c8901..96248ef 100644 --- a/tests/test_rpc_router.py +++ b/tests/test_rpc_router.py @@ -1,3 +1,5 @@ + + import pytest from pybotx_smartapp_rpc import RPCResultResponse, RPCRouter, SmartApp From 1b56d81314c46d7cb2e3c263710c73e6e221e570 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Mon, 18 Aug 2025 17:23:38 +0300 Subject: [PATCH 04/31] add tests --- pybotx_smartapp_rpc/models/request.py | 7 ++-- pybotx_smartapp_rpc/models/responses.py | 5 +-- tests/test_rpc_calls.py | 39 ++++++++++++++++++++ tests/test_rpc_router.py | 47 +++++++++++++++++++++++-- 4 files changed, 91 insertions(+), 7 deletions(-) diff --git a/pybotx_smartapp_rpc/models/request.py b/pybotx_smartapp_rpc/models/request.py index 063562a..c4094b6 100644 --- a/pybotx_smartapp_rpc/models/request.py +++ b/pybotx_smartapp_rpc/models/request.py @@ -1,11 +1,12 @@ from typing import Any, Dict, Literal -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict class RPCArgsBaseModel(BaseModel): - class Config: - allow_population_by_field_name = True + model_config = { + "populate_by_name": True + } class RPCRequest(BaseModel): diff --git a/pybotx_smartapp_rpc/models/responses.py b/pybotx_smartapp_rpc/models/responses.py index c7bd3f7..1bfe4e5 100644 --- a/pybotx_smartapp_rpc/models/responses.py +++ b/pybotx_smartapp_rpc/models/responses.py @@ -15,8 +15,9 @@ class RPCResponseBaseModel(BaseModel): - class Config: - allow_population_by_field_name = True + model_config = { + "populate_by_name": True + } @dataclass diff --git a/tests/test_rpc_calls.py b/tests/test_rpc_calls.py index 8e6d764..0b6fc98 100644 --- a/tests/test_rpc_calls.py +++ b/tests/test_rpc_calls.py @@ -2,6 +2,7 @@ from unittest.mock import AsyncMock from uuid import UUID +import pytest from deepdiff import DeepDiff from pybotx import ( BotAPISyncSmartAppEventErrorResponse, @@ -23,6 +24,7 @@ SmartApp, SmartAppRPC, ) +from pybotx_smartapp_rpc.empty_args import EmptyArgs async def test_rpc_call_rpc_error_returned( @@ -526,3 +528,40 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: diff = DeepDiff(response.model_dump(), expected_response.model_dump()) assert not diff, diff +@pytest.mark.asyncio +async def test_rpc_call_with_middleware_effect(smartapp_event_factory, bot): + applied = [] + + # Middleware to track execution + + + async def router_mw(smartapp: SmartApp, args, call_next): + applied.append("router_mw") + response = await call_next(smartapp, args) + return response + + async def method_mw(smartapp: SmartApp, args, call_next): + applied.append("method_mw") + response = await call_next(smartapp, args) + return response + + # Create router with our middleware + rpc = RPCRouter(middlewares=[router_mw]) + + @rpc.method("test",middlewares=[method_mw]) + async def test_method(smartapp: SmartApp,): + applied.append("handler") + return RPCResultResponse(result=123) + + # Include router in SmartAppRPC + smartapp_rpc = SmartAppRPC(routers=[rpc]) + + # Create event with empty params + event = smartapp_event_factory("test",) + + # Execute the RPC call + result = await smartapp_rpc.handle_sync_smartapp_event(event, bot) + + # Assertions + assert applied == ["router_mw",'method_mw' ,"handler"] + assert result.data == 123 \ No newline at end of file diff --git a/tests/test_rpc_router.py b/tests/test_rpc_router.py index 96248ef..16332bc 100644 --- a/tests/test_rpc_router.py +++ b/tests/test_rpc_router.py @@ -1,8 +1,11 @@ - +from unittest.mock import AsyncMock, MagicMock import pytest +from pydantic import ValidationError -from pybotx_smartapp_rpc import RPCResultResponse, RPCRouter, SmartApp +from pybotx_smartapp_rpc import RPCResultResponse, RPCRouter, SmartApp, \ + RPCArgsBaseModel, RPCError +from pybotx_smartapp_rpc.middlewares.empty_args_middleware import empty_args_middleware async def test_collect_rpc_method_exists() -> None: @@ -45,3 +48,43 @@ async def other_get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: # - Assert - assert "get_api_version" in str(exc.value) assert "already registered" in str(exc.value) + + +def test_middlewares_order(): + """Check the order in which mixed middlewares are stored + Note - the last middleware is added automatically + """ + + + router_middleware = AsyncMock() + method_middleware = AsyncMock() + + + rpc = RPCRouter(middlewares=[router_middleware]) + + @rpc.method("test_method", middlewares=[method_middleware]) + async def test_method(smartapp): + pass + + rpc_method = rpc.rpc_methods["test_method"] + + # Check the order of middlewares + assert rpc_method.middlewares[0] is router_middleware + assert rpc_method.middlewares[1] is method_middleware + assert rpc_method.middlewares[-1] is empty_args_middleware + +async def test_rpc_router_call_method_directly(): + rpc = RPCRouter() + + called = False + + @rpc.method("dummy") + async def dummy_method(smartapp: SmartApp): + nonlocal called + called = True + return RPCResultResponse(result=42) + + response = await rpc.rpc_methods["dummy"].handler(MagicMock()) + assert called + assert response.result == 42 + From e19cbf2e4ab138bc46e557497f8409376f2362fc Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 16:13:40 +0300 Subject: [PATCH 05/31] add tests, refactor --- README.md | 17 +- pybotx_smartapp_rpc/exceptions.py | 3 +- pybotx_smartapp_rpc/models/method.py | 3 +- pybotx_smartapp_rpc/models/model_field.py | 39 ++++ pybotx_smartapp_rpc/models/request.py | 6 +- pybotx_smartapp_rpc/models/responses.py | 4 +- pybotx_smartapp_rpc/openapi/__init__.py | 0 .../{openapi_utils.py => openapi/openapi.py} | 196 ++++++++++-------- pybotx_smartapp_rpc/openapi/utils.py | 118 +++++++++++ pybotx_smartapp_rpc/pydantic_v1_override.py | 88 -------- pybotx_smartapp_rpc/router.py | 94 +++++---- tests/test_openapi.py | 140 ++++++++++++- tests/test_rpc_calls.py | 17 +- tests/test_rpc_router.py | 8 +- 14 files changed, 482 insertions(+), 251 deletions(-) create mode 100644 pybotx_smartapp_rpc/models/model_field.py create mode 100644 pybotx_smartapp_rpc/openapi/__init__.py rename pybotx_smartapp_rpc/{openapi_utils.py => openapi/openapi.py} (60%) create mode 100644 pybotx_smartapp_rpc/openapi/utils.py delete mode 100644 pybotx_smartapp_rpc/pydantic_v1_override.py diff --git a/README.md b/README.md index b18aaf3..a455366 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +from pybotx_smartapp_rpc.new_openapi.refactored_utils.support_methods import get_rpc_flat_models_from_routes + # BotX-SmartApp-RPC Библиотека, позволяющая писать смартаппы, используя [наш JSONRPC-like протокол](https://ccsteam.atlassian.net/wiki/spaces/EI/pages/193167368/SmartApp+RPC) @@ -203,23 +205,24 @@ application.openapi = get_custom_openapi ``` Пример функции `custom_openapi`: + ```python from fastapi.encoders import jsonable_encoder from fastapi.openapi.models import OpenAPI from fastapi.openapi.utils import get_openapi from pybotx_smartapp_rpc import RPCRouter -from pybotx_smartapp_rpc.openapi_utils import * +from pybotx_smartapp_rpc.openapi.old.openapi_utils import * from pydantic.schema import get_model_name_map from starlette.routing import BaseRoute def custom_openapi( - *, - title: str, - version: str, - fastapi_routes: Sequence[BaseRoute], - rpc_router: RPCRouter, - **kwargs: Any, + *, + title: str, + version: str, + fastapi_routes: Sequence[BaseRoute], + rpc_router: RPCRouter, + **kwargs: Any, ) -> Dict[str, Any]: openapi_dict = get_openapi( title=title, diff --git a/pybotx_smartapp_rpc/exceptions.py b/pybotx_smartapp_rpc/exceptions.py index f00e997..85c13d0 100644 --- a/pybotx_smartapp_rpc/exceptions.py +++ b/pybotx_smartapp_rpc/exceptions.py @@ -3,8 +3,7 @@ from pybotx_smartapp_rpc.models.errors import RPCError -class BaseRPCErrorExc(Exception): - ... # noqa: WPS428, WPS604 +class BaseRPCErrorExc(Exception): ... # noqa: WPS428, WPS604 class RPCErrorExc(BaseRPCErrorExc): diff --git a/pybotx_smartapp_rpc/models/method.py b/pybotx_smartapp_rpc/models/method.py index ef641c3..a52d24e 100644 --- a/pybotx_smartapp_rpc/models/method.py +++ b/pybotx_smartapp_rpc/models/method.py @@ -3,8 +3,7 @@ from functools import partial from typing import Dict, List, Optional, Union -from pydantic.v1.fields import ModelField - +from pybotx_smartapp_rpc.models.model_field import ModelField from pybotx_smartapp_rpc.smartapp import SmartApp from pybotx_smartapp_rpc.typing import ( Handler, diff --git a/pybotx_smartapp_rpc/models/model_field.py b/pybotx_smartapp_rpc/models/model_field.py new file mode 100644 index 0000000..1176ed2 --- /dev/null +++ b/pybotx_smartapp_rpc/models/model_field.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass +from typing import Annotated, Any + +from pydantic import TypeAdapter +from pydantic.fields import FieldInfo +from pydantic_core import PydanticUndefined as Undefined + + +@dataclass +class ModelField: + field_info: FieldInfo + name: str + + @property + def alias(self) -> str: + a = self.field_info.alias + return a if a is not None else self.name + + @property + def required(self) -> bool: + return self.field_info.is_required() + + @property + def default(self) -> Any: + return self.get_default() + + @property + def type_(self) -> Any: + return self.field_info.annotation + + def __post_init__(self) -> None: + self._type_adapter: TypeAdapter[Any] = TypeAdapter( + Annotated[self.field_info.annotation, self.field_info] + ) + + def get_default(self) -> Any: + if self.field_info.is_required(): + return Undefined + return self.field_info.get_default(call_default_factory=True) diff --git a/pybotx_smartapp_rpc/models/request.py b/pybotx_smartapp_rpc/models/request.py index c4094b6..4e04e37 100644 --- a/pybotx_smartapp_rpc/models/request.py +++ b/pybotx_smartapp_rpc/models/request.py @@ -1,12 +1,10 @@ from typing import Any, Dict, Literal -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel class RPCArgsBaseModel(BaseModel): - model_config = { - "populate_by_name": True - } + model_config = {"populate_by_name": True} class RPCRequest(BaseModel): diff --git a/pybotx_smartapp_rpc/models/responses.py b/pybotx_smartapp_rpc/models/responses.py index 1bfe4e5..6f2b42b 100644 --- a/pybotx_smartapp_rpc/models/responses.py +++ b/pybotx_smartapp_rpc/models/responses.py @@ -15,9 +15,7 @@ class RPCResponseBaseModel(BaseModel): - model_config = { - "populate_by_name": True - } + model_config = {"populate_by_name": True} @dataclass diff --git a/pybotx_smartapp_rpc/openapi/__init__.py b/pybotx_smartapp_rpc/openapi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pybotx_smartapp_rpc/openapi_utils.py b/pybotx_smartapp_rpc/openapi/openapi.py similarity index 60% rename from pybotx_smartapp_rpc/openapi_utils.py rename to pybotx_smartapp_rpc/openapi/openapi.py index bf98807..cef14d3 100644 --- a/pybotx_smartapp_rpc/openapi_utils.py +++ b/pybotx_smartapp_rpc/openapi/openapi.py @@ -1,60 +1,63 @@ from enum import Enum from typing import Any, Dict, List, Optional, Set, Type, Union -from pydantic import BaseModel, TypeAdapter - -from pydantic.v1.fields import ModelField -from pydantic.v1.schema import model_process_schema, \ - field_schema +from pydantic import BaseModel from pybotx_smartapp_rpc import RPCRouter from pybotx_smartapp_rpc.models.method import RPCMethod -from pybotx_smartapp_rpc.pydantic_v1_override import get_flat_models_from_fields +from pybotx_smartapp_rpc.models.model_field import ModelField +from pybotx_smartapp_rpc.openapi.utils import ( + deep_dict_update, + get_flat_models_from_fields, + get_model_name_map, + get_schema_or_ref, +) REF_PREFIX = "#/components/schemas/" +ModelNameMap: type[dict[type[BaseModel | Enum], str]] = Dict[ + Union[Type[BaseModel], Type[Enum]], str +] -def deep_dict_update( - destination_dict: Dict[Any, Any], - source_dict: Dict[Any, Any], +def update_fastapi_paths_by_rpc_router( + openapi_dict: dict[str:Any], + rpc_router: RPCRouter, + security_definitions: Optional[dict[str, Any]] = None, + operation_security: Optional[dict[str, Any]] = Any, ) -> None: - for key in source_dict.keys(): - if ( - key in destination_dict - and isinstance(destination_dict[key], dict) - and isinstance(source_dict[key], dict) - ): - deep_dict_update(destination_dict[key], source_dict[key]) - else: - destination_dict[key] = source_dict[key] + """Update the fastapi dict returned from get_openapi by registered SmartApp methods information""" + if security_definitions is not None: + openapi_dict.setdefault("components", {}).setdefault( + "securitySchemes", {} + ).update(security_definitions) -def get_rpc_flat_models_from_routes( - router: RPCRouter, -) -> Set[Union[Type[BaseModel], Type[Enum]]]: - body_fields_from_routes: List[ModelField] = [] - responses_from_routes: List[ModelField] = [] + paths: Dict[str, Dict[str, Any]] = {} + + flat_rpc_models = get_rpc_flat_models_from_routes(rpc_router) + rpc_model_name_map = get_model_name_map(flat_rpc_models) + rpc_definitions = get_rpc_model_definitions( + flat_models=flat_rpc_models, model_name_map=rpc_model_name_map + ) - for method_name in router.rpc_methods.keys(): - if not router.rpc_methods[method_name].include_in_schema: + for method_name, method in rpc_router.rpc_methods.items(): + if not method.include_in_schema: continue - if router.rpc_methods[method_name].arguments_field: - body_fields_from_routes.append( - router.rpc_methods[method_name].arguments_field, # type: ignore - ) - if router.rpc_methods[method_name].response_field: - responses_from_routes.append(router.rpc_methods[method_name].response_field) + if path := get_rpc_openapi_path( # type: ignore + method_name=method_name, + route=method, + model_name_map=rpc_model_name_map, + security_scheme=operation_security, + ): + paths.setdefault(f"/{method_name}", {}).update(path) - if router.rpc_methods[method_name].errors_models: - responses_from_routes.extend( - router.rpc_methods[method_name].errors_models.values(), - ) + if rpc_definitions: + openapi_dict.setdefault("components", {}).setdefault("schemas", {}).update( + {k: rpc_definitions[k] for k in sorted(rpc_definitions)} + ) - return get_flat_models_from_fields( - body_fields_from_routes + responses_from_routes, - known_models=set(), - ) + openapi_dict.setdefault("paths", {}).update(paths) def get_rpc_model_definitions( @@ -66,17 +69,16 @@ def get_rpc_model_definitions( definitions: Dict[str, Dict[str, Any]] = {} for model in flat_models: # Get the JSON schema of the model (including nested definitions under '$defs') - m_schema = model.model_json_schema(ref_template=REF_PREFIX + '{model}') + m_schema = model.model_json_schema(ref_template=REF_PREFIX + "{model}") # Extract nested model definitions from $defs (if present) - nested_defs = m_schema.pop('$defs', {}) + nested_defs = m_schema.pop("$defs", {}) # Update overall definitions with nested definitions definitions.update(nested_defs) model_name = model_name_map[model] - # Optionally modify the description to remove form feed characters as before if "description" in m_schema: m_schema["description"] = m_schema["description"].split("\f")[0] @@ -85,52 +87,35 @@ def get_rpc_model_definitions( return definitions -def get_openapi_operation_rpc_args( - *, - body_field: Optional[Any], - model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], -) -> Optional[Dict[str, Any]]: - if not body_field: - return None - - model_type = body_field.outer_type_ - body_schema = schema_or_ref(model_type, model_name_map) - - request_media_type = "application/json" - request_body_oai: Dict[str, Any] = {} - if body_field.required: - request_body_oai["required"] = True - - request_body_oai["content"] = {request_media_type: {"schema": body_schema}} - - return request_body_oai +def get_rpc_flat_models_from_routes( + router: RPCRouter, +) -> Set[Union[Type[BaseModel], Type[Enum]]]: + body_fields_from_routes: List[ModelField] = [] + responses_from_routes: List[ModelField] = [] -def get_openapi_rpc_metadata(*, name: str, route: RPCMethod) -> Dict[str, Any]: - operation: Dict[str, Any] = { - "summary": route.handler.__name__.replace(".", " ").replace("_", " ").title(), - "description": route.handler.__doc__, - "operationId": ( - f"rpc_{name.replace('.', '_').replace(':', '_').replace('-', '_').lower()}" - ), - } + for method_name, rpc_method in router.rpc_methods.items(): + if not rpc_method.include_in_schema: + continue - if route.tags: - operation["tags"] = route.tags + if rpc_method.arguments_field: + body_fields_from_routes.append( + rpc_method.arguments_field, # type: ignore + ) + if rpc_method.response_field: + responses_from_routes.append(rpc_method.response_field) - return operation + if rpc_method.errors_models: + responses_from_routes.extend( + rpc_method.errors_models.values(), + ) + return get_flat_models_from_fields( + body_fields_from_routes + responses_from_routes, + known_models=set(), + ) -def schema_or_ref(model_type, model_name_map): - """Return a $ref if model_type is a Pydantic model or Enum in model_name_map, else inline schema.""" - if ( - isinstance(model_type, type) - and (issubclass(model_type, BaseModel) or issubclass(model_type, Enum)) - and model_type in model_name_map - ): - return {"$ref": f"{REF_PREFIX}{model_name_map[model_type]}"} - return TypeAdapter(model_type).json_schema(ref_template=REF_PREFIX + '{model}') def get_rpc_openapi_path( # noqa: WPS231 *, method_name: str, @@ -151,11 +136,12 @@ def get_rpc_openapi_path( # noqa: WPS231 operation["requestBody"] = request_body_oai # - Successful response - - response_model = route.response_field.outer_type_ - response_schema = schema_or_ref(response_model, model_name_map) - response_schema["title"] = route.response_field.name.replace('_', ' ').title() + response_schema = get_schema_or_ref( + route.response_field, model_name_map, REF_PREFIX + ) + response_schema["title"] = route.response_field.name.replace("_", " ").title() - operation.setdefault("responses", {}).setdefault("ok", {}).update( + operation.setdefault("responses", {}).setdefault(200, {}).update( { "description": "Successful response. **result** field:", "content": {"application/json": {"schema": response_schema}}, @@ -165,15 +151,14 @@ def get_rpc_openapi_path( # noqa: WPS231 # - Errors - if route.errors: operation_errors = operation.setdefault("responses", {}) - for (error_status_code, error_response) in route.errors.items(): + for error_status_code, error_response in route.errors.items(): process_response: Dict[str, Any] = {} openapi_response = operation_errors.setdefault(str(error_status_code), {}) if route.errors_models and ( field := route.errors_models[error_status_code] # noqa: WPS332 ): - error_model = field.outer_type_ - error_schema = schema_or_ref(error_model, model_name_map) + error_schema = get_schema_or_ref(field, model_name_map, REF_PREFIX) process_response.setdefault("content", {}).setdefault( "application/json", {} @@ -188,3 +173,38 @@ def get_rpc_openapi_path( # noqa: WPS231 path["post"] = operation return path + + +def get_openapi_operation_rpc_args( + *, + body_field: Optional[ModelField], + model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], +) -> Optional[Dict[str, Any]]: + if not body_field: + return None + + body_schema = get_schema_or_ref(body_field, model_name_map, REF_PREFIX) + + request_media_type = "application/json" + request_body_oai: Dict[str, Any] = {} + if body_field.required: + request_body_oai["required"] = True + + request_body_oai["content"] = {request_media_type: {"schema": body_schema}} + + return request_body_oai + + +def get_openapi_rpc_metadata(*, name: str, route: RPCMethod) -> Dict[str, Any]: + operation: Dict[str, Any] = { + "summary": route.handler.__name__.replace(".", " ").replace("_", " ").title(), + "description": route.handler.__doc__, + "operationId": ( + f"rpc_{name.replace('.', '_').replace(':', '_').replace('-', '_').lower()}" + ), + } + + if route.tags: + operation["tags"] = route.tags + + return operation diff --git a/pybotx_smartapp_rpc/openapi/utils.py b/pybotx_smartapp_rpc/openapi/utils.py new file mode 100644 index 0000000..d736271 --- /dev/null +++ b/pybotx_smartapp_rpc/openapi/utils.py @@ -0,0 +1,118 @@ +import re +from enum import Enum +from typing import Any, Dict, Sequence, Set, Type, Union + +from pydantic import BaseModel, TypeAdapter +from pydantic.fields import FieldInfo + +from pybotx_smartapp_rpc.models.model_field import ModelField + +TypeModelOrEnum = Union[Type[BaseModel], Type[Enum]] +TypeModelSet = Set[TypeModelOrEnum] + + +def _get_field_type(field: Union[ModelField, FieldInfo]) -> Any: + """Extract declared type from our wrapper or a native FieldInfo.""" + if isinstance(field, ModelField): + return field.type_ + elif isinstance(field, FieldInfo): + return field.annotation + else: + raise TypeError(f"Unsupported field type: {type(field)}") + + +def get_flat_models_from_model( + model: Type[BaseModel], + known_models: TypeModelSet, +) -> TypeModelSet: + """ + Recursively collect BaseModel and Enum types from a model definition. + """ + if model in known_models: + return set() + + known_models.add(model) + flat_models: TypeModelSet = {model} + + for field in model.model_fields.values(): + flat_models |= get_flat_models_from_field(field, known_models) + + return flat_models + + +def get_flat_models_from_fields( + fields: Sequence[Union[ModelField, FieldInfo]], + known_models: TypeModelSet, +) -> TypeModelSet: + flat_models: TypeModelSet = set() + for field in fields: + flat_models |= get_flat_models_from_field(field, known_models) + return flat_models + + +def get_flat_models_from_field( + field: Union[ModelField, FieldInfo], + known_models: TypeModelSet, +) -> TypeModelSet: + flat_models: TypeModelSet = set() + field_type = _get_field_type(field) + + if isinstance(field_type, type): + if issubclass(field_type, BaseModel): + flat_models |= get_flat_models_from_model(field_type, known_models) + elif issubclass(field_type, Enum): + flat_models.add(field_type) + + return flat_models + + +def normalize_name(name: str) -> str: + return re.sub(r"[^a-zA-Z0-9.\-_]", "_", name) + + +def get_long_model_name(model: TypeModelOrEnum) -> str: + return f"{model.__module__}__{model.__qualname__}".replace(".", "__") + + +def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str]: + """ + Generate unique schema names, avoiding collisions by using module path. + """ + name_model_map: dict[str, TypeModelOrEnum] = {} + conflicting_names: Set[str] = set() + + for model in unique_models: + model_name = normalize_name(model.__name__) + if model_name in conflicting_names: + name_model_map[get_long_model_name(model)] = model + elif model_name in name_model_map: + conflicting_names.add(model_name) + other = name_model_map.pop(model_name) + name_model_map[get_long_model_name(other)] = other + name_model_map[get_long_model_name(model)] = model + else: + name_model_map[model_name] = model + + return {v: k for k, v in name_model_map.items()} + + +def deep_dict_update( + destination_dict: Dict[Any, Any], + source_dict: Dict[Any, Any], +) -> None: + for key in source_dict.keys(): + if ( + key in destination_dict + and isinstance(destination_dict[key], dict) + and isinstance(source_dict[key], dict) + ): + deep_dict_update(destination_dict[key], source_dict[key]) + else: + destination_dict[key] = source_dict[key] + + +def get_schema_or_ref(model: ModelField, model_name_map, ref_prefix: str) -> dict: + if model_name := model_name_map.get(model.type_): + return {"$ref": ref_prefix + model_name} + + return TypeAdapter(model.type_).json_schema(ref_template=ref_prefix + "{model}") diff --git a/pybotx_smartapp_rpc/pydantic_v1_override.py b/pybotx_smartapp_rpc/pydantic_v1_override.py deleted file mode 100644 index d917433..0000000 --- a/pybotx_smartapp_rpc/pydantic_v1_override.py +++ /dev/null @@ -1,88 +0,0 @@ -from enum import Enum -from typing import Union, Sequence, Type - -from pydantic.fields import FieldInfo -from pydantic.v1.fields import ModelField -from pydantic.v1.schema import TypeModelSet -from pydantic.v1.utils import lenient_issubclass - -from pydantic.v1 import BaseModel as BaseModelV1 -from pydantic import BaseModel as BaseModelV2 - -BaseModelTypes = (BaseModelV1, BaseModelV2) - - -def _get_field_type(field: Union[ModelField, FieldInfo]): - """ - Extract the declared type from either a Pydantic v1 ModelField - or a Pydantic v2 FieldInfo. - """ - if isinstance(field, ModelField): - return field.type_ - elif isinstance(field, FieldInfo): - return field.annotation - else: - raise TypeError(f"Unsupported field type: {type(field)}") - -def get_flat_models_from_model(model: Type[Union[BaseModelV1, BaseModelV2]], - known_models: TypeModelSet) -> TypeModelSet: - """ - Custom version of pydantic.v1.schema.get_flat_models_from_model - that supports v2 BaseModel and v2 FieldInfo. - """ - flat_models: TypeModelSet = set() - - if model in known_models: - return flat_models - - known_models.add(model) - flat_models.add(model) - - # v1 stores __fields__, v2 stores model_fields - fields = getattr(model, '__fields__', None) or getattr(model, 'model_fields', {}) - flat_models |= get_flat_models_from_fields(fields.values(), known_models=known_models) - - return flat_models - - - -def get_flat_models_from_fields( - fields: Sequence[Union[ModelField, FieldInfo]], - known_models: TypeModelSet, -) -> TypeModelSet: - """ - Take a list of fields (v1 ModelField or v2 FieldInfo) and return all BaseModel/Enum types used, - recursively. - """ - flat_models: TypeModelSet = set() - for field in fields: - flat_models |= get_flat_models_from_field(field, known_models=known_models) - return flat_models - - -def get_flat_models_from_field( - field: Union[ModelField, FieldInfo], - known_models: TypeModelSet, -) -> TypeModelSet: - """ - Handle one field (v1 ModelField or v2 FieldInfo) and return all models in its type tree. - """ - - flat_models: TypeModelSet = set() - - field_type = _get_field_type(field) - - # Support TypeAdapter-style wrappers - if lenient_issubclass(getattr(field_type, '__pydantic_model__', None), BaseModelTypes): - field_type = field_type.__pydantic_model__ - - sub_fields = getattr(field, "sub_fields", None) - - if sub_fields and not lenient_issubclass(field_type, BaseModelTypes): - flat_models |= get_flat_models_from_fields(sub_fields, known_models=known_models) - elif lenient_issubclass(field_type, BaseModelTypes) and field_type not in known_models: - flat_models |= get_flat_models_from_model(field_type, known_models=known_models) - elif lenient_issubclass(field_type, Enum): - flat_models.add(field_type) - - return flat_models \ No newline at end of file diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index 666c765..a10fd10 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -1,10 +1,10 @@ import inspect from enum import Enum -from typing import Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union -from pydantic.v1 import BaseConfig from pydantic.error_wrappers import ValidationError -from pydantic.v1.fields import ModelField +from pydantic.fields import FieldInfo +from pydantic_core import PydanticUndefined from pybotx_smartapp_rpc import RPCError from pybotx_smartapp_rpc.empty_args import EmptyArgs @@ -16,13 +16,10 @@ build_invalid_rpc_args_error_response, build_method_not_found_error_response, ) +from pybotx_smartapp_rpc.models.model_field import ModelField from pybotx_smartapp_rpc.smartapp import SmartApp from pybotx_smartapp_rpc.typing import Handler, Middleware, RPCResponse -# Create a config that allows arbitrary types -class FlexibleConfig(BaseConfig): - arbitrary_types_allowed = True - class RPCRouter: def __init__( @@ -119,29 +116,51 @@ def include_router(self, router: "RPCRouter") -> None: rpc_method.errors_models = {**errors_models, **rpc_method.errors_models} self.rpc_methods[rpc_method_name] = rpc_method + def _get_handler_request_argument_model( + self, handler_signature: inspect.Signature + ) -> Optional[ModelField]: + args_annotations = [ + arg[1].annotation for arg in handler_signature.parameters.items() + ] + if len(args_annotations) >= 2: + return self.create_model_field( + name=str(args_annotations[1].__name__), + type_=args_annotations[1], + ) + return None + + def _get_handler_response_model( + self, + handler_signature: inspect.Signature, + return_type: Optional[Type[ResultType]], + name: str, + ) -> Optional[ModelField]: + + if return_type: + response_type = return_type + else: + return_annotation = handler_signature.return_annotation + if hasattr(return_annotation, "__args__"): # noqa: WPS421 + response_type = return_annotation.__args__[0] + else: + response_type = None + + return self.create_model_field( + name=name, + type_=response_type, + ) + def _get_args_and_return_field( self, handler: Handler, return_type: Optional[Type[ResultType]] = None, ) -> Tuple[Optional[ModelField], ModelField]: signature = inspect.signature(handler) - return_annotation = signature.return_annotation - if hasattr(return_annotation, "__args__"): # noqa: WPS421 - response_type = return_annotation.__args__[0] - else: - response_type = None - - if return_type: - response_type = return_type - - response_field = self._get_ModelField(name=f"Response_{handler.__name__}", - type_=response_type,) - args_annotations = [arg[1].annotation for arg in signature.parameters.items()] - if len(args_annotations) >= 2: - arg_field = self._get_ModelField(name=str(args_annotations[1].__name__), type_=args_annotations[1]) - else: - arg_field = None # type: ignore + response_field = self._get_handler_response_model( + signature, return_type, f"Response_{handler.__name__}" + ) + arg_field = self._get_handler_request_argument_model(signature) return arg_field, response_field @@ -156,25 +175,30 @@ def _get_error_fields_and_models( errors_fields = { error.model_fields["id"].default: { - "description": error.__doc__ or error.__fields__["reason"].default, + "description": error.__doc__ or error.model_fields["reason"].default, } for error in errors - if error.__fields__["id"].default + if error.model_fields["id"].default } errors_models = { - error.__fields__["id"].default: self._get_ModelField(name=error.__name__, type_=error) + error.model_fields["id"].default: self.create_model_field( + name=error.__name__, type_=error + ) for error in errors - if error.__fields__["id"].default + if error.model_fields["id"].default } return errors_fields, errors_models - def _get_ModelField(self, name:str, type_:type) -> ModelField: - model_field = ModelField( - name=name, - type_=type_, - model_config=FlexibleConfig, - class_validators={}, + @staticmethod + def create_model_field( + name: str, + type_: Any, + default: Optional[Any] = PydanticUndefined, + field_info: Optional[FieldInfo] = None, + alias: Optional[str] = None, + ) -> ModelField: + field_info = field_info or FieldInfo( + annotation=type_, default=default, alias=alias ) - model_field.prepare() - return model_field \ No newline at end of file + return ModelField(name=name, field_info=field_info) diff --git a/tests/test_openapi.py b/tests/test_openapi.py index d8d2fcc..6a89375 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -9,12 +9,14 @@ SmartApp, SmartAppRPC, ) -from pybotx_smartapp_rpc.openapi_utils import ( - deep_dict_update, +from pybotx_smartapp_rpc.openapi.openapi import ( get_rpc_flat_models_from_routes, get_rpc_model_definitions, get_rpc_openapi_path, ) +from pybotx_smartapp_rpc.openapi.utils import ( + deep_dict_update, +) class UserArgs(BaseModel): @@ -32,18 +34,18 @@ class Meta(BaseModel): class UserNotFound(RPCError): """Error description.""" - id:str = "UserNotFound" - reason:str = "User not found in system" + id: str = "UserNotFound" + reason: str = "User not found in system" meta: Meta class OneUserNotFound(UserNotFound): - id:str = "OneUserNotFound" + id: str = "OneUserNotFound" class InvalidCredentialsError(RPCError): - id:str = "InvalidCredentialsError" - reason:str = "Invalid credentials" + id: str = "InvalidCredentialsError" + reason: str = "Invalid credentials" def test__deep_dict_update() -> None: @@ -107,7 +109,7 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: "description": None, "operationId": "rpc_get_api_version", "responses": { - "ok": { + 200: { "content": { "application/json": { "schema": { @@ -166,7 +168,7 @@ async def get_api_version( }, }, "responses": { - "ok": { + 200: { "description": "Successful response. **result** field:", "content": { "application/json": { @@ -207,6 +209,126 @@ async def get_api_version( } } + # expected_path = { + # "post": { + # "summary": "Get Api Version", + # "tags": ["rpc", "user"], + # "description": None, + # "operationId": "rpc_get_user", + # "requestBody": { + # "required": True, + # "content": { + # "application/json": { + # "schema": { + # "properties": {"id": {"title": "Id", "type": "integer"}}, + # "required": ["id"], + # "title": "UserArgs", + # "type": "object", + # } + # } + # }, + # }, + # "responses": { + # 200: { + # "description": "Successful response. **result** field:", + # "content": { + # "application/json": { + # "schema": {"title": "Response Get Api Version", + # "type": "integer"} + # } + # }, + # }, + # "UserNotFound": { + # "description": "**Error**: Error description.", + # "content": { + # "application/json": { + # "schema": { + # "$defs": { + # "Meta": { + # "properties": {"user_id": {"title": "User Id", + # "type": "integer"}}, + # "required": ["user_id"], + # "title": "Meta", + # "type": "object", + # } + # }, + # "description": "Error description.", + # "properties": { + # "reason": {"default": "User not found in system", + # "title": "Reason", "type": "string"}, + # "id": {"default": "UserNotFound", "title": "Id", + # "type": "string"}, + # "meta": {"$ref": "#/$defs/Meta"}, + # }, + # "required": ["meta"], + # "title": "UserNotFound", + # "type": "object", + # } + # } + # }, + # }, + # "OneUserNotFound": { + # "description": "**Error**: User not found in system", + # "content": { + # "application/json": { + # "schema": { + # "$defs": { + # "Meta": { + # "properties": {"user_id": {"title": "User Id", + # "type": "integer"}}, + # "required": ["user_id"], + # "title": "Meta", + # "type": "object", + # } + # }, + # "properties": { + # "reason": {"default": "User not found in system", + # "title": "Reason", "type": "string"}, + # "id": {"default": "OneUserNotFound", "title": "Id", + # "type": "string"}, + # "meta": {"$ref": "#/$defs/Meta"}, + # }, + # "required": ["meta"], + # "title": "OneUserNotFound", + # "type": "object", + # } + # } + # }, + # }, + # "InvalidCredentialsError": { + # "description": "**Error**: Invalid credentials", + # "content": { + # "application/json": { + # "schema": { + # "$defs": { + # "BaseModel": {"properties": {}, + # "title": "BaseModel", + # "type": "object"} + # }, + # "properties": { + # "reason": {"default": "Invalid credentials", + # "title": "Reason", "type": "string"}, + # "id": {"default": "InvalidCredentialsError", + # "title": "Id", "type": "string"}, + # "meta": { + # "anyOf": [ + # {"additionalProperties": True, + # "type": "object"}, + # {"$ref": "#/$defs/BaseModel"}, + # ], + # "title": "Meta", + # }, + # }, + # "title": "InvalidCredentialsError", + # "type": "object", + # } + # } + # }, + # }, + # }, + # }, + # } + diff = DeepDiff(expected_path, path, ignore_order=True) assert not diff, diff diff --git a/tests/test_rpc_calls.py b/tests/test_rpc_calls.py index 0b6fc98..2352c00 100644 --- a/tests/test_rpc_calls.py +++ b/tests/test_rpc_calls.py @@ -24,7 +24,6 @@ SmartApp, SmartAppRPC, ) -from pybotx_smartapp_rpc.empty_args import EmptyArgs async def test_rpc_call_rpc_error_returned( @@ -528,13 +527,13 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: diff = DeepDiff(response.model_dump(), expected_response.model_dump()) assert not diff, diff + @pytest.mark.asyncio async def test_rpc_call_with_middleware_effect(smartapp_event_factory, bot): applied = [] # Middleware to track execution - async def router_mw(smartapp: SmartApp, args, call_next): applied.append("router_mw") response = await call_next(smartapp, args) @@ -548,8 +547,10 @@ async def method_mw(smartapp: SmartApp, args, call_next): # Create router with our middleware rpc = RPCRouter(middlewares=[router_mw]) - @rpc.method("test",middlewares=[method_mw]) - async def test_method(smartapp: SmartApp,): + @rpc.method("test", middlewares=[method_mw]) + async def test_method( + smartapp: SmartApp, + ): applied.append("handler") return RPCResultResponse(result=123) @@ -557,11 +558,13 @@ async def test_method(smartapp: SmartApp,): smartapp_rpc = SmartAppRPC(routers=[rpc]) # Create event with empty params - event = smartapp_event_factory("test",) + event = smartapp_event_factory( + "test", + ) # Execute the RPC call result = await smartapp_rpc.handle_sync_smartapp_event(event, bot) # Assertions - assert applied == ["router_mw",'method_mw' ,"handler"] - assert result.data == 123 \ No newline at end of file + assert applied == ["router_mw", "method_mw", "handler"] + assert result.data == 123 diff --git a/tests/test_rpc_router.py b/tests/test_rpc_router.py index 16332bc..786680a 100644 --- a/tests/test_rpc_router.py +++ b/tests/test_rpc_router.py @@ -1,10 +1,8 @@ from unittest.mock import AsyncMock, MagicMock import pytest -from pydantic import ValidationError -from pybotx_smartapp_rpc import RPCResultResponse, RPCRouter, SmartApp, \ - RPCArgsBaseModel, RPCError +from pybotx_smartapp_rpc import RPCResultResponse, RPCRouter, SmartApp from pybotx_smartapp_rpc.middlewares.empty_args_middleware import empty_args_middleware @@ -55,11 +53,9 @@ def test_middlewares_order(): Note - the last middleware is added automatically """ - router_middleware = AsyncMock() method_middleware = AsyncMock() - rpc = RPCRouter(middlewares=[router_middleware]) @rpc.method("test_method", middlewares=[method_middleware]) @@ -73,6 +69,7 @@ async def test_method(smartapp): assert rpc_method.middlewares[1] is method_middleware assert rpc_method.middlewares[-1] is empty_args_middleware + async def test_rpc_router_call_method_directly(): rpc = RPCRouter() @@ -87,4 +84,3 @@ async def dummy_method(smartapp: SmartApp): response = await rpc.rpc_methods["dummy"].handler(MagicMock()) assert called assert response.result == 42 - From e1959f3a04ce62c344f3c1c95e6fa11b7dfb93e9 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 18:14:53 +0300 Subject: [PATCH 06/31] add fastapi openapi --- poetry.lock | 221 +++++++++++------- pybotx_smartapp_rpc/fastapi_utils/security.py | 68 ++++++ pyproject.toml | 5 + 3 files changed, 204 insertions(+), 90 deletions(-) create mode 100644 pybotx_smartapp_rpc/fastapi_utils/security.py diff --git a/poetry.lock b/poetry.lock index 102d6cb..b402c10 100644 --- a/poetry.lock +++ b/poetry.lock @@ -263,99 +263,99 @@ files = [ [[package]] name = "coverage" -version = "7.10.3" +version = "7.10.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53808194afdf948c462215e9403cca27a81cf150d2f9b386aee4dab614ae2ffe"}, - {file = "coverage-7.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f4d1b837d1abf72187a61645dbf799e0d7705aa9232924946e1f57eb09a3bf00"}, - {file = "coverage-7.10.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2a90dd4505d3cc68b847ab10c5ee81822a968b5191664e8a0801778fa60459fa"}, - {file = "coverage-7.10.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d52989685ff5bf909c430e6d7f6550937bc6d6f3e6ecb303c97a86100efd4596"}, - {file = "coverage-7.10.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdb558a1d97345bde3a9f4d3e8d11c9e5611f748646e9bb61d7d612a796671b5"}, - {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c9e6331a8f09cb1fc8bda032752af03c366870b48cce908875ba2620d20d0ad4"}, - {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:992f48bf35b720e174e7fae916d943599f1a66501a2710d06c5f8104e0756ee1"}, - {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c5595fc4ad6a39312c786ec3326d7322d0cf10e3ac6a6df70809910026d67cfb"}, - {file = "coverage-7.10.3-cp310-cp310-win32.whl", hash = "sha256:9e92fa1f2bd5a57df9d00cf9ce1eb4ef6fccca4ceabec1c984837de55329db34"}, - {file = "coverage-7.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b96524d6e4a3ce6a75c56bb15dbd08023b0ae2289c254e15b9fbdddf0c577416"}, - {file = "coverage-7.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2ff2e2afdf0d51b9b8301e542d9c21a8d084fd23d4c8ea2b3a1b3c96f5f7397"}, - {file = "coverage-7.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:18ecc5d1b9a8c570f6c9b808fa9a2b16836b3dd5414a6d467ae942208b095f85"}, - {file = "coverage-7.10.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1af4461b25fe92889590d438905e1fc79a95680ec2a1ff69a591bb3fdb6c7157"}, - {file = "coverage-7.10.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3966bc9a76b09a40dc6063c8b10375e827ea5dfcaffae402dd65953bef4cba54"}, - {file = "coverage-7.10.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:205a95b87ef4eb303b7bc5118b47b6b6604a644bcbdb33c336a41cfc0a08c06a"}, - {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b3801b79fb2ad61e3c7e2554bab754fc5f105626056980a2b9cf3aef4f13f84"}, - {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0dc69c60224cda33d384572da945759756e3f06b9cdac27f302f53961e63160"}, - {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a83d4f134bab2c7ff758e6bb1541dd72b54ba295ced6a63d93efc2e20cb9b124"}, - {file = "coverage-7.10.3-cp311-cp311-win32.whl", hash = "sha256:54e409dd64e5302b2a8fdf44ec1c26f47abd1f45a2dcf67bd161873ee05a59b8"}, - {file = "coverage-7.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:30c601610a9b23807c5e9e2e442054b795953ab85d525c3de1b1b27cebeb2117"}, - {file = "coverage-7.10.3-cp311-cp311-win_arm64.whl", hash = "sha256:dabe662312a97958e932dee056f2659051d822552c0b866823e8ba1c2fe64770"}, - {file = "coverage-7.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:449c1e2d3a84d18bd204258a897a87bc57380072eb2aded6a5b5226046207b42"}, - {file = "coverage-7.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d4f9ce50b9261ad196dc2b2e9f1fbbee21651b54c3097a25ad783679fd18294"}, - {file = "coverage-7.10.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4dd4564207b160d0d45c36a10bc0a3d12563028e8b48cd6459ea322302a156d7"}, - {file = "coverage-7.10.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ca3c9530ee072b7cb6a6ea7b640bcdff0ad3b334ae9687e521e59f79b1d0437"}, - {file = "coverage-7.10.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6df359e59fa243c9925ae6507e27f29c46698359f45e568fd51b9315dbbe587"}, - {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a181e4c2c896c2ff64c6312db3bda38e9ade2e1aa67f86a5628ae85873786cea"}, - {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a374d4e923814e8b72b205ef6b3d3a647bb50e66f3558582eda074c976923613"}, - {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:daeefff05993e5e8c6e7499a8508e7bd94502b6b9a9159c84fd1fe6bce3151cb"}, - {file = "coverage-7.10.3-cp312-cp312-win32.whl", hash = "sha256:187ecdcac21f9636d570e419773df7bd2fda2e7fa040f812e7f95d0bddf5f79a"}, - {file = "coverage-7.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:4a50ad2524ee7e4c2a95e60d2b0b83283bdfc745fe82359d567e4f15d3823eb5"}, - {file = "coverage-7.10.3-cp312-cp312-win_arm64.whl", hash = "sha256:c112f04e075d3495fa3ed2200f71317da99608cbb2e9345bdb6de8819fc30571"}, - {file = "coverage-7.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b99e87304ffe0eb97c5308447328a584258951853807afdc58b16143a530518a"}, - {file = "coverage-7.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4af09c7574d09afbc1ea7da9dcea23665c01f3bc1b1feb061dac135f98ffc53a"}, - {file = "coverage-7.10.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:488e9b50dc5d2aa9521053cfa706209e5acf5289e81edc28291a24f4e4488f46"}, - {file = "coverage-7.10.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:913ceddb4289cbba3a310704a424e3fb7aac2bc0c3a23ea473193cb290cf17d4"}, - {file = "coverage-7.10.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b1f91cbc78c7112ab84ed2a8defbccd90f888fcae40a97ddd6466b0bec6ae8a"}, - {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0bac054d45af7cd938834b43a9878b36ea92781bcb009eab040a5b09e9927e3"}, - {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fe72cbdd12d9e0f4aca873fa6d755e103888a7f9085e4a62d282d9d5b9f7928c"}, - {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c1e2e927ab3eadd7c244023927d646e4c15c65bb2ac7ae3c3e9537c013700d21"}, - {file = "coverage-7.10.3-cp313-cp313-win32.whl", hash = "sha256:24d0c13de473b04920ddd6e5da3c08831b1170b8f3b17461d7429b61cad59ae0"}, - {file = "coverage-7.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:3564aae76bce4b96e2345cf53b4c87e938c4985424a9be6a66ee902626edec4c"}, - {file = "coverage-7.10.3-cp313-cp313-win_arm64.whl", hash = "sha256:f35580f19f297455f44afcd773c9c7a058e52eb6eb170aa31222e635f2e38b87"}, - {file = "coverage-7.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07009152f497a0464ffdf2634586787aea0e69ddd023eafb23fc38267db94b84"}, - {file = "coverage-7.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd2ba5f0c7e7e8cc418be2f0c14c4d9e3f08b8fb8e4c0f83c2fe87d03eb655e"}, - {file = "coverage-7.10.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1ae22b97003c74186e034a93e4f946c75fad8c0ce8d92fbbc168b5e15ee2841f"}, - {file = "coverage-7.10.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb329f1046888a36b1dc35504d3029e1dd5afe2196d94315d18c45ee380f67d5"}, - {file = "coverage-7.10.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce01048199a91f07f96ca3074b0c14021f4fe7ffd29a3e6a188ac60a5c3a4af8"}, - {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08b989a06eb9dfacf96d42b7fb4c9a22bafa370d245dc22fa839f2168c6f9fa1"}, - {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:669fe0d4e69c575c52148511029b722ba8d26e8a3129840c2ce0522e1452b256"}, - {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3262d19092771c83f3413831d9904b1ccc5f98da5de4ffa4ad67f5b20c7aaf7b"}, - {file = "coverage-7.10.3-cp313-cp313t-win32.whl", hash = "sha256:cc0ee4b2ccd42cab7ee6be46d8a67d230cb33a0a7cd47a58b587a7063b6c6b0e"}, - {file = "coverage-7.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:03db599f213341e2960430984e04cf35fb179724e052a3ee627a068653cf4a7c"}, - {file = "coverage-7.10.3-cp313-cp313t-win_arm64.whl", hash = "sha256:46eae7893ba65f53c71284585a262f083ef71594f05ec5c85baf79c402369098"}, - {file = "coverage-7.10.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:bce8b8180912914032785850d8f3aacb25ec1810f5f54afc4a8b114e7a9b55de"}, - {file = "coverage-7.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07790b4b37d56608536f7c1079bd1aa511567ac2966d33d5cec9cf520c50a7c8"}, - {file = "coverage-7.10.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e79367ef2cd9166acedcbf136a458dfe9a4a2dd4d1ee95738fb2ee581c56f667"}, - {file = "coverage-7.10.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:419d2a0f769f26cb1d05e9ccbc5eab4cb5d70231604d47150867c07822acbdf4"}, - {file = "coverage-7.10.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee221cf244757cdc2ac882e3062ab414b8464ad9c884c21e878517ea64b3fa26"}, - {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c2079d8cdd6f7373d628e14b3357f24d1db02c9dc22e6a007418ca7a2be0435a"}, - {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bd8df1f83c0703fa3ca781b02d36f9ec67ad9cb725b18d486405924f5e4270bd"}, - {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6b4e25e0fa335c8aa26e42a52053f3786a61cc7622b4d54ae2dad994aa754fec"}, - {file = "coverage-7.10.3-cp314-cp314-win32.whl", hash = "sha256:d7c3d02c2866deb217dce664c71787f4b25420ea3eaf87056f44fb364a3528f5"}, - {file = "coverage-7.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:9c8916d44d9e0fe6cdb2227dc6b0edd8bc6c8ef13438bbbf69af7482d9bb9833"}, - {file = "coverage-7.10.3-cp314-cp314-win_arm64.whl", hash = "sha256:1007d6a2b3cf197c57105cc1ba390d9ff7f0bee215ced4dea530181e49c65ab4"}, - {file = "coverage-7.10.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ebc8791d346410d096818788877d675ca55c91db87d60e8f477bd41c6970ffc6"}, - {file = "coverage-7.10.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f4e4d8e75f6fd3c6940ebeed29e3d9d632e1f18f6fb65d33086d99d4d073241"}, - {file = "coverage-7.10.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:24581ed69f132b6225a31b0228ae4885731cddc966f8a33fe5987288bdbbbd5e"}, - {file = "coverage-7.10.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec151569ddfccbf71bac8c422dce15e176167385a00cd86e887f9a80035ce8a5"}, - {file = "coverage-7.10.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ae8e7c56290b908ee817200c0b65929b8050bc28530b131fe7c6dfee3e7d86b"}, - {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fb742309766d7e48e9eb4dc34bc95a424707bc6140c0e7d9726e794f11b92a0"}, - {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c65e2a5b32fbe1e499f1036efa6eb9cb4ea2bf6f7168d0e7a5852f3024f471b1"}, - {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d48d2cb07d50f12f4f18d2bb75d9d19e3506c26d96fffabf56d22936e5ed8f7c"}, - {file = "coverage-7.10.3-cp314-cp314t-win32.whl", hash = "sha256:dec0d9bc15ee305e09fe2cd1911d3f0371262d3cfdae05d79515d8cb712b4869"}, - {file = "coverage-7.10.3-cp314-cp314t-win_amd64.whl", hash = "sha256:424ea93a323aa0f7f01174308ea78bde885c3089ec1bef7143a6d93c3e24ef64"}, - {file = "coverage-7.10.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f5983c132a62d93d71c9ef896a0b9bf6e6828d8d2ea32611f58684fba60bba35"}, - {file = "coverage-7.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da749daa7e141985487e1ff90a68315b0845930ed53dc397f4ae8f8bab25b551"}, - {file = "coverage-7.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3126fb6a47d287f461d9b1aa5d1a8c97034d1dffb4f452f2cf211289dae74ef"}, - {file = "coverage-7.10.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3da794db13cc27ca40e1ec8127945b97fab78ba548040047d54e7bfa6d442dca"}, - {file = "coverage-7.10.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4e27bebbd184ef8d1c1e092b74a2b7109dcbe2618dce6e96b1776d53b14b3fe8"}, - {file = "coverage-7.10.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fd4ee2580b9fefbd301b4f8f85b62ac90d1e848bea54f89a5748cf132782118"}, - {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6999920bdd73259ce11cabfc1307484f071ecc6abdb2ca58d98facbcefc70f16"}, - {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3623f929db885fab100cb88220a5b193321ed37e03af719efdbaf5d10b6e227"}, - {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:25b902c5e15dea056485d782e420bb84621cc08ee75d5131ecb3dbef8bd1365f"}, - {file = "coverage-7.10.3-cp39-cp39-win32.whl", hash = "sha256:f930a4d92b004b643183451fe9c8fe398ccf866ed37d172ebaccfd443a097f61"}, - {file = "coverage-7.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:08e638a93c8acba13c7842953f92a33d52d73e410329acd472280d2a21a6c0e1"}, - {file = "coverage-7.10.3-py3-none-any.whl", hash = "sha256:416a8d74dc0adfd33944ba2f405897bab87b7e9e84a391e09d241956bd953ce1"}, - {file = "coverage-7.10.3.tar.gz", hash = "sha256:812ba9250532e4a823b070b0420a36499859542335af3dca8f47fc6aa1a05619"}, + {file = "coverage-7.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d92d6edb0ccafd20c6fbf9891ca720b39c2a6a4b4a6f9cf323ca2c986f33e475"}, + {file = "coverage-7.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7202da14dc0236884fcc45665ffb2d79d4991a53fbdf152ab22f69f70923cc22"}, + {file = "coverage-7.10.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ada418633ae24ec8d0fcad5efe6fc7aa3c62497c6ed86589e57844ad04365674"}, + {file = "coverage-7.10.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b828e33eca6c3322adda3b5884456f98c435182a44917ded05005adfa1415500"}, + {file = "coverage-7.10.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:802793ba397afcfdbe9f91f89d65ae88b958d95edc8caf948e1f47d8b6b2b606"}, + {file = "coverage-7.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d0b23512338c54101d3bf7a1ab107d9d75abda1d5f69bc0887fd079253e4c27e"}, + {file = "coverage-7.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f36b7dcf72d06a8c5e2dd3aca02be2b1b5db5f86404627dff834396efce958f2"}, + {file = "coverage-7.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fce316c367a1dc2c411821365592eeb335ff1781956d87a0410eae248188ba51"}, + {file = "coverage-7.10.4-cp310-cp310-win32.whl", hash = "sha256:8c5dab29fc8070b3766b5fc85f8d89b19634584429a2da6d42da5edfadaf32ae"}, + {file = "coverage-7.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:4b0d114616f0fccb529a1817457d5fb52a10e106f86c5fb3b0bd0d45d0d69b93"}, + {file = "coverage-7.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:05d5f98ec893d4a2abc8bc5f046f2f4367404e7e5d5d18b83de8fde1093ebc4f"}, + {file = "coverage-7.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9267efd28f8994b750d171e58e481e3bbd69e44baed540e4c789f8e368b24b88"}, + {file = "coverage-7.10.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4456a039fdc1a89ea60823d0330f1ac6f97b0dbe9e2b6fb4873e889584b085fb"}, + {file = "coverage-7.10.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c2bfbd2a9f7e68a21c5bd191be94bfdb2691ac40d325bac9ef3ae45ff5c753d9"}, + {file = "coverage-7.10.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab7765f10ae1df7e7fe37de9e64b5a269b812ee22e2da3f84f97b1c7732a0d8"}, + {file = "coverage-7.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a09b13695166236e171ec1627ff8434b9a9bae47528d0ba9d944c912d33b3d2"}, + {file = "coverage-7.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5c9e75dfdc0167d5675e9804f04a56b2cf47fb83a524654297000b578b8adcb7"}, + {file = "coverage-7.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c751261bfe6481caba15ec005a194cb60aad06f29235a74c24f18546d8377df0"}, + {file = "coverage-7.10.4-cp311-cp311-win32.whl", hash = "sha256:051c7c9e765f003c2ff6e8c81ccea28a70fb5b0142671e4e3ede7cebd45c80af"}, + {file = "coverage-7.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a647b152f10be08fb771ae4a1421dbff66141e3d8ab27d543b5eb9ea5af8e52"}, + {file = "coverage-7.10.4-cp311-cp311-win_arm64.whl", hash = "sha256:b09b9e4e1de0d406ca9f19a371c2beefe3193b542f64a6dd40cfcf435b7d6aa0"}, + {file = "coverage-7.10.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a1f0264abcabd4853d4cb9b3d164adbf1565da7dab1da1669e93f3ea60162d79"}, + {file = "coverage-7.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:536cbe6b118a4df231b11af3e0f974a72a095182ff8ec5f4868c931e8043ef3e"}, + {file = "coverage-7.10.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9a4c0d84134797b7bf3f080599d0cd501471f6c98b715405166860d79cfaa97e"}, + {file = "coverage-7.10.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7c155fc0f9cee8c9803ea0ad153ab6a3b956baa5d4cd993405dc0b45b2a0b9e0"}, + {file = "coverage-7.10.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5f2ab6e451d4b07855d8bcf063adf11e199bff421a4ba57f5bb95b7444ca62"}, + {file = "coverage-7.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:685b67d99b945b0c221be0780c336b303a7753b3e0ec0d618c795aada25d5e7a"}, + {file = "coverage-7.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c079027e50c2ae44da51c2e294596cbc9dbb58f7ca45b30651c7e411060fc23"}, + {file = "coverage-7.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3749aa72b93ce516f77cf5034d8e3c0dfd45c6e8a163a602ede2dc5f9a0bb927"}, + {file = "coverage-7.10.4-cp312-cp312-win32.whl", hash = "sha256:fecb97b3a52fa9bcd5a7375e72fae209088faf671d39fae67261f37772d5559a"}, + {file = "coverage-7.10.4-cp312-cp312-win_amd64.whl", hash = "sha256:26de58f355626628a21fe6a70e1e1fad95702dafebfb0685280962ae1449f17b"}, + {file = "coverage-7.10.4-cp312-cp312-win_arm64.whl", hash = "sha256:67e8885408f8325198862bc487038a4980c9277d753cb8812510927f2176437a"}, + {file = "coverage-7.10.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b8e1d2015d5dfdbf964ecef12944c0c8c55b885bb5c0467ae8ef55e0e151233"}, + {file = "coverage-7.10.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:25735c299439018d66eb2dccf54f625aceb78645687a05f9f848f6e6c751e169"}, + {file = "coverage-7.10.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:715c06cb5eceac4d9b7cdf783ce04aa495f6aff657543fea75c30215b28ddb74"}, + {file = "coverage-7.10.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e017ac69fac9aacd7df6dc464c05833e834dc5b00c914d7af9a5249fcccf07ef"}, + {file = "coverage-7.10.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bad180cc40b3fccb0f0e8c702d781492654ac2580d468e3ffc8065e38c6c2408"}, + {file = "coverage-7.10.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:becbdcd14f685fada010a5f792bf0895675ecf7481304fe159f0cd3f289550bd"}, + {file = "coverage-7.10.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0b485ca21e16a76f68060911f97ebbe3e0d891da1dbbce6af7ca1ab3f98b9097"}, + {file = "coverage-7.10.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6c1d098ccfe8e1e0a1ed9a0249138899948afd2978cbf48eb1cc3fcd38469690"}, + {file = "coverage-7.10.4-cp313-cp313-win32.whl", hash = "sha256:8630f8af2ca84b5c367c3df907b1706621abe06d6929f5045fd628968d421e6e"}, + {file = "coverage-7.10.4-cp313-cp313-win_amd64.whl", hash = "sha256:f68835d31c421736be367d32f179e14ca932978293fe1b4c7a6a49b555dff5b2"}, + {file = "coverage-7.10.4-cp313-cp313-win_arm64.whl", hash = "sha256:6eaa61ff6724ca7ebc5326d1fae062d85e19b38dd922d50903702e6078370ae7"}, + {file = "coverage-7.10.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:702978108876bfb3d997604930b05fe769462cc3000150b0e607b7b444f2fd84"}, + {file = "coverage-7.10.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e8f978e8c5521d9c8f2086ac60d931d583fab0a16f382f6eb89453fe998e2484"}, + {file = "coverage-7.10.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:df0ac2ccfd19351411c45e43ab60932b74472e4648b0a9edf6a3b58846e246a9"}, + {file = "coverage-7.10.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:73a0d1aaaa3796179f336448e1576a3de6fc95ff4f07c2d7251d4caf5d18cf8d"}, + {file = "coverage-7.10.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:873da6d0ed6b3ffc0bc01f2c7e3ad7e2023751c0d8d86c26fe7322c314b031dc"}, + {file = "coverage-7.10.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c6446c75b0e7dda5daa876a1c87b480b2b52affb972fedd6c22edf1aaf2e00ec"}, + {file = "coverage-7.10.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6e73933e296634e520390c44758d553d3b573b321608118363e52113790633b9"}, + {file = "coverage-7.10.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52073d4b08d2cb571234c8a71eb32af3c6923149cf644a51d5957ac128cf6aa4"}, + {file = "coverage-7.10.4-cp313-cp313t-win32.whl", hash = "sha256:e24afb178f21f9ceb1aefbc73eb524769aa9b504a42b26857243f881af56880c"}, + {file = "coverage-7.10.4-cp313-cp313t-win_amd64.whl", hash = "sha256:be04507ff1ad206f4be3d156a674e3fb84bbb751ea1b23b142979ac9eebaa15f"}, + {file = "coverage-7.10.4-cp313-cp313t-win_arm64.whl", hash = "sha256:f3e3ff3f69d02b5dad67a6eac68cc9c71ae343b6328aae96e914f9f2f23a22e2"}, + {file = "coverage-7.10.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a59fe0af7dd7211ba595cf7e2867458381f7e5d7b4cffe46274e0b2f5b9f4eb4"}, + {file = "coverage-7.10.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a6c35c5b70f569ee38dc3350cd14fdd0347a8b389a18bb37538cc43e6f730e6"}, + {file = "coverage-7.10.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:acb7baf49f513554c4af6ef8e2bd6e8ac74e6ea0c7386df8b3eb586d82ccccc4"}, + {file = "coverage-7.10.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a89afecec1ed12ac13ed203238b560cbfad3522bae37d91c102e690b8b1dc46c"}, + {file = "coverage-7.10.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:480442727f464407d8ade6e677b7f21f3b96a9838ab541b9a28ce9e44123c14e"}, + {file = "coverage-7.10.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a89bf193707f4a17f1ed461504031074d87f035153239f16ce86dfb8f8c7ac76"}, + {file = "coverage-7.10.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3ddd912c2fc440f0fb3229e764feec85669d5d80a988ff1b336a27d73f63c818"}, + {file = "coverage-7.10.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a538944ee3a42265e61c7298aeba9ea43f31c01271cf028f437a7b4075592cf"}, + {file = "coverage-7.10.4-cp314-cp314-win32.whl", hash = "sha256:fd2e6002be1c62476eb862b8514b1ba7e7684c50165f2a8d389e77da6c9a2ebd"}, + {file = "coverage-7.10.4-cp314-cp314-win_amd64.whl", hash = "sha256:ec113277f2b5cf188d95fb66a65c7431f2b9192ee7e6ec9b72b30bbfb53c244a"}, + {file = "coverage-7.10.4-cp314-cp314-win_arm64.whl", hash = "sha256:9744954bfd387796c6a091b50d55ca7cac3d08767795b5eec69ad0f7dbf12d38"}, + {file = "coverage-7.10.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5af4829904dda6aabb54a23879f0f4412094ba9ef153aaa464e3c1b1c9bc98e6"}, + {file = "coverage-7.10.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7bba5ed85e034831fac761ae506c0644d24fd5594727e174b5a73aff343a7508"}, + {file = "coverage-7.10.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d57d555b0719834b55ad35045de6cc80fc2b28e05adb6b03c98479f9553b387f"}, + {file = "coverage-7.10.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ba62c51a72048bb1ea72db265e6bd8beaabf9809cd2125bbb5306c6ce105f214"}, + {file = "coverage-7.10.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0acf0c62a6095f07e9db4ec365cc58c0ef5babb757e54745a1aa2ea2a2564af1"}, + {file = "coverage-7.10.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1033bf0f763f5cf49ffe6594314b11027dcc1073ac590b415ea93463466deec"}, + {file = "coverage-7.10.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:92c29eff894832b6a40da1789b1f252305af921750b03ee4535919db9179453d"}, + {file = "coverage-7.10.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:822c4c830989c2093527e92acd97be4638a44eb042b1bdc0e7a278d84a070bd3"}, + {file = "coverage-7.10.4-cp314-cp314t-win32.whl", hash = "sha256:e694d855dac2e7cf194ba33653e4ba7aad7267a802a7b3fc4347d0517d5d65cd"}, + {file = "coverage-7.10.4-cp314-cp314t-win_amd64.whl", hash = "sha256:efcc54b38ef7d5bfa98050f220b415bc5bb3d432bd6350a861cf6da0ede2cdcd"}, + {file = "coverage-7.10.4-cp314-cp314t-win_arm64.whl", hash = "sha256:6f3a3496c0fa26bfac4ebc458747b778cff201c8ae94fa05e1391bab0dbc473c"}, + {file = "coverage-7.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:48fd4d52600c2a9d5622e52dfae674a7845c5e1dceaf68b88c99feb511fbcfd6"}, + {file = "coverage-7.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:56217b470d09d69e6b7dcae38200f95e389a77db801cb129101697a4553b18b6"}, + {file = "coverage-7.10.4-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:44ac3f21a6e28c5ff7f7a47bca5f87885f6a1e623e637899125ba47acd87334d"}, + {file = "coverage-7.10.4-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3387739d72c84d17b4d2f7348749cac2e6700e7152026912b60998ee9a40066b"}, + {file = "coverage-7.10.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f111ff20d9a6348e0125be892608e33408dd268f73b020940dfa8511ad05503"}, + {file = "coverage-7.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:01a852f0a9859734b018a3f483cc962d0b381d48d350b1a0c47d618c73a0c398"}, + {file = "coverage-7.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:225111dd06759ba4e37cee4c0b4f3df2b15c879e9e3c37bf986389300b9917c3"}, + {file = "coverage-7.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2178d4183bd1ba608f0bb12e71e55838ba1b7dbb730264f8b08de9f8ef0c27d0"}, + {file = "coverage-7.10.4-cp39-cp39-win32.whl", hash = "sha256:93d175fe81913aee7a6ea430abbdf2a79f1d9fd451610e12e334e4fe3264f563"}, + {file = "coverage-7.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:2221a823404bb941c7721cf0ef55ac6ee5c25d905beb60c0bba5e5e85415d353"}, + {file = "coverage-7.10.4-py3-none-any.whl", hash = "sha256:065d75447228d05121e5c938ca8f0e91eed60a1eb2d1258d42d5084fecfc3302"}, + {file = "coverage-7.10.4.tar.gz", hash = "sha256:25f5130af6c8e7297fd14634955ba9e1697f47143f289e2a23284177c0061d27"}, ] [package.dependencies] @@ -437,6 +437,26 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "fastapi" +version = "0.115.14" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = true +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca"}, + {file = "fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.40.0,<0.47.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] + [[package]] name = "flake8" version = "7.3.0" @@ -1389,6 +1409,24 @@ files = [ {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, ] +[[package]] +name = "starlette" +version = "0.46.2" +description = "The little ASGI library that shines." +optional = true +python-versions = ">=3.9" +files = [ + {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, + {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, +] + +[package.dependencies] +anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] + [[package]] name = "stevedore" version = "5.4.1" @@ -1527,7 +1565,10 @@ files = [ [package.extras] dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] +[extras] +fastapi-utils = ["fastapi"] + [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.14" -content-hash = "750c55f6b350d16273e13711cc9a283968c2f24d45ea0cb0c7689d9115144dd8" +content-hash = "2fcab5786a1818f811ee9ea9cb6c9f555da2b17aeea851c65d4b070fbaa451eb" diff --git a/pybotx_smartapp_rpc/fastapi_utils/security.py b/pybotx_smartapp_rpc/fastapi_utils/security.py new file mode 100644 index 0000000..af87351 --- /dev/null +++ b/pybotx_smartapp_rpc/fastapi_utils/security.py @@ -0,0 +1,68 @@ +import re +from typing import Any +from uuid import UUID, uuid4 + +from fastapi import HTTPException +from fastapi.encoders import jsonable_encoder +from fastapi.security import APIKeyHeader +from fastapi.security.base import SecurityBase +from pydantic import ValidationError, BaseModel +from starlette.status import HTTP_403_FORBIDDEN + + +def get_openapi_security_definitions( + security_component: SecurityBase, +) -> tuple[dict[str, Any], dict[str, Any]]: + """Get the security definitions and operation security for a security component.""" + security_definition = jsonable_encoder( + security_component.model, + by_alias=True, + exclude_none=True, + ) + security_name = security_component.scheme_name + security_definitions = {security_name: security_definition} + operation_security = {security_name: []} # type: ignore + return security_definitions, operation_security + +DOCS = """Установка параметров для выполнение RPC методов. + +* `bot_id` - huid бота. Необязательное поле. +* `sender_huid` - huid пользователя. Необязательное поле. +* `sender_udid` - udid пользователя. Необязательное поле. +* `chat_id` - id чата. Необязательное поле. + +**Example**: `bot_id=UUID&sender_huid=UUID&sender_udid=UUID&chat_id=UUID`""" + +class RPCAuthConfig(BaseModel): + bot_id: UUID + sender_huid: UUID = uuid4() + sender_udid: UUID = uuid4() + chat_id: UUID = uuid4() + +class RPCAuth(APIKeyHeader): + PATTERN = "([^?=&]+)=([^&]*)" + + def __init__(self,*,bot_id:UUID, scheme_name:str="RPC Auth", name:str="X-RPC-AUTH", description:str=DOCS, **kwargs) -> None: + self.bot_id = bot_id + super().__init__(scheme_name=scheme_name,name=name, description=description, **kwargs) + + async def __call__(self, request: Request) -> RPCAuthConfig: # type: ignore + api_key = request.headers.get(self.model.name) + if not api_key: + return RPCAuthConfig(bot_id=self.bot_id) + + params = re.findall(self.PATTERN, api_key) # noqa: WPS110 + if not params: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Invalid RPC Auth format" + ) + try: + params_dict = dict(params) + if (bot_id:=params_dict.get("bot_id")) is None: + params_dict["bot_id"] = bot_id + + config = RPCAuthConfig(**params_dict) + except ValidationError as ex: + raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail=str(ex)) + + return config diff --git a/pyproject.toml b/pyproject.toml index beba216..df0d9ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,11 @@ pydantic = ">=2.8.2,<3.0" pybotx = "~0.76.0a1" deepdiff = "^8.6.0" +fastapi = { version = "~0.115.1", optional = true } + +[tool.poetry.extras] +fastapi_utils = ["fastapi"] + [tool.poetry.dev-dependencies] add-trailing-comma = "^3.2.0" black = "25.1.0" From d0d7dd9d40fd3faba92358c6ce863beeca54e9e7 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 18:30:52 +0300 Subject: [PATCH 07/31] add fastapi openapi --- pybotx_smartapp_rpc/fastapi_utils/__init__.py | 0 .../fastapi_utils/custom_openapi.py | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 pybotx_smartapp_rpc/fastapi_utils/__init__.py create mode 100644 pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py diff --git a/pybotx_smartapp_rpc/fastapi_utils/__init__.py b/pybotx_smartapp_rpc/fastapi_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py b/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py new file mode 100644 index 0000000..c436802 --- /dev/null +++ b/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py @@ -0,0 +1,36 @@ +from typing import Sequence, Any + +from fastapi.encoders import jsonable_encoder +from fastapi.openapi.models import OpenAPI +from fastapi.openapi.utils import get_openapi +from starlette.routing import BaseRoute + +from pybotx_smartapp_rpc import RPCRouter +from pybotx_smartapp_rpc.fastapi_utils.security import RPCAuth, \ + get_openapi_security_definitions +from pybotx_smartapp_rpc.openapi.openapi import update_fastapi_paths_by_rpc_router + + +def rpc_openapi( + *, + title: str, + version: str, + fastapi_routes: Sequence[BaseRoute], + rpc_router: RPCRouter, + security:RPCAuth, + **kwargs: Any, +) -> dict[str, Any]: + openapi_dict = get_openapi( + title=title, + version=version, + routes=fastapi_routes, + **kwargs, + ) + + security_definitions, operation_security = get_openapi_security_definitions( + security_component=security + ) + + update_fastapi_paths_by_rpc_router(openapi_dict,rpc_router,security_definitions,operation_security) + + return jsonable_encoder(OpenAPI(**openapi_dict), by_alias=True, exclude_none=True) From 6d328c018e8f27a3a56865805637e10f442162ac Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 18:37:16 +0300 Subject: [PATCH 08/31] fix import --- pybotx_smartapp_rpc/fastapi_utils/security.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pybotx_smartapp_rpc/fastapi_utils/security.py b/pybotx_smartapp_rpc/fastapi_utils/security.py index af87351..b91aa2b 100644 --- a/pybotx_smartapp_rpc/fastapi_utils/security.py +++ b/pybotx_smartapp_rpc/fastapi_utils/security.py @@ -8,6 +8,7 @@ from fastapi.security.base import SecurityBase from pydantic import ValidationError, BaseModel from starlette.status import HTTP_403_FORBIDDEN +from starlette.requests import Request def get_openapi_security_definitions( From 0759323acb0a932cb6b4f2ebea0cebaa699459af Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 22:54:48 +0300 Subject: [PATCH 09/31] lints passed --- poetry.lock | 795 ++---------------- pybotx_smartapp_rpc/empty_args.py | 2 +- pybotx_smartapp_rpc/exceptions.py | 4 +- .../fastapi_utils/custom_openapi.py | 14 +- pybotx_smartapp_rpc/fastapi_utils/security.py | 31 +- .../middlewares/exception_middleware.py | 2 +- pybotx_smartapp_rpc/models/method.py | 2 +- pybotx_smartapp_rpc/models/responses.py | 6 +- pybotx_smartapp_rpc/openapi/openapi.py | 44 +- pybotx_smartapp_rpc/openapi/utils.py | 6 +- pybotx_smartapp_rpc/router.py | 9 +- pybotx_smartapp_rpc/rpc.py | 2 +- pyproject.toml | 10 +- ruff.toml | 38 + scripts/format | 21 +- scripts/lint | 21 +- scripts/test | 87 +- setup.cfg | 112 +-- tests/conftest.py | 22 + tests/test_openapi.py | 279 +++--- tests/test_rpc_calls.py | 6 +- tests/test_security.py | 108 +++ 22 files changed, 580 insertions(+), 1041 deletions(-) create mode 100644 ruff.toml create mode 100644 tests/test_security.py diff --git a/poetry.lock b/poetry.lock index b402c10..7e2417b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,19 +1,5 @@ # This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. -[[package]] -name = "add-trailing-comma" -version = "3.2.0" -description = "Automatically add trailing commas to calls and literals" -optional = false -python-versions = ">=3.9" -files = [ - {file = "add_trailing_comma-3.2.0-py2.py3-none-any.whl", hash = "sha256:e88c66d0172754cc31998440a040d8c67a47d9ec488a0b83681b28a257fa88ab"}, - {file = "add_trailing_comma-3.2.0.tar.gz", hash = "sha256:b3bb8b7184983a15199ce282b849f9db0612d067936f2c2610f2af1e1a53b97d"}, -] - -[package.dependencies] -tokenize-rt = ">=3.0.1" - [[package]] name = "aiocsv" version = "1.3.2" @@ -99,51 +85,6 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.26.1)"] -[[package]] -name = "astor" -version = "0.8.1" -description = "Read/rewrite/write Python ASTs" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -files = [ - {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, - {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, -] - -[[package]] -name = "attrs" -version = "25.3.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.8" -files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, -] - -[package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] - -[[package]] -name = "autoflake" -version = "2.3.1" -description = "Removes unused imports and unused variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840"}, - {file = "autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e"}, -] - -[package.dependencies] -pyflakes = ">=3.0.0" -tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} - [[package]] name = "backports-asyncio-runner" version = "1.2.0" @@ -155,76 +96,6 @@ files = [ {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, ] -[[package]] -name = "bandit" -version = "1.8.6" -description = "Security oriented static analyser for python code." -optional = false -python-versions = ">=3.9" -files = [ - {file = "bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0"}, - {file = "bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b"}, -] - -[package.dependencies] -colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -PyYAML = ">=5.3.1" -rich = "*" -stevedore = ">=1.20.0" - -[package.extras] -baseline = ["GitPython (>=3.1.30)"] -sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] -test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] -toml = ["tomli (>=1.1.0)"] -yaml = ["PyYAML"] - -[[package]] -name = "black" -version = "25.1.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.9" -files = [ - {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, - {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, - {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, - {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, - {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2025.8.3" @@ -236,20 +107,6 @@ files = [ {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] -[[package]] -name = "click" -version = "8.1.8" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "colorama" version = "0.4.6" @@ -364,17 +221,6 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] -[[package]] -name = "darglint" -version = "1.8.1" -description = "A utility for ensuring Google-style docstrings stay up to date with the source code." -optional = false -python-versions = ">=3.6,<4.0" -files = [ - {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, - {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, -] - [[package]] name = "deepdiff" version = "8.6.0" @@ -398,28 +244,6 @@ optimize = ["orjson"] static = ["flake8 (>=7.1.0,<7.2.0)", "flake8-pyproject (>=1.2.3,<1.3.0)", "pydantic (>=2.10.0,<2.11.0)"] test = ["pytest (>=8.3.0,<8.4.0)", "pytest-benchmark (>=5.1.0,<5.2.0)", "pytest-cov (>=6.0.0,<6.1.0)", "python-dotenv (>=1.0.0,<1.1.0)"] -[[package]] -name = "docutils" -version = "0.22" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.9" -files = [ - {file = "docutils-0.22-py3-none-any.whl", hash = "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e"}, - {file = "docutils-0.22.tar.gz", hash = "sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f"}, -] - -[[package]] -name = "eradicate" -version = "2.3.0" -description = "Removes commented-out code." -optional = false -python-versions = "*" -files = [ - {file = "eradicate-2.3.0-py3-none-any.whl", hash = "sha256:2b29b3dd27171f209e4ddd8204b70c02f0682ae95eecb353f10e8d72b149c63e"}, - {file = "eradicate-2.3.0.tar.gz", hash = "sha256:06df115be3b87d0fc1c483db22a2ebb12bcf40585722810d809cc770f5031c37"}, -] - [[package]] name = "exceptiongroup" version = "1.3.0" @@ -457,208 +281,6 @@ typing-extensions = ">=4.8.0" all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] -[[package]] -name = "flake8" -version = "7.3.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.9" -files = [ - {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, - {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.14.0,<2.15.0" -pyflakes = ">=3.4.0,<3.5.0" - -[[package]] -name = "flake8-bandit" -version = "4.1.1" -description = "Automated security testing with bandit and flake8." -optional = false -python-versions = ">=3.6" -files = [ - {file = "flake8_bandit-4.1.1-py3-none-any.whl", hash = "sha256:4c8a53eb48f23d4ef1e59293657181a3c989d0077c9952717e98a0eace43e06d"}, - {file = "flake8_bandit-4.1.1.tar.gz", hash = "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e"}, -] - -[package.dependencies] -bandit = ">=1.7.3" -flake8 = ">=5.0.0" - -[[package]] -name = "flake8-broken-line" -version = "1.0.0" -description = "Flake8 plugin to forbid backslashes for line breaks" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "flake8_broken_line-1.0.0-py3-none-any.whl", hash = "sha256:96c964336024a5030dc536a9f6fb02aa679e2d2a6b35b80a558b5136c35832a9"}, - {file = "flake8_broken_line-1.0.0.tar.gz", hash = "sha256:e2c6a17f8d9a129e99c1320fce89b33843e2963871025c4c2bb7b8b8d8732a85"}, -] - -[package.dependencies] -flake8 = ">5" - -[[package]] -name = "flake8-bugbear" -version = "23.12.2" -description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-bugbear-23.12.2.tar.gz", hash = "sha256:32b2903e22331ae04885dae25756a32a8c666c85142e933f43512a70f342052a"}, - {file = "flake8_bugbear-23.12.2-py3-none-any.whl", hash = "sha256:83324bad4d90fee4bf64dd69c61aff94debf8073fbd807c8b6a36eec7a2f0719"}, -] - -[package.dependencies] -attrs = ">=19.2.0" -flake8 = ">=6.0.0" - -[package.extras] -dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", "tox"] - -[[package]] -name = "flake8-commas" -version = "2.1.0" -description = "Flake8 lint for trailing commas." -optional = false -python-versions = "*" -files = [ - {file = "flake8-commas-2.1.0.tar.gz", hash = "sha256:940441ab8ee544df564ae3b3f49f20462d75d5c7cac2463e0b27436e2050f263"}, - {file = "flake8_commas-2.1.0-py2.py3-none-any.whl", hash = "sha256:ebb96c31e01d0ef1d0685a21f3f0e2f8153a0381430e748bf0bbbb5d5b453d54"}, -] - -[package.dependencies] -flake8 = ">=2" - -[[package]] -name = "flake8-comprehensions" -version = "3.16.0" -description = "A flake8 plugin to help you write better list/set/dict comprehensions." -optional = false -python-versions = ">=3.9" -files = [ - {file = "flake8_comprehensions-3.16.0-py3-none-any.whl", hash = "sha256:7c1eadc9d22e765f39857798febe7766b4d9c519793c6c149e3e13bf99693f70"}, - {file = "flake8_comprehensions-3.16.0.tar.gz", hash = "sha256:9cbf789905a8f03f9d350fb82b17b264d9a16c7ce3542b2a7b871ef568cafabe"}, -] - -[package.dependencies] -flake8 = ">=3,<3.2 || >3.2" - -[[package]] -name = "flake8-debugger" -version = "4.1.2" -description = "ipdb/pdb statement checker plugin for flake8" -optional = false -python-versions = ">=3.7" -files = [ - {file = "flake8-debugger-4.1.2.tar.gz", hash = "sha256:52b002560941e36d9bf806fca2523dc7fb8560a295d5f1a6e15ac2ded7a73840"}, - {file = "flake8_debugger-4.1.2-py3-none-any.whl", hash = "sha256:0a5e55aeddcc81da631ad9c8c366e7318998f83ff00985a49e6b3ecf61e571bf"}, -] - -[package.dependencies] -flake8 = ">=3.0" -pycodestyle = "*" - -[[package]] -name = "flake8-docstrings" -version = "1.7.0" -description = "Extension for flake8 which uses pydocstyle to check docstrings" -optional = false -python-versions = ">=3.7" -files = [ - {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, - {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, -] - -[package.dependencies] -flake8 = ">=3" -pydocstyle = ">=2.1" - -[[package]] -name = "flake8-eradicate" -version = "1.5.0" -description = "Flake8 plugin to find commented out code" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "flake8_eradicate-1.5.0-py3-none-any.whl", hash = "sha256:18acc922ad7de623f5247c7d5595da068525ec5437dd53b22ec2259b96ce9d22"}, - {file = "flake8_eradicate-1.5.0.tar.gz", hash = "sha256:aee636cb9ecb5594a7cd92d67ad73eb69909e5cc7bd81710cf9d00970f3983a6"}, -] - -[package.dependencies] -attrs = "*" -eradicate = ">=2.0,<3.0" -flake8 = ">5" - -[[package]] -name = "flake8-isort" -version = "6.1.2" -description = "flake8 plugin that integrates isort" -optional = false -python-versions = ">=3.9" -files = [ - {file = "flake8_isort-6.1.2-py3-none-any.whl", hash = "sha256:549197dedf0273502fb74f04c080beed9e62a7eb70244610413d27052e78bd3b"}, - {file = "flake8_isort-6.1.2.tar.gz", hash = "sha256:9d0452acdf0e1cd6f2d6848e3605e66b54d920e73471fb4744eef0f93df62d5d"}, -] - -[package.dependencies] -flake8 = "*" -isort = ">=5.0.0,<7" - -[package.extras] -test = ["pytest"] - -[[package]] -name = "flake8-quotes" -version = "3.4.0" -description = "Flake8 lint for quotes." -optional = false -python-versions = "*" -files = [ - {file = "flake8-quotes-3.4.0.tar.gz", hash = "sha256:aad8492fb710a2d3eabe68c5f86a1428de650c8484127e14c43d0504ba30276c"}, -] - -[package.dependencies] -flake8 = "*" -setuptools = "*" - -[[package]] -name = "flake8-rst-docstrings" -version = "0.3.1" -description = "Python docstring reStructuredText (RST) validator for flake8" -optional = false -python-versions = ">=3.8" -files = [ - {file = "flake8_rst_docstrings-0.3.1-py3-none-any.whl", hash = "sha256:ed831afca7ee47851e2162d5fa726b823b446fd46085c2164d7979ae5d9a96d7"}, - {file = "flake8_rst_docstrings-0.3.1.tar.gz", hash = "sha256:26dcc1338caf985990677696a8a6a274f73a0c6845b85f567befd3b648db78e2"}, -] - -[package.dependencies] -flake8 = ">=3" -pygments = "*" -restructuredtext_lint = "*" - -[package.extras] -develop = ["build", "twine"] - -[[package]] -name = "flake8-string-format" -version = "0.3.0" -description = "string format checker, plugin for flake8" -optional = false -python-versions = "*" -files = [ - {file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"}, - {file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"}, -] - -[package.dependencies] -flake8 = "*" - [[package]] name = "h11" version = "0.16.0" @@ -740,21 +362,6 @@ files = [ {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] -[[package]] -name = "isort" -version = "6.0.1" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.9.0" -files = [ - {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, - {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, -] - -[package.extras] -colors = ["colorama"] -plugins = ["setuptools"] - [[package]] name = "loguru" version = "0.6.0" @@ -773,97 +380,45 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)"] -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - [[package]] name = "mypy" -version = "1.17.1" +version = "1.16.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" files = [ - {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, - {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, - {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, - {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, - {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, - {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, - {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, - {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, - {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, - {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, - {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, - {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, - {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, - {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, - {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, - {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, + {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, + {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, + {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, + {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, + {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, + {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, + {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, + {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, + {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, + {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, + {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, + {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, + {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, + {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, ] [package.dependencies] @@ -930,50 +485,6 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "pbr" -version = "7.0.0" -description = "Python Build Reasonableness" -optional = false -python-versions = ">=2.6" -files = [ - {file = "pbr-7.0.0-py2.py3-none-any.whl", hash = "sha256:b447e63a2bc04fd975fc0480b8d5ebf979179e2c0ae203bf1eff9ea20073bc38"}, - {file = "pbr-7.0.0.tar.gz", hash = "sha256:cf4127298723dafbce3afd13775ccf3885be5d3c8435751b867f9a6a10b71a39"}, -] - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "pep8-naming" -version = "0.13.3" -description = "Check PEP-8 naming conventions, plugin for flake8" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pep8-naming-0.13.3.tar.gz", hash = "sha256:1705f046dfcd851378aac3be1cd1551c7c1e5ff363bacad707d43007877fa971"}, - {file = "pep8_naming-0.13.3-py3-none-any.whl", hash = "sha256:1a86b8c71a03337c97181917e2b472f0f5e4ccb06844a0d6f0a33522549e7a80"}, -] - -[package.dependencies] -flake8 = ">=5.0.0" - -[[package]] -name = "platformdirs" -version = "4.3.8" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.9" -files = [ - {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, - {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] - [[package]] name = "pluggy" version = "1.6.0" @@ -1010,17 +521,6 @@ mypy-extensions = ">=0.2.0,<=1.1.0" pydantic = ">=2.8.2,<3.0" pyjwt = ">=2.0.0,<3.0.0" -[[package]] -name = "pycodestyle" -version = "2.14.0" -description = "Python style guide checker" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, - {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, -] - [[package]] name = "pydantic" version = "2.11.7" @@ -1153,34 +653,6 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" -[[package]] -name = "pydocstyle" -version = "6.3.0" -description = "Python docstring style checker" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, - {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, -] - -[package.dependencies] -snowballstemmer = ">=2.2.0" - -[package.extras] -toml = ["tomli (>=1.2.3)"] - -[[package]] -name = "pyflakes" -version = "3.4.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, - {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, -] - [[package]] name = "pygments" version = "2.19.2" @@ -1275,118 +747,32 @@ pytest = ">=6.2.5" testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {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]] -name = "restructuredtext-lint" -version = "1.4.0" -description = "reStructuredText linter" -optional = false -python-versions = "*" -files = [ - {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, -] - -[package.dependencies] -docutils = ">=0.11,<1.0" - -[[package]] -name = "rich" -version = "14.1.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, - {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "setuptools" -version = "80.9.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" +name = "ruff" +version = "0.12.0" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false -python-versions = ">=3.9" +python-versions = ">=3.7" files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, + {file = "ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848"}, + {file = "ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6"}, + {file = "ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a"}, + {file = "ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb"}, + {file = "ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0"}, + {file = "ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b"}, + {file = "ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c"}, ] -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] - [[package]] name = "sniffio" version = "1.3.1" @@ -1398,17 +784,6 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] -[[package]] -name = "snowballstemmer" -version = "3.0.1" -description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" -files = [ - {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, - {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, -] - [[package]] name = "starlette" version = "0.46.2" @@ -1427,31 +802,6 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] -[[package]] -name = "stevedore" -version = "5.4.1" -description = "Manage dynamic plugins for Python applications" -optional = false -python-versions = ">=3.9" -files = [ - {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, - {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, -] - -[package.dependencies] -pbr = ">=2.0.0" - -[[package]] -name = "tokenize-rt" -version = "6.2.0" -description = "A wrapper around the stdlib `tokenize` which roundtrips." -optional = false -python-versions = ">=3.9" -files = [ - {file = "tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44"}, - {file = "tokenize_rt-6.2.0.tar.gz", hash = "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6"}, -] - [[package]] name = "tomli" version = "2.2.1" @@ -1518,39 +868,6 @@ files = [ [package.dependencies] typing-extensions = ">=4.12.0" -[[package]] -name = "wemake-python-styleguide" -version = "0.18.0" -description = "The strictest and most opinionated python linter ever" -optional = false -python-versions = ">=3.8.1,<4.0" -files = [ - {file = "wemake_python_styleguide-0.18.0-py3-none-any.whl", hash = "sha256:2219be145185edcd5e01f4ce49e3dea11acc34f2c377face0c175bb6ea6ac988"}, - {file = "wemake_python_styleguide-0.18.0.tar.gz", hash = "sha256:69139858cf5b2a9ba09dac136e2873a4685515768f68fdef2684ebefd7b1dafd"}, -] - -[package.dependencies] -astor = ">=0.8,<0.9" -attrs = "*" -darglint = ">=1.2,<2.0" -flake8 = ">5" -flake8-bandit = ">=4.1,<5.0" -flake8-broken-line = ">=1.0,<2.0" -flake8-bugbear = ">=23.5,<24.0" -flake8-commas = ">=2.0,<3.0" -flake8-comprehensions = ">=3.1,<4.0" -flake8-debugger = ">=4.0,<5.0" -flake8-docstrings = ">=1.3,<2.0" -flake8-eradicate = ">=1.5,<2.0" -flake8-isort = ">=6.0,<7.0" -flake8-quotes = ">=3.0,<4.0" -flake8-rst-docstrings = ">=0.3,<0.4" -flake8-string-format = ">=0.3,<0.4" -pep8-naming = ">=0.13,<0.14" -pygments = ">=2.4,<3.0" -setuptools = "*" -typing_extensions = ">=4.0,<5.0" - [[package]] name = "win32-setctime" version = "1.2.0" @@ -1571,4 +888,4 @@ fastapi-utils = ["fastapi"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.14" -content-hash = "2fcab5786a1818f811ee9ea9cb6c9f555da2b17aeea851c65d4b070fbaa451eb" +content-hash = "dce23d3a4f6d764d8b77adab3373466629f5bfe39e13e531c706a83940852a91" diff --git a/pybotx_smartapp_rpc/empty_args.py b/pybotx_smartapp_rpc/empty_args.py index 085be29..c27995f 100644 --- a/pybotx_smartapp_rpc/empty_args.py +++ b/pybotx_smartapp_rpc/empty_args.py @@ -2,4 +2,4 @@ class EmptyArgs(RPCArgsBaseModel): - pass # noqa: WPS420, WPS604 + pass diff --git a/pybotx_smartapp_rpc/exceptions.py b/pybotx_smartapp_rpc/exceptions.py index 85c13d0..ba0daf3 100644 --- a/pybotx_smartapp_rpc/exceptions.py +++ b/pybotx_smartapp_rpc/exceptions.py @@ -3,10 +3,10 @@ from pybotx_smartapp_rpc.models.errors import RPCError -class BaseRPCErrorExc(Exception): ... # noqa: WPS428, WPS604 +class BaseRPCErrorExc(Exception): ... # noqa: N818 -class RPCErrorExc(BaseRPCErrorExc): +class RPCErrorExc(BaseRPCErrorExc): # noqa: N818 def __init__(self, error_or_errors: Union[RPCError, List[RPCError]]): if isinstance(error_or_errors, RPCError): self.errors = [error_or_errors] diff --git a/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py b/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py index c436802..0beb2e1 100644 --- a/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py +++ b/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py @@ -1,4 +1,4 @@ -from typing import Sequence, Any +from typing import Any, Sequence from fastapi.encoders import jsonable_encoder from fastapi.openapi.models import OpenAPI @@ -6,8 +6,10 @@ from starlette.routing import BaseRoute from pybotx_smartapp_rpc import RPCRouter -from pybotx_smartapp_rpc.fastapi_utils.security import RPCAuth, \ - get_openapi_security_definitions +from pybotx_smartapp_rpc.fastapi_utils.security import ( + RPCAuth, + get_openapi_security_definitions, +) from pybotx_smartapp_rpc.openapi.openapi import update_fastapi_paths_by_rpc_router @@ -17,7 +19,7 @@ def rpc_openapi( version: str, fastapi_routes: Sequence[BaseRoute], rpc_router: RPCRouter, - security:RPCAuth, + security: RPCAuth, **kwargs: Any, ) -> dict[str, Any]: openapi_dict = get_openapi( @@ -31,6 +33,8 @@ def rpc_openapi( security_component=security ) - update_fastapi_paths_by_rpc_router(openapi_dict,rpc_router,security_definitions,operation_security) + update_fastapi_paths_by_rpc_router( + openapi_dict, rpc_router, security_definitions, operation_security + ) return jsonable_encoder(OpenAPI(**openapi_dict), by_alias=True, exclude_none=True) diff --git a/pybotx_smartapp_rpc/fastapi_utils/security.py b/pybotx_smartapp_rpc/fastapi_utils/security.py index b91aa2b..e269de5 100644 --- a/pybotx_smartapp_rpc/fastapi_utils/security.py +++ b/pybotx_smartapp_rpc/fastapi_utils/security.py @@ -6,9 +6,9 @@ from fastapi.encoders import jsonable_encoder from fastapi.security import APIKeyHeader from fastapi.security.base import SecurityBase -from pydantic import ValidationError, BaseModel -from starlette.status import HTTP_403_FORBIDDEN +from pydantic import BaseModel, ValidationError from starlette.requests import Request +from starlette.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN def get_openapi_security_definitions( @@ -25,6 +25,7 @@ def get_openapi_security_definitions( operation_security = {security_name: []} # type: ignore return security_definitions, operation_security + DOCS = """Установка параметров для выполнение RPC методов. * `bot_id` - huid бота. Необязательное поле. @@ -34,36 +35,50 @@ def get_openapi_security_definitions( **Example**: `bot_id=UUID&sender_huid=UUID&sender_udid=UUID&chat_id=UUID`""" + class RPCAuthConfig(BaseModel): bot_id: UUID sender_huid: UUID = uuid4() sender_udid: UUID = uuid4() chat_id: UUID = uuid4() + class RPCAuth(APIKeyHeader): PATTERN = "([^?=&]+)=([^&]*)" - def __init__(self,*,bot_id:UUID, scheme_name:str="RPC Auth", name:str="X-RPC-AUTH", description:str=DOCS, **kwargs) -> None: + def __init__( + self, + *, + bot_id: UUID, + scheme_name: str = "RPC Auth", + name: str = "X-RPC-AUTH", + description: str = DOCS, + **kwargs: Any, + ) -> None: self.bot_id = bot_id - super().__init__(scheme_name=scheme_name,name=name, description=description, **kwargs) + super().__init__( + scheme_name=scheme_name, name=name, description=description, **kwargs + ) async def __call__(self, request: Request) -> RPCAuthConfig: # type: ignore api_key = request.headers.get(self.model.name) if not api_key: return RPCAuthConfig(bot_id=self.bot_id) - params = re.findall(self.PATTERN, api_key) # noqa: WPS110 + params = re.findall(self.PATTERN, api_key) if not params: raise HTTPException( status_code=HTTP_403_FORBIDDEN, detail="Invalid RPC Auth format" ) try: params_dict = dict(params) - if (bot_id:=params_dict.get("bot_id")) is None: - params_dict["bot_id"] = bot_id + if "bot_id" not in params_dict: + params_dict["bot_id"] = self.bot_id config = RPCAuthConfig(**params_dict) except ValidationError as ex: - raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail=str(ex)) + raise HTTPException( + status_code=HTTP_400_BAD_REQUEST, detail=str(ex) + ) from None return config diff --git a/pybotx_smartapp_rpc/middlewares/exception_middleware.py b/pybotx_smartapp_rpc/middlewares/exception_middleware.py index 79486fa..605ae65 100644 --- a/pybotx_smartapp_rpc/middlewares/exception_middleware.py +++ b/pybotx_smartapp_rpc/middlewares/exception_middleware.py @@ -28,7 +28,7 @@ async def __call__( rpc_result = await call_next(smartapp, rpc_arguments) except Exception as exc: exception_handler = self._get_exception_handler(exc) - try: # noqa: WPS505 + try: return await exception_handler(exc, smartapp) except Exception as error_handler_exc: return await default_exception_handler(error_handler_exc, smartapp) diff --git a/pybotx_smartapp_rpc/models/method.py b/pybotx_smartapp_rpc/models/method.py index a52d24e..960dfd2 100644 --- a/pybotx_smartapp_rpc/models/method.py +++ b/pybotx_smartapp_rpc/models/method.py @@ -35,7 +35,7 @@ async def __call__( # then stack will be m1(m2(m3(m4(handler())))) handler: HandlerWithArgs = self.handler # type: ignore for middleware in self.middlewares[::-1]: - part = partial(middleware, call_next=handler) + part = partial(middleware, call_next=handler) # type: ignore handler = part return await handler(smartapp, rpc_args) diff --git a/pybotx_smartapp_rpc/models/responses.py b/pybotx_smartapp_rpc/models/responses.py index 6f2b42b..9eb7a30 100644 --- a/pybotx_smartapp_rpc/models/responses.py +++ b/pybotx_smartapp_rpc/models/responses.py @@ -31,11 +31,11 @@ def jsonable_dict(self) -> Dict[str, Any]: "result": self.jsonable_result(), } - def jsonable_result(self) -> JsonableResultType: + def jsonable_result(self) -> _JsonableResultType: if isinstance(self.result, BaseModel): - return self.result.dict(by_alias=True) # type: ignore + return self.result.model_dump(by_alias=True) - return self.result # type: ignore + return self.result @dataclass diff --git a/pybotx_smartapp_rpc/openapi/openapi.py b/pybotx_smartapp_rpc/openapi/openapi.py index cef14d3..d72a7b5 100644 --- a/pybotx_smartapp_rpc/openapi/openapi.py +++ b/pybotx_smartapp_rpc/openapi/openapi.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Any, Dict, List, Optional, Set, Type, Union -from pydantic import BaseModel +from pydantic import BaseModel, TypeAdapter from pybotx_smartapp_rpc import RPCRouter from pybotx_smartapp_rpc.models.method import RPCMethod @@ -20,13 +20,11 @@ def update_fastapi_paths_by_rpc_router( - openapi_dict: dict[str:Any], + openapi_dict: dict[str, Any], rpc_router: RPCRouter, security_definitions: Optional[dict[str, Any]] = None, - operation_security: Optional[dict[str, Any]] = Any, + operation_security: Optional[dict[str, Any]] = None, ) -> None: - """Update the fastapi dict returned from get_openapi by registered SmartApp methods information""" - if security_definitions is not None: openapi_dict.setdefault("components", {}).setdefault( "securitySchemes", {} @@ -44,7 +42,7 @@ def update_fastapi_paths_by_rpc_router( if not method.include_in_schema: continue - if path := get_rpc_openapi_path( # type: ignore + if path := get_rpc_openapi_path( method_name=method_name, route=method, model_name_map=rpc_model_name_map, @@ -62,27 +60,33 @@ def update_fastapi_paths_by_rpc_router( def get_rpc_model_definitions( *, - flat_models: Set[Union[Type[BaseModel], Type[Enum]]], - model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], -) -> Dict[str, Any]: + flat_models: set[type[BaseModel | Enum]], + model_name_map: dict[type[BaseModel | Enum], str], +) -> dict[str, Any]: + definitions: dict[str, dict[str, Any]] = {} - definitions: Dict[str, Dict[str, Any]] = {} for model in flat_models: - # Get the JSON schema of the model (including nested definitions under '$defs') - m_schema = model.model_json_schema(ref_template=REF_PREFIX + "{model}") + if isinstance(model, type) and issubclass(model, BaseModel): + m_schema = model.model_json_schema(ref_template=REF_PREFIX + "{model}") + elif isinstance(model, type) and issubclass(model, Enum): + m_schema = TypeAdapter(model).json_schema( + ref_template=REF_PREFIX + "{model}" + ) + else: + m_schema = TypeAdapter(model).json_schema( + ref_template=REF_PREFIX + "{model}" + ) - # Extract nested model definitions from $defs (if present) nested_defs = m_schema.pop("$defs", {}) - - # Update overall definitions with nested definitions definitions.update(nested_defs) model_name = model_name_map[model] + # Trim FastAPI-style docstrings if present if "description" in m_schema: m_schema["description"] = m_schema["description"].split("\f")[0] - # Add the model's own schema under its mapped name + # Register schema under the resolved name definitions[model_name] = m_schema return definitions @@ -94,13 +98,13 @@ def get_rpc_flat_models_from_routes( body_fields_from_routes: List[ModelField] = [] responses_from_routes: List[ModelField] = [] - for method_name, rpc_method in router.rpc_methods.items(): + for rpc_method in router.rpc_methods.values(): if not rpc_method.include_in_schema: continue if rpc_method.arguments_field: body_fields_from_routes.append( - rpc_method.arguments_field, # type: ignore + rpc_method.arguments_field, ) if rpc_method.response_field: responses_from_routes.append(rpc_method.response_field) @@ -116,7 +120,7 @@ def get_rpc_flat_models_from_routes( ) -def get_rpc_openapi_path( # noqa: WPS231 +def get_rpc_openapi_path( *, method_name: str, route: RPCMethod, @@ -156,7 +160,7 @@ def get_rpc_openapi_path( # noqa: WPS231 openapi_response = operation_errors.setdefault(str(error_status_code), {}) if route.errors_models and ( - field := route.errors_models[error_status_code] # noqa: WPS332 + field := route.errors_models[error_status_code] ): error_schema = get_schema_or_ref(field, model_name_map, REF_PREFIX) diff --git a/pybotx_smartapp_rpc/openapi/utils.py b/pybotx_smartapp_rpc/openapi/utils.py index d736271..dd9e2c9 100644 --- a/pybotx_smartapp_rpc/openapi/utils.py +++ b/pybotx_smartapp_rpc/openapi/utils.py @@ -111,7 +111,11 @@ def deep_dict_update( destination_dict[key] = source_dict[key] -def get_schema_or_ref(model: ModelField, model_name_map, ref_prefix: str) -> dict: +def get_schema_or_ref( + model: ModelField, + model_name_map: dict[type[BaseModel | Enum], str], + ref_prefix: str, +) -> dict: if model_name := model_name_map.get(model.type_): return {"$ref": ref_prefix + model_name} diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index a10fd10..3800a9a 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -10,13 +10,13 @@ from pybotx_smartapp_rpc.empty_args import EmptyArgs from pybotx_smartapp_rpc.middlewares.empty_args_middleware import empty_args_middleware from pybotx_smartapp_rpc.models.method import RPCMethod +from pybotx_smartapp_rpc.models.model_field import ModelField from pybotx_smartapp_rpc.models.request import RPCRequest from pybotx_smartapp_rpc.models.responses import ( ResultType, build_invalid_rpc_args_error_response, build_method_not_found_error_response, ) -from pybotx_smartapp_rpc.models.model_field import ModelField from pybotx_smartapp_rpc.smartapp import SmartApp from pybotx_smartapp_rpc.typing import Handler, Middleware, RPCResponse @@ -134,13 +134,12 @@ def _get_handler_response_model( handler_signature: inspect.Signature, return_type: Optional[Type[ResultType]], name: str, - ) -> Optional[ModelField]: - + ) -> ModelField: if return_type: response_type = return_type else: return_annotation = handler_signature.return_annotation - if hasattr(return_annotation, "__args__"): # noqa: WPS421 + if hasattr(return_annotation, "__args__"): response_type = return_annotation.__args__[0] else: response_type = None @@ -154,7 +153,7 @@ def _get_args_and_return_field( self, handler: Handler, return_type: Optional[Type[ResultType]] = None, - ) -> Tuple[Optional[ModelField], ModelField]: + ) -> tuple[Optional[ModelField], ModelField]: signature = inspect.signature(handler) response_field = self._get_handler_response_model( diff --git a/pybotx_smartapp_rpc/rpc.py b/pybotx_smartapp_rpc/rpc.py index 01e31ff..2ac4949 100644 --- a/pybotx_smartapp_rpc/rpc.py +++ b/pybotx_smartapp_rpc/rpc.py @@ -26,7 +26,7 @@ class SmartAppRPC: - def __init__( # noqa: WPS234 + def __init__( self, routers: List[RPCRouter], middlewares: Optional[List[Middleware]] = None, diff --git a/pyproject.toml b/pyproject.toml index df0d9ce..09a4fcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,12 +18,10 @@ fastapi = { version = "~0.115.1", optional = true } fastapi_utils = ["fastapi"] [tool.poetry.dev-dependencies] -add-trailing-comma = "^3.2.0" -black = "25.1.0" -isort = "6.0.1" -autoflake = "^2.3.1" -mypy = "1.17.1" -wemake-python-styleguide = "0.18.0" + +mypy = "1.16.1" +ruff = "0.12.0" + pytest = "^8.4.1" pytest-asyncio = "^1.1.0" diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..39816b5 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,38 @@ +target-version = "py310" +line-length = 88 +exclude = ["app/db/migrations"] # папки/файлы, которые Ruff вообще не читает + +[lint] +# группы правил +select = ["E", "F", "B", "S", "W", "I", "N", "Q"] +extend-select = ["B006", "B007"] + +ignore = [ + # Bugbear + "B008", + # pydocstyle + "D101", "D102", "D103", "D106", "D107", "D202", + # whitespace-совместимость c Black + "E203", + # Bandit + "S101", "S702", +] + +[lint.per-file-ignores] +"*/__init__.py" = ["D104"] +"app/bot/commands/*.py" = ["D104"] +"app/resources/strings.py" = ["E501"] +"tests/*" = ["D100"] +# alembic migrations +"*/migrations/*" = ["ALL"] +"*/env.py" = ["ALL"] +# ignore test functons missing return annotations +"*/test_*.py" = ["ANN201", "ANN202"] + +[lint.flake8-quotes] +inline-quotes = "double" +avoid-escape = true + +[lint.isort] +known-first-party = ["app"] +combine-as-imports = true # from x import y as z, w as q \ No newline at end of file diff --git a/scripts/format b/scripts/format index 6625ae6..5a5fc27 100755 --- a/scripts/format +++ b/scripts/format @@ -1,10 +1,17 @@ #!/usr/bin/env bash +set -euo pipefail -set -ex - -autoflake --recursive --in-place \ - --remove-all-unused-imports \ - --ignore-init-module-imports \ +# ------------------------------------------------------------ +# 1. Удаляем неиспользуемые импорты (F401/F841) и +# сортируем оставшиеся (I###) — «исправляем» только эти коды. +# ------------------------------------------------------------ +ruff check \ + --select F401,F841,I \ + --fix \ pybotx_smartapp_rpc tests -isort --profile black pybotx_smartapp_rpc tests -black pybotx_smartapp_rpc tests + +# ------------------------------------------------------------ +# 2. Применяем Black-совместимое форматирование +# (эквивалент: `black app tests`) +# ------------------------------------------------------------ +ruff format pybotx_smartapp_rpc tests \ No newline at end of file diff --git a/scripts/lint b/scripts/lint index c4d170e..623c9df 100755 --- a/scripts/lint +++ b/scripts/lint @@ -1,9 +1,20 @@ #!/usr/bin/env bash +set -euo pipefail -set -ex +# --------------------------------------------------------------- +# 1. Проверяем форматирование (equivalent to: black --check --diff) +# --diff → вывод изменений +# --check → не править файлы, вернуть ненулевой код, если найдены проблемы +# --------------------------------------------------------------- +ruff format --check --diff pybotx_smartapp_rpc tests -black --check --diff pybotx_smartapp_rpc tests -isort --profile black --check-only pybotx_smartapp_rpc tests +# --------------------------------------------------------------- +# 2. Запускаем все lint-правила (B, E, F, S, B*, W*, N*, Q*, ...) +# Конфигурация берётся из pyproject.toml / ruff.toml +# --------------------------------------------------------------- +ruff check pybotx_smartapp_rpc tests -mypy pybotx_smartapp_rpc tests -flake8 pybotx_smartapp_rpc tests +# --------------------------------------------------------------- +# 3. Type-checking +# --------------------------------------------------------------- +mypy --config-file=setup.cfg pybotx_smartapp_rpc tests diff --git a/scripts/test b/scripts/test index 743b0a6..3093c7f 100755 --- a/scripts/test +++ b/scripts/test @@ -1,5 +1,88 @@ #!/usr/bin/env bash +# +# scripts/test – удобный раннер для pytest. +# +# ┌───────────────────────────────┐ +# │ ПРИМЕРЫ │ +# ├───────────────────────────────┤ +# │ ./scripts/test │ → все тесты │ +# │ ./scripts/test --unit -q │ → только unit, тихо │ +# │ ./scripts/test --integ -k api │ → только integ, по кею │ +# │ ./scripts/test --unit --integ │ → unit + integ │ +# └───────────────────────────────┘ +# +# Если скрипт запустили не через bash (например, `sh scripts/test`), +# он перезапустит себя под bash, чтобы работали массивы. +# --------------------------------------------------------------------- -set -ex +# --- re-exec в bash, если нужно -------------------------------------- +if [ -z "${BASH_VERSION:-}" ]; then + exec bash "$0" "$@" +fi -pytest ${@} +set -euo pipefail + +show_help() { + cat < Image: _file_mimetype="image/png", _file_hash="Jd9r+OKpw5y+FSCg1xNTSUkwEo4nCW1Sn1AkotkOpH0=", ) + + +@pytest.fixture +def request_factory() -> Callable[[dict[str, str]], Request]: + def _make_request(headers: dict[str, str] | None = None) -> Request: + headers = headers or {} + # Starlette expects lowercase header names and bytes + raw_headers = [ + (k.lower().encode("utf-8"), v.encode("utf-8")) for k, v in headers.items() + ] + scope = { + "type": "http", + "http_version": "1.1", + "method": "GET", + "path": "/", + "query_string": b"", + "headers": raw_headers, + } + return Request(scope) + + return _make_request diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 6a89375..4810411 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -1,6 +1,7 @@ +from typing import Any + from deepdiff import DeepDiff from pydantic import BaseModel -from pydantic.v1.schema import get_model_name_map from pybotx_smartapp_rpc import ( RPCError, @@ -13,9 +14,11 @@ get_rpc_flat_models_from_routes, get_rpc_model_definitions, get_rpc_openapi_path, + update_fastapi_paths_by_rpc_router, ) from pybotx_smartapp_rpc.openapi.utils import ( deep_dict_update, + get_model_name_map, ) @@ -31,16 +34,16 @@ class Meta(BaseModel): user_id: int -class UserNotFound(RPCError): +class UserNotFoundError(RPCError): """Error description.""" - id: str = "UserNotFound" + id: str = "UserNotFoundError" reason: str = "User not found in system" meta: Meta -class OneUserNotFound(UserNotFound): - id: str = "OneUserNotFound" +class OneUserNotFoundError(UserNotFoundError): + id: str = "OneUserNotFoundError" class InvalidCredentialsError(RPCError): @@ -71,7 +74,7 @@ async def get_api_version(smartapp: SmartApp, args: Meta) -> RPCResultResponse[i flat_rpc_models = get_rpc_flat_models_from_routes(rpc) - assert flat_rpc_models == {Response, UserArgs} + assert flat_rpc_models == {Response, UserArgs} # type: ignore async def test_flat_models_with_return_type() -> None: @@ -83,7 +86,7 @@ async def hidden(smartapp: SmartApp, rpc_args: UserArgs) -> RPCResultResponse[Me flat_rpc_models = get_rpc_flat_models_from_routes(rpc) - assert flat_rpc_models == {Response, UserArgs} + assert flat_rpc_models == {Response, UserArgs} # type: ignore async def test_get_rpc_openapi_path__without_args() -> None: @@ -104,7 +107,7 @@ async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: security_scheme={"auth": []}, ) - expected_path = { + expected_path: dict[str, Any] = { "post": { "description": None, "operationId": "rpc_get_api_version", @@ -135,13 +138,13 @@ async def test_collect_rpc_method_exists__with_errors() -> None: # - Arrange - rpc = RPCRouter(tags=["rpc"], errors=[InvalidCredentialsError]) - @rpc.method("get_user", errors=[UserNotFound], tags=["user"]) + @rpc.method("get_user", errors=[UserNotFoundError], tags=["user"]) async def get_api_version( smartapp: SmartApp, rpc_args: UserArgs ) -> RPCResultResponse[int]: return RPCResultResponse(result=42) - smartapp_rpc = SmartAppRPC(routers=[rpc], errors=[OneUserNotFound]) + smartapp_rpc = SmartAppRPC(routers=[rpc], errors=[OneUserNotFoundError]) rpc_model_name_map = get_model_name_map( get_rpc_flat_models_from_routes(smartapp_rpc.router) ) @@ -179,18 +182,20 @@ async def get_api_version( } }, }, - "UserNotFound": { + "UserNotFoundError": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/UserNotFound"} + "schema": {"$ref": "#/components/schemas/UserNotFoundError"} } }, "description": "**Error**: Error description.", }, - "OneUserNotFound": { + "OneUserNotFoundError": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/OneUserNotFound"} + "schema": { + "$ref": "#/components/schemas/OneUserNotFoundError" + } } }, "description": "**Error**: User not found in system", @@ -209,126 +214,6 @@ async def get_api_version( } } - # expected_path = { - # "post": { - # "summary": "Get Api Version", - # "tags": ["rpc", "user"], - # "description": None, - # "operationId": "rpc_get_user", - # "requestBody": { - # "required": True, - # "content": { - # "application/json": { - # "schema": { - # "properties": {"id": {"title": "Id", "type": "integer"}}, - # "required": ["id"], - # "title": "UserArgs", - # "type": "object", - # } - # } - # }, - # }, - # "responses": { - # 200: { - # "description": "Successful response. **result** field:", - # "content": { - # "application/json": { - # "schema": {"title": "Response Get Api Version", - # "type": "integer"} - # } - # }, - # }, - # "UserNotFound": { - # "description": "**Error**: Error description.", - # "content": { - # "application/json": { - # "schema": { - # "$defs": { - # "Meta": { - # "properties": {"user_id": {"title": "User Id", - # "type": "integer"}}, - # "required": ["user_id"], - # "title": "Meta", - # "type": "object", - # } - # }, - # "description": "Error description.", - # "properties": { - # "reason": {"default": "User not found in system", - # "title": "Reason", "type": "string"}, - # "id": {"default": "UserNotFound", "title": "Id", - # "type": "string"}, - # "meta": {"$ref": "#/$defs/Meta"}, - # }, - # "required": ["meta"], - # "title": "UserNotFound", - # "type": "object", - # } - # } - # }, - # }, - # "OneUserNotFound": { - # "description": "**Error**: User not found in system", - # "content": { - # "application/json": { - # "schema": { - # "$defs": { - # "Meta": { - # "properties": {"user_id": {"title": "User Id", - # "type": "integer"}}, - # "required": ["user_id"], - # "title": "Meta", - # "type": "object", - # } - # }, - # "properties": { - # "reason": {"default": "User not found in system", - # "title": "Reason", "type": "string"}, - # "id": {"default": "OneUserNotFound", "title": "Id", - # "type": "string"}, - # "meta": {"$ref": "#/$defs/Meta"}, - # }, - # "required": ["meta"], - # "title": "OneUserNotFound", - # "type": "object", - # } - # } - # }, - # }, - # "InvalidCredentialsError": { - # "description": "**Error**: Invalid credentials", - # "content": { - # "application/json": { - # "schema": { - # "$defs": { - # "BaseModel": {"properties": {}, - # "title": "BaseModel", - # "type": "object"} - # }, - # "properties": { - # "reason": {"default": "Invalid credentials", - # "title": "Reason", "type": "string"}, - # "id": {"default": "InvalidCredentialsError", - # "title": "Id", "type": "string"}, - # "meta": { - # "anyOf": [ - # {"additionalProperties": True, - # "type": "object"}, - # {"$ref": "#/$defs/BaseModel"}, - # ], - # "title": "Meta", - # }, - # }, - # "title": "InvalidCredentialsError", - # "type": "object", - # } - # } - # }, - # }, - # }, - # }, - # } - diff = DeepDiff(expected_path, path, ignore_order=True) assert not diff, diff @@ -337,7 +222,7 @@ async def test_get_rpc_model_definition() -> None: # - Arrange - rpc = RPCRouter(tags=["rpc"]) - @rpc.method("get_user", errors=[UserNotFound], tags=["user"]) + @rpc.method("get_user", errors=[UserNotFoundError], tags=["user"]) async def get_api_version( smartapp: SmartApp, rpc_args: UserArgs ) -> RPCResultResponse[int]: @@ -366,22 +251,138 @@ async def get_api_version( "title": "UserArgs", "type": "object", }, - "UserNotFound": { + "UserNotFoundError": { "description": "Error description.", "properties": { - "id": {"default": "UserNotFound", "title": "Id", "type": "string"}, + "id": {"default": "UserNotFoundError", "title": "Id", "type": "string"}, "meta": {"$ref": "#/components/schemas/Meta"}, "reason": { - "default": "User not found in " "system", + "default": "User not found in system", "title": "Reason", "type": "string", }, }, "required": ["meta"], - "title": "UserNotFound", + "title": "UserNotFoundError", "type": "object", }, } diff = DeepDiff(expected_definitions, rpc_definitions, ignore_order=True) assert not diff, diff + + +def test_update_fastapi_paths_adds_security_schemas_and_paths(): + # Arrange + rpc = RPCRouter(tags=["rpc"]) + + @rpc.method("get_user", errors=[UserNotFoundError], tags=["user"]) + async def get_user(smartapp: SmartApp, args: UserArgs) -> RPCResultResponse[int]: + return RPCResultResponse(result=1) + + # pre-existing OpenAPI dict with unrelated path to ensure non-destructive merge + openapi_dict = { + "openapi": "3.1.0", + "info": {"title": "Test API", "version": "1.0.0"}, + "paths": {"/health": {"get": {"responses": {"200": {"description": "ok"}}}}}, + "components": {"schemas": {"Existing": {"type": "object"}}}, + } + + security_definitions = { + "RPC Auth": { + "type": "apiKey", + "in": "header", + "name": "X-RPC-AUTH", + "description": "...", + } + } + operation_security = {"RPC Auth": []} + + # Act + update_fastapi_paths_by_rpc_router( + openapi_dict, + rpc_router=rpc, + security_definitions=security_definitions, + operation_security=operation_security, + ) + + # Assert: security schemes merged + assert "components" in openapi_dict + assert "securitySchemes" in openapi_dict["components"] + assert openapi_dict["components"]["securitySchemes"]["RPC Auth"]["type"] == "apiKey" + + # Assert: new RPC path exists and has POST operation with security + assert "/get_user" in openapi_dict["paths"] + post_op = openapi_dict["paths"]["/get_user"]["post"] + assert post_op["operationId"] == "rpc_get_user" + assert {"RPC Auth": []} in post_op.get("security", []) + + # Request body should be present (because of args) + assert post_op["requestBody"]["content"]["application/json"] + + # Responses include success and error codes by id + responses = post_op["responses"] + assert "200" in { + str(k) for k in responses.keys() + } # numeric key 200 serialized later + assert "UserNotFoundError" in responses + # Error references use $ref to components/schemas + err_schema = responses["UserNotFoundError"]["content"]["application/json"]["schema"] + assert err_schema == {"$ref": "#/components/schemas/UserNotFoundError"} + + # Assert: schemas got updated with our models, while preserving existing schema + schemas = openapi_dict["components"]["schemas"] + assert "Existing" in schemas # preserved + # UserArgs schema present + assert "UserArgs" in schemas + # UserNotFound schema present and description trimmed + assert "UserNotFoundError" in schemas + assert schemas["UserNotFoundError"]["title"] == "UserNotFoundError" + + +def test_update_fastapi_paths_skips_hidden_and_handles_no_definitions(): + # Arrange + rpc = RPCRouter() + + # method with primitive response, no args, included + @rpc.method("get_api_version") + async def get_api_version(smartapp: SmartApp) -> RPCResultResponse[int]: + return RPCResultResponse(result=1) + + # method hidden from schema + @rpc.method("__hidden_method", include_in_schema=False) + async def hidden(smartapp: SmartApp, args: UserArgs) -> RPCResultResponse[int]: + return RPCResultResponse(result=1) + + openapi_dict = {"openapi": "3.1.0", "info": {"title": "T", "version": "1"}} + + # Act + update_fastapi_paths_by_rpc_router(openapi_dict, rpc) + + # Assert: only visible method path is added + assert "/get_api_version" in openapi_dict["paths"] + assert "/__hidden_method" not in openapi_dict["paths"] + + # Because only primitive types are involved, there should be no schemas added + # The function adds schemas only if there are rpc_definitions + components = openapi_dict.get("components", {}) + assert "schemas" not in components or components.get("schemas") == {} + + +def test_update_fastapi_paths_merges_into_existing_empty_sections_gracefully(): + # Arrange: empty containers + rpc = RPCRouter() + + @rpc.method("ping") + async def ping(smartapp: SmartApp) -> RPCResultResponse[str]: + return RPCResultResponse(result="pong") + + # Existing dict without components/paths + openapi_dict = {"openapi": "3.1.0", "info": {"title": "X", "version": "1"}} + + # Act + update_fastapi_paths_by_rpc_router(openapi_dict, rpc) + + # Assert minimal structure created and path added + assert "paths" in openapi_dict + assert "/ping" in openapi_dict["paths"] diff --git a/tests/test_rpc_calls.py b/tests/test_rpc_calls.py index 2352c00..61a897c 100644 --- a/tests/test_rpc_calls.py +++ b/tests/test_rpc_calls.py @@ -111,7 +111,8 @@ async def sum_handler(smartapp: SmartApp, args: SumArgs) -> RPCResultResponse[in "status": "error", "errors": [ { - "reason": "Input should be a valid integer, unable to parse string as an integer", + "reason": "Input should be a valid integer, " + "unable to parse string as an integer", "id": "INT_PARSING", "meta": {"location": ("first",)}, }, @@ -473,7 +474,8 @@ async def sum_handler(smartapp: SmartApp, args: SumArgs) -> RPCResultResponse[in expected_response = BotAPISyncSmartAppEventErrorResponse.from_domain( errors=[ { - "reason": "Input should be a valid integer, unable to parse string as an integer", + "reason": "Input should be a valid integer, " + "unable to parse string as an integer", "id": "INT_PARSING", "meta": {"location": ("first",)}, }, diff --git a/tests/test_security.py b/tests/test_security.py new file mode 100644 index 0000000..414f72e --- /dev/null +++ b/tests/test_security.py @@ -0,0 +1,108 @@ +from uuid import UUID, uuid4 + +import pytest +from fastapi import HTTPException +from starlette.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN + +from pybotx_smartapp_rpc.fastapi_utils.security import RPCAuth + + +@pytest.mark.asyncio +async def test_rpcauth_with_full_header_parses_values(request_factory): + # Arrange + default_bot_id = uuid4() # shouldn't be used when provided in header + ra = RPCAuth(bot_id=default_bot_id) + + bot_id = uuid4() + sender_huid = uuid4() + sender_udid = uuid4() + chat_id = uuid4() + + header_value = ( + f"bot_id={bot_id}&sender_huid={sender_huid}" + f"&sender_udid={sender_udid}&chat_id={chat_id}" + ) + req = request_factory({"X-RPC-AUTH": header_value}) + + # Act + cfg = await ra(req) + + # Assert + assert cfg.bot_id == bot_id + assert cfg.sender_huid == sender_huid + assert cfg.sender_udid == sender_udid + assert cfg.chat_id == chat_id + + +@pytest.mark.asyncio +async def test_rpcauth_with_invalid_format_raises_403(request_factory): + # Arrange + ra = RPCAuth(bot_id=uuid4()) + # Invalid format: no key=value pairs + req = request_factory({"X-RPC-AUTH": "not-a-key-value-pair"}) + + # Act / Assert + with pytest.raises(HTTPException) as ei: + await ra(req) + + exc = ei.value + assert exc.status_code == HTTP_403_FORBIDDEN + assert exc.detail == "Invalid RPC Auth format" + + +@pytest.mark.asyncio +async def test_rpcauth_with_invalid_uuid_values_raises_403(request_factory): + # Arrange + ra = RPCAuth(bot_id=uuid4()) + + # bot_id invalid UUID + req = request_factory({"X-RPC-AUTH": "bot_id=not-a-uuid"}) + + # Act / Assert + with pytest.raises(HTTPException) as ei: + await ra(req) + + exc = ei.value + assert exc.status_code == HTTP_400_BAD_REQUEST + + +@pytest.mark.asyncio +async def test_rpcauth_header_without_bot_id_uses_default_from_constructor( + request_factory, +): + # This test documents the intended behavior that if the header is present but + # bot_id is not specified, + # the RPCAuth should fall back to the configured default bot_id. + + # Arrange + default_bot_id = uuid4() + ra = RPCAuth(bot_id=default_bot_id) + + sender_huid = uuid4() + header_value = f"sender_huid={sender_huid}" + req = request_factory({"X-RPC-AUTH": header_value}) + + # Act + cfg = await ra(req) + + # Assert + assert cfg.bot_id == default_bot_id # expected fallback + assert cfg.sender_huid == sender_huid + # Others should be auto-filled UUIDs + assert isinstance(cfg.sender_udid, UUID) + assert isinstance(cfg.chat_id, UUID) + + +@pytest.mark.asyncio +async def test_rpcauth_with_empty_bot_id_in_header_raises_400(request_factory): + ra = RPCAuth(bot_id=uuid4()) + # bot_id is present but empty -> should NOT fallback, should raise 400 + req = request_factory( + {"X-RPC-AUTH": "bot_id=&sender_huid=00000000-0000-0000-0000-000000000000"} + ) + + with pytest.raises(HTTPException) as ei: + await ra(req) + + exc = ei.value + assert exc.status_code == HTTP_400_BAD_REQUEST From fe177034fe83b553ce81bd211eeccafe418c0216 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 23:35:08 +0300 Subject: [PATCH 10/31] lints passed, fix some typos --- README.md | 85 ++++++----------------- pybotx_smartapp_rpc/models/model_field.py | 10 --- pybotx_smartapp_rpc/openapi/openapi.py | 4 -- setup.cfg | 2 +- tests/test_openapi.py | 53 ++++++++++++++ 5 files changed, 76 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index a455366..9da9f90 100644 --- a/README.md +++ b/README.md @@ -187,79 +187,38 @@ smartapp = SmartAppRPC(..., exception_handlers={KeyError: key_error_handler}) ### Swagger documentation Можно подключить rpc роутеры к авто генерируемой документации FastAPI и использовать документацию в Swagger. Для этого необходимо переопределить функцию для генерации -OpenAPI схемы: +OpenAPI схемы. + +Для этого необходимо: +1. Установить данную библиотеку с подключенной опцией +["fastapi_utils"]: +``` +pybotx-smartapp-rpc = {version, extras = ["fastapi_utils"]} +``` +2. При создании приложения подключить метод из библиотеки: ```python from fastapi import FastAPI - +from pybotx_smartapp_rpc.fastapi_utils.custom_openapi import rpc_openapi +from pybotx_smartapp_rpc.fastapi_utils.security import RPCAuth application = FastAPI() -def get_custom_openapi(): - return custom_openapi( - title="Smartapp API", +security = RPCAuth( + bot_id=bot_id_from_credentials +) + +def get_custom_openapi(): + return rpc_openapi( + title="Project Name", version="0.1.0", fastapi_routes=application.routes, - rpc_router=smartapp.router, + rpc_router=your_rpc_router, openapi_version="3.0.2", + security=security ) -application.openapi = get_custom_openapi -``` - -Пример функции `custom_openapi`: - -```python -from fastapi.encoders import jsonable_encoder -from fastapi.openapi.models import OpenAPI -from fastapi.openapi.utils import get_openapi -from pybotx_smartapp_rpc import RPCRouter -from pybotx_smartapp_rpc.openapi.old.openapi_utils import * -from pydantic.schema import get_model_name_map -from starlette.routing import BaseRoute - - -def custom_openapi( - *, - title: str, - version: str, - fastapi_routes: Sequence[BaseRoute], - rpc_router: RPCRouter, - **kwargs: Any, -) -> Dict[str, Any]: - openapi_dict = get_openapi( - title=title, - version=version, - routes=fastapi_routes, - **kwargs, - ) - - paths: Dict[str, Dict[str, Any]] = {} - - flat_rpc_models = get_rpc_flat_models_from_routes(rpc_router) - rpc_model_name_map = get_model_name_map(flat_rpc_models) - rpc_definitions = get_rpc_model_definitions( - flat_models=flat_rpc_models, model_name_map=rpc_model_name_map - ) - - for method_name in rpc_router.rpc_methods.keys(): - if not rpc_router.rpc_methods[method_name].include_in_schema: - continue + application.openapi = get_custom_openapi # type: ignore - path = get_rpc_openapi_path( # type: ignore - method_name=method_name, - route=rpc_router.rpc_methods[method_name], - model_name_map=rpc_model_name_map, - ) - if path: - paths.setdefault(f"/{method_name}", {}).update(path) - - if rpc_definitions: - openapi_dict.setdefault("components", {}).setdefault("schemas", {}).update( - {k: rpc_definitions[k] for k in sorted(rpc_definitions)} - ) - - openapi_dict.setdefault("paths", {}).update(paths) - - return jsonable_encoder(OpenAPI(**openapi_dict), by_alias=True, exclude_none=True) ``` + ### Возможности RPC Swagger * Можно добавлять теги к запросам, анaлогично FastAPI. diff --git a/pybotx_smartapp_rpc/models/model_field.py b/pybotx_smartapp_rpc/models/model_field.py index 1176ed2..deb76f4 100644 --- a/pybotx_smartapp_rpc/models/model_field.py +++ b/pybotx_smartapp_rpc/models/model_field.py @@ -11,11 +11,6 @@ class ModelField: field_info: FieldInfo name: str - @property - def alias(self) -> str: - a = self.field_info.alias - return a if a is not None else self.name - @property def required(self) -> bool: return self.field_info.is_required() @@ -28,11 +23,6 @@ def default(self) -> Any: def type_(self) -> Any: return self.field_info.annotation - def __post_init__(self) -> None: - self._type_adapter: TypeAdapter[Any] = TypeAdapter( - Annotated[self.field_info.annotation, self.field_info] - ) - def get_default(self) -> Any: if self.field_info.is_required(): return Undefined diff --git a/pybotx_smartapp_rpc/openapi/openapi.py b/pybotx_smartapp_rpc/openapi/openapi.py index d72a7b5..1d45680 100644 --- a/pybotx_smartapp_rpc/openapi/openapi.py +++ b/pybotx_smartapp_rpc/openapi/openapi.py @@ -68,10 +68,6 @@ def get_rpc_model_definitions( for model in flat_models: if isinstance(model, type) and issubclass(model, BaseModel): m_schema = model.model_json_schema(ref_template=REF_PREFIX + "{model}") - elif isinstance(model, type) and issubclass(model, Enum): - m_schema = TypeAdapter(model).json_schema( - ref_template=REF_PREFIX + "{model}" - ) else: m_schema = TypeAdapter(model).json_schema( ref_template=REF_PREFIX + "{model}" diff --git a/setup.cfg b/setup.cfg index c5cf4f3..5af40de 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ addopts = --cov-report term-missing [coverage:run] -concurrency = thread,greenlet +concurrency = thread [coverage:report] precision = 2 diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 4810411..3f7d95b 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -1,5 +1,7 @@ +from enum import Enum from typing import Any +import pytest from deepdiff import DeepDiff from pydantic import BaseModel @@ -34,6 +36,20 @@ class Meta(BaseModel): user_id: int +class Color(str, Enum): + RED = "RED" + GREEN = "GREEN" + + +class Status(Enum): + OK = 1 + FAIL = 2 + + +class Item(BaseModel): + color: Color + + class UserNotFoundError(RPCError): """Error description.""" @@ -386,3 +402,40 @@ async def ping(smartapp: SmartApp) -> RPCResultResponse[str]: # Assert minimal structure created and path added assert "paths" in openapi_dict assert "/ping" in openapi_dict["paths"] + + + +@pytest.mark.parametrize( + "enum_cls, expected_type, expected_values", + [ + (Color, "string", ["RED", "GREEN"]), + (Status, "integer", [1, 2]), + ], +) +def test_openapi_definitions_for_enums(enum_cls, expected_type, expected_values): + defs = get_rpc_model_definitions( + flat_models={enum_cls}, + model_name_map={enum_cls: enum_cls.__name__}, + ) + + enum_schema = defs[enum_cls.__name__] + assert enum_schema["enum"] == expected_values + assert enum_schema["type"] == expected_type + + +def test_openapi_definitions_for_model_with_enum_field(): + defs = get_rpc_model_definitions( + flat_models={Item, Color}, + model_name_map={Item: "Item", Color: "Color"}, + ) + + assert "Color" in defs + assert "Item" in defs + + item_schema = defs["Item"] + assert item_schema["type"] == "object" + props = item_schema["properties"] + assert "color" in props + # Убедимся, что поле color ссылается на определение Color + assert "$ref" in props["color"] + assert props["color"]["$ref"].endswith("/Color") From 79ede53f4f874bb0acf2b6a6300b1f08804dd025 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 23:45:13 +0300 Subject: [PATCH 11/31] fix github actions --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 5b4639d..11878f9 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Setup dependencies - uses: ExpressApp/github-actions-poetry@v0.3 + uses: ExpressApp/github-actions-poetry@v0.4 with: python-version: ${{ matrix.python-version }} poetry-version: "2.1.4" From ff74315ae4259a3f63cf8fe0db1243f85eda3836 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 23:54:51 +0300 Subject: [PATCH 12/31] fix github actions --- .github/workflows/actions.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 11878f9..6b2781c 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -16,6 +16,9 @@ jobs: python-version: ${{ matrix.python-version }} poetry-version: "2.1.4" + - name: Install dependencies with fastapi + run: poetry install --with fastapi_utils + - name: Run tests run: | poetry run ./scripts/test @@ -30,11 +33,14 @@ jobs: steps: - name: Setup dependencies - uses: ExpressApp/github-actions-poetry@v0.3 + uses: ExpressApp/github-actions-poetry@v0.4 with: python-version: ${{ matrix.python-version }} poetry-version: "2.1.4" + - name: Install dependencies with fastapi + run: poetry install --with fastapi_utils + - name: Run lint run: | poetry run ./scripts/lint From 910a2d6e8d0c195fc7ea9396f02e66aeca7a35dd Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 23:56:58 +0300 Subject: [PATCH 13/31] fix github actions --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 6b2781c..c51db90 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -17,7 +17,7 @@ jobs: poetry-version: "2.1.4" - name: Install dependencies with fastapi - run: poetry install --with fastapi_utils + run: poetry install --extras fastapi_utils - name: Run tests run: | @@ -39,7 +39,7 @@ jobs: poetry-version: "2.1.4" - name: Install dependencies with fastapi - run: poetry install --with fastapi_utils + run: poetry install --extras fastapi_utils - name: Run lint run: | From 998784cfad6fa85ee4dd1a4e3b5127968586266a Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Tue, 19 Aug 2025 23:59:45 +0300 Subject: [PATCH 14/31] fix lint --- pybotx_smartapp_rpc/models/model_field.py | 3 +-- pybotx_smartapp_rpc/openapi/utils.py | 2 +- tests/test_openapi.py | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pybotx_smartapp_rpc/models/model_field.py b/pybotx_smartapp_rpc/models/model_field.py index deb76f4..e29590b 100644 --- a/pybotx_smartapp_rpc/models/model_field.py +++ b/pybotx_smartapp_rpc/models/model_field.py @@ -1,7 +1,6 @@ from dataclasses import dataclass -from typing import Annotated, Any +from typing import Any -from pydantic import TypeAdapter from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined as Undefined diff --git a/pybotx_smartapp_rpc/openapi/utils.py b/pybotx_smartapp_rpc/openapi/utils.py index dd9e2c9..3600934 100644 --- a/pybotx_smartapp_rpc/openapi/utils.py +++ b/pybotx_smartapp_rpc/openapi/utils.py @@ -113,7 +113,7 @@ def deep_dict_update( def get_schema_or_ref( model: ModelField, - model_name_map: dict[type[BaseModel | Enum], str], + model_name_map: dict[type[Union[BaseModel | Enum]], str], ref_prefix: str, ) -> dict: if model_name := model_name_map.get(model.type_): diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 3f7d95b..615497d 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -404,7 +404,6 @@ async def ping(smartapp: SmartApp) -> RPCResultResponse[str]: assert "/ping" in openapi_dict["paths"] - @pytest.mark.parametrize( "enum_cls, expected_type, expected_values", [ From b3061067872b30eafba38553329a09cd2dd96c30 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:00:58 +0300 Subject: [PATCH 15/31] fix lint --- pybotx_smartapp_rpc/openapi/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybotx_smartapp_rpc/openapi/utils.py b/pybotx_smartapp_rpc/openapi/utils.py index 3600934..ac2eac6 100644 --- a/pybotx_smartapp_rpc/openapi/utils.py +++ b/pybotx_smartapp_rpc/openapi/utils.py @@ -113,7 +113,7 @@ def deep_dict_update( def get_schema_or_ref( model: ModelField, - model_name_map: dict[type[Union[BaseModel | Enum]], str], + model_name_map: dict[type[Union[BaseModel , Enum]], str], ref_prefix: str, ) -> dict: if model_name := model_name_map.get(model.type_): From ac959bbc0c80ff02d4ab56a09ad953880c6c53e5 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:01:15 +0300 Subject: [PATCH 16/31] fix lint --- pybotx_smartapp_rpc/openapi/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybotx_smartapp_rpc/openapi/utils.py b/pybotx_smartapp_rpc/openapi/utils.py index ac2eac6..e608e0e 100644 --- a/pybotx_smartapp_rpc/openapi/utils.py +++ b/pybotx_smartapp_rpc/openapi/utils.py @@ -113,7 +113,7 @@ def deep_dict_update( def get_schema_or_ref( model: ModelField, - model_name_map: dict[type[Union[BaseModel , Enum]], str], + model_name_map: dict[type[Union[BaseModel, Enum]], str], ref_prefix: str, ) -> dict: if model_name := model_name_map.get(model.type_): From 4fdb58e9df30db15d61ba9c66e491be01f48e96d Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:04:03 +0300 Subject: [PATCH 17/31] fix lint --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 25396cb..c957a2d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Dict, Optional, Union from unittest.mock import AsyncMock from uuid import UUID, uuid4 @@ -134,7 +134,7 @@ def image() -> Image: @pytest.fixture def request_factory() -> Callable[[dict[str, str]], Request]: - def _make_request(headers: dict[str, str] | None = None) -> Request: + def _make_request(headers: Union[dict[str, str], None] = None) -> Request: headers = headers or {} # Starlette expects lowercase header names and bytes raw_headers = [ From 550235893896097139271353d9742a5fd49acbd5 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:08:09 +0300 Subject: [PATCH 18/31] fix lint --- pybotx_smartapp_rpc/openapi/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybotx_smartapp_rpc/openapi/openapi.py b/pybotx_smartapp_rpc/openapi/openapi.py index 1d45680..b34db04 100644 --- a/pybotx_smartapp_rpc/openapi/openapi.py +++ b/pybotx_smartapp_rpc/openapi/openapi.py @@ -14,7 +14,7 @@ ) REF_PREFIX = "#/components/schemas/" -ModelNameMap: type[dict[type[BaseModel | Enum], str]] = Dict[ +ModelNameMap: type[dict[type[Union[BaseModel | Enum]], str]] = Dict[ Union[Type[BaseModel], Type[Enum]], str ] From 3deac5dd42814c1cb2cc43f18d7b15de0530ced1 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:11:37 +0300 Subject: [PATCH 19/31] fix lint --- pybotx_smartapp_rpc/openapi/openapi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pybotx_smartapp_rpc/openapi/openapi.py b/pybotx_smartapp_rpc/openapi/openapi.py index b34db04..d748c5d 100644 --- a/pybotx_smartapp_rpc/openapi/openapi.py +++ b/pybotx_smartapp_rpc/openapi/openapi.py @@ -14,7 +14,7 @@ ) REF_PREFIX = "#/components/schemas/" -ModelNameMap: type[dict[type[Union[BaseModel | Enum]], str]] = Dict[ +ModelNameMap: type[dict[type[Union[BaseModel, Enum]], str]] = Dict[ Union[Type[BaseModel], Type[Enum]], str ] @@ -60,8 +60,8 @@ def update_fastapi_paths_by_rpc_router( def get_rpc_model_definitions( *, - flat_models: set[type[BaseModel | Enum]], - model_name_map: dict[type[BaseModel | Enum], str], + flat_models: set[type[Union[BaseModel, Enum]]], + model_name_map: dict[type[Union[BaseModel, Enum]], str], ) -> dict[str, Any]: definitions: dict[str, dict[str, Any]] = {} From 25c3ff6bcc781a800f4e660973fecc9c6cfc72e9 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:24:11 +0300 Subject: [PATCH 20/31] fix lint --- poetry.lock | 2 +- pybotx_smartapp_rpc/models/responses.py | 2 +- pybotx_smartapp_rpc/router.py | 2 +- pyproject.toml | 4 ++-- setup.cfg | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7e2417b..038d8d3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -888,4 +888,4 @@ fastapi-utils = ["fastapi"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.14" -content-hash = "dce23d3a4f6d764d8b77adab3373466629f5bfe39e13e531c706a83940852a91" +content-hash = "5b6d55bb91483725142e08075e3510fd2c153be87a836d5756d4ac67825f07bb" diff --git a/pybotx_smartapp_rpc/models/responses.py b/pybotx_smartapp_rpc/models/responses.py index 9eb7a30..377461c 100644 --- a/pybotx_smartapp_rpc/models/responses.py +++ b/pybotx_smartapp_rpc/models/responses.py @@ -52,7 +52,7 @@ def jsonable_dict(self) -> Dict[str, Any]: } def jsonable_errors(self) -> List[Dict[str, Any]]: - return [error.dict() for error in self.errors] + return [error.model_dump() for error in self.errors] def build_invalid_rpc_request_error_response( diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index 3800a9a..e8fa32e 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union -from pydantic.error_wrappers import ValidationError +from pydantic import ValidationError from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined diff --git a/pyproject.toml b/pyproject.toml index 09a4fcd..39b3a4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pybotx-smartapp-rpc" -version = "0.11.0" +version = "0.12.0" description = "eXpress SmartApp JSON-RPC library" authors = ["Arseniy Zhiltsov "] readme = "README.md" @@ -17,7 +17,7 @@ fastapi = { version = "~0.115.1", optional = true } [tool.poetry.extras] fastapi_utils = ["fastapi"] -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] mypy = "1.16.1" ruff = "0.12.0" diff --git a/setup.cfg b/setup.cfg index 5af40de..82eef36 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ testpaths = tests asyncio_mode = auto addopts = - --cov=app + --cov=pybotx_smartapp_rpc --no-cov-on-fail --cov-report term-missing From 5cf6652ee53220212ccb49578576dc90eeb7461a Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:26:27 +0300 Subject: [PATCH 21/31] fix lint --- pybotx_smartapp_rpc/models/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybotx_smartapp_rpc/models/responses.py b/pybotx_smartapp_rpc/models/responses.py index 377461c..247d466 100644 --- a/pybotx_smartapp_rpc/models/responses.py +++ b/pybotx_smartapp_rpc/models/responses.py @@ -3,7 +3,7 @@ from pybotx import File from pydantic import BaseModel -from pydantic.error_wrappers import ValidationError +from pydantic import ValidationError from pybotx_smartapp_rpc.models.errors import RPCError From e1f05162df3485d994ed92044a4233d45dc5edac Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:27:55 +0300 Subject: [PATCH 22/31] fix lint --- pybotx_smartapp_rpc/models/responses.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pybotx_smartapp_rpc/models/responses.py b/pybotx_smartapp_rpc/models/responses.py index 247d466..856732a 100644 --- a/pybotx_smartapp_rpc/models/responses.py +++ b/pybotx_smartapp_rpc/models/responses.py @@ -2,8 +2,7 @@ from typing import Any, Dict, Generic, List, TypeVar, Union from pybotx import File -from pydantic import BaseModel -from pydantic import ValidationError +from pydantic import BaseModel, ValidationError from pybotx_smartapp_rpc.models.errors import RPCError From 9ca68104550bac29a25d2e1c047efa56ac578cba Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:31:13 +0300 Subject: [PATCH 23/31] fix lint --- .github/workflows/publish-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index da5dd6d..0d83c24 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -11,6 +11,6 @@ jobs: - name: Assert tag is from master run: git branch -a --contains $(git describe --tags) | grep master - name: Build and publish to pypi - uses: JRubics/poetry-publish@v1.8 + uses: JRubics/poetry-publish@v2.1 with: pypi_token: ${{ secrets.PYPI_TOKEN }} From ef0ff2d5710b974edf8cf1f6c792891353878d87 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:33:51 +0300 Subject: [PATCH 24/31] fix lint --- pybotx_smartapp_rpc/router.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index e8fa32e..1f2a6fe 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -116,6 +116,19 @@ def include_router(self, router: "RPCRouter") -> None: rpc_method.errors_models = {**errors_models, **rpc_method.errors_models} self.rpc_methods[rpc_method_name] = rpc_method + @staticmethod + def _create_model_field( + name: str, + type_: Any, + default: Optional[Any] = PydanticUndefined, + field_info: Optional[FieldInfo] = None, + alias: Optional[str] = None, + ) -> ModelField: + field_info = field_info or FieldInfo( + annotation=type_, default=default, alias=alias + ) + return ModelField(name=name, field_info=field_info) + def _get_handler_request_argument_model( self, handler_signature: inspect.Signature ) -> Optional[ModelField]: @@ -123,7 +136,7 @@ def _get_handler_request_argument_model( arg[1].annotation for arg in handler_signature.parameters.items() ] if len(args_annotations) >= 2: - return self.create_model_field( + return self._create_model_field( name=str(args_annotations[1].__name__), type_=args_annotations[1], ) @@ -144,7 +157,7 @@ def _get_handler_response_model( else: response_type = None - return self.create_model_field( + return self._create_model_field( name=name, type_=response_type, ) @@ -180,7 +193,7 @@ def _get_error_fields_and_models( if error.model_fields["id"].default } errors_models = { - error.model_fields["id"].default: self.create_model_field( + error.model_fields["id"].default: self._create_model_field( name=error.__name__, type_=error ) for error in errors @@ -188,16 +201,3 @@ def _get_error_fields_and_models( } return errors_fields, errors_models - - @staticmethod - def create_model_field( - name: str, - type_: Any, - default: Optional[Any] = PydanticUndefined, - field_info: Optional[FieldInfo] = None, - alias: Optional[str] = None, - ) -> ModelField: - field_info = field_info or FieldInfo( - annotation=type_, default=default, alias=alias - ) - return ModelField(name=name, field_info=field_info) From c98dd0d328dfd17c5cdda6f45e405635465820d5 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 00:55:30 +0300 Subject: [PATCH 25/31] add some docs --- .../fastapi_utils/custom_openapi.py | 22 +++++++++++ pybotx_smartapp_rpc/fastapi_utils/security.py | 19 ++++++++++ .../middlewares/empty_args_middleware.py | 11 ++++++ .../middlewares/exception_middleware.py | 10 +++++ pybotx_smartapp_rpc/router.py | 37 +++++++++++++++++++ pybotx_smartapp_rpc/rpc.py | 16 ++++++++ pybotx_smartapp_rpc/smartapp.py | 19 ++++++++++ 7 files changed, 134 insertions(+) diff --git a/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py b/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py index 0beb2e1..0ef25ac 100644 --- a/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py +++ b/pybotx_smartapp_rpc/fastapi_utils/custom_openapi.py @@ -22,6 +22,28 @@ def rpc_openapi( security: RPCAuth, **kwargs: Any, ) -> dict[str, Any]: + """ + Generates an OpenAPI dictionary for FastAPI routes combined with RPC routes. + + This function integrates a traditional FastAPI route setup with an RPC-based routing + mechanism, generating a single OpenAPI schema definition. + It also constructs security definitions and integrates them into the resulting + OpenAPI schema. + + Args: + title: Title of the OpenAPI schema document. + version: Version of the API described by the schema. + fastapi_routes: List of FastAPI routes to include in the schema. + rpc_router: Router containing RPC route definitions. + security: Security component describing authentication mechanisms. + **kwargs: Additional keyword arguments to pass to the OpenAPI generation. + + Returns: + dict[str, Any]: A dictionary representing the OpenAPI schema. + + Raises: + ValueError: If the given arguments are invalid. + """ openapi_dict = get_openapi( title=title, version=version, diff --git a/pybotx_smartapp_rpc/fastapi_utils/security.py b/pybotx_smartapp_rpc/fastapi_utils/security.py index e269de5..9c48ff9 100644 --- a/pybotx_smartapp_rpc/fastapi_utils/security.py +++ b/pybotx_smartapp_rpc/fastapi_utils/security.py @@ -44,6 +44,25 @@ class RPCAuthConfig(BaseModel): class RPCAuth(APIKeyHeader): + """ + Handles RPC Authentication implementation via custom API key headers. + + This class extends `APIKeyHeader` to enable a mechanism for validating and + parsing RPC API authentication headers. It extracts authentication parameters + from the provided API key in the request headers, validates their format, and + returns a properly configured instance of `RPCAuthConfig`. The `bot_id` is + included as a mandatory attribute for any authentication config. + + Attributes: + PATTERN: str + Regex pattern used to extract key-value pairs from the API key header. + + Methods: + __call__(request: Request) -> RPCAuthConfig + Parses and validates the API key from the incoming request, returning + the RPC authentication config or raising an appropriate HTTP exception. + """ + PATTERN = "([^?=&]+)=([^&]*)" def __init__( diff --git a/pybotx_smartapp_rpc/middlewares/empty_args_middleware.py b/pybotx_smartapp_rpc/middlewares/empty_args_middleware.py index a56f9b8..19be612 100644 --- a/pybotx_smartapp_rpc/middlewares/empty_args_middleware.py +++ b/pybotx_smartapp_rpc/middlewares/empty_args_middleware.py @@ -16,6 +16,17 @@ async def empty_args_middleware( rpc_arguments: RPCArgsBaseModel, call_next: Handler, ) -> RPCResponse: + """ + Middleware function to handle RPC arguments and route the call accordingly based on + whether the arguments are of type `EmptyArgs` or not. + + :param smartapp: An instance of `SmartApp` representing. + :param rpc_arguments: An instance of `RPCArgsBaseModel` containing the RPC arguments + to be validated and passed to the next handler. + :param call_next: A handler function that takes either `smartapp` alone or both + `smartapp` and `rpc_arguments`, determined by the type of the RPC arguments. + :return: An `RPCResponse` object resulting from calling the appropriate handler. + """ if isinstance(rpc_arguments, EmptyArgs): call_next = cast(HandlerWithoutArgs, call_next) return await call_next(smartapp) diff --git a/pybotx_smartapp_rpc/middlewares/exception_middleware.py b/pybotx_smartapp_rpc/middlewares/exception_middleware.py index 605ae65..ebe963d 100644 --- a/pybotx_smartapp_rpc/middlewares/exception_middleware.py +++ b/pybotx_smartapp_rpc/middlewares/exception_middleware.py @@ -12,6 +12,16 @@ class ExceptionMiddleware: + """ + Middleware for handling exceptions during the execution of a request. + + This class allows defining custom exception handlers for different types of + exceptions that might occur during the execution of a specific request. + + :ivar _exception_handlers: A dictionary mapping exception types to their + corresponding handlers. Each handler should be a coroutine function. + """ + def __init__( self, exception_handlers: Optional[ExceptionHandlerDict] = None, diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index 1f2a6fe..33e3a72 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -22,6 +22,27 @@ class RPCRouter: + """ + Manages the registration, execution, and handling of Remote Procedure Calls (RPCs). + + This class is designed to facilitate the creation of RPC methods with support for + middlewares, error handling, and schema inclusion. Developers can utilize this class + to register RPC methods, include other routers, and handle incoming RPC requests. + + :ivar rpc_methods: Dictionary containing registered RPC method names as keys and + theircorresponding RPCMethod objects as values. + This maintains the set of methodsavailable for invocation. + :ivar middlewares: List of middleware instances to be applied to all RPC methods by + default. + :ivar tags: List of tags associated with the router, used for categorization or + documentation purposes. + :ivar include_in_schema: Boolean flag indicating whether the router and its methods + should be included in the autogenerated schema. + :ivar errors: List of error types specific to the router that can be associated with + its RPC methods. + These errors will be included in the schema or used for validation. + """ + def __init__( self, middlewares: Optional[List[Middleware]] = None, @@ -44,6 +65,22 @@ def method( errors: Optional[List[Type[RPCError]]] = None, include_in_schema: bool = True, ) -> Callable[[Handler], Handler]: + """ + Decorator, used to registers the RPC method with appropriate configurations. + + :param rpc_method_name: Unique name for the RPC method to be registered. + :param middlewares: List of middlewares objects. + If not provided, the default middlewares of the router are used. + :param return_type: The expected return type of the RPC method. + If not provided, it tries to infer the type. + :param tags: List of tags associated with the RPC method. + :param errors: List of possible error types the RPC method can raise. + If not provided, router-level errors are used. + :param include_in_schema: Boolean flag indicating whether this RPC method should + be included in the schema. + :return: A decorator function which can be applied to a handler to register it + as the specified RPC method. + """ if rpc_method_name in self.rpc_methods: raise ValueError(f"RPC method {rpc_method_name} already registered!") diff --git a/pybotx_smartapp_rpc/rpc.py b/pybotx_smartapp_rpc/rpc.py index 2ac4949..05a0933 100644 --- a/pybotx_smartapp_rpc/rpc.py +++ b/pybotx_smartapp_rpc/rpc.py @@ -26,6 +26,22 @@ class SmartAppRPC: + """ + Class for managing SmartApp RPC events and handling RPC requests with configured + routers, middlewares, and exception handlers. + + This class is responsible for processing incoming SmartApp events, generating + appropriate RPC responses, and managing the lifecycle of the associated RPC + request and response objects. It integrates middlewares, exception handling, + and a routing mechanism to facilitate proper processing of SmartApp events. + The class supports both asynchronous and synchronous event handling for improved + flexibility and integration with external systems. + + :ivar router: The main router that manages registered RPC routes and includes + middlewares and error handlers for handling RPC requests. + :type router: RPCRouter + """ + def __init__( self, routers: List[RPCRouter], diff --git a/pybotx_smartapp_rpc/smartapp.py b/pybotx_smartapp_rpc/smartapp.py index 69d16a5..3f5f6a5 100644 --- a/pybotx_smartapp_rpc/smartapp.py +++ b/pybotx_smartapp_rpc/smartapp.py @@ -7,6 +7,25 @@ class SmartApp: + """ + Represents a SmartApp that integrates with a bot for communication and events. + + This class is designed to facilitate interaction with a bot to send smartapp + events and notifications. Its primary purpose is to provide managing + smartapp-related notifications and events within a chat context. + + :ivar bot: The bot instance used for communicating with the smart app. + :type bot: Bot + :ivar event: Optional event related to the smart app context. + :type event: Optional[SmartAppEvent] + :ivar bot_id: Unique identifier for the bot. + :type bot_id: UUID + :ivar chat_id: Unique identifier for the chat associated with the smart app. + :type chat_id: UUID + :ivar state: A simple namespace object for maintaining state information. + :type state: SimpleNamespace + """ + def __init__( self, bot: Bot, From ed7e69ca37fee479289da9b5448e624806752bdf Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 01:08:21 +0300 Subject: [PATCH 26/31] add some docs --- pybotx_smartapp_rpc/models/method.py | 23 +++++++++++++++++++++++ pybotx_smartapp_rpc/models/model_field.py | 10 ++++++++++ pybotx_smartapp_rpc/openapi/openapi.py | 14 ++++++++++++++ pybotx_smartapp_rpc/openapi/utils.py | 19 ++++++++++--------- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/pybotx_smartapp_rpc/models/method.py b/pybotx_smartapp_rpc/models/method.py index 960dfd2..5b7316b 100644 --- a/pybotx_smartapp_rpc/models/method.py +++ b/pybotx_smartapp_rpc/models/method.py @@ -16,6 +16,29 @@ @dataclass class RPCMethod: + """ + Represents a Remote Procedure Call (RPC) method and its associated behavior. + + This class encapsulates the details and execution logic for an RPC method, + including its handler, middlewares, response structure, and associated configurations. + It is responsible for assembling the middleware stack and invoking the RPC handler + with the provided arguments. + + :ivar handler: The main handler function for the RPC method. + :ivar middlewares: A list of middleware to be applied to the RPC method. + :ivar response_field: The field defining the structure of the response returned by + the RPC method. + :ivar arguments_field: Optional field defining the structure of input arguments + for the RPC method. + :ivar tags: Tags associated with the RPC method, used for classification or + documentation purposes. + :ivar errors: A dictionary mapping error codes to error details relevant + to the RPC method. + :ivar errors_models: A dictionary mapping error codes to model descriptions + for error handling in the RPC method. + :ivar include_in_schema: Indicates whether the RPC method should be + included in the API schema. + """ handler: Handler middlewares: List[Middleware] response_field: ModelField diff --git a/pybotx_smartapp_rpc/models/model_field.py b/pybotx_smartapp_rpc/models/model_field.py index e29590b..dcd74c1 100644 --- a/pybotx_smartapp_rpc/models/model_field.py +++ b/pybotx_smartapp_rpc/models/model_field.py @@ -7,6 +7,16 @@ @dataclass class ModelField: + """ + Represents a model field with metadata and utilities for managing its properties. + + The ModelField class encapsulates information about a model's field, including its + name, type hint, default value, and whether it is required. It provides methods + and properties to query and manage these attributes effectively. + + :ivar field_info: Metadata and information about the field. + :ivar name: The name of the field. + """ field_info: FieldInfo name: str diff --git a/pybotx_smartapp_rpc/openapi/openapi.py b/pybotx_smartapp_rpc/openapi/openapi.py index d748c5d..7f5c6b2 100644 --- a/pybotx_smartapp_rpc/openapi/openapi.py +++ b/pybotx_smartapp_rpc/openapi/openapi.py @@ -25,6 +25,20 @@ def update_fastapi_paths_by_rpc_router( security_definitions: Optional[dict[str, Any]] = None, operation_security: Optional[dict[str, Any]] = None, ) -> None: + """ + Updates the OpenAPI dictionary by adding paths and components related to the + provided RPCRouter and its configured methods. + Additionally, updates security definitions and operation-specific security + configurations if provided. + + :param openapi_dict: The base OpenAPI dictionary to be updated with the RPC router paths. + :param rpc_router: The RPCRouter instance containing the RPC methods to include in the schema. + :param security_definitions: Optional dictionary defining security schemes to add to the + OpenAPI specification. + :param operation_security: Optional dictionary specifying operation-level security + configurations for RPC methods in the OpenAPI schema. + :return: Nothing. Modifies the `openapi_dict` in place. + """ if security_definitions is not None: openapi_dict.setdefault("components", {}).setdefault( "securitySchemes", {} diff --git a/pybotx_smartapp_rpc/openapi/utils.py b/pybotx_smartapp_rpc/openapi/utils.py index e608e0e..6dd83dc 100644 --- a/pybotx_smartapp_rpc/openapi/utils.py +++ b/pybotx_smartapp_rpc/openapi/utils.py @@ -1,3 +1,4 @@ +"""This module contains utility functions for OpenAPI generation.""" import re from enum import Enum from typing import Any, Dict, Sequence, Set, Type, Union @@ -11,15 +12,6 @@ TypeModelSet = Set[TypeModelOrEnum] -def _get_field_type(field: Union[ModelField, FieldInfo]) -> Any: - """Extract declared type from our wrapper or a native FieldInfo.""" - if isinstance(field, ModelField): - return field.type_ - elif isinstance(field, FieldInfo): - return field.annotation - else: - raise TypeError(f"Unsupported field type: {type(field)}") - def get_flat_models_from_model( model: Type[BaseModel], @@ -120,3 +112,12 @@ def get_schema_or_ref( return {"$ref": ref_prefix + model_name} return TypeAdapter(model.type_).json_schema(ref_template=ref_prefix + "{model}") + +def _get_field_type(field: Union[ModelField, FieldInfo]) -> Any: + """Extract declared type from our wrapper or a native FieldInfo.""" + if isinstance(field, ModelField): + return field.type_ + elif isinstance(field, FieldInfo): + return field.annotation + else: + raise TypeError(f"Unsupported field type: {type(field)}") \ No newline at end of file From 9df2367fcc386a778fa60777f654c8bb92e8768b Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 01:11:02 +0300 Subject: [PATCH 27/31] add some docs --- pybotx_smartapp_rpc/models/method.py | 3 ++- pybotx_smartapp_rpc/models/model_field.py | 1 + pybotx_smartapp_rpc/openapi/openapi.py | 10 ++++++---- pybotx_smartapp_rpc/openapi/utils.py | 5 +++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pybotx_smartapp_rpc/models/method.py b/pybotx_smartapp_rpc/models/method.py index 5b7316b..d692ee2 100644 --- a/pybotx_smartapp_rpc/models/method.py +++ b/pybotx_smartapp_rpc/models/method.py @@ -20,7 +20,7 @@ class RPCMethod: Represents a Remote Procedure Call (RPC) method and its associated behavior. This class encapsulates the details and execution logic for an RPC method, - including its handler, middlewares, response structure, and associated configurations. + including its handler, middlewares, response structure, and configurations. It is responsible for assembling the middleware stack and invoking the RPC handler with the provided arguments. @@ -39,6 +39,7 @@ class RPCMethod: :ivar include_in_schema: Indicates whether the RPC method should be included in the API schema. """ + handler: Handler middlewares: List[Middleware] response_field: ModelField diff --git a/pybotx_smartapp_rpc/models/model_field.py b/pybotx_smartapp_rpc/models/model_field.py index dcd74c1..098aa2c 100644 --- a/pybotx_smartapp_rpc/models/model_field.py +++ b/pybotx_smartapp_rpc/models/model_field.py @@ -17,6 +17,7 @@ class ModelField: :ivar field_info: Metadata and information about the field. :ivar name: The name of the field. """ + field_info: FieldInfo name: str diff --git a/pybotx_smartapp_rpc/openapi/openapi.py b/pybotx_smartapp_rpc/openapi/openapi.py index 7f5c6b2..b1e5d48 100644 --- a/pybotx_smartapp_rpc/openapi/openapi.py +++ b/pybotx_smartapp_rpc/openapi/openapi.py @@ -31,10 +31,12 @@ def update_fastapi_paths_by_rpc_router( Additionally, updates security definitions and operation-specific security configurations if provided. - :param openapi_dict: The base OpenAPI dictionary to be updated with the RPC router paths. - :param rpc_router: The RPCRouter instance containing the RPC methods to include in the schema. - :param security_definitions: Optional dictionary defining security schemes to add to the - OpenAPI specification. + :param openapi_dict: The base OpenAPI dictionary to be updated with the RPC + router paths. + :param rpc_router: The RPCRouter instance containing the RPC methods to include + in the schema. + :param security_definitions: Optional dictionary defining security schemes to add + to the OpenAPI specification. :param operation_security: Optional dictionary specifying operation-level security configurations for RPC methods in the OpenAPI schema. :return: Nothing. Modifies the `openapi_dict` in place. diff --git a/pybotx_smartapp_rpc/openapi/utils.py b/pybotx_smartapp_rpc/openapi/utils.py index 6dd83dc..1d78f04 100644 --- a/pybotx_smartapp_rpc/openapi/utils.py +++ b/pybotx_smartapp_rpc/openapi/utils.py @@ -1,4 +1,5 @@ """This module contains utility functions for OpenAPI generation.""" + import re from enum import Enum from typing import Any, Dict, Sequence, Set, Type, Union @@ -12,7 +13,6 @@ TypeModelSet = Set[TypeModelOrEnum] - def get_flat_models_from_model( model: Type[BaseModel], known_models: TypeModelSet, @@ -113,6 +113,7 @@ def get_schema_or_ref( return TypeAdapter(model.type_).json_schema(ref_template=ref_prefix + "{model}") + def _get_field_type(field: Union[ModelField, FieldInfo]) -> Any: """Extract declared type from our wrapper or a native FieldInfo.""" if isinstance(field, ModelField): @@ -120,4 +121,4 @@ def _get_field_type(field: Union[ModelField, FieldInfo]) -> Any: elif isinstance(field, FieldInfo): return field.annotation else: - raise TypeError(f"Unsupported field type: {type(field)}") \ No newline at end of file + raise TypeError(f"Unsupported field type: {type(field)}") From 62de88ef682664080d31b93a836b54def8569964 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 01:14:29 +0300 Subject: [PATCH 28/31] coerage --- .github/workflows/actions.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index c51db90..2e0e771 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -21,7 +21,15 @@ jobs: - name: Run tests run: | - poetry run ./scripts/test + poetry run ./scripts/test --cov=pybotx_smartapp_rpc --cov-report=xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 + with: + fail_ci_if_error: true + files: ./coverage.xml + flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} lint: From 85bd17081da47a90a09448339d6f8bdecf109149 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Wed, 20 Aug 2025 01:16:15 +0300 Subject: [PATCH 29/31] remove coverage --- .github/workflows/actions.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 2e0e771..c51db90 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -21,15 +21,7 @@ jobs: - name: Run tests run: | - poetry run ./scripts/test --cov=pybotx_smartapp_rpc --cov-report=xml - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - fail_ci_if_error: true - files: ./coverage.xml - flags: unittests - token: ${{ secrets.CODECOV_TOKEN }} + poetry run ./scripts/test lint: From 6dca24c8252c0e5d7c54da5ba709fd489f7998b8 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Mon, 25 Aug 2025 15:13:31 +0300 Subject: [PATCH 30/31] add extra handler arguments support --- pybotx_smartapp_rpc/router.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index 33e3a72..49e9a2a 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union -from pydantic import ValidationError +from pydantic import ValidationError, BaseModel from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined @@ -66,7 +66,12 @@ def method( include_in_schema: bool = True, ) -> Callable[[Handler], Handler]: """ - Decorator, used to registers the RPC method with appropriate configurations. + Decorator, used to register the RPC method with appropriate configurations. + + Wrapped function should have attributes: + - smartapp: SmartApp + - argument: BaseModel + - any other arguments :param rpc_method_name: Unique name for the RPC method to be registered. :param middlewares: List of middlewares objects. @@ -172,7 +177,12 @@ def _get_handler_request_argument_model( args_annotations = [ arg[1].annotation for arg in handler_signature.parameters.items() ] - if len(args_annotations) >= 2: + + is_second_arg_pydantic_model = len(args_annotations) >= 2 and issubclass( + args_annotations[1], BaseModel + ) + + if is_second_arg_pydantic_model: return self._create_model_field( name=str(args_annotations[1].__name__), type_=args_annotations[1], From 7eda91145e34decae77fb0759f515688f08aeac4 Mon Sep 17 00:00:00 2001 From: "gubarik.vladimir" Date: Mon, 25 Aug 2025 15:15:14 +0300 Subject: [PATCH 31/31] fix lints --- pybotx_smartapp_rpc/router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybotx_smartapp_rpc/router.py b/pybotx_smartapp_rpc/router.py index 49e9a2a..31783b5 100644 --- a/pybotx_smartapp_rpc/router.py +++ b/pybotx_smartapp_rpc/router.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union -from pydantic import ValidationError, BaseModel +from pydantic import BaseModel, ValidationError from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined @@ -179,7 +179,7 @@ def _get_handler_request_argument_model( ] is_second_arg_pydantic_model = len(args_annotations) >= 2 and issubclass( - args_annotations[1], BaseModel + args_annotations[1], BaseModel ) if is_second_arg_pydantic_model: