diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 67cab0b9..b062a51a 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -3,27 +3,39 @@ name: Builds and tests the Python Package on pull requests on: pull_request: workflow_dispatch: + workflow_call: + inputs: + cache-key: + description: 'Cache key for the build artifacts' + required: false + type: string + default: '' jobs: build-package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + + - name: "Set up Python" + uses: actions/setup-python@v6 with: - python-version-file: '.python-version' + python-version-file: ".python-version" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Install dependencies and build package run: | - pip install pipenv wheel - pipenv sync --system - ./build.sh + uv sync + uv run ./build.sh - name: Cache build - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ./dist - key: build-cache-${{ github.sha }} + key: ${{ inputs.cache-key || format('build-cache-{0}', github.sha) }} test-package: needs: build-package @@ -34,21 +46,32 @@ jobs: fail-fast: true steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + + - name: "Set up Python" + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v7 with: + enable-cache: true python-version: ${{ matrix.python-version }} - name: Recover cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ./dist - key: build-cache-${{ github.sha }} + key: ${{ inputs.cache-key || format('build-cache-{0}', github.sha) }} - name: Install SDK from cache + env: + UV_SYSTEM_PYTHON: 1 run: | PACKAGE=$(ls ./dist/ | grep -P .+\.whl$) - pip install ./dist/$PACKAGE --no-cache-dir + uv pip install "./dist/$PACKAGE[snowflake,bigquery,athena]" + uv pip install pytest - name: Run unit tests run: ./test_installed.sh diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e9dc98dc..9b1035bb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,68 +3,33 @@ name: Build, test and publish Python Package on: release: types: [published] + branches: [main] workflow_dispatch: jobs: - build-package: + validate-version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - name: Check git tag against package version and get python version - run: | - ./check_package_version.sh - echo "::set-output name=VERSION::$(<.python-version)" - id: python-version - - - uses: actions/setup-python@v5 - with: - python-version: ${{ steps.python-version.outputs.VERSION }} - - - name: Install dependencies and build package - run: | - pip install pipenv wheel - pipenv sync --system - ./build.sh - - - name: Cache build - uses: actions/cache@v4 - with: - path: ./dist - key: build-cache - - test-package: - needs: build-package - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] - fail-fast: true - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Recover cache - uses: actions/cache@v4 + - name: Set up Python + uses: actions/setup-python@v6 with: - path: ./dist - key: build-cache + python-version-file: ".python-version" - - name: Install SDK from cache - run: | - PACKAGE=$(ls ./dist/ | grep -P .+\.whl$) - pip install ./dist/$PACKAGE --no-cache-dir + - name: Check git tag against package version + run: ./check_package_version.sh - - name: Run unit tests - run: ./test_installed.sh + build-and-test: + needs: validate-version + uses: ./.github/workflows/build_test.yml + with: + cache-key: 'build-cache' publish-package-to-pypi: if: always() - needs: test-package + needs: build-and-test runs-on: ubuntu-latest environment: release permissions: @@ -72,21 +37,21 @@ jobs: steps: - name: Recover cache - if: needs.test-package.result == 'success' - uses: actions/cache@v4 + if: needs.build-and-test.result == 'success' + uses: actions/cache@v5 with: path: ./dist key: build-cache - name: Publish package to PyPI - if: needs.test-package.result == 'success' + if: needs.build-and-test.result == 'success' uses: pypa/gh-action-pypi-publish@release/v1 - name: Post status to Slack if: ${{ success() }} uses: ravsamhq/notify-slack-action@2.5.0 with: - status: ${{ needs.test-package.result == 'success' && 'success' || 'failure' }} + status: ${{ needs.build-and-test.result == 'success' && 'success' || 'failure' }} env: SLACK_WEBHOOK_URL: ${{ secrets.NOTIFY_SLACK_ACTION_WEBHOOK_URL }} diff --git a/.github/workflows/test_installability.yml b/.github/workflows/test_installability.yml index cdb99c55..4f7c17fb 100644 --- a/.github/workflows/test_installability.yml +++ b/.github/workflows/test_installability.yml @@ -15,7 +15,7 @@ jobs: fail-fast: true steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index d69b2bb2..aaa585d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,19 @@ -build +# Distribution dist *.egg-info *.pyc __pycache__ -/.idea/ .DS_Store + +# Virtual environment +.venv + +# Linters +.mypy_cache +.pytest_cache +.ruff_cache + +# IDE +/.idea/ +.claude/ +.vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..7140bb56 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,60 @@ +# See https://pre-commit.com for more information +# pre-commit can be installed using uv or pipx: +# - uv tool install pre-commit +# - pipx install pre-commit +# +# The hooks needs to be installed in the git repository by using: +# +# pre-commit install +# +# Then pre-commit runs the hooks on each commit. +# To run the pre-commit hooks manually on all staged files use: +# +# pre-commit run +# + +default_language_version: + python: python3.10 + +exclude: (^|/)stubs/ +fail_fast: true +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + exclude: \.md$ + - id: end-of-file-fixer + - id: check-yaml + - id: check-toml + - id: debug-statements # no lines with breakpoints() + - id: check-merge-conflict + - id: no-commit-to-branch + args: [--branch, main] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.13 + hooks: + - id: ruff-check + types_or: [ python ] + args: [ --fix, --show-fixes, --config=pyproject.toml ] + - id: ruff-format + types_or: [ python ] + args: [ --config=pyproject.toml ] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.19.1 + hooks: + - id: mypy + types: [python] + args: + [ + "--config-file=pyproject.toml", + # https://mypy.readthedocs.io/en/stable/running_mypy.html#following-imports + "--follow-imports=silent", + "--disable-error-code=unused-ignore", + ] + additional_dependencies: + - types-protobuf + - types-python-dateutil + - types-requests diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 23ade548..00000000 --- a/Pipfile +++ /dev/null @@ -1,36 +0,0 @@ -[[source]] -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -black = "*" -google-api-core = "*" -google-cloud-bigquery = "*" -googleapis-common-protos = "*" -grpcio = "*" -isort = "*" -mypy = "*" -openpyxl = "*" -pandas = "*" -protobuf = "*" -pyathena = "*" -pyarrow = "*" -pylint = "*" -requests = "*" -setuptools = "*" -snowflake-connector-python = "*" -snowflake-sqlalchemy = "*" -sqlalchemy = "*" -sqlalchemy-bigquery = "*" -tqdm = "*" -twine = "*" -types-protobuf = "*" -types-python-dateutil = "*" -types-requests = "*" -wheel = "*" - -[dev-packages] - -[requires] -python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 0340d86d..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,1442 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "aeab4f58bd92c16471515a579901deb2a48f6f01371e2f79523c1abe159fe068" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.10" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "asn1crypto": { - "hashes": [ - "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", - "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67" - ], - "version": "==1.5.1" - }, - "astroid": { - "hashes": [ - "sha256:ac8fb7ca1c08eb9afec91ccc23edbd8ac73bb22cbdd7da1d488d9fb8d6579070", - "sha256:d7546c00a12efc32650b19a2bb66a153883185d3179ab0d4868086f807338b9b" - ], - "markers": "python_full_version >= '3.10.0'", - "version": "==4.0.2" - }, - "backports.tarfile": { - "hashes": [ - "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", - "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991" - ], - "markers": "python_version >= '3.8'", - "version": "==1.2.0" - }, - "black": { - "hashes": [ - "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", - "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37", - "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0", - "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170", - "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc", - "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", - "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", - "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", - "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", - "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e", - "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", - "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a", - "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc", - "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", - "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2", - "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", - "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06", - "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", - "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", - "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170", - "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc", - "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", - "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", - "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e", - "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", - "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==25.11.0" - }, - "boto3": { - "hashes": [ - "sha256:41fc8844b37ae27b24bcabf8369769df246cc12c09453988d0696ad06d6aa9ef", - "sha256:484e46bf394b03a7c31b34f90945ebe1390cb1e2ac61980d128a9079beac87d4" - ], - "markers": "python_version >= '3.9'", - "version": "==1.40.74" - }, - "botocore": { - "hashes": [ - "sha256:57de0b9ffeada06015b3c7e5186c77d0692b210d9e5efa294f3214df97e2f8ee", - "sha256:f39f5763e35e75f0bd91212b7b36120b1536203e8003cd952ef527db79702b15" - ], - "markers": "python_version >= '3.9'", - "version": "==1.40.74" - }, - "cachetools": { - "hashes": [ - "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", - "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6" - ], - "markers": "python_version >= '3.9'", - "version": "==6.2.2" - }, - "certifi": { - "hashes": [ - "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", - "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316" - ], - "markers": "python_version >= '3.7'", - "version": "==2025.11.12" - }, - "cffi": { - "hashes": [ - "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", - "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", - "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", - "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", - "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", - "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", - "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", - "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", - "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", - "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", - "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", - "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", - "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", - "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", - "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", - "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", - "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", - "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", - "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", - "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", - "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", - "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", - "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", - "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", - "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", - "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", - "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", - "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", - "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", - "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", - "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", - "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", - "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", - "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", - "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", - "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", - "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", - "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", - "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", - "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", - "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", - "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", - "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", - "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", - "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", - "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", - "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", - "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", - "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", - "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", - "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", - "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", - "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", - "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", - "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", - "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", - "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", - "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", - "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", - "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", - "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", - "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", - "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", - "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", - "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", - "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", - "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" - ], - "markers": "python_version >= '3.8'", - "version": "==1.17.1" - }, - "charset-normalizer": { - "hashes": [ - "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", - "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", - "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", - "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", - "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", - "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", - "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", - "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", - "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", - "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", - "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", - "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", - "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", - "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", - "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", - "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", - "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", - "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", - "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", - "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", - "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", - "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", - "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", - "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", - "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", - "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", - "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", - "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", - "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", - "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", - "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", - "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", - "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", - "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", - "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", - "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", - "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", - "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", - "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", - "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", - "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", - "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", - "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", - "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", - "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", - "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", - "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", - "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", - "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", - "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", - "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", - "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", - "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", - "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", - "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", - "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", - "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", - "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", - "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", - "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", - "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", - "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", - "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", - "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", - "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", - "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", - "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", - "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", - "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", - "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", - "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", - "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", - "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", - "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", - "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", - "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", - "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", - "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", - "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", - "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", - "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", - "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", - "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", - "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", - "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", - "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", - "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", - "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", - "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", - "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", - "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", - "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", - "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", - "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", - "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", - "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", - "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", - "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", - "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", - "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", - "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", - "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", - "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", - "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", - "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", - "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", - "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", - "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", - "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", - "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", - "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", - "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", - "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" - ], - "markers": "python_version >= '3.7'", - "version": "==3.4.4" - }, - "click": { - "hashes": [ - "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", - "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6" - ], - "markers": "python_version >= '3.10'", - "version": "==8.3.1" - }, - "cryptography": { - "hashes": [ - "sha256:07a1be54f995ce14740bf8bbe1cc35f7a37760f992f73cf9f98a2a60b9b97419", - "sha256:0f58183453032727a65e6605240e7a3824fd1d6a7e75d2b537e280286ab79a52", - "sha256:16b5ac72a965ec9d1e34d9417dbce235d45fa04dac28634384e3ce40dfc66495", - "sha256:1b4fba84166d906a22027f0d958e42f3a4dbbb19c28ea71f0fb7812380b04e3c", - "sha256:1d2073313324226fd846e6b5fc340ed02d43fd7478f584741bd6b791c33c9fee", - "sha256:249c41f2bbfa026615e7bdca47e4a66135baa81b08509ab240a2e666f6af5966", - "sha256:274f8b2eb3616709f437326185eb563eb4e5813d01ebe2029b61bfe7d9995fbb", - "sha256:2fc30be952dd4334801d345d134c9ef0e9ccbaa8c3e1bc18925cbc4247b3e29c", - "sha256:32670ca085150ff36b438c17f2dfc54146fe4a074ebf0a76d72fb1b419a974bc", - "sha256:35aa1a44bd3e0efc3ef09cf924b3a0e2a57eda84074556f4506af2d294076685", - "sha256:3738f50215211cee1974193a1809348d33893696ce119968932ea117bcbc9b1d", - "sha256:378eff89b040cbce6169528f130ee75dceeb97eef396a801daec03b696434f06", - "sha256:399ef4c9be67f3902e5ca1d80e64b04498f8b56c19e1bc8d0825050ea5290410", - "sha256:40ee4ce3c34acaa5bc347615ec452c74ae8ff7db973a98c97c62293120f668c6", - "sha256:4bc257c2d5d865ed37d0bd7c500baa71f939a7952c424f28632298d80ccd5ec1", - "sha256:4f70cbade61a16f5e238c4b0eb4e258d177a2fcb59aa0aae1236594f7b0ae338", - "sha256:523153480d7575a169933f083eb47b1edd5fef45d87b026737de74ffeb300f69", - "sha256:6460866a92143a24e3ed68eaeb6e98d0cedd85d7d9a8ab1fc293ec91850b1b38", - "sha256:65e9117ebed5b16b28154ed36b164c20021f3a480e9cbb4b4a2a59b95e74c25d", - "sha256:6c39fd5cd9b7526afa69d64b5e5645a06e1b904f342584b3885254400b63f1b3", - "sha256:6d8945bc120dcd90ae39aa841afddaeafc5f2e832809dc54fb906e3db829dfdc", - "sha256:75d2ddde8f1766ab2db48ed7f2aa3797aeb491ea8dfe9b4c074201aec00f5c16", - "sha256:77e3bd53c9c189cea361bc18ceb173959f8b2dd8f8d984ae118e9ac641410252", - "sha256:7f3f88df0c9b248dcc2e76124f9140621aca187ccc396b87bc363f890acf3a30", - "sha256:80a548a5862d6912a45557a101092cd6c64ae1475b82cef50ee305d14a75f598", - "sha256:834af45296083d892e23430e3b11df77e2ac5c042caede1da29c9bf59016f4d2", - "sha256:83af84ebe7b6e9b6de05050c79f8cc0173c864ce747b53abce6a11e940efdc0d", - "sha256:88c09da8a94ac27798f6b62de6968ac78bb94805b5d272dbcfd5fdc8c566999f", - "sha256:8e8b222eb54e3e7d3743a7c2b1f7fa7df7a9add790307bb34327c88ec85fe087", - "sha256:91585fc9e696abd7b3e48a463a20dda1a5c0eeeca4ba60fa4205a79527694390", - "sha256:99f64a6d15f19f3afd78720ad2978f6d8d4c68cd4eb600fab82ab1a7c2071dca", - "sha256:9aa85222f03fdb30defabc7a9e1e3d4ec76eb74ea9fe1504b2800844f9c98440", - "sha256:ab3a14cecc741c8c03ad0ad46dfbf18de25218551931a23bca2731d46c706d83", - "sha256:b8e7db4ce0b7297e88f3d02e6ee9a39382e0efaf1e8974ad353120a2b5a57ef7", - "sha256:bbaa5eef3c19c66613317dc61e211b48d5f550db009c45e1c28b59d5a9b7812a", - "sha256:be7479f9504bfb46628544ec7cb4637fe6af8b70445d4455fbb9c395ad9b7290", - "sha256:bf1961037309ee0bdf874ccba9820b1c2f720c2016895c44d8eb2316226c1ad5", - "sha256:c1f6ccd6f2eef3b2eb52837f0463e853501e45a916b3fc42e5d93cf244a4b97b", - "sha256:c3648d6a5878fd1c9a22b1d43fa75efc069d5f54de12df95c638ae7ba88701d0", - "sha256:c39f0947d50f74b1b3523cec3931315072646286fb462995eb998f8136779319", - "sha256:c3cd09b1490c1509bf3892bde9cef729795fae4a2fee0621f19be3321beca7e4", - "sha256:c457ad3f151d5fb380be99425b286167b358f76d97ad18b188b68097193ed95a", - "sha256:c9c4121f9a41cc3d02164541d986f59be31548ad355a5c96ac50703003c50fb7", - "sha256:d14eaf1569d6252280516bedaffdd65267428cdbc3a8c2d6de63753cf0863d5e", - "sha256:d1eccae15d5c28c74b2bea228775c63ac5b6c36eedb574e002440c0bc28750d3", - "sha256:d349af4d76a93562f1dce4d983a4a34d01cb22b48635b0d2a0b8372cdb4a8136", - "sha256:d5c0cbb2fb522f7e39b59a5482a1c9c5923b7c506cfe96a1b8e7368c31617ac0", - "sha256:da7f93551d39d462263b6b5c9056c49f780b9200bf9fc2656d7c88c7bdb9b363", - "sha256:df932ac70388be034b2e046e34d636245d5eeb8140db24a6b4c2268cd2073270", - "sha256:f09a3a108223e319168b7557810596631a8cb864657b0c16ed7a6017f0be9433", - "sha256:f85e6a7d42ad60024fa1347b1d4ef82c4df517a4deb7f829d301f1a92ded038c", - "sha256:f9aaf2a91302e1490c068d2f3af7df4137ac2b36600f5bd26e53d9ec320412d3", - "sha256:f9f85d9cf88e3ba2b2b6da3c2310d1cf75bdf04a5bc1a2e972603054f82c4dd5", - "sha256:fe9ff1139b2b1f59a5a0b538bbd950f8660a39624bbe10cf3640d17574f973bb" - ], - "markers": "python_version >= '3.8' and python_full_version not in '3.9.0, 3.9.1'", - "version": "==46.0.0" - }, - "dill": { - "hashes": [ - "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", - "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049" - ], - "markers": "python_version >= '3.8'", - "version": "==0.4.0" - }, - "docutils": { - "hashes": [ - "sha256:21486ae730e4ca9f622677b1412b879af1791efcfba517e4c6f60be543fc8cdd", - "sha256:bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb" - ], - "markers": "python_version >= '3.9'", - "version": "==0.22.3" - }, - "et-xmlfile": { - "hashes": [ - "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", - "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54" - ], - "markers": "python_version >= '3.8'", - "version": "==2.0.0" - }, - "filelock": { - "hashes": [ - "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", - "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4" - ], - "markers": "python_version >= '3.10'", - "version": "==3.20.0" - }, - "fsspec": { - "hashes": [ - "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", - "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59" - ], - "markers": "python_version >= '3.9'", - "version": "==2025.10.0" - }, - "google-api-core": { - "extras": [ - "grpc" - ], - "hashes": [ - "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8", - "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.28.1" - }, - "google-auth": { - "hashes": [ - "sha256:88228eee5fc21b62a1b5fe773ca15e67778cb07dc8363adcb4a8827b52d81483", - "sha256:af628ba6fa493f75c7e9dbe9373d148ca9f4399b5ea29976519e0a3848eddd16" - ], - "markers": "python_version >= '3.7'", - "version": "==2.43.0" - }, - "google-cloud-bigquery": { - "hashes": [ - "sha256:8afcb7116f5eac849097a344eb8bfda78b7cfaae128e60e019193dd483873520", - "sha256:e06e93ff7b245b239945ef59cb59616057598d369edac457ebf292bd61984da6" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==3.38.0" - }, - "google-cloud-core": { - "hashes": [ - "sha256:67d977b41ae6c7211ee830c7912e41003ea8194bff15ae7d72fd6f51e57acabc", - "sha256:7c1b7ef5c92311717bd05301aa1a91ffbc565673d3b0b4163a52d8413a186963" - ], - "markers": "python_version >= '3.7'", - "version": "==2.5.0" - }, - "google-crc32c": { - "hashes": [ - "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", - "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", - "sha256:1c67ca0a1f5b56162951a9dae987988679a7db682d6f97ce0f6381ebf0fbea4c", - "sha256:1f2b3522222746fff0e04a9bd0a23ea003ba3cccc8cf21385c564deb1f223242", - "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", - "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", - "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", - "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", - "sha256:3bda0fcb632d390e3ea8b6b07bf6b4f4a66c9d02dcd6fbf7ba00a197c143f582", - "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", - "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", - "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", - "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", - "sha256:713121af19f1a617054c41f952294764e0c5443d5a5d9034b2cd60f5dd7e0349", - "sha256:754561c6c66e89d55754106739e22fdaa93fafa8da7221b29c8b8e8270c6ec8a", - "sha256:7cc81b3a2fbd932a4313eb53cc7d9dde424088ca3a0337160f35d91826880c1d", - "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", - "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", - "sha256:9fc196f0b8d8bd2789352c6a522db03f89e83a0ed6b64315923c396d7a932315", - "sha256:a8e9afc74168b0b2232fb32dd202c93e46b7d5e4bf03e66ba5dc273bb3559589", - "sha256:b07d48faf8292b4db7c3d64ab86f950c2e94e93a11fd47271c28ba458e4a0d76", - "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", - "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", - "sha256:bb5e35dcd8552f76eed9461a23de1030920a3c953c1982f324be8f97946e7127", - "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", - "sha256:dcdf5a64adb747610140572ed18d011896e3b9ae5195f2514b7ff678c80f1603", - "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", - "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", - "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", - "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", - "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", - "sha256:f2226b6a8da04f1d9e61d3e357f2460b9551c5e6950071437e122c958a18ae14", - "sha256:fa8136cc14dd27f34a3221c0f16fd42d8a40e4778273e61a3c19aedaa44daf6b", - "sha256:fc5319db92daa516b653600794d5b9f9439a9a121f3e162f94b0e1891c7933cb" - ], - "markers": "python_version >= '3.9'", - "version": "==1.7.1" - }, - "google-resumable-media": { - "hashes": [ - "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", - "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0" - ], - "markers": "python_version >= '3.7'", - "version": "==2.7.2" - }, - "googleapis-common-protos": { - "hashes": [ - "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", - "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==1.72.0" - }, - "grpcio": { - "hashes": [ - "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3", - "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", - "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b", - "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", - "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", - "sha256:0aaa82d0813fd4c8e589fac9b65d7dd88702555f702fb10417f96e2a2a6d4c0f", - "sha256:0b7604868b38c1bfd5cf72d768aedd7db41d78cb6a4a18585e33fb0f9f2363fd", - "sha256:0c37db8606c258e2ee0c56b78c62fc9dee0e901b5dbdcf816c2dd4ad652b8b0c", - "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", - "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", - "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", - "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", - "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", - "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", - "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", - "sha256:3bf0f392c0b806905ed174dcd8bdd5e418a40d5567a05615a030a5aeddea692d", - "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", - "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde", - "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990", - "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", - "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", - "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", - "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", - "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af", - "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", - "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", - "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", - "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", - "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", - "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc", - "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", - "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", - "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", - "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", - "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", - "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", - "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2", - "sha256:8ebe63ee5f8fa4296b1b8cfc743f870d10e902ca18afc65c68cf46fd39bb0783", - "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", - "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", - "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", - "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6", - "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", - "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3", - "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", - "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b", - "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", - "sha256:acab0277c40eff7143c2323190ea57b9ee5fd353d8190ee9652369fae735668a", - "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", - "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", - "sha256:d099566accf23d21037f18a2a63d323075bebace807742e4b0ac210971d4dd70", - "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", - "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", - "sha256:e6d1db20594d9daba22f90da738b1a0441a7427552cc6e2e3d1297aeddc00378", - "sha256:ebea5cc3aa8ea72e04df9913492f9a96d9348db876f9dda3ad729cfedf7ac416", - "sha256:ebebf83299b0cb1721a8859ea98f3a77811e35dce7609c5c963b9ad90728f886", - "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", - "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", - "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", - "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", - "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.76.0" - }, - "grpcio-status": { - "hashes": [ - "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd", - "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18" - ], - "markers": "python_version >= '3.9'", - "version": "==1.76.0" - }, - "id": { - "hashes": [ - "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d", - "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658" - ], - "markers": "python_version >= '3.8'", - "version": "==1.5.0" - }, - "idna": { - "hashes": [ - "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", - "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" - ], - "markers": "python_version >= '3.8'", - "version": "==3.11" - }, - "importlib-metadata": { - "hashes": [ - "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", - "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" - ], - "markers": "python_version >= '3.9'", - "version": "==8.7.0" - }, - "isort": { - "hashes": [ - "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", - "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187" - ], - "index": "pypi", - "markers": "python_full_version >= '3.10.0'", - "version": "==7.0.0" - }, - "jaraco.classes": { - "hashes": [ - "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", - "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" - ], - "markers": "python_version >= '3.8'", - "version": "==3.4.0" - }, - "jaraco.context": { - "hashes": [ - "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", - "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4" - ], - "markers": "python_version >= '3.8'", - "version": "==6.0.1" - }, - "jaraco.functools": { - "hashes": [ - "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", - "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294" - ], - "markers": "python_version >= '3.9'", - "version": "==4.3.0" - }, - "jmespath": { - "hashes": [ - "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", - "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" - ], - "markers": "python_version >= '3.7'", - "version": "==1.0.1" - }, - "keyring": { - "hashes": [ - "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", - "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b" - ], - "markers": "python_version >= '3.9'", - "version": "==25.7.0" - }, - "markdown-it-py": { - "hashes": [ - "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", - "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" - ], - "markers": "python_version >= '3.10'", - "version": "==4.0.0" - }, - "mccabe": { - "hashes": [ - "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.0" - }, - "mdurl": { - "hashes": [ - "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", - "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" - ], - "markers": "python_version >= '3.7'", - "version": "==0.1.2" - }, - "more-itertools": { - "hashes": [ - "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", - "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd" - ], - "markers": "python_version >= '3.9'", - "version": "==10.8.0" - }, - "mypy": { - "hashes": [ - "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", - "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", - "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", - "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", - "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", - "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", - "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", - "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", - "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", - "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", - "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", - "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", - "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", - "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", - "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", - "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", - "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", - "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", - "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", - "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", - "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", - "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", - "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", - "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", - "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", - "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", - "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", - "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", - "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", - "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", - "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", - "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", - "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", - "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", - "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", - "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", - "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", - "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.18.2" - }, - "mypy-extensions": { - "hashes": [ - "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", - "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" - ], - "markers": "python_version >= '3.8'", - "version": "==1.1.0" - }, - "nh3": { - "hashes": [ - "sha256:019ecbd007536b67fdf76fab411b648fb64e2257ca3262ec80c3425c24028c80", - "sha256:03d617e5c8aa7331bd2659c654e021caf9bba704b109e7b2b28b039a00949fe5", - "sha256:0dca4365db62b2d71ff1620ee4f800c4729849906c5dd504ee1a7b2389558e31", - "sha256:0fe7ee035dd7b2290715baf29cb27167dddd2ff70ea7d052c958dbd80d323c99", - "sha256:13398e676a14d6233f372c75f52d5ae74f98210172991f7a3142a736bd92b131", - "sha256:169db03df90da63286e0560ea0efa9b6f3b59844a9735514a1d47e6bb2c8c61b", - "sha256:1710f3901cd6440ca92494ba2eb6dc260f829fa8d9196b659fa10de825610ce0", - "sha256:1f9ba555a797dbdcd844b89523f29cdc90973d8bd2e836ea6b962cf567cadd93", - "sha256:2ab70e8c6c7d2ce953d2a58102eefa90c2d0a5ed7aa40c7e29a487bc5e613131", - "sha256:2c9850041b77a9147d6bbd6dbbf13eeec7009eb60b44e83f07fcb2910075bf9b", - "sha256:403c11563e50b915d0efdb622866d1d9e4506bce590ef7da57789bf71dd148b5", - "sha256:45c953e57028c31d473d6b648552d9cab1efe20a42ad139d78e11d8f42a36130", - "sha256:562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe", - "sha256:6d66f41672eb4060cf87c037f760bdbc6847852ca9ef8e9c5a5da18f090abf87", - "sha256:7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e", - "sha256:72d67c25a84579f4a432c065e8b4274e53b7cf1df8f792cf846abfe2c3090866", - "sha256:7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7", - "sha256:91e9b001101fb4500a2aafe3e7c92928d85242d38bf5ac0aba0b7480da0a4cd6", - "sha256:a40202fd58e49129764f025bbaae77028e420f1d5b3c8e6f6fd3a6490d513868", - "sha256:c8745454cdd28bbbc90861b80a0111a195b0e3961b9fa2e672be89eb199fa5d8", - "sha256:cf5964d54edd405e68583114a7cba929468bcd7db5e676ae38ee954de1cfc104", - "sha256:d18957a90806d943d141cc5e4a0fefa1d77cf0d7a156878bf9a66eed52c9cc7d", - "sha256:dce4248edc427c9b79261f3e6e2b3ecbdd9b88c267012168b4a7b3fc6fd41d13", - "sha256:f2f55c4d2d5a207e74eefe4d828067bbb01300e06e2a7436142f915c5928de07", - "sha256:f394759a06df8b685a4ebfb1874fb67a9cbfd58c64fc5ed587a663c0e63ec376", - "sha256:f97f8b25cb2681d25e2338148159447e4d689aafdccfcf19e61ff7db3905768a" - ], - "markers": "python_version >= '3.8'", - "version": "==0.3.2" - }, - "numpy": { - "hashes": [ - "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", - "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", - "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", - "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", - "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", - "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", - "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", - "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", - "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", - "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", - "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", - "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", - "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", - "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", - "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", - "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", - "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", - "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", - "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", - "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", - "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", - "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", - "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", - "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", - "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", - "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", - "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", - "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", - "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", - "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", - "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", - "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", - "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", - "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", - "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", - "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", - "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", - "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", - "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", - "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", - "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", - "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", - "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", - "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", - "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", - "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", - "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", - "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", - "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", - "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", - "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", - "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", - "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", - "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", - "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8" - ], - "markers": "python_version >= '3.10'", - "version": "==2.2.6" - }, - "openpyxl": { - "hashes": [ - "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", - "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==3.1.5" - }, - "packaging": { - "hashes": [ - "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", - "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" - ], - "markers": "python_version >= '3.8'", - "version": "==25.0" - }, - "pandas": { - "hashes": [ - "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", - "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", - "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", - "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", - "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", - "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", - "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", - "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", - "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", - "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", - "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", - "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", - "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", - "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", - "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", - "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", - "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", - "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", - "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", - "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", - "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", - "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", - "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", - "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", - "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", - "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", - "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", - "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", - "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", - "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", - "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", - "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", - "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", - "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", - "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", - "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", - "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", - "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", - "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", - "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", - "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", - "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", - "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", - "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", - "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", - "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", - "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", - "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", - "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", - "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", - "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", - "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", - "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", - "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", - "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.3.3" - }, - "pathspec": { - "hashes": [ - "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", - "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" - ], - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, - "platformdirs": { - "hashes": [ - "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", - "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3" - ], - "markers": "python_version >= '3.10'", - "version": "==4.5.0" - }, - "proto-plus": { - "hashes": [ - "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", - "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012" - ], - "markers": "python_version >= '3.7'", - "version": "==1.26.1" - }, - "protobuf": { - "hashes": [ - "sha256:023af8449482fa884d88b4563d85e83accab54138ae098924a985bcbb734a213", - "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53", - "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1", - "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed", - "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b", - "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa", - "sha256:df051de4fd7e5e4371334e234c62ba43763f15ab605579e04c7008c05735cd82", - "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178", - "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b", - "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==6.33.1" - }, - "pyarrow": { - "hashes": [ - "sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901", - "sha256:00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84", - "sha256:0c34fe18094686194f204a3b1787a27456897d8a2d62caf84b61e8dfbc0252ae", - "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8", - "sha256:1a812a5b727bc09c3d7ea072c4eebf657c2f7066155506ba31ebf4792f88f016", - "sha256:252be4a05f9d9185bb8c18e83764ebcfea7185076c07a7a662253af3a8c07941", - "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5", - "sha256:35ad0f0378c9359b3f297299c3309778bb03b8612f987399a0333a560b43862d", - "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", - "sha256:3e294c5eadfb93d78b0763e859a0c16d4051fc1c5231ae8956d61cb0b5666f5a", - "sha256:3e739edd001b04f654b166204fc7a9de896cf6007eaff33409ee9e50ceaff754", - "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d", - "sha256:44d2d26cda26d18f7af7db71453b7b783788322d756e81730acb98f24eb90ace", - "sha256:4c19236ae2402a8663a2c8f21f1870a03cc57f0bef7e4b6eb3238cc82944de80", - "sha256:69763ab2445f632d90b504a815a2a033f74332997052b721002298ed6de40f2e", - "sha256:6dda1ddac033d27421c20d7a7943eec60be44e0db4e079f33cc5af3b8280ccde", - "sha256:6f9762274496c244d951c819348afbcf212714902742225f649cf02823a6a10f", - "sha256:710624ab925dc2b05a6229d47f6f0dac1c1155e6ed559be7109f684eba048a48", - "sha256:7388ac685cab5b279a41dfe0a6ccd99e4dbf322edfb63e02fc0443bf24134e91", - "sha256:77718810bd3066158db1e95a63c160ad7ce08c6b0710bc656055033e39cdad88", - "sha256:7a820d8ae11facf32585507c11f04e3f38343c1e784c9b5a8b1da5c930547fe2", - "sha256:8382ad21458075c2e66a82a29d650f963ce51c7708c7c0ff313a8c206c4fd5e8", - "sha256:84378110dd9a6c06323b41b56e129c504d157d1a983ce8f5443761eb5256bafc", - "sha256:854794239111d2b88b40b6ef92aa478024d1e5074f364033e73e21e3f76b25e0", - "sha256:92843c305330aa94a36e706c16209cd4df274693e777ca47112617db7d0ef3d7", - "sha256:9bddc2cade6561f6820d4cd73f99a0243532ad506bc510a75a5a65a522b2d74d", - "sha256:a4893d31e5ef780b6edcaf63122df0f8d321088bb0dee4c8c06eccb1ca28d145", - "sha256:a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746", - "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9", - "sha256:b41f37cabfe2463232684de44bad753d6be08a7a072f6a83447eeaf0e4d2a215", - "sha256:b883fe6fd85adad7932b3271c38ac289c65b7337c2c132e9569f9d3940620730", - "sha256:b9d71701ce97c95480fecb0039ec5bb889e75f110da72005743451339262f4ce", - "sha256:ba95112d15fd4f1105fb2402c4eab9068f0554435e9b7085924bcfaac2cc306f", - "sha256:bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc", - "sha256:bd0d42297ace400d8febe55f13fdf46e86754842b860c978dfec16f081e5c653", - "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", - "sha256:c064e28361c05d72eed8e744c9605cbd6d2bb7481a511c74071fd9b24bc65d7d", - "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e", - "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe", - "sha256:c6ec3675d98915bf1ec8b3c7986422682f7232ea76cad276f4c8abd5b7319b70", - "sha256:ce20fe000754f477c8a9125543f1936ea5b8867c5406757c224d745ed033e691", - "sha256:cedb9dd9358e4ea1d9bce3665ce0797f6adf97ff142c8e25b46ba9cdd508e9b6", - "sha256:e0a15757fccb38c410947df156f9749ae4a3c89b2393741a50521f39a8cf202a", - "sha256:e6e95176209257803a8b3d0394f21604e796dadb643d2f7ca21b66c9c0b30c9a", - "sha256:e70ff90c64419709d38c8932ea9fe1cc98415c4f87ea8da81719e43f02534bc9", - "sha256:ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95", - "sha256:ec5d40dd494882704fb876c16fa7261a69791e784ae34e6b5992e977bd2e238c", - "sha256:f633074f36dbc33d5c05b5dc75371e5660f1dbf9c8b1d95669def05e5425989c", - "sha256:f7fe3dbe871294ba70d789be16b6e7e52b418311e166e0e3cba9522f0f437fb1", - "sha256:f963ba8c3b0199f9d6b794c90ec77545e05eadc83973897a4523c9e8d84e9340" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==22.0.0" - }, - "pyasn1": { - "hashes": [ - "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", - "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" - ], - "markers": "python_version >= '3.8'", - "version": "==0.6.1" - }, - "pyasn1-modules": { - "hashes": [ - "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", - "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6" - ], - "markers": "python_version >= '3.8'", - "version": "==0.4.2" - }, - "pyathena": { - "hashes": [ - "sha256:b85ece213fac1c8332337b1e0e87e0ed70420168e05779f213c35e9318e71e3f", - "sha256:bf7d12f89fcc84e2b882549e0f0b2b661edd3404cbd7d04eefcef8651ca3e92f" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==3.21.1" - }, - "pycparser": { - "hashes": [ - "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", - "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934" - ], - "markers": "python_version >= '3.8'", - "version": "==2.23" - }, - "pygments": { - "hashes": [ - "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", - "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" - ], - "markers": "python_version >= '3.8'", - "version": "==2.19.2" - }, - "pyjwt": { - "hashes": [ - "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", - "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb" - ], - "markers": "python_version >= '3.9'", - "version": "==2.10.1" - }, - "pylint": { - "hashes": [ - "sha256:896d09afb0e78bbf2e030cd1f3d8dc92771a51f7e46828cbc3948a89cd03433a", - "sha256:a427fe76e0e5355e9fb9b604fd106c419cafb395886ba7f3cebebb03f30e081d" - ], - "index": "pypi", - "markers": "python_full_version >= '3.10.0'", - "version": "==4.0.3" - }, - "pyopenssl": { - "hashes": [ - "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", - "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329" - ], - "markers": "python_version >= '3.7'", - "version": "==25.3.0" - }, - "python-dateutil": { - "hashes": [ - "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", - "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==2.9.0.post0" - }, - "pytokens": { - "hashes": [ - "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", - "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3" - ], - "markers": "python_version >= '3.8'", - "version": "==0.3.0" - }, - "pytz": { - "hashes": [ - "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", - "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" - ], - "version": "==2025.2" - }, - "readme-renderer": { - "hashes": [ - "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", - "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1" - ], - "markers": "python_version >= '3.9'", - "version": "==44.0" - }, - "requests": { - "hashes": [ - "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", - "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.32.5" - }, - "requests-toolbelt": { - "hashes": [ - "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", - "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.0.0" - }, - "rfc3986": { - "hashes": [ - "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", - "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" - }, - "rich": { - "hashes": [ - "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", - "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" - ], - "markers": "python_full_version >= '3.8.0'", - "version": "==14.2.0" - }, - "rsa": { - "hashes": [ - "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", - "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75" - ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==4.9.1" - }, - "s3transfer": { - "hashes": [ - "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", - "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125" - ], - "markers": "python_version >= '3.9'", - "version": "==0.14.0" - }, - "setuptools": { - "hashes": [ - "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", - "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==80.9.0" - }, - "six": { - "hashes": [ - "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", - "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.17.0" - }, - "snowflake-connector-python": { - "hashes": [ - "sha256:0af10b207af3d2de2b130e89018d49a60f2e5cfe841f3bf459e58f2e1c4c4506", - "sha256:1841b60dc376639493dfc520cf39ad4f4da1f30286bba57e878d57414263d628", - "sha256:1afbd9e21180d2b4a76500ac2978b11865fdb3230609f2a9d80ba459fc27f2e4", - "sha256:1fb9fc9d8c2c7d209ba89282d367a32e75b0688afd4a3f02409e24f153c1a32e", - "sha256:283366b35df88cd0c71caf0215ba80370ddef4dd37d2adf43b24208c747231ee", - "sha256:2e4c285cc6a7f6431cff98c8f235a0fe9da2262462dd3dfc2b97120574a95cf9", - "sha256:32b1abfea32561d817b0a2f80b06d936cb32712af06bf7b848a428bfd857a10a", - "sha256:3fee7035f865088f948510b094101c8a0e5b22501891f2115f7fb1cb555de76a", - "sha256:41a46eb9824574c5f8068e3ed5c02a2dc0a733ed08ee81fa1fb3dd0ebe921728", - "sha256:4c068c8d3cd0c9736cb0679a9f544d34327e64415303bbfe07ec8ce3c5dae800", - "sha256:4ed2d593f1983939d5d8d88b212d86fd4f14f0ceefc1df9882b4a18534adbde9", - "sha256:51eb789a09dc6c62119cfabd044fba1a6b8378206f05a1e83ddb2e9cb49acc0b", - "sha256:5d89f608fde2fb0597ca5e020c4ac602027dc67f11b61b4d1e5448163bae4edc", - "sha256:65d37263dd288abb649820b7e34af96dc6b2d2115bf5521a2526245f81ddb0cb", - "sha256:7116cfa410d517328fd25fabffb54845b88667586718578c4333ce034fead1ba", - "sha256:783a9ab206563d7b52fdcdd7a72af62de811d3381ca64132fd3445537b4d041b", - "sha256:7a5fcb9a25a9b77b6cd86dfc6a6324b9910e15a493a916983229011ce3509b5f", - "sha256:8d3e96e1d09b07edca6c1f6ca675b6fdd05a4a7e428e4cdf6fb697d87b9f60fc", - "sha256:94e041e347b5151b66d19d6cfc3b3172dac1f51e44bbf7cf58f3989427dd464a", - "sha256:a8c570edff5a4888840dbe1e9e65c5e4d77d55c5c800cd359fe0903a769201e0", - "sha256:aeeb181a156333480f60b5f8ddbb3d087e288b4509adbef7993236defe4d7570", - "sha256:b211b4240596a225b895261a4ced2633e0262e82e2e32f6fb8dfc7d4bfedf8ca", - "sha256:b99f261c82be92224ac20c8c12bdf26ce3ed5dfd8a3df8a97f15a1e11c46ad27", - "sha256:bd1de3038b6d7059ca59f93e105aba2a673151c693cc4292f72f38bfaf147df2", - "sha256:cfa6b234f53ec624149e21156d0a98e43408d194f2e65bcfaf30acefd35a581e", - "sha256:e17a9e806823d3a0e578cf9349f6a93810a582b3132903ea9e1683854d08da00" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==3.18.0" - }, - "snowflake-sqlalchemy": { - "hashes": [ - "sha256:4ae5e5b458596ab2f0380c79b049978681a0490791add478d3c953613417d086", - "sha256:e6cf9f6309a9c3f4b3fd6e8808b2fb04886da123f4d58d96323a491732a5d496" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.7.7" - }, - "sortedcontainers": { - "hashes": [ - "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", - "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" - ], - "version": "==2.4.0" - }, - "sqlalchemy": { - "hashes": [ - "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7", - "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", - "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", - "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd", - "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1", - "sha256:11bac86b0deada30b6b5f93382712ff0e911fe8d31cb9bf46e6b149ae175eff0", - "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", - "sha256:17835885016b9e4d0135720160db3095dc78c583e7b902b6be799fb21035e749", - "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", - "sha256:1df4763760d1de0dfc8192cc96d8aa293eb1a44f8f7a5fbe74caf1b551905c5e", - "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", - "sha256:22be14009339b8bc16d6b9dc8780bacaba3402aa7581658e246114abbd2236e3", - "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3", - "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", - "sha256:2bf4bb6b3d6228fcf3a71b50231199fb94d2dd2611b66d33be0578ea3e6c2726", - "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d", - "sha256:2f19644f27c76f07e10603580a47278abb2a70311136a7f8fd27dc2e096b9013", - "sha256:2fc44e5965ea46909a416fff0af48a219faefd5773ab79e5f8a5fcd5d62b2667", - "sha256:2fcc4901a86ed81dc76703f3b93ff881e08761c63263c46991081fd7f034b165", - "sha256:3255d821ee91bdf824795e936642bbf43a4c7cedf5d1aed8d24524e66843aa74", - "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399", - "sha256:357bade0e46064f88f2c3a99808233e67b0051cdddf82992379559322dfeb183", - "sha256:3caef1ff89b1caefc28f0368b3bde21a7e3e630c2eddac16abd9e47bd27cc36a", - "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e", - "sha256:3fe166c7d00912e8c10d3a9a0ce105569a31a3d0db1a6e82c4e0f4bf16d5eca9", - "sha256:471733aabb2e4848d609141a9e9d56a427c0a038f4abf65dd19d7a21fd563632", - "sha256:4848395d932e93c1595e59a8672aa7400e8922c39bb9b0668ed99ac6fa867822", - "sha256:48bf7d383a35e668b984c805470518b635d48b95a3c57cb03f37eaa3551b5f9f", - "sha256:4c26ef74ba842d61635b0152763d057c8d48215d5be9bb8b7604116a059e9985", - "sha256:4d18cd0e9a0f37c9f4088e50e3839fcb69a380a0ec957408e0b57cff08ee0a26", - "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29", - "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b", - "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250", - "sha256:78e6c137ba35476adb5432103ae1534f2f5295605201d946a4198a0dea4b38e7", - "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5", - "sha256:7c77f3080674fc529b1bd99489378c7f63fcb4ba7f8322b79732e0258f0ea3ce", - "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e", - "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4", - "sha256:8e0e4e66fd80f277a8c3de016a81a554e76ccf6b8d881ee0b53200305a8433f6", - "sha256:9919e77403a483ab81e3423151e8ffc9dd992c20d2603bf17e4a8161111e55f5", - "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44", - "sha256:9e9018544ab07614d591a26c1bd4293ddf40752cc435caf69196740516af7100", - "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", - "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", - "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e", - "sha256:cbe4f85f50c656d753890f39468fcd8190c5f08282caf19219f684225bfd5fd2", - "sha256:cc2856d24afa44295735e72f3c75d6ee7fdd4336d8d3a8f3d44de7aa6b766df2", - "sha256:d733dec0614bb8f4bcb7c8af88172b974f685a31dc3a65cca0527e3120de5606", - "sha256:dc8b3850d2a601ca2320d081874033684e246d28e1c5e89db0864077cfc8f5a9", - "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa", - "sha256:e998cf7c29473bd077704cea3577d23123094311f59bdc4af551923b168332b1", - "sha256:ebac3f0b5732014a126b43c2b7567f2f0e0afea7d9119a3378bde46d3dcad88e", - "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", - "sha256:f4a172b31785e2f00780eccab00bc240ccdbfdb8345f1e6063175b3ff12ad1b0", - "sha256:f7027414f2b88992877573ab780c19ecb54d3a536bef3397933573d6b5068be4", - "sha256:f9480c0740aabd8cb29c329b422fb65358049840b34aba0adf63162371d2a96e", - "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.0.44" - }, - "sqlalchemy-bigquery": { - "hashes": [ - "sha256:0fe7634cd954f3e74f5e2db6d159f9e5ee87a47fbe8d52eac3cd3bb3dadb3a77", - "sha256:fe937a0d1f4cf7219fcf5d4995c6718805b38d4df43e29398dec5dc7b6d1987e" - ], - "index": "pypi", - "markers": "python_version < '3.15' and python_version >= '3.8'", - "version": "==1.16.0" - }, - "tenacity": { - "hashes": [ - "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", - "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138" - ], - "markers": "python_version >= '3.9'", - "version": "==9.1.2" - }, - "tomli": { - "hashes": [ - "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", - "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", - "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", - "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", - "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", - "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", - "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", - "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", - "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", - "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", - "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", - "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", - "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", - "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", - "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", - "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", - "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", - "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", - "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", - "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", - "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", - "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", - "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", - "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", - "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", - "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", - "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", - "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", - "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", - "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", - "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", - "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", - "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", - "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", - "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", - "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", - "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", - "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", - "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", - "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", - "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", - "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876" - ], - "markers": "python_version >= '3.8'", - "version": "==2.3.0" - }, - "tomlkit": { - "hashes": [ - "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", - "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0" - ], - "markers": "python_version >= '3.8'", - "version": "==0.13.3" - }, - "tqdm": { - "hashes": [ - "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", - "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==4.67.1" - }, - "twine": { - "hashes": [ - "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", - "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==6.2.0" - }, - "types-protobuf": { - "hashes": [ - "sha256:641002611ff87dd9fedc38a39a29cacb9907ae5ce61489b53e99ca2074bef764", - "sha256:a15109d38f7cfefd2539ef86d3f93a6a41c7cad53924f8aa1a51eaddbb72a660" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==6.32.1.20251105" - }, - "types-python-dateutil": { - "hashes": [ - "sha256:8a47f2c3920f52a994056b8786309b43143faa5a64d4cbb2722d6addabdf1a58", - "sha256:9cf9c1c582019753b8639a081deefd7e044b9fa36bd8217f565c6c4e36ee0624" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.9.0.20251115" - }, - "types-requests": { - "hashes": [ - "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", - "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.32.4.20250913" - }, - "typing-extensions": { - "hashes": [ - "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", - "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" - ], - "markers": "python_version >= '3.9'", - "version": "==4.15.0" - }, - "tzdata": { - "hashes": [ - "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", - "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" - ], - "markers": "python_version >= '2'", - "version": "==2025.2" - }, - "urllib3": { - "hashes": [ - "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", - "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" - ], - "markers": "python_version >= '3.9'", - "version": "==2.5.0" - }, - "wheel": { - "hashes": [ - "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", - "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==0.45.1" - }, - "zipp": { - "hashes": [ - "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", - "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166" - ], - "markers": "python_version >= '3.9'", - "version": "==3.23.0" - } - }, - "develop": {} -} diff --git a/VERSION b/VERSION deleted file mode 100644 index 66ce77b7..00000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -7.0.0 diff --git a/autoformat.sh b/autoformat.sh index 56e71af7..643426b4 100755 --- a/autoformat.sh +++ b/autoformat.sh @@ -2,5 +2,5 @@ # Autoformats all Python files with isort and black. -isort -s exabel_data_sdk/stubs exabel_data_sdk setup.py -black --exclude exabel_data_sdk/stubs exabel_data_sdk setup.py +ruff check --fix exabel_data_sdk +ruff format exabel_data_sdk diff --git a/build.sh b/build.sh index 705b65e2..e29c2ca1 100755 --- a/build.sh +++ b/build.sh @@ -7,4 +7,4 @@ bash style.sh bash tests.sh rm -rf build rm -rf dist -python3 setup.py sdist bdist_wheel +uv build diff --git a/check_package_version.sh b/check_package_version.sh index 725c4beb..40c9675a 100755 --- a/check_package_version.sh +++ b/check_package_version.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -VERSION="v$( BatchCreateFiscalPeriodsResponse: @@ -30,6 +32,7 @@ def batch_create_fiscal_periods( request, metadata=self.metadata, timeout=self.config.timeout ) + @handle_grpc_error def list_company_fiscal_periods( self, request: ListFiscalPeriodsRequest ) -> ListFiscalPeriodsResponse: @@ -37,9 +40,11 @@ def list_company_fiscal_periods( request, metadata=self.metadata, timeout=self.config.timeout ) + @handle_grpc_error def delete_fiscal_periods(self, request: DeleteFiscalPeriodRequest) -> None: self.stub.DeleteFiscalPeriod(request, metadata=self.metadata, timeout=self.config.timeout) + @handle_grpc_error def list_companies_with_fiscal_periods( self, request: ListCompaniesWithFiscalPeriodsRequest ) -> ListCompaniesWithFiscalPeriodsResponse: diff --git a/exabel_data_sdk/client/api/api_client/grpc/holiday_grpc_client.py b/exabel_data_sdk/client/api/api_client/grpc/holiday_grpc_client.py new file mode 100644 index 00000000..9f088be8 --- /dev/null +++ b/exabel_data_sdk/client/api/api_client/grpc/holiday_grpc_client.py @@ -0,0 +1,63 @@ +from exabel_data_sdk.client.api.api_client.exabel_api_group import ExabelApiGroup +from exabel_data_sdk.client.api.api_client.grpc.base_grpc_client import BaseGrpcClient +from exabel_data_sdk.client.api.api_client.holiday_api_client import HolidayApiClient +from exabel_data_sdk.client.api.error_handler import handle_grpc_error +from exabel_data_sdk.client.client_config import ClientConfig +from exabel_data_sdk.stubs.exabel.api.data.v1.holiday_messages_pb2 import HolidaySpecification +from exabel_data_sdk.stubs.exabel.api.data.v1.holiday_service_pb2 import ( + CreateHolidaySpecificationRequest, + DeleteHolidaySpecificationRequest, + GetHolidaySpecificationRequest, + ListHolidaySpecificationsRequest, + ListHolidaySpecificationsResponse, + UpdateHolidaySpecificationRequest, +) +from exabel_data_sdk.stubs.exabel.api.data.v1.holiday_service_pb2_grpc import HolidayServiceStub + + +class HolidayGrpcClient(HolidayApiClient, BaseGrpcClient): + """ + Client which sends holiday requests to the Exabel Data API with gRPC. + """ + + def __init__(self, config: ClientConfig): + super().__init__(config, ExabelApiGroup.DATA_API) + self.stub = HolidayServiceStub(self.channel) + + @handle_grpc_error + def list_holiday_specifications( + self, request: ListHolidaySpecificationsRequest + ) -> ListHolidaySpecificationsResponse: + return self.stub.ListHolidaySpecifications( + request, metadata=self.metadata, timeout=self.config.timeout + ) + + @handle_grpc_error + def get_holiday_specification( + self, request: GetHolidaySpecificationRequest + ) -> HolidaySpecification: + return self.stub.GetHolidaySpecification( + request, metadata=self.metadata, timeout=self.config.timeout + ) + + @handle_grpc_error + def create_holiday_specification( + self, request: CreateHolidaySpecificationRequest + ) -> HolidaySpecification: + return self.stub.CreateHolidaySpecification( + request, metadata=self.metadata, timeout=self.config.timeout + ) + + @handle_grpc_error + def update_holiday_specification( + self, request: UpdateHolidaySpecificationRequest + ) -> HolidaySpecification: + return self.stub.UpdateHolidaySpecification( + request, metadata=self.metadata, timeout=self.config.timeout + ) + + @handle_grpc_error + def delete_holiday_specification(self, request: DeleteHolidaySpecificationRequest) -> None: + self.stub.DeleteHolidaySpecification( + request, metadata=self.metadata, timeout=self.config.timeout + ) diff --git a/exabel_data_sdk/client/api/api_client/grpc/kpi_grpc_client.py b/exabel_data_sdk/client/api/api_client/grpc/kpi_grpc_client.py index c6ab4244..c41a48bc 100644 --- a/exabel_data_sdk/client/api/api_client/grpc/kpi_grpc_client.py +++ b/exabel_data_sdk/client/api/api_client/grpc/kpi_grpc_client.py @@ -13,6 +13,8 @@ ListCompanyKpiModelResultsResponse, ListKpiMappingResultsRequest, ListKpiMappingResultsResponse, + ListKpiScreenResultsRequest, + ListKpiScreenResultsResponse, ) from exabel_data_sdk.stubs.exabel.api.analytics.v1.kpi_service_pb2_grpc import KpiServiceStub @@ -60,3 +62,10 @@ def list_company_kpi_model_results( return self.stub.ListCompanyKpiModelResults( request, metadata=self.metadata, timeout=self.config.timeout ) + + def list_kpi_screen_results( + self, request: ListKpiScreenResultsRequest + ) -> ListKpiScreenResultsResponse: + return self.stub.ListKpiScreenResults( + request, metadata=self.metadata, timeout=self.config.timeout + ) diff --git a/exabel_data_sdk/client/api/api_client/holiday_api_client.py b/exabel_data_sdk/client/api/api_client/holiday_api_client.py new file mode 100644 index 00000000..86b29eb2 --- /dev/null +++ b/exabel_data_sdk/client/api/api_client/holiday_api_client.py @@ -0,0 +1,43 @@ +from abc import ABC, abstractmethod + +from exabel_data_sdk.stubs.exabel.api.data.v1.holiday_messages_pb2 import HolidaySpecification +from exabel_data_sdk.stubs.exabel.api.data.v1.holiday_service_pb2 import ( + CreateHolidaySpecificationRequest, + DeleteHolidaySpecificationRequest, + GetHolidaySpecificationRequest, + ListHolidaySpecificationsRequest, + ListHolidaySpecificationsResponse, + UpdateHolidaySpecificationRequest, +) + + +class HolidayApiClient(ABC): + """Superclass for clients that send holiday requests to the Exabel Data API.""" + + @abstractmethod + def list_holiday_specifications( + self, request: ListHolidaySpecificationsRequest + ) -> ListHolidaySpecificationsResponse: + """List all holiday specifications.""" + + @abstractmethod + def get_holiday_specification( + self, request: GetHolidaySpecificationRequest + ) -> HolidaySpecification: + """Get a holiday specification by name.""" + + @abstractmethod + def create_holiday_specification( + self, request: CreateHolidaySpecificationRequest + ) -> HolidaySpecification: + """Create a new holiday specification.""" + + @abstractmethod + def update_holiday_specification( + self, request: UpdateHolidaySpecificationRequest + ) -> HolidaySpecification: + """Update an existing holiday specification.""" + + @abstractmethod + def delete_holiday_specification(self, request: DeleteHolidaySpecificationRequest) -> None: + """Delete a holiday specification.""" diff --git a/exabel_data_sdk/client/api/api_client/kpi_api_client.py b/exabel_data_sdk/client/api/api_client/kpi_api_client.py index c63a4211..acd07665 100644 --- a/exabel_data_sdk/client/api/api_client/kpi_api_client.py +++ b/exabel_data_sdk/client/api/api_client/kpi_api_client.py @@ -11,6 +11,8 @@ ListCompanyKpiModelResultsResponse, ListKpiMappingResultsRequest, ListKpiMappingResultsResponse, + ListKpiScreenResultsRequest, + ListKpiScreenResultsResponse, ) @@ -48,3 +50,9 @@ def list_company_kpi_model_results( self, request: ListCompanyKpiModelResultsRequest ) -> ListCompanyKpiModelResultsResponse: """List model results for a single company KPI.""" + + @abstractmethod + def list_kpi_screen_results( + self, request: ListKpiScreenResultsRequest + ) -> ListKpiScreenResultsResponse: + """List KPI screen results.""" diff --git a/exabel_data_sdk/client/api/bulk_import.py b/exabel_data_sdk/client/api/bulk_import.py index fa5e59d2..0e7705e2 100644 --- a/exabel_data_sdk/client/api/bulk_import.py +++ b/exabel_data_sdk/client/api/bulk_import.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from itertools import chain from time import sleep, time -from typing import Callable, Generic, Mapping, Optional, Sequence, Union, overload +from typing import Callable, Generic, Mapping, Sequence, overload import pandas as pd @@ -58,8 +58,8 @@ def get_time_series_result_from_violations( @staticmethod def get_time_series_result_from_violations( - resource: Union[pd.Series, TimeSeries], violations: Mapping[str, Violation] - ) -> Union[ResourceCreationResult[pd.Series], ResourceCreationResult[TimeSeries]]: + resource: pd.Series | TimeSeries, violations: Mapping[str, Violation] + ) -> ResourceCreationResult[pd.Series] | ResourceCreationResult[TimeSeries]: """Get a time series creation result from precondition failure violations.""" resource_name = TimeSeriesResourceName.from_string(resource.name) entity_name = resource_name.entity_name @@ -107,7 +107,7 @@ def _process( resources: Sequence[ResourceT], import_func: Callable[ [Sequence[ResourceT]], - Union[Sequence[ResourceCreationStatus], Sequence[ResourceCreationResult]], + Sequence[ResourceCreationStatus] | Sequence[ResourceCreationResult], ], abort: Callable, ) -> None: @@ -159,20 +159,16 @@ def bulk_import( resources: Sequence[ResourceT], import_func: Callable[ [Sequence[ResourceT]], - Union[Sequence[ResourceCreationStatus], Sequence[ResourceCreationResult]], + Sequence[ResourceCreationStatus] | Sequence[ResourceCreationResult], ], threads: int = DEFAULT_NUMBER_OF_THREADS_FOR_IMPORT, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, # Deprecated arguments - resources_batches: Optional[ # pylint: disable=unused-argument - Sequence[Sequence[ResourceT]] - ] = None, - threads_for_import_func: Optional[int] = None, # pylint: disable=unused-argument - threads_for_insert_func: Optional[int] = None, # pylint: disable=unused-argument - insert_func: Optional[ # pylint: disable=unused-argument - Callable[[ResourceT], ResourceCreationStatus] - ] = None, + resources_batches: Sequence[Sequence[ResourceT]] | None = None, + threads_for_import_func: int | None = None, + threads_for_insert_func: int | None = None, + insert_func: Callable[[ResourceT], ResourceCreationStatus] | None = None, ) -> ResourceCreationResults[ResourceT]: """ Call the provided import function with batches of the provided resources, while catching errors @@ -236,7 +232,7 @@ def _bulk_import( resources: Sequence[ResourceT], import_func: Callable[ [Sequence[ResourceT]], - Union[Sequence[ResourceCreationStatus], Sequence[ResourceCreationResult]], + Sequence[ResourceCreationStatus] | Sequence[ResourceCreationResult], ], threads: int, ) -> None: diff --git a/exabel_data_sdk/client/api/bulk_insert.py b/exabel_data_sdk/client/api/bulk_insert.py index 73469869..75b92759 100644 --- a/exabel_data_sdk/client/api/bulk_insert.py +++ b/exabel_data_sdk/client/api/bulk_insert.py @@ -1,7 +1,7 @@ import logging from concurrent.futures.thread import ThreadPoolExecutor from time import sleep, time -from typing import Callable, Optional, Sequence +from typing import Callable, Sequence from exabel_data_sdk.client.api.data_classes.request_error import ErrorType, RequestError from exabel_data_sdk.client.api.resource_creation_result import ( @@ -118,7 +118,7 @@ def bulk_insert( insert_func: Callable[[ResourceT], ResourceCreationStatus], retries: int = DEFAULT_NUMBER_OF_RETRIES, threads: int = DEFAULT_NUMBER_OF_THREADS, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, ) -> ResourceCreationResults[ResourceT]: """ Calls the provided insert function with each of the provided resources, diff --git a/exabel_data_sdk/client/api/calendar_api.py b/exabel_data_sdk/client/api/calendar_api.py index b68f44c4..d5f33b51 100644 --- a/exabel_data_sdk/client/api/calendar_api.py +++ b/exabel_data_sdk/client/api/calendar_api.py @@ -1,5 +1,4 @@ from collections.abc import Sequence -from typing import Optional from exabel_data_sdk.client.api.api_client.grpc.calendar_grpc_client import CalendarGrpcClient from exabel_data_sdk.client.client_config import ClientConfig @@ -43,7 +42,7 @@ def create_fiscal_periods( self.client.batch_create_fiscal_periods(request) def list_fiscal_periods( - self, company: str, frequency: Optional[Frequency.ValueType] = None + self, company: str, frequency: Frequency.ValueType | None = None ) -> Sequence[FiscalPeriod]: """ List the fiscal periods for a company. diff --git a/exabel_data_sdk/client/api/data_classes/company_kpi_mapping_results.py b/exabel_data_sdk/client/api/data_classes/company_kpi_mapping_results.py index f5f975ae..aa95aff0 100644 --- a/exabel_data_sdk/client/api/data_classes/company_kpi_mapping_results.py +++ b/exabel_data_sdk/client/api/data_classes/company_kpi_mapping_results.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List, Sequence +from typing import Sequence from exabel_data_sdk.client.api.data_classes.company import Company from exabel_data_sdk.client.api.data_classes.kpi import Kpi @@ -23,7 +23,7 @@ class SingleCompanyKpiMappingResults: """ kpi: Kpi - data: List[KpiMappingResultData] + data: list[KpiMappingResultData] @staticmethod def from_proto(proto: ProtoSingleCompanyKpiMappingResults) -> "SingleCompanyKpiMappingResults": diff --git a/exabel_data_sdk/client/api/data_classes/company_kpi_models.py b/exabel_data_sdk/client/api/data_classes/company_kpi_models.py index 0f5a9c53..f541bd4d 100644 --- a/exabel_data_sdk/client/api/data_classes/company_kpi_models.py +++ b/exabel_data_sdk/client/api/data_classes/company_kpi_models.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional, Sequence +from typing import Sequence from exabel_data_sdk.client.api.data_classes.kpi_mapping_model import KpiMappingModel from exabel_data_sdk.client.api.data_classes.kpi_model import KpiModel @@ -20,8 +20,8 @@ class CompanyKpiModels: kpi_mapping_models: The KPI mapping models. """ - exabel_model: Optional[KpiModel] - hierarchical_model: Optional[KpiModel] + exabel_model: KpiModel | None + hierarchical_model: KpiModel | None custom_models: Sequence[KpiModel] kpi_mapping_models: Sequence[KpiMappingModel] diff --git a/exabel_data_sdk/client/api/data_classes/data_set.py b/exabel_data_sdk/client/api/data_classes/data_set.py index e54cb78e..0b703fb2 100644 --- a/exabel_data_sdk/client/api/data_classes/data_set.py +++ b/exabel_data_sdk/client/api/data_classes/data_set.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence +from typing import Sequence from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import DataSet as ProtoDataSet @@ -23,10 +23,10 @@ def __init__( name: str, display_name: str, description: str = "", - signals: Optional[Sequence[str]] = None, + signals: Sequence[str] | None = None, read_only: bool = False, - derived_signals: Optional[Sequence[str]] = None, - highlighted_signals: Optional[Sequence[str]] = None, + derived_signals: Sequence[str] | None = None, + highlighted_signals: Sequence[str] | None = None, ): """ Create a data set resource in the Data API. diff --git a/exabel_data_sdk/client/api/data_classes/derived_signal.py b/exabel_data_sdk/client/api/data_classes/derived_signal.py index f160e368..9260cb40 100644 --- a/exabel_data_sdk/client/api/data_classes/derived_signal.py +++ b/exabel_data_sdk/client/api/data_classes/derived_signal.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Optional from google.protobuf.wrappers_pb2 import Int32Value @@ -68,10 +67,10 @@ class DerivedSignalMetaData: def __init__( self, unit: DerivedSignalUnit = DerivedSignalUnit.NUMBER, - decimals: Optional[int] = None, + decimals: int | None = None, signal_type: DerivedSignalType = DerivedSignalType.DERIVED_SIGNAL, - downsampling_method: Optional[Aggregation.ValueType] = None, - change: Optional[Change.ValueType] = None, + downsampling_method: Aggregation.ValueType | None = None, + change: Change.ValueType | None = None, ): """ Create metadata for a derived signal. @@ -111,11 +110,9 @@ def to_proto(self) -> ProtoDerivedSignalMetadata: decimals=Int32Value(value=self.decimals) if self.decimals is not None else None, type=self.signal_type.value, downsampling_method=( - int(self.downsampling_method) - if self.downsampling_method - else 0 # type: ignore[arg-type] + self.downsampling_method if self.downsampling_method else Aggregation.ValueType(0) ), - change=int(self.change) if self.change else 0, # type: ignore[arg-type] + change=self.change if self.change else Change.ValueType(0), ) def __eq__(self, other: object) -> bool: @@ -154,11 +151,11 @@ class DerivedSignal: def __init__( self, - name: Optional[str], + name: str | None, label: str, expression: str, - description: Optional[str] = None, - display_name: Optional[str] = None, + description: str | None = None, + display_name: str | None = None, metadata: DerivedSignalMetaData = DerivedSignalMetaData(), ): """ diff --git a/exabel_data_sdk/client/api/data_classes/entity.py b/exabel_data_sdk/client/api/data_classes/entity.py index 947747be..53be733d 100644 --- a/exabel_data_sdk/client/api/data_classes/entity.py +++ b/exabel_data_sdk/client/api/data_classes/entity.py @@ -1,5 +1,5 @@ import re -from typing import Mapping, Optional, Union +from typing import Mapping from exabel_data_sdk.client.api.proto_utils import from_struct, to_struct from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import Entity as ProtoEntity @@ -28,7 +28,7 @@ def __init__( name: str, display_name: str, description: str = "", - properties: Optional[Mapping[str, Union[str, bool, int, float]]] = None, + properties: Mapping[str, str | bool | int | float] | None = None, read_only: bool = False, ): r""" diff --git a/exabel_data_sdk/client/api/data_classes/fiscal_period.py b/exabel_data_sdk/client/api/data_classes/fiscal_period.py index 478cc829..5edff18f 100644 --- a/exabel_data_sdk/client/api/data_classes/fiscal_period.py +++ b/exabel_data_sdk/client/api/data_classes/fiscal_period.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Optional import pandas as pd @@ -14,12 +13,14 @@ class FiscalPeriod: Represents a fiscal period. Attributes: - period_end: The end date of the fiscal period. - label: The label of the fiscal period. + period_end: The end date of the fiscal period. + label: The label of the fiscal period. + report_date: The report date of the fiscal period. """ - period_end: Optional[pd.Timestamp] - label: Optional[str] + period_end: pd.Timestamp | None + label: str | None + report_date: pd.Timestamp | None @staticmethod def from_proto(proto: ProtoFiscalPeriod) -> "FiscalPeriod": @@ -35,4 +36,13 @@ def from_proto(proto: ProtoFiscalPeriod) -> "FiscalPeriod": else None ), label=proto.label if proto.label else None, + report_date=( + pd.Timestamp( + year=proto.report_date.year, + month=proto.report_date.month, + day=proto.report_date.day, + ) + if proto.HasField("report_date") + else None + ), ) diff --git a/exabel_data_sdk/client/api/data_classes/folder.py b/exabel_data_sdk/client/api/data_classes/folder.py index bef342b3..beffd187 100644 --- a/exabel_data_sdk/client/api/data_classes/folder.py +++ b/exabel_data_sdk/client/api/data_classes/folder.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence +from typing import Sequence from exabel_data_sdk.client.api.data_classes.folder_item import FolderItem from exabel_data_sdk.stubs.exabel.api.management.v1.all_pb2 import Folder as ProtoFolder @@ -22,7 +22,7 @@ def __init__( display_name: str, write: bool, items: Sequence[FolderItem], - description: Optional[str] = None, + description: str | None = None, ): """ Create a Folder. diff --git a/exabel_data_sdk/client/api/data_classes/folder_item.py b/exabel_data_sdk/client/api/data_classes/folder_item.py index 37e2bc0f..3b431dba 100644 --- a/exabel_data_sdk/client/api/data_classes/folder_item.py +++ b/exabel_data_sdk/client/api/data_classes/folder_item.py @@ -1,6 +1,5 @@ from datetime import datetime from enum import Enum -from typing import Optional from dateutil import tz from google.protobuf.timestamp_pb2 import Timestamp as ProtoTimestamp @@ -61,8 +60,8 @@ def __init__( description: str, create_time: datetime, update_time: datetime, - created_by: Optional[str], - updated_by: Optional[str], + created_by: str | None, + updated_by: str | None, ): """ Create a FolderItem. diff --git a/exabel_data_sdk/client/api/data_classes/kpi.py b/exabel_data_sdk/client/api/data_classes/kpi.py index 9d6af52c..6c0d4057 100644 --- a/exabel_data_sdk/client/api/data_classes/kpi.py +++ b/exabel_data_sdk/client/api/data_classes/kpi.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from enum import Enum -from typing import Optional from exabel_data_sdk.stubs.exabel.api.analytics.v1.kpi_messages_pb2 import Kpi as ProtoKpi @@ -34,8 +33,8 @@ class Kpi: type: KpiType value: str freq: str - display_name: Optional[str] = None - is_ratio: Optional[bool] = None + display_name: str | None = None + is_ratio: bool | None = None @staticmethod def from_proto(proto: ProtoKpi) -> "Kpi": diff --git a/exabel_data_sdk/client/api/data_classes/kpi_hierarchy.py b/exabel_data_sdk/client/api/data_classes/kpi_hierarchy.py index af348aea..94ec3012 100644 --- a/exabel_data_sdk/client/api/data_classes/kpi_hierarchy.py +++ b/exabel_data_sdk/client/api/data_classes/kpi_hierarchy.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional, Sequence +from typing import Sequence from exabel_data_sdk.client.api.data_classes.kpi import Kpi from exabel_data_sdk.stubs.exabel.api.analytics.v1.kpi_messages_pb2 import ( @@ -25,8 +25,8 @@ class KpiBreakdownNode: children: The children of this node. """ - kpi: Optional[Kpi] - header: Optional[str] + kpi: Kpi | None + header: str | None children: Sequence["KpiBreakdownNode"] @staticmethod diff --git a/exabel_data_sdk/client/api/data_classes/kpi_mapping_result_data.py b/exabel_data_sdk/client/api/data_classes/kpi_mapping_result_data.py index 5d62b3c9..6389fd79 100644 --- a/exabel_data_sdk/client/api/data_classes/kpi_mapping_result_data.py +++ b/exabel_data_sdk/client/api/data_classes/kpi_mapping_result_data.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Optional import pandas as pd @@ -39,20 +38,20 @@ class KpiMappingResultData: """ source: KpiMappingGroupReference - model_mape: Optional[float] - model_mae: Optional[float] - model_hit_rate: Optional[float] - model_quality: Optional[ModelQuality] - number_of_data_points: Optional[int] - mae_pop: Optional[float] - mae_yoy: Optional[float] - correlation_abs: Optional[float] - correlation_pop: Optional[float] - correlation_yoy: Optional[float] - p_value_abs: Optional[float] - p_value_pop: Optional[float] - p_value_yoy: Optional[float] - last_value_date: Optional[pd.Timestamp] + model_mape: float | None + model_mae: float | None + model_hit_rate: float | None + model_quality: ModelQuality | None + number_of_data_points: int | None + mae_pop: float | None + mae_yoy: float | None + correlation_abs: float | None + correlation_pop: float | None + correlation_yoy: float | None + p_value_abs: float | None + p_value_pop: float | None + p_value_yoy: float | None + last_value_date: pd.Timestamp | None @staticmethod def from_proto(proto: ProtoKpiMappingResultData) -> "KpiMappingResultData": diff --git a/exabel_data_sdk/client/api/data_classes/kpi_model.py b/exabel_data_sdk/client/api/data_classes/kpi_model.py index dea131ff..e29f0441 100644 --- a/exabel_data_sdk/client/api/data_classes/kpi_model.py +++ b/exabel_data_sdk/client/api/data_classes/kpi_model.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Optional from exabel_data_sdk.client.api.data_classes.kpi_model_data import KpiModelData from exabel_data_sdk.client.api.data_classes.kpi_model_runs import KpiModelRuns @@ -26,7 +25,7 @@ class KpiModel: display_name: str data: KpiModelData weights: KpiModelWeightGroups - model_runs: Optional[KpiModelRuns] + model_runs: KpiModelRuns | None @staticmethod def from_proto(proto: ProtoKpiModel) -> "KpiModel": diff --git a/exabel_data_sdk/client/api/data_classes/kpi_model_data.py b/exabel_data_sdk/client/api/data_classes/kpi_model_data.py index db531d29..d693cfdf 100644 --- a/exabel_data_sdk/client/api/data_classes/kpi_model_data.py +++ b/exabel_data_sdk/client/api/data_classes/kpi_model_data.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Optional import pandas as pd @@ -40,26 +39,26 @@ class KpiModelData: error: Optional error, if the model estimation failed. """ - prediction: Optional[float] - prediction_yoy_rel: Optional[float] - prediction_yoy_abs: Optional[float] - consensus: Optional[float] - consensus_yoy_rel: Optional[float] - consensus_yoy_abs: Optional[float] - delta_abs: Optional[float] - delta_rel: Optional[float] - delta_by_error: Optional[float] - model_quality: Optional[ModelQuality] - mape: Optional[float] - mape_pit: Optional[float] - mae: Optional[float] - mae_pit: Optional[float] - error_count: Optional[int] - hit_rate: Optional[float] - hit_rate_count: Optional[int] - revision_1_week: Optional[float] - revision_1_month: Optional[float] - date: Optional[pd.Timestamp] + prediction: float | None + prediction_yoy_rel: float | None + prediction_yoy_abs: float | None + consensus: float | None + consensus_yoy_rel: float | None + consensus_yoy_abs: float | None + delta_abs: float | None + delta_rel: float | None + delta_by_error: float | None + model_quality: ModelQuality | None + mape: float | None + mape_pit: float | None + mae: float | None + mae_pit: float | None + error_count: int | None + hit_rate: float | None + hit_rate_count: int | None + revision_1_week: float | None + revision_1_month: float | None + date: pd.Timestamp | None error: str @staticmethod diff --git a/exabel_data_sdk/client/api/data_classes/kpi_model_run.py b/exabel_data_sdk/client/api/data_classes/kpi_model_run.py index 60408562..e0a50c9b 100644 --- a/exabel_data_sdk/client/api/data_classes/kpi_model_run.py +++ b/exabel_data_sdk/client/api/data_classes/kpi_model_run.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from datetime import datetime -from typing import Optional from dateutil import tz from google.protobuf.timestamp_pb2 import Timestamp as ProtoTimestamp @@ -22,10 +21,10 @@ class KpiModelRun: error: An optional error message if the run failed. """ - created_at: Optional[datetime] - started_at: Optional[datetime] - finished_at: Optional[datetime] - error: Optional[str] + created_at: datetime | None + started_at: datetime | None + finished_at: datetime | None + error: str | None @staticmethod def from_proto(proto: ProtoKpiModelRun) -> "KpiModelRun": @@ -50,6 +49,6 @@ def from_proto(proto: ProtoKpiModelRun) -> "KpiModelRun": ) @staticmethod - def _proto_timestamp_to_datetime(timestamp: ProtoTimestamp) -> Optional[datetime]: + def _proto_timestamp_to_datetime(timestamp: ProtoTimestamp) -> datetime | None: """Convert a protobuf Timestamp to a datetime object.""" return datetime.fromtimestamp(timestamp.seconds, tz=tz.tzutc()) diff --git a/exabel_data_sdk/client/api/data_classes/kpi_model_runs.py b/exabel_data_sdk/client/api/data_classes/kpi_model_runs.py index 8d2acb7a..9c4792a6 100644 --- a/exabel_data_sdk/client/api/data_classes/kpi_model_runs.py +++ b/exabel_data_sdk/client/api/data_classes/kpi_model_runs.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Optional from exabel_data_sdk.client.api.data_classes.kpi_model_run import KpiModelRun from exabel_data_sdk.stubs.exabel.api.analytics.v1.kpi_messages_pb2 import ( @@ -18,9 +17,9 @@ class KpiModelRuns: pit_backtest_run: PiT backtest run. """ - initial_run: Optional[KpiModelRun] - daily_run: Optional[KpiModelRun] - pit_backtest_run: Optional[KpiModelRun] + initial_run: KpiModelRun | None + daily_run: KpiModelRun | None + pit_backtest_run: KpiModelRun | None @staticmethod def from_proto(proto: ProtoKpiModelRuns) -> "KpiModelRuns": diff --git a/exabel_data_sdk/client/api/data_classes/kpi_model_weight_groups.py b/exabel_data_sdk/client/api/data_classes/kpi_model_weight_groups.py index cc1dc684..6358dbc8 100644 --- a/exabel_data_sdk/client/api/data_classes/kpi_model_weight_groups.py +++ b/exabel_data_sdk/client/api/data_classes/kpi_model_weight_groups.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional, Sequence +from typing import Sequence from exabel_data_sdk.client.api.data_classes.kpi_mapping_group_reference import ( KpiMappingGroupReference, @@ -26,7 +26,7 @@ class KpiModelFeatureWeight: """ display_name: str - weight: Optional[float] + weight: float | None @staticmethod def from_proto( @@ -51,7 +51,7 @@ class KpiModelWeightGroup: """ display_name: str - group: Optional[KpiMappingGroupReference] + group: KpiMappingGroupReference | None weights: Sequence[KpiModelFeatureWeight] @staticmethod diff --git a/exabel_data_sdk/client/api/data_classes/kpi_screen_results.py b/exabel_data_sdk/client/api/data_classes/kpi_screen_results.py new file mode 100644 index 00000000..725fce27 --- /dev/null +++ b/exabel_data_sdk/client/api/data_classes/kpi_screen_results.py @@ -0,0 +1,38 @@ +from dataclasses import dataclass +from typing import Sequence + +from exabel_data_sdk.client.api.data_classes.company import Company +from exabel_data_sdk.client.api.data_classes.company_kpi_model_result import CompanyKpiModelResult +from exabel_data_sdk.client.api.data_classes.fiscal_period import FiscalPeriod +from exabel_data_sdk.stubs.exabel.api.analytics.v1.kpi_messages_pb2 import ( + KpiScreenCompanyResult as ProtoKpiScreenCompanyResult, +) + + +@dataclass +class KpiScreenCompanyResult: + """ + KPI screen results for a single company. + + Attributes: + company: The company. + fiscal_period: The fiscal period for this result. + results: KPI model results for this company. + """ + + company: Company + fiscal_period: FiscalPeriod | None + results: Sequence[CompanyKpiModelResult] + + @staticmethod + def from_proto(proto: ProtoKpiScreenCompanyResult) -> "KpiScreenCompanyResult": + """Create a KpiScreenCompanyResult from the given protobuf message.""" + return KpiScreenCompanyResult( + company=Company.from_proto(proto.company), + fiscal_period=( + FiscalPeriod.from_proto(proto.fiscal_period) + if proto.HasField("fiscal_period") + else None + ), + results=[CompanyKpiModelResult.from_proto(r) for r in proto.results], + ) diff --git a/exabel_data_sdk/client/api/data_classes/prediction_model_run.py b/exabel_data_sdk/client/api/data_classes/prediction_model_run.py index a15f7984..a73dcf26 100644 --- a/exabel_data_sdk/client/api/data_classes/prediction_model_run.py +++ b/exabel_data_sdk/client/api/data_classes/prediction_model_run.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Optional from exabel_data_sdk.stubs.exabel.api.analytics.v1.all_pb2 import ( PredictionModelRun as ProtoPredictionModelRun, @@ -39,10 +38,10 @@ class PredictionModelRun: def __init__( self, - name: Optional[str] = None, + name: str | None = None, description: str = "", configuration: ModelConfiguration = ModelConfiguration.LATEST, - configuration_source: Optional[int] = None, + configuration_source: int | None = None, auto_activate: bool = False, ): """ diff --git a/exabel_data_sdk/client/api/data_classes/relationship.py b/exabel_data_sdk/client/api/data_classes/relationship.py index 2e2b5e42..75c690b9 100644 --- a/exabel_data_sdk/client/api/data_classes/relationship.py +++ b/exabel_data_sdk/client/api/data_classes/relationship.py @@ -1,4 +1,4 @@ -from typing import Mapping, Optional, Union +from typing import Mapping from exabel_data_sdk.client.api.proto_utils import from_struct, to_struct from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import Relationship as ProtoRelationship @@ -30,7 +30,7 @@ def __init__( from_entity: str, to_entity: str, description: str = "", - properties: Optional[Mapping[str, Union[str, bool, int, float]]] = None, + properties: Mapping[str, str | bool | int | float] | None = None, read_only: bool = False, ): """ diff --git a/exabel_data_sdk/client/api/data_classes/relationship_type.py b/exabel_data_sdk/client/api/data_classes/relationship_type.py index deb4b829..c2d1f559 100644 --- a/exabel_data_sdk/client/api/data_classes/relationship_type.py +++ b/exabel_data_sdk/client/api/data_classes/relationship_type.py @@ -1,4 +1,4 @@ -from typing import Mapping, Optional, Union +from typing import Mapping from exabel_data_sdk.client.api.proto_utils import from_struct, to_struct from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import ( @@ -27,7 +27,7 @@ def __init__( self, name: str, description: str = "", - properties: Optional[Mapping[str, Union[str, bool, int, float]]] = None, + properties: Mapping[str, str | bool | int | float] | None = None, read_only: bool = False, is_ownership: bool = False, ): diff --git a/exabel_data_sdk/client/api/data_classes/request_error.py b/exabel_data_sdk/client/api/data_classes/request_error.py index 94841d7b..f852e99f 100644 --- a/exabel_data_sdk/client/api/data_classes/request_error.py +++ b/exabel_data_sdk/client/api/data_classes/request_error.py @@ -3,7 +3,7 @@ import logging from dataclasses import dataclass from enum import Enum, unique -from typing import Optional, Sequence +from typing import Sequence logger = logging.getLogger(__name__) @@ -87,5 +87,5 @@ class RequestError(Exception): """ error_type: ErrorType - message: Optional[str] = None - precondition_failure: Optional[PreconditionFailure] = None + message: str | None = None + precondition_failure: PreconditionFailure | None = None diff --git a/exabel_data_sdk/client/api/data_classes/tag.py b/exabel_data_sdk/client/api/data_classes/tag.py index eda84887..0f03ba04 100644 --- a/exabel_data_sdk/client/api/data_classes/tag.py +++ b/exabel_data_sdk/client/api/data_classes/tag.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from datetime import datetime -from typing import Optional from dateutil import tz from google.protobuf.timestamp_pb2 import Timestamp as ProtoTimestamp @@ -22,11 +21,11 @@ class TagMetaData: write_access: Whether the current user has write access to the tag. """ - create_time: Optional[datetime] - update_time: Optional[datetime] - created_by: Optional[str] - updated_by: Optional[str] - write_access: Optional[bool] + create_time: datetime | None + update_time: datetime | None + created_by: str | None + updated_by: str | None + write_access: bool | None @classmethod def from_proto(cls, metadata: ProtoTagMetadata) -> "TagMetaData": @@ -50,11 +49,11 @@ def to_proto(self) -> ProtoTagMetadata: ) @staticmethod - def _proto_timestamp_to_datetime(timestamp: ProtoTimestamp) -> Optional[datetime]: + def _proto_timestamp_to_datetime(timestamp: ProtoTimestamp) -> datetime | None: return datetime.fromtimestamp(timestamp.seconds, tz=tz.tzutc()) @staticmethod - def _datetime_to_proto_timestamp(date: Optional[datetime]) -> Optional[ProtoTimestamp]: + def _datetime_to_proto_timestamp(date: datetime | None) -> ProtoTimestamp | None: if date is None: return date timestamp = ProtoTimestamp() @@ -78,11 +77,11 @@ class Tag: metadata: Metadata of the tag. """ - name: Optional[str] + name: str | None display_name: str - description: Optional[str] - entity_type: Optional[str] = None - metadata: Optional[TagMetaData] = None + description: str | None + entity_type: str | None = None + metadata: TagMetaData | None = None @staticmethod def from_proto(tag: ProtoTag) -> "Tag": diff --git a/exabel_data_sdk/client/api/data_classes/time_series.py b/exabel_data_sdk/client/api/data_classes/time_series.py index 3a8622d5..eb6ac4f9 100644 --- a/exabel_data_sdk/client/api/data_classes/time_series.py +++ b/exabel_data_sdk/client/api/data_classes/time_series.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, field from enum import Enum -from typing import Optional, Sequence +from typing import Sequence import numpy as np import pandas as pd @@ -113,7 +113,7 @@ class TimeSeries: """ series: pd.Series - units: Optional[Units] = None + units: Units | None = None @property def name(self) -> str: @@ -143,7 +143,7 @@ def to_proto(self) -> ProtoTimeSeries: @staticmethod def _time_series_points_to_series( - points: Sequence[TimeSeriesPoint], name: Optional[str] = None + points: Sequence[TimeSeriesPoint], name: str | None = None ) -> pd.Series: """Convert a sequence of TimeSeriesPoint to a pandas Series.""" known_time = False @@ -219,7 +219,7 @@ class Units: """ units: Sequence[Unit] = field(default_factory=list) - description: Optional[str] = None + description: str | None = None @staticmethod def from_proto(proto_units: ProtoUnits) -> Units: @@ -250,8 +250,8 @@ class Unit: """ dimension: Dimension - unit: Optional[str] = None - exponent: Optional[int] = None + unit: str | None = None + exponent: int | None = None @staticmethod def from_proto(proto_unit: ProtoUnit) -> Unit: diff --git a/exabel_data_sdk/client/api/data_set_api.py b/exabel_data_sdk/client/api/data_set_api.py index f1245c24..4ab8086e 100644 --- a/exabel_data_sdk/client/api/data_set_api.py +++ b/exabel_data_sdk/client/api/data_set_api.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence +from typing import Sequence from google.protobuf.field_mask_pb2 import FieldMask @@ -30,7 +30,7 @@ def list_data_sets(self) -> Sequence[DataSet]: response = self.client.list_data_sets(ListDataSetsRequest()) return [DataSet.from_proto(d) for d in response.data_sets] - def get_data_set(self, name: str) -> Optional[DataSet]: + def get_data_set(self, name: str) -> DataSet | None: """ Get one data set. @@ -61,7 +61,7 @@ def create_data_set(self, data_set: DataSet) -> DataSet: def update_data_set( self, data_set: DataSet, - update_mask: Optional[FieldMask] = None, + update_mask: FieldMask | None = None, allow_missing: bool = False, ) -> DataSet: """ diff --git a/exabel_data_sdk/client/api/derived_signal_api.py b/exabel_data_sdk/client/api/derived_signal_api.py index 913ace8f..cbf5befc 100644 --- a/exabel_data_sdk/client/api/derived_signal_api.py +++ b/exabel_data_sdk/client/api/derived_signal_api.py @@ -1,5 +1,3 @@ -from typing import Optional - from exabel_data_sdk.client.api.api_client.grpc.derived_signal_grpc_client import ( DerivedSignalGrpcClient, ) @@ -23,7 +21,7 @@ def __init__(self, config: ClientConfig): self.client = DerivedSignalGrpcClient(config) def create_derived_signal( - self, signal: DerivedSignal, folder: Optional[str] = None + self, signal: DerivedSignal, folder: str | None = None ) -> DerivedSignal: """ Create a derived signal. @@ -38,7 +36,7 @@ def create_derived_signal( ) return DerivedSignal.from_analytics_api_proto(response) - def get_derived_signal(self, name: str) -> Optional[DerivedSignal]: + def get_derived_signal(self, name: str) -> DerivedSignal | None: """ Get a derived signal. diff --git a/exabel_data_sdk/client/api/entity_api.py b/exabel_data_sdk/client/api/entity_api.py index 6cd0e6b3..41d7f438 100644 --- a/exabel_data_sdk/client/api/entity_api.py +++ b/exabel_data_sdk/client/api/entity_api.py @@ -1,4 +1,4 @@ -from typing import Iterator, Optional, Sequence +from typing import Iterator, Sequence from google.protobuf.field_mask_pb2 import FieldMask @@ -50,7 +50,7 @@ def __init__(self, config: ClientConfig): self.search = SearchService(self.client) def list_entity_types( - self, page_size: int = 1000, page_token: Optional[str] = None + self, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[EntityType]: """ List all known entity types. @@ -74,7 +74,7 @@ def get_entity_type_iterator(self) -> Iterator[EntityType]: """Return an iterator with all known entity types.""" return self._get_resource_iterator(self.list_entity_types) - def get_entity_type(self, name: str) -> Optional[EntityType]: + def get_entity_type(self, name: str) -> EntityType | None: """ Get one entity type. @@ -107,7 +107,7 @@ def create_entity_type(self, entity_type: EntityType) -> EntityType: def update_entity_type( self, entity_type: EntityType, - update_mask: Optional[FieldMask] = None, + update_mask: FieldMask | None = None, allow_missing: bool = False, ) -> EntityType: """ @@ -136,7 +136,7 @@ def delete_entity_type(self, name: str) -> None: self.client.delete_entity_type(DeleteEntityTypeRequest(name=name)) def list_entities( - self, entity_type: str, page_size: int = 1000, page_token: Optional[str] = None + self, entity_type: str, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[Entity]: """ List all entities of a given entity type. @@ -161,7 +161,7 @@ def get_entities_iterator(self, entity_type: str) -> Iterator[Entity]: """Return an iterator with all entities of a given entity type.""" return self._get_resource_iterator(self.list_entities, entity_type=entity_type) - def get_entity(self, name: str) -> Optional[Entity]: + def get_entity(self, name: str) -> Entity | None: """ Get one entity. @@ -193,7 +193,7 @@ def create_entity(self, entity: Entity, entity_type: str) -> Entity: return Entity.from_proto(response) def update_entity( - self, entity: Entity, update_mask: Optional[FieldMask] = None, allow_missing: bool = False + self, entity: Entity, update_mask: FieldMask | None = None, allow_missing: bool = False ) -> Entity: """ Update an entity. @@ -285,7 +285,7 @@ def bulk_create_entities( threads: int = DEFAULT_NUMBER_OF_THREADS, upsert: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, ) -> ResourceCreationResults[Entity]: """ Check if the provided entities exist, and create them if they don't. diff --git a/exabel_data_sdk/client/api/error_handler.py b/exabel_data_sdk/client/api/error_handler.py index a7efe09c..dc017d74 100644 --- a/exabel_data_sdk/client/api/error_handler.py +++ b/exabel_data_sdk/client/api/error_handler.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Optional, TypeVar, cast +from typing import Any, Callable, TypeVar, cast from google.rpc.error_details_pb2 import PreconditionFailure as PreconditionFailureProto from google.rpc.status_pb2 import Status as StatusProto @@ -25,7 +25,7 @@ def is_status_detail(metadata: Any) -> bool: ) -def extract_precondition_failure_proto(e: RpcError) -> Optional[PreconditionFailureProto]: +def extract_precondition_failure_proto(e: RpcError) -> PreconditionFailureProto | None: """ Try to find a precondition failure in the gRPC exception, which indicates that we have thrown the exception with structured precondition failure violations. @@ -46,8 +46,8 @@ def extract_precondition_failure_proto(e: RpcError) -> Optional[PreconditionFail def precondition_failure_proto_to_precondition_failure( - precondition_failure: Optional[PreconditionFailureProto] = None, -) -> Optional[PreconditionFailure]: + precondition_failure: PreconditionFailureProto | None = None, +) -> PreconditionFailure | None: """Convert PreconditionFailureProto into a PreconditionFailure.""" if precondition_failure is None: return None diff --git a/exabel_data_sdk/client/api/export_api.py b/exabel_data_sdk/client/api/export_api.py index 7a643bf8..58171ef7 100644 --- a/exabel_data_sdk/client/api/export_api.py +++ b/exabel_data_sdk/client/api/export_api.py @@ -1,14 +1,14 @@ import logging import pickle from time import time -from typing import List, Mapping, Optional, Sequence, Union +from typing import Sequence import pandas as pd from requests import Session from requests.adapters import HTTPAdapter from urllib3 import Retry -from exabel_data_sdk.client.user_login import UserLogin +from exabel_data_sdk.client.client_config import ClientConfig from exabel_data_sdk.query.column import Column from exabel_data_sdk.query.predicate import Predicate from exabel_data_sdk.query.query import Query @@ -21,64 +21,32 @@ class ExportApi: """ API class for data export operations. - - If authentication headers are not provided, this method will attempt to obtain them by logging - in with the UserLogin script. - - Args: - auth_headers: authentication headers for the HTTPS requests to the backend server - backend: the address of the backend server. This field is only for internal use at - Exabel and should never be set by customers or partners. - reauthenticate: if True, the user will be prompted to log in again. - user: the Exabel user to log in as, e.g. `my_user@enterprise.com`. If not - provided, the default user will be logged in. - retries: the number of times to retry requests """ - def __init__( - self, - auth_headers: Optional[Mapping[str, str]] = None, - *, - backend: str = "endpoints.exabel.com", - reauthenticate: bool = False, - user: Optional[str] = None, - retries: int = 0, - ): - if auth_headers is None: - # Attempt to log in - auth_headers = UserLogin(reauthenticate=reauthenticate, user=user).get_auth_headers() + def __init__(self, client_config: ClientConfig): + auth_headers = {} + if client_config.access_token: + auth_headers["Authorization"] = f"Bearer {client_config.access_token}" + elif client_config.api_key: + auth_headers["x-api-key"] = client_config.api_key session = Session() session.headers.update(auth_headers) - if retries: + if client_config.retries: retry = Retry( - total=retries, + total=client_config.retries, backoff_factor=1.0, allowed_methods=["POST"], status_forcelist=[500, 502, 503, 504], ) session.mount("https://", HTTPAdapter(max_retries=retry)) self._session = session - self._backend = backend - - @staticmethod - def from_api_key(api_key: str, use_test_backend: bool = False, retries: int = 0) -> "ExportApi": - """Create an `ExportApi` from an API key.""" - backend = "export.api-test.exabel.com" if use_test_backend else "export.api.exabel.com" - return ExportApi(auth_headers={"x-api-key": api_key}, backend=backend, retries=retries) - - @staticmethod - def from_access_token( - access_token: str, use_test_backend: bool = False, retries: int = 0 - ) -> "ExportApi": - """Create an `ExportApi` from an access token.""" - backend = "export.api-test.exabel.com" if use_test_backend else "export.api.exabel.com" - return ExportApi( - auth_headers={"authorization": f"Bearer {access_token}"}, - backend=backend, - retries=retries, + self._backend = ( + client_config.export_api_host + if not client_config.export_api_host or client_config.export_api_port == 443 + else f"{client_config.export_api_host}:{client_config.export_api_port}" ) - def run_query_bytes(self, query: Union[str, Query], file_format: str) -> bytes: + def run_query_bytes(self, query: str | Query, file_format: str) -> bytes: """ Run an export data query, and returns a byte string with the file in the requested format. Raises an exception if the status code is not 200. @@ -114,7 +82,7 @@ def run_query_bytes(self, query: Union[str, Query], file_format: str) -> bytes: error_message = f"Got {response.status_code}: {error_message} for query {query}" raise ValueError(error_message) - def run_query(self, query: Union[str, Query]) -> pd.DataFrame: + def run_query(self, query: str | Query) -> pd.DataFrame: """ Run an export data query, and returns a DataFrame with the results. Raises an exception if the status code is not 200. @@ -126,17 +94,17 @@ def run_query(self, query: Union[str, Query]) -> pd.DataFrame: def signal_query( self, - signal: Union[str, Column, Sequence[Union[str, Column]]], - bloomberg_ticker: Optional[Union[str, Sequence[str]]] = None, + signal: str | Column | Sequence[str | Column], + bloomberg_ticker: str | Sequence[str] | None = None, *, - factset_id: Optional[Union[str, Sequence[str]]] = None, - resource_name: Optional[Union[str, Sequence[str]]] = None, - tag: Optional[Union[str, Sequence[str]]] = None, - start_time: Optional[Union[str, pd.Timestamp]] = None, - end_time: Optional[Union[str, pd.Timestamp]] = None, - identifier: Optional[Union[Column, Sequence[Column]]] = None, - version: Optional[Union[str, pd.Timestamp, Sequence[str], Sequence[pd.Timestamp]]] = None, - ) -> Union[pd.Series, pd.DataFrame]: + factset_id: str | Sequence[str] | None = None, + resource_name: str | Sequence[str] | None = None, + tag: str | Sequence[str] | None = None, + start_time: str | pd.Timestamp | None = None, + end_time: str | pd.Timestamp | None = None, + identifier: Column | Sequence[Column] | None = None, + version: str | pd.Timestamp | Sequence[str] | Sequence[pd.Timestamp] | None = None, + ) -> pd.Series | pd.DataFrame: """ Run a query for one or more signals. @@ -178,7 +146,7 @@ def signal_query( # Specify entity filter multi_entity = False - predicates: List[Predicate] = [] + predicates: list[Predicate] = [] if factset_id: if isinstance(factset_id, str): predicates.append(Signals.FACTSET_ID.equal(factset_id)) @@ -207,7 +175,7 @@ def signal_query( raise ValueError("At most one entity identification method can be specified") # Specify the identifier(s) - index: List[Column] = [] + index: list[Column] = [] if identifier is None: if multi_entity: index.append(Signals.NAME) @@ -226,7 +194,7 @@ def signal_query( index.insert(0, Signals.VERSION) # The columns to query for - columns: List[Union[str, Column]] = list(index) + columns: list[str | Column] = list(index) if isinstance(signal, (str, Column)): columns.append(signal) else: @@ -247,17 +215,17 @@ def signal_query( def batched_signal_query( self, batch_size: int, - signal: Union[str, Column, Sequence[Union[str, Column]]], + signal: str | Column | Sequence[str | Column], *, - bloomberg_ticker: Optional[Sequence[str]] = None, - factset_id: Optional[Sequence[str]] = None, - resource_name: Optional[Sequence[str]] = None, - start_time: Optional[Union[str, pd.Timestamp]] = None, - end_time: Optional[Union[str, pd.Timestamp]] = None, - identifier: Optional[Union[Column, Sequence[Column]]] = None, - version: Optional[Union[str, pd.Timestamp, Sequence[str], Sequence[pd.Timestamp]]] = None, + bloomberg_ticker: Sequence[str] | None = None, + factset_id: Sequence[str] | None = None, + resource_name: Sequence[str] | None = None, + start_time: str | pd.Timestamp | None = None, + end_time: str | pd.Timestamp | None = None, + identifier: Column | Sequence[Column] | None = None, + version: str | pd.Timestamp | Sequence[str] | Sequence[pd.Timestamp] | None = None, show_progress: bool = False, - ) -> Union[pd.Series, pd.DataFrame]: + ) -> pd.Series | pd.DataFrame: """ Run a query for one or more signals. @@ -275,7 +243,7 @@ def batched_signal_query( or a pandas DataFrame if there are multiple time series in the result. The index is a MultiIndex with entity on the first level and time on the second level. """ - entity_identifiers: List[str] = [] + entity_identifiers: list[str] = [] entities: Sequence[str] = [] if factset_id: entity_identifiers.append("factset_id") diff --git a/exabel_data_sdk/client/api/holiday_api.py b/exabel_data_sdk/client/api/holiday_api.py new file mode 100644 index 00000000..0c62e6f5 --- /dev/null +++ b/exabel_data_sdk/client/api/holiday_api.py @@ -0,0 +1,122 @@ +from collections.abc import Sequence + +from exabel_data_sdk.client.api.api_client.grpc.holiday_grpc_client import HolidayGrpcClient +from exabel_data_sdk.client.client_config import ClientConfig +from exabel_data_sdk.stubs.exabel.api.data.v1.holiday_messages_pb2 import HolidaySpecification +from exabel_data_sdk.stubs.exabel.api.data.v1.holiday_service_pb2 import ( + CreateHolidaySpecificationRequest, + DeleteHolidaySpecificationRequest, + GetHolidaySpecificationRequest, + ListHolidaySpecificationsRequest, + UpdateHolidaySpecificationRequest, +) + + +class HolidayApi: + """ + API class for CRUD operations on holiday specifications. + + Holiday specifications define sets of holidays with their properties (dates, windows, weights) + that can be used when forecasting with the Prophet model. + + Example: + from exabel_data_sdk import ExabelClient + from exabel_data_sdk.stubs.exabel.api.data.v1.holiday_messages_pb2 import ( + Holiday, HolidaySpecification + ) + from exabel_data_sdk.stubs.exabel.api.time.date_pb2 import Date + + client = ExabelClient(api_key="YOUR_API_KEY") + + # Create a holiday specification + spec = HolidaySpecification( + display_name="US Holidays", + holidays=[ + Holiday( + label="Christmas", + dates=[Date(year=y, month=12, day=25) for y in range(2000, 2040)], + lower_window=-1, + upper_window=1, + prior_scale=10.0, + ), + ], + ) + created = client.holiday_api.create_holiday_specification(spec) + print(f"Created: {created.name}") + + # Use the holiday specification in a forecast signal: + # signal.forecast('prophet', holidays=f'{created.name}') + """ + + def __init__(self, config: ClientConfig): + self.client = HolidayGrpcClient(config) + + def list_holiday_specifications(self) -> Sequence[HolidaySpecification]: + """ + List all holiday specifications. + + Returns: + A sequence of all holiday specifications. + """ + request = ListHolidaySpecificationsRequest() + return self.client.list_holiday_specifications(request).holiday_specifications + + def get_holiday_specification(self, name: str) -> HolidaySpecification: + """ + Get a holiday specification by name. + + Args: + name: The resource name of the holiday specification, + e.g. "holidaySpecifications/123". + + Returns: + The holiday specification. + """ + request = GetHolidaySpecificationRequest(name=name) + return self.client.get_holiday_specification(request) + + def create_holiday_specification( + self, holiday_specification: HolidaySpecification + ) -> HolidaySpecification: + """ + Create a new holiday specification. + + Args: + holiday_specification: The holiday specification to create. + The name field should not be set. + + Returns: + The created holiday specification with its assigned name. + """ + request = CreateHolidaySpecificationRequest(holiday_specification=holiday_specification) + return self.client.create_holiday_specification(request) + + def update_holiday_specification( + self, holiday_specification: HolidaySpecification + ) -> HolidaySpecification: + """ + Update an existing holiday specification. + + Args: + holiday_specification: The holiday specification to update. + The name field must be set to identify which specification to update. + + Returns: + The updated holiday specification. + """ + request = UpdateHolidaySpecificationRequest(holiday_specification=holiday_specification) + return self.client.update_holiday_specification(request) + + def delete_holiday_specification(self, name: str) -> None: + """ + Delete a holiday specification. + + Note: Deleting a holiday specification that is referenced by KPI mapping groups + will cause forecasting to fail for those groups until the reference is removed or updated. + + Args: + name: The resource name of the holiday specification to delete, + e.g. "holidaySpecifications/123". + """ + request = DeleteHolidaySpecificationRequest(name=name) + self.client.delete_holiday_specification(request) diff --git a/exabel_data_sdk/client/api/kpi_api.py b/exabel_data_sdk/client/api/kpi_api.py index 7907a82d..b2b611a2 100644 --- a/exabel_data_sdk/client/api/kpi_api.py +++ b/exabel_data_sdk/client/api/kpi_api.py @@ -1,4 +1,5 @@ -from typing import Literal, Optional, Sequence, Union +import logging +from typing import Literal, Sequence import pandas as pd @@ -9,6 +10,7 @@ from exabel_data_sdk.client.api.data_classes.company_kpi_models import CompanyKpiModels from exabel_data_sdk.client.api.data_classes.kpi import Kpi, KpiType from exabel_data_sdk.client.api.data_classes.kpi_mapping_result_data import KpiMappingResultData +from exabel_data_sdk.client.api.data_classes.kpi_screen_results import KpiScreenCompanyResult from exabel_data_sdk.client.api.data_classes.kpi_source import KpiSource from exabel_data_sdk.client.api.data_classes.model_results import ( BaseModelResults, @@ -26,9 +28,12 @@ ListCompanyKpiMappingResultsRequest, ListCompanyKpiModelResultsRequest, ListKpiMappingResultsRequest, + ListKpiScreenResultsRequest, ) from exabel_data_sdk.stubs.exabel.api.time.date_pb2 import Date +logger = logging.getLogger(__name__) + class KpiApi: """ @@ -62,23 +67,28 @@ def list_kpi_mapping_results( def list_company_base_model_results( self, company: str, - source_filter: Optional[KpiSource] = None, - fiscal_period: Union[Literal["previous", "current", "next"], pd.Timestamp] = "current", - freq: Optional[Literal["FQ", "FS", "FY"]] = None, + source_filter: KpiSource | None = None, + fiscal_period: Literal["previous", "current", "next"] | pd.Timestamp = "current", + freq: Literal["FQ", "FS", "FY"] | None = None, ) -> BaseModelResults: """ List base model results for a company. Args: company: Company resource name. - source_filter: Optional KPI source filter. + source_filter: DEPRECATED. KPI source filtering is now determined by user settings. + This parameter is ignored. Set user preferences via UserApi instead. fiscal_period: Fiscal period to retrieve data for. freq: Frequency to retrieve data for. If not provided, frequency is determined based on KPI counts. """ + if source_filter is not None: + logger.warning( + "source_filter parameter is deprecated and ignored. " + "KPI source filtering is now determined by user settings. ", + ) request = ListCompanyBaseModelResultsRequest( parent=company, - kpi_source=source_filter.to_proto() if source_filter else None, period=self._get_fiscal_period_selector(fiscal_period, freq), ) response = self.client.list_company_base_model_results(request) @@ -87,23 +97,28 @@ def list_company_base_model_results( def list_company_hierarchical_model_results( self, company: str, - source_filter: Optional[KpiSource] = None, - fiscal_period: Union[Literal["previous", "current", "next"], pd.Timestamp] = "current", - freq: Optional[Literal["FQ", "FS", "FY"]] = None, + source_filter: KpiSource | None = None, + fiscal_period: Literal["previous", "current", "next"] | pd.Timestamp = "current", + freq: Literal["FQ", "FS", "FY"] | None = None, ) -> HierarchicalModelResults: """ List hierarchical model results for a company. Args: company: Company resource name. - source_filter: Optional KPI source filter. + source_filter: DEPRECATED. KPI source filtering is now determined by user settings. + This parameter is ignored. Set user preferences via UserApi instead. fiscal_period: Fiscal period to retrieve data for. freq: Frequency to retrieve data for. If not provided, frequency is determined based on KPI counts. """ + if source_filter is not None: + logger.warning( + "source_filter parameter is deprecated and ignored. " + "KPI source filtering is now determined by user settings. ", + ) request = ListCompanyHierarchicalModelResultsRequest( parent=company, - kpi_source=source_filter.to_proto() if source_filter else None, period=self._get_fiscal_period_selector(fiscal_period, freq), ) response = self.client.list_company_hierarchical_model_results(request) @@ -149,10 +164,31 @@ def list_company_kpi_model_results( response = self.client.list_company_kpi_model_results(request) return CompanyKpiModels.from_proto(response) + def list_kpi_screen_results( + self, kpi_screen: str, page_size: int = 20, page_token: str = "" + ) -> PagingResult[KpiScreenCompanyResult]: + """ + List KPI screen results. + + Args: + kpi_screen: KPI screen resource name. + page_size: The maximum number of results to return. + page_token: The page token to resume the results from. + """ + request = ListKpiScreenResultsRequest( + name=kpi_screen, page_size=page_size, page_token=page_token + ) + response = self.client.list_kpi_screen_results(request) + return PagingResult( + [KpiScreenCompanyResult.from_proto(result) for result in response.results], + next_page_token=response.next_page_token, + total_size=response.total_size, + ) + @staticmethod def _get_fiscal_period_selector( - fiscal_period: Union[Literal["previous", "current", "next"], pd.Timestamp], - freq: Optional[Literal["FQ", "FS", "FY"]], + fiscal_period: Literal["previous", "current", "next"] | pd.Timestamp, + freq: Literal["FQ", "FS", "FY"] | None, ) -> FiscalPeriodSelector: if freq not in ["FQ", "FS", "FY", None]: raise ValueError("Frequency must be one of FQ, FS and FY.") diff --git a/exabel_data_sdk/client/api/library_api.py b/exabel_data_sdk/client/api/library_api.py index 749171c6..48af9027 100644 --- a/exabel_data_sdk/client/api/library_api.py +++ b/exabel_data_sdk/client/api/library_api.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence +from typing import Sequence from google.protobuf.field_mask_pb2 import FieldMask @@ -62,7 +62,7 @@ def create_folder(self, folder: Folder) -> Folder: return Folder.from_proto(proto_folder) def update_folder( - self, folder: Folder, update_mask: Optional[FieldMask] = None, allow_missing: bool = False + self, folder: Folder, update_mask: FieldMask | None = None, allow_missing: bool = False ) -> Folder: """ Update a folder. @@ -98,7 +98,7 @@ def delete_folder(self, name: str) -> None: def list_items( self, item_type: FolderItemType, - folder_name: Optional[str] = None, + folder_name: str | None = None, ) -> Sequence[FolderItem]: """ List all items of a specific type. @@ -165,9 +165,9 @@ def unshare_folder(self, folder_name: str, group_name: str) -> None: def search_items( self, query: str, - folder_item_type: Optional[FolderItemType] = None, - page_size: Optional[int] = None, - page_token: Optional[str] = None, + folder_item_type: FolderItemType | None = None, + page_size: int | None = None, + page_token: str | None = None, ) -> PagingResult[FolderItem]: """ Search for folder items. diff --git a/exabel_data_sdk/client/api/pageable_resource.py b/exabel_data_sdk/client/api/pageable_resource.py index af4e7c21..a673a446 100644 --- a/exabel_data_sdk/client/api/pageable_resource.py +++ b/exabel_data_sdk/client/api/pageable_resource.py @@ -1,4 +1,4 @@ -from typing import Callable, Iterator, Optional, TypeVar +from typing import Callable, Iterator, TypeVar from exabel_data_sdk.client.api.data_classes.data_set import DataSet from exabel_data_sdk.client.api.data_classes.entity import Entity @@ -35,7 +35,7 @@ def _get_resource_iterator( Return an iterator with all the resources returnable by function, paging through the results. """ - page_token: Optional[str] = None + page_token: str | None = None while True: result = pageable_func(**kwargs, page_token=page_token, page_size=page_size) yield from result.results diff --git a/exabel_data_sdk/client/api/proto_utils.py b/exabel_data_sdk/client/api/proto_utils.py index e2ab970a..8f462fca 100644 --- a/exabel_data_sdk/client/api/proto_utils.py +++ b/exabel_data_sdk/client/api/proto_utils.py @@ -1,19 +1,19 @@ -from typing import Mapping, Union +from typing import Mapping from google.protobuf.struct_pb2 import Struct -def from_struct(struct: Struct) -> Mapping[str, Union[str, bool, int, float]]: +def from_struct(struct: Struct) -> Mapping[str, str | bool | int | float]: """ Convert a protobuf Struct into a Dict. """ for value in struct.values(): if not isinstance(value, (str, bool, int, float, type(None))): raise ValueError(f"Struct contains unsupported value: {value}") - return dict(struct.items()) # type: ignore[arg-type] + return dict(struct.items()) -def to_struct(values: Mapping[str, Union[str, bool, int, float]]) -> Struct: +def to_struct(values: Mapping[str, str | bool | int | float]) -> Struct: """ Convert a Dict into a protobuf Struct. """ diff --git a/exabel_data_sdk/client/api/relationship_api.py b/exabel_data_sdk/client/api/relationship_api.py index 9d7ccd4c..8af3d442 100644 --- a/exabel_data_sdk/client/api/relationship_api.py +++ b/exabel_data_sdk/client/api/relationship_api.py @@ -1,5 +1,5 @@ from functools import partial -from typing import Iterator, Optional, Sequence +from typing import Iterator, Sequence from google.protobuf.field_mask_pb2 import FieldMask @@ -45,7 +45,7 @@ def __init__(self, config: ClientConfig): self.client = RelationshipGrpcClient(config) def list_relationship_types( - self, page_size: int = 1000, page_token: Optional[str] = None + self, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[RelationshipType]: """ List all relationship types. @@ -68,7 +68,7 @@ def get_relationship_types_iterator(self) -> Iterator[RelationshipType]: """Return an iterator with all known relationship types.""" return self._get_resource_iterator(self.list_relationship_types) - def get_relationship_type(self, name: str) -> Optional[RelationshipType]: + def get_relationship_type(self, name: str) -> RelationshipType | None: """ Get one relationship type. @@ -101,7 +101,7 @@ def create_relationship_type(self, relationship_type: RelationshipType) -> Relat def update_relationship_type( self, relationship_type: RelationshipType, - update_mask: Optional[FieldMask] = None, + update_mask: FieldMask | None = None, allow_missing: bool = False, ) -> RelationshipType: """ @@ -139,7 +139,7 @@ def get_relationships_from_entity( relationship_type: str, from_entity: str, page_size: int = 1000, - page_token: Optional[str] = None, + page_token: str | None = None, ) -> PagingResult[Relationship]: """ Get relationships from the given entity. @@ -186,7 +186,7 @@ def get_relationships_to_entity( relationship_type: str, to_entity: str, page_size: int = 1000, - page_token: Optional[str] = None, + page_token: str | None = None, ) -> PagingResult[Relationship]: """ Get relationships to the given entity. @@ -232,7 +232,7 @@ def list_relationships( self, relationship_type: str, page_size: int = 1000, - page_token: Optional[str] = None, + page_token: str | None = None, ) -> PagingResult[Relationship]: """ List all relationships of the given relationship type. @@ -269,7 +269,7 @@ def get_relationships_iterator( def get_relationship( self, relationship_type: str, from_entity: str, to_entity: str - ) -> Optional[Relationship]: + ) -> Relationship | None: """ Get one relationship. @@ -310,7 +310,7 @@ def create_relationship(self, relationship: Relationship) -> Relationship: def update_relationship( self, relationship: Relationship, - update_mask: Optional[FieldMask] = None, + update_mask: FieldMask | None = None, allow_missing: bool = False, ) -> Relationship: """ @@ -379,7 +379,7 @@ def bulk_create_relationships( threads: int = DEFAULT_NUMBER_OF_THREADS, upsert: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, ) -> ResourceCreationResults[Relationship]: """ Check if the provided relationships exist, and create them if they don't. diff --git a/exabel_data_sdk/client/api/resource_creation_result.py b/exabel_data_sdk/client/api/resource_creation_result.py index f618269c..135be53a 100644 --- a/exabel_data_sdk/client/api/resource_creation_result.py +++ b/exabel_data_sdk/client/api/resource_creation_result.py @@ -3,7 +3,7 @@ import logging from collections import Counter from enum import Enum -from typing import Generic, MutableSequence, Optional, Sequence, Set, TypeVar +from typing import Generic, MutableSequence, Sequence, TypeVar import numpy as np import pandas as pd @@ -62,11 +62,11 @@ class ResourceCreationResult(Generic[ResourceT]): def __init__( self, status: ResourceCreationStatus, - resource: Optional[ResourceT] = None, - error: Optional[RequestError] = None, + resource: ResourceT | None = None, + error: RequestError | None = None, ): self.status = status - self.resource: Optional[ResourceT] = ( + self.resource: ResourceT | None = ( None if status != ResourceCreationStatus.FAILED else resource ) self.resource_name = get_resource_name(resource) @@ -107,7 +107,7 @@ def __init__( self, total_count: int, print_status: bool = True, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, ) -> None: """ Args: @@ -128,7 +128,7 @@ def __init__( ) @staticmethod - def _get_progress_checkpoints(total: int, checkpoints: int) -> Set[int]: + def _get_progress_checkpoints(total: int, checkpoints: int) -> set[int]: """ Return a list of progress checkpoints. """ @@ -155,7 +155,7 @@ def add(self, result: ResourceCreationResult[ResourceT]) -> None: if count > 20 and count != self.total_count: self.check_failures() - def count(self, status: Optional[ResourceCreationStatus] = None) -> int: + def count(self, status: ResourceCreationStatus | None = None) -> int: """ Number of results with the given status, or total number of results if no status is specified. @@ -213,7 +213,7 @@ def check_failures(self) -> None: self.abort_threshold * 100, ) - def print_summary(self, failure_log_limit: Optional[int] = FAILURE_LOG_LIMIT) -> None: + def print_summary(self, failure_log_limit: int | None = FAILURE_LOG_LIMIT) -> None: """Prints a human legible summary of the resource creation results to screen.""" if self.counter[ResourceCreationStatus.CREATED]: logger.info("%s new resources created", self.counter[ResourceCreationStatus.CREATED]) diff --git a/exabel_data_sdk/client/api/search_service.py b/exabel_data_sdk/client/api/search_service.py index 6b5af39c..1cbf8f1e 100644 --- a/exabel_data_sdk/client/api/search_service.py +++ b/exabel_data_sdk/client/api/search_service.py @@ -1,5 +1,5 @@ import itertools -from typing import Mapping, Sequence, Tuple, TypeVar, Union +from typing import Mapping, Sequence, TypeVar from exabel_data_sdk.client.api.api_client.entity_api_client import EntityApiClient from exabel_data_sdk.client.api.data_classes.entity import Entity @@ -109,8 +109,8 @@ def companies_by_text(self, *texts: str) -> Mapping[str, Sequence[Entity]]: return self._companies_by_field("text", *texts) def company_by_mic_and_ticker( - self, *mic_and_ticker: Tuple[str, str] - ) -> Mapping[Tuple[str, str], Entity]: + self, *mic_and_ticker: tuple[str, str] + ) -> Mapping[tuple[str, str], Entity]: """ Look up companies by MIC (Market Identifier Code) and ticker. @@ -120,8 +120,8 @@ def company_by_mic_and_ticker( return self._by_mic_and_ticker(_COMPANY_ENTITY_TYPE, *mic_and_ticker) def security_by_mic_and_ticker( - self, *mic_and_ticker: Tuple[str, str] - ) -> Mapping[Tuple[str, str], Entity]: + self, *mic_and_ticker: tuple[str, str] + ) -> Mapping[tuple[str, str], Entity]: """ Look up securities by MIC (Market Identifier Code) and ticker. @@ -131,8 +131,8 @@ def security_by_mic_and_ticker( return self._by_mic_and_ticker(_SECURITY_ENTITY_TYPE, *mic_and_ticker) def listing_by_mic_and_ticker( - self, *mic_and_ticker: Tuple[str, str] - ) -> Mapping[Tuple[str, str], Entity]: + self, *mic_and_ticker: tuple[str, str] + ) -> Mapping[tuple[str, str], Entity]: """ Look up listings by MIC (Market Identifier Code) and ticker. @@ -160,7 +160,7 @@ def security_by_cusip(self, *cusips: str) -> Mapping[str, Entity]: return self._security_by_field("cusip", *cusips) def entities_by_terms( - self, entity_type: str, terms: Sequence[Union[SearchTerm, Tuple[str, str]]] + self, entity_type: str, terms: Sequence[SearchTerm | tuple[str, str]] ) -> Sequence[SearchEntitiesResponse.SearchResult]: """ Look up entities of a given type based on a series of search terms. @@ -192,8 +192,8 @@ def _security_by_field(self, field: str, *values: str) -> Mapping[str, Entity]: return self._single_result(self._by_field(_SECURITY_ENTITY_TYPE, field, *values)) def _by_mic_and_ticker( - self, entity_type: str, *mic_and_ticker: Tuple[str, str] - ) -> Mapping[Tuple[str, str], Entity]: + self, entity_type: str, *mic_and_ticker: tuple[str, str] + ) -> Mapping[tuple[str, str], Entity]: results = self._by_fields(entity_type, ("mic", "ticker"), *itertools.chain(*mic_and_ticker)) return self._single_result(results) # type: ignore[arg-type] @@ -207,14 +207,14 @@ def _single_result(self, results: Mapping[KeyT, Sequence[Entity]]) -> Mapping[Ke def _by_field( self, entity_type: str, field: str, *values: str ) -> Mapping[str, Sequence[Entity]]: - result: Mapping[Tuple[str, ...], Sequence[Entity]] = self._by_fields( + result: Mapping[tuple[str, ...], Sequence[Entity]] = self._by_fields( entity_type, [field], *values ) return {query[0]: entities for query, entities in result.items()} def _by_fields( self, entity_type: str, fields: Sequence[str], *values: str - ) -> Mapping[Tuple[str, ...], Sequence[Entity]]: + ) -> Mapping[tuple[str, ...], Sequence[Entity]]: if not values: raise ValueError("No search terms provided.") tuples = [] @@ -224,9 +224,9 @@ def _by_fields( to_return = {} for result in results: assert len(result.terms) == len(fields) - assert list(fields) == [ - term.field for term in result.terms - ], f"{fields} != {[term.field for term in result.terms]}" + assert list(fields) == [term.field for term in result.terms], ( + f"{fields} != {[term.field for term in result.terms]}" + ) if result.entities: to_return[tuple(term.query for term in result.terms)] = [ Entity.from_proto(e) for e in result.entities diff --git a/exabel_data_sdk/client/api/signal_api.py b/exabel_data_sdk/client/api/signal_api.py index 3b8472b4..79799f0e 100644 --- a/exabel_data_sdk/client/api/signal_api.py +++ b/exabel_data_sdk/client/api/signal_api.py @@ -1,4 +1,4 @@ -from typing import Dict, Iterator, List, Optional +from typing import Iterator from google.protobuf.field_mask_pb2 import FieldMask @@ -28,7 +28,7 @@ def __init__(self, config: ClientConfig): self.client = SignalGrpcClient(config) def list_signals( - self, page_size: int = 1000, page_token: Optional[str] = None + self, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[Signal]: """ List all signals. @@ -51,7 +51,7 @@ def get_signal_iterator(self) -> Iterator[Signal]: """Return an iterator with all signals.""" return self._get_resource_iterator(self.list_signals) - def get_signal(self, name: str) -> Optional[Signal]: + def get_signal(self, name: str) -> Signal | None: """ Get one signal. @@ -88,7 +88,7 @@ def create_signal(self, signal: Signal, create_library_signal: bool = False) -> def update_signal( self, signal: Signal, - update_mask: Optional[FieldMask] = None, + update_mask: FieldMask | None = None, allow_missing: bool = False, create_library_signal: bool = False, ) -> Signal: @@ -128,7 +128,7 @@ def delete_signal(self, name: str) -> None: DeleteSignalRequest(name=name), ) - def filter_derived_signals(self, entity_names: List[str]) -> Dict[str, List[DerivedSignal]]: + def filter_derived_signals(self, entity_names: list[str]) -> dict[str, list[DerivedSignal]]: """ Filter derived signals by entity names. diff --git a/exabel_data_sdk/client/api/tag_api.py b/exabel_data_sdk/client/api/tag_api.py index 1a85df66..a916d769 100644 --- a/exabel_data_sdk/client/api/tag_api.py +++ b/exabel_data_sdk/client/api/tag_api.py @@ -1,4 +1,4 @@ -from typing import Iterator, List, Optional +from typing import Iterator from exabel_data_sdk.client.api.api_client.grpc.tag_grpc_client import TagGrpcClient from exabel_data_sdk.client.api.data_classes.paging_result import PagingResult @@ -26,7 +26,7 @@ class TagApi(PageableResourceMixin): def __init__(self, config: ClientConfig): self.client = TagGrpcClient(config) - def create_tag(self, tag: Tag, folder: Optional[str] = None) -> Tag: + def create_tag(self, tag: Tag, folder: str | None = None) -> Tag: """ Create a tag. @@ -38,7 +38,7 @@ def create_tag(self, tag: Tag, folder: Optional[str] = None) -> Tag: response = self.client.create_tag(CreateTagRequest(tag=tag.to_proto(), folder=folder)) return Tag.from_proto(response) - def get_tag(self, name: str) -> Optional[Tag]: + def get_tag(self, name: str) -> Tag | None: """ Get a tag. @@ -74,9 +74,7 @@ def delete_tag(self, name: str) -> None: """ self.client.delete_tag(DeleteTagRequest(name=name)) - def list_tags( - self, page_size: int = 1000, page_token: Optional[str] = None - ) -> PagingResult[Tag]: + def list_tags(self, page_size: int = 1000, page_token: str | None = None) -> PagingResult[Tag]: """ List tags accesible to the user. @@ -100,7 +98,7 @@ def get_tag_iterator(self) -> Iterator[Tag]: """Return an iterator with all tags.""" return self._get_resource_iterator(self.list_tags) - def add_entities(self, name: str, entity_names: List[str]) -> None: + def add_entities(self, name: str, entity_names: list[str]) -> None: """ Add entities to a tag. @@ -110,7 +108,7 @@ def add_entities(self, name: str, entity_names: List[str]) -> None: """ self.client.add_entities(AddEntitiesRequest(name=name, entity_names=entity_names)) - def remove_entities(self, name: str, entity_names: List[str]) -> None: + def remove_entities(self, name: str, entity_names: list[str]) -> None: """ Remove entities from a tag. @@ -121,7 +119,7 @@ def remove_entities(self, name: str, entity_names: List[str]) -> None: self.client.remove_entities(RemoveEntitiesRequest(name=name, entity_names=entity_names)) def list_entities( - self, parent: str, page_size: int = 1000, page_token: Optional[str] = None + self, parent: str, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[str]: """ List resource names of the entities in the parent tag. diff --git a/exabel_data_sdk/client/api/time_series_api.py b/exabel_data_sdk/client/api/time_series_api.py index d2bd469e..7f9d2f82 100644 --- a/exabel_data_sdk/client/api/time_series_api.py +++ b/exabel_data_sdk/client/api/time_series_api.py @@ -1,4 +1,4 @@ -from typing import Iterator, Optional, Sequence, Union +from typing import Iterator, Sequence import numpy as np import pandas as pd @@ -52,7 +52,7 @@ def __init__(self, config: ClientConfig): self.client = TimeSeriesGrpcClient(config) def get_signal_time_series( - self, signal: str, page_size: int = 1000, page_token: Optional[str] = None + self, signal: str, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[str]: """ Get the resource names of all time series for one signal. @@ -79,7 +79,7 @@ def get_signal_time_series_iterator(self, signal: str) -> Iterator[str]: return self._get_resource_iterator(self.get_signal_time_series, signal=signal) def get_entity_time_series( - self, entity: str, page_size: int = 1000, page_token: Optional[str] = None + self, entity: str, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[str]: """ Get the resource names of all time series for one entity. @@ -109,11 +109,11 @@ def get_entity_time_series_iterator(self, entity: str) -> Iterator[str]: def get_time_series( self, name: str, - start: Optional[pd.Timestamp] = None, - end: Optional[pd.Timestamp] = None, - known_time: Optional[pd.Timestamp] = None, + start: pd.Timestamp | None = None, + end: pd.Timestamp | None = None, + known_time: pd.Timestamp | None = None, include_metadata: bool = False, - ) -> Optional[Union[pd.Series, TimeSeries]]: + ) -> pd.Series | TimeSeries | None: """ Get one time series. @@ -167,10 +167,10 @@ def get_time_series( def create_time_series( self, name: str, - series: Union[pd.Series, TimeSeries], - create_tag: Optional[bool] = None, # pylint: disable=unused-argument - default_known_time: Optional[DefaultKnownTime] = None, - should_optimise: Optional[bool] = None, + series: pd.Series | TimeSeries, + create_tag: bool | None = None, + default_known_time: DefaultKnownTime | None = None, + should_optimise: bool | None = None, ) -> None: """ Create a time series. @@ -213,9 +213,9 @@ def upsert_time_series( self, name: str, series: pd.Series, - create_tag: Optional[bool] = None, # pylint: disable=unused-argument - default_known_time: Optional[DefaultKnownTime] = None, - should_optimise: Optional[bool] = None, + create_tag: bool | None = None, + default_known_time: DefaultKnownTime | None = None, + should_optimise: bool | None = None, ) -> None: """ Create or update a time series. @@ -251,11 +251,11 @@ def upsert_time_series( def append_time_series_data( self, name: str, - series: Union[pd.Series, TimeSeries], - default_known_time: Optional[DefaultKnownTime] = None, + series: pd.Series | TimeSeries, + default_known_time: DefaultKnownTime | None = None, allow_missing: bool = False, - create_tag: bool = False, # pylint: disable=unused-argument - should_optimise: Optional[bool] = None, + create_tag: bool = False, + should_optimise: bool | None = None, ) -> None: """ Append data to the given time series. @@ -297,15 +297,15 @@ def append_time_series_data( def import_time_series( self, parent: str, - series: Sequence[Union[pd.Series, TimeSeries]], - default_known_time: Optional[DefaultKnownTime] = None, + series: Sequence[pd.Series | TimeSeries], + default_known_time: DefaultKnownTime | None = None, allow_missing: bool = False, - create_tag: Optional[bool] = None, # pylint: disable=unused-argument + create_tag: bool | None = None, status_in_response: bool = False, replace_existing_time_series: bool = False, replace_existing_data_points: bool = False, - should_optimise: Optional[bool] = None, - ) -> Optional[Sequence[ResourceCreationResult]]: + should_optimise: bool | None = None, + ) -> Sequence[ResourceCreationResult] | None: """ Import multiple time series. @@ -382,13 +382,13 @@ def import_time_series( def append_time_series_data_and_return( self, name: str, - series: Union[pd.Series, TimeSeries], - default_known_time: Optional[DefaultKnownTime] = None, - allow_missing: Optional[bool] = False, - create_tag: Optional[bool] = None, # pylint: disable=unused-argument - include_metadata: Optional[bool] = False, - should_optimise: Optional[bool] = None, - ) -> Union[pd.Series, TimeSeries]: + series: pd.Series | TimeSeries, + default_known_time: DefaultKnownTime | None = None, + allow_missing: bool | None = False, + create_tag: bool | None = None, + include_metadata: bool | None = False, + should_optimise: bool | None = None, + ) -> pd.Series | TimeSeries: """ Append data to the given time series, and return the full series. @@ -460,17 +460,17 @@ def time_series_exists(self, name: str) -> bool: @deprecate_arguments(threads_for_import=None, create_tag=None) def bulk_upsert_time_series( self, - series: Sequence[Union[pd.Series, TimeSeries]], - create_tag: Optional[bool] = None, # pylint: disable=unused-argument + series: Sequence[pd.Series | TimeSeries], + create_tag: bool | None = None, threads: int = DEFAULT_NUMBER_OF_THREADS_FOR_IMPORT, - default_known_time: Optional[DefaultKnownTime] = None, + default_known_time: DefaultKnownTime | None = None, replace_existing_time_series: bool = False, replace_existing_data_points: bool = False, - should_optimise: Optional[bool] = None, + should_optimise: bool | None = None, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, # Deprecated arguments - threads_for_import: int = 4, # pylint: disable=unused-argument + threads_for_import: int = 4, ) -> ResourceCreationResults[pd.Series]: """Import the provided time series in batches. @@ -516,7 +516,7 @@ def bulk_upsert_time_series( """ def import_func( - ts_sequence: Sequence[Union[pd.Series, TimeSeries]], + ts_sequence: Sequence[pd.Series | TimeSeries], ) -> Sequence[ResourceCreationResult]: result = self.import_time_series( parent="signals/-", @@ -542,7 +542,7 @@ def import_func( def delete_time_series_points( self, series: Sequence[pd.Series], - ) -> Optional[Sequence[ResourceCreationResult]]: + ) -> Sequence[ResourceCreationResult] | None: """ Delete data points from time series. A data point that is deleted will be removed from the time series at the given date and known_time if it exists with a value or NaN. @@ -566,7 +566,7 @@ def batch_delete_time_series_points( series: Sequence[pd.Series], threads: int = DEFAULT_NUMBER_OF_THREADS_FOR_IMPORT, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, ) -> ResourceCreationResults[pd.Series]: """Delete the provided time series data points in batches. @@ -610,8 +610,8 @@ def _datetime_index(nanoseconds: Sequence[int]) -> pd.DatetimeIndex: @staticmethod def _get_time_range( - start: Optional[pd.Timestamp], - end: Optional[pd.Timestamp], + start: pd.Timestamp | None, + end: pd.Timestamp | None, include_start: bool = True, include_end: bool = True, ) -> TimeRange: @@ -638,8 +638,8 @@ def _get_time_range( @staticmethod def _pandas_timestamp_to_proto( - timestamp: Optional[pd.Timestamp], - ) -> Optional[timestamp_pb2.Timestamp]: + timestamp: pd.Timestamp | None, + ) -> timestamp_pb2.Timestamp | None: """ Convert a pandas Timestamp to a protobuf Timestamp. Note that second time resolution is used, and any fraction of a second is discarded. @@ -674,7 +674,7 @@ def _handle_time_series_response( return time_series_results @staticmethod - def _handle_time_series(name: str, series: Union[pd.Series, TimeSeries]) -> TimeSeries: + def _handle_time_series(name: str, series: pd.Series | TimeSeries) -> TimeSeries: """ Helper function to convert to TimeSeries if necessary, and set the name equal to the given name. diff --git a/exabel_data_sdk/client/client_config.py b/exabel_data_sdk/client/client_config.py index 08ff8e37..8c23e411 100644 --- a/exabel_data_sdk/client/client_config.py +++ b/exabel_data_sdk/client/client_config.py @@ -1,5 +1,5 @@ import os -from typing import Optional, Sequence, Tuple +from typing import Sequence FIFTEEN_MINUTES_IN_SECONDS = 15 * 60 @@ -18,12 +18,15 @@ def __init__(self) -> None: self.management_api_host = os.getenv( "EXABEL_MANAGEMENT_API_HOST", "management.api.exabel.com" ) + self.export_api_host = os.getenv("EXABEL_EXPORT_API_HOST", "export.api.exabel.com") self.data_api_port = int(os.getenv("EXABEL_DATA_API_PORT", "21443")) self.analytics_api_port = int(os.getenv("EXABEL_ANALYTICS_API_PORT", "21443")) self.management_api_port = int(os.getenv("EXABEL_MANAGEMENT_API_PORT", "21443")) + self.export_api_port = int(os.getenv("EXABEL_EXPORT_API_PORT", "443")) self.timeout = int(os.environ.get("EXABEL_TIMEOUT", FIFTEEN_MINUTES_IN_SECONDS)) - self.root_certificates: Optional[str] = None - self.extra_headers: Sequence[Tuple[str, str]] = () + self.retries = int(os.getenv("EXABEL_RETRIES", "0")) + self.root_certificates: str | None = None + self.extra_headers: Sequence[tuple[str, str]] = () class ClientConfig(DefaultConfig): @@ -33,18 +36,21 @@ class ClientConfig(DefaultConfig): def __init__( self, - api_key: Optional[str] = None, - access_token: Optional[str] = None, - client_name: Optional[str] = None, - data_api_host: Optional[str] = None, - analytics_api_host: Optional[str] = None, - management_api_host: Optional[str] = None, - data_api_port: Optional[int] = None, - analytics_api_port: Optional[int] = None, - management_api_port: Optional[int] = None, - timeout: Optional[int] = None, - root_certificates: Optional[str] = None, - extra_headers: Optional[Sequence[Tuple[str, str]]] = None, + api_key: str | None = None, + access_token: str | None = None, + client_name: str | None = None, + data_api_host: str | None = None, + analytics_api_host: str | None = None, + management_api_host: str | None = None, + export_api_host: str | None = None, + data_api_port: int | None = None, + analytics_api_port: int | None = None, + management_api_port: int | None = None, + export_api_port: int | None = None, + timeout: int | None = None, + retries: int | None = None, + root_certificates: str | None = None, + extra_headers: Sequence[tuple[str, str]] | None = None, ): """ Initialize a new client configuration. @@ -56,16 +62,19 @@ def __init__( data_api_host: Exabel Data API host. analytics_api_host: Exabel Analytics API host. management_api_host: Exabel Management API host. + export_api_host: Exabel Export API host. data_api_port: Exabel Data API port. analytics_api_port: Exabel Analytics API port. management_api_port: Exabel Management API port. + export_api_port: Exabel Export API port. timeout: Default timeout in seconds to use for API requests. + retries: Default number of retries to use for API requests. root_certificates: Additional allowed root certificates for verifying TLS connection. extra_headers: A list of headers to include in the request. """ super().__init__() - # Arguments take presedence over environment variables + # Arguments take precedence over environment variables if api_key or access_token: self.api_key = api_key self.access_token = access_token @@ -73,10 +82,13 @@ def __init__( self.data_api_host = data_api_host or self.data_api_host self.analytics_api_host = analytics_api_host or self.analytics_api_host self.management_api_host = management_api_host or self.management_api_host + self.export_api_host = export_api_host or self.export_api_host self.data_api_port = data_api_port or self.data_api_port self.analytics_api_port = analytics_api_port or self.analytics_api_port self.management_api_port = management_api_port or self.management_api_port + self.export_api_port = export_api_port or self.export_api_port self.timeout = timeout or self.timeout + self.retries = retries or self.retries self.root_certificates = root_certificates or self.root_certificates self.extra_headers = extra_headers or self.extra_headers diff --git a/exabel_data_sdk/client/exabel_client.py b/exabel_data_sdk/client/exabel_client.py index c3fe152b..f5c2be36 100644 --- a/exabel_data_sdk/client/exabel_client.py +++ b/exabel_data_sdk/client/exabel_client.py @@ -1,9 +1,11 @@ -from typing import Optional, Sequence, Tuple +from typing import Sequence from exabel_data_sdk.client.api.calendar_api import CalendarApi from exabel_data_sdk.client.api.data_set_api import DataSetApi from exabel_data_sdk.client.api.derived_signal_api import DerivedSignalApi from exabel_data_sdk.client.api.entity_api import EntityApi +from exabel_data_sdk.client.api.export_api import ExportApi +from exabel_data_sdk.client.api.holiday_api import HolidayApi from exabel_data_sdk.client.api.kpi_api import KpiApi from exabel_data_sdk.client.api.library_api import LibraryApi from exabel_data_sdk.client.api.namespace_api import NamespaceApi @@ -23,18 +25,21 @@ class ExabelClient: def __init__( self, - api_key: Optional[str] = None, - access_token: Optional[str] = None, - client_name: Optional[str] = None, - data_api_host: Optional[str] = None, - analytics_api_host: Optional[str] = None, - management_api_host: Optional[str] = None, - data_api_port: Optional[int] = None, - analytics_api_port: Optional[int] = None, - management_api_port: Optional[int] = None, - timeout: Optional[int] = None, - root_certificates: Optional[str] = None, - extra_headers: Optional[Sequence[Tuple[str, str]]] = None, + api_key: str | None = None, + access_token: str | None = None, + client_name: str | None = None, + data_api_host: str | None = None, + analytics_api_host: str | None = None, + management_api_host: str | None = None, + export_api_host: str | None = None, + data_api_port: int | None = None, + analytics_api_port: int | None = None, + management_api_port: int | None = None, + export_api_port: int | None = None, + timeout: int | None = None, + retries: int | None = None, + root_certificates: str | None = None, + extra_headers: Sequence[tuple[str, str]] | None = None, ): """ Initialize a new client. @@ -49,11 +54,17 @@ def __init__( data_api_host: Override default Exabel Data API host. analytics_api_host: Override default Exabel Analytics API host. management_api_host: Override default Exabel Management API host. + export_api_host: Override default Exabel Export API host. data_api_port: Override default Exabel Data API port. analytics_api_port: Override default Exabel Analytics API port. management_api_port: Override default Exabel Management API port. - timeout: Override default timeout in seconds to use for API requests. - root_certificates: Additional allowed root certificates for verifying TLS connection. + export_api_port: Override default Exabel Export API port. + timeout: Override default timeout in seconds to use for API requests (only + affects gRPC APIs). + retries: Override default number of retrie for requests (only affects Export + API). + root_certificates: Additional allowed root certificates for verifying TLS connection + (only affects gRPC APIs). """ config = ClientConfig( api_key=api_key, @@ -62,10 +73,13 @@ def __init__( data_api_host=data_api_host, analytics_api_host=analytics_api_host, management_api_host=management_api_host, + export_api_host=export_api_host, data_api_port=data_api_port, analytics_api_port=analytics_api_port, management_api_port=management_api_port, + export_api_port=export_api_port, timeout=timeout, + retries=retries, root_certificates=root_certificates, extra_headers=extra_headers, ) @@ -82,8 +96,10 @@ def __init__( self.library_api = LibraryApi(config) self.kpi_api = KpiApi(config) self.calendar_api = CalendarApi(config) + self.holiday_api = HolidayApi(config) self.namespace_api = NamespaceApi(config) - self._namespace: Optional[str] = None + self.export_api = ExportApi(config) + self._namespace: str | None = None @property def namespace(self) -> str: diff --git a/exabel_data_sdk/client/user_login.py b/exabel_data_sdk/client/user_login.py deleted file mode 100644 index 8ce28eee..00000000 --- a/exabel_data_sdk/client/user_login.py +++ /dev/null @@ -1,336 +0,0 @@ -from __future__ import annotations - -import base64 -import errno -import json -import os -import threading -import uuid -import webbrowser -from collections import defaultdict -from collections.abc import Mapping as MappingABC -from dataclasses import dataclass -from datetime import datetime -from functools import partial -from http.server import BaseHTTPRequestHandler, HTTPServer -from typing import Any, Dict, Mapping, MutableMapping, Optional -from urllib.parse import parse_qs, quote_plus, urlparse - -import requests - -DEFAULT_TOKEN_FILE_PATH = "~/.exabel" -DEFAULT_USER = "__default__" -GET_CURRENT_USER_URL_TEMPLATE = "https://{host}/v1/users/current" - - -@dataclass(frozen=True) -class RefreshTokens: - """Holds mappings from Exabel API hosts to mappings of users to refresh tokens.""" - - tokens: Mapping[str, Mapping[str, str]] - - def __post_init__(self) -> None: - """Validate the refresh token file.""" - if not isinstance(self.tokens, MappingABC): - raise ValueError(f"tokens must be a mapping, got: {self.tokens}") - for host, users in self.tokens.items(): - if not isinstance(host, str): - raise ValueError(f"host must be a string, got {host} in tokens {self.tokens}") - if not isinstance(users, MappingABC): - raise ValueError(f"users must be a mapping, got {users} in host: {host}") - for user, refresh_token in users.items(): - if not isinstance(user, str): - raise ValueError(f"user must be a string, got {user} in users: {users}") - if not isinstance(refresh_token, str): - raise ValueError( - f"refresh_token must be a string, got {refresh_token} in user: {user}" - ) - - def get_refresh_token(self, host: str, user: str) -> str: - """Get the refresh token for a given user and host.""" - return self.tokens.get(host, {}).get(user, "") - - def merge_with(self, other: RefreshTokens) -> RefreshTokens: - """ - Merge two RefreshTokens objects, values in the other RefreshTokens overwrite values in - this one. - """ - tokens: MutableMapping[str, MutableMapping[str, str]] = defaultdict(dict) - hosts = set(self.tokens.keys()).union(other.tokens.keys()) - for host in hosts: - tokens[host].update(self.tokens.get(host, {})) - tokens[host].update(other.tokens.get(host, {})) - return RefreshTokens(tokens) - - def to_json(self) -> str: - """Convert to JSON.""" - return json.dumps(self.tokens, indent=4) - - @staticmethod - def from_json(json_string: str) -> RefreshTokens: - """Create from JSON.""" - return RefreshTokens(json.loads(json_string)) - - @staticmethod - def from_host_user_token(host: str, user: str, token: str) -> RefreshTokens: - """Create from a host, user and token.""" - return RefreshTokens({host: {user: token}}) - - @staticmethod - def from_host_user(host: str, user: str) -> RefreshTokens: - """Create from a host and a user with the default empty token.""" - return RefreshTokens.from_host_user_token(host, user, "") - - -class UserLogin: - """ - Class for interactive programs that need to log in to the API as a user. - - Stores refresh tokens in '~/.exabel'. If a token is missing or expired, or - `reauthenticate` is set, the script starts a local web server and opens the user’s - web browser to log in to Auth0 to get a refresh token. - - The argument `use_test_backend` is only for internal engineering use at Exabel. - Customers and partners should leave it at the default value. - - To login with a specific user, e.g. `my_user@enterprise.com`, use the `user` argument. This is - useful if you have multiple users in your organization and want to switch between them without - having to reauthorize for each login. - - Get access token (to be used as “Bearer” authentication): - login.log_in() - login.access_token - """ - - def __init__( - self, - reauthenticate: bool = False, - use_test_backend: bool = False, - user: Optional[str] = None, - ): - self.auth0 = "auth-test.exabel.com" if use_test_backend else "auth.exabel.com" - self.client_id = ( - "VcQvJBLqhsTvKh1KMu6kXDCLWXumlubj" - if use_test_backend - else "6OoAPIEgqz1CQokkBuwtBcYKgNiLKsMF" - ) - self.backend = "endpoints-test.exabel.com" if use_test_backend else "endpoints.exabel.com" - self.authorization_code = "" - self.access_token = "" - self.redirect_uri = "" - self.state = "" - self.expires = datetime.utcnow() - self.callback_received = threading.Event() - self.reauthenticate = reauthenticate - self.user = user or DEFAULT_USER - self.tokens = RefreshTokens({}) - - @property - def refresh_token(self) -> str: - """The refresh token for the set user and host.""" - return self.tokens.get_refresh_token(host=self.backend, user=self.user) - - def start_http_server(self) -> HTTPServer: - """Start a local web server.""" - httpd = None - for port in range(8090, 8100): - try: - server_address = ("localhost", port) - handler = partial(TokenHandler, self) - httpd = HTTPServer(server_address, handler) - except OSError as error: - if error.errno != errno.EADDRINUSE: - raise error - if httpd is None: - raise ValueError("Cannot start a local HTTP server to receive the login token.") - - thread = threading.Thread(target=httpd.serve_forever, daemon=True) - thread.start() - return httpd - - def read_refresh_token(self) -> None: - """Read the refresh token from the user’s home directory.""" - filename = os.path.expanduser(DEFAULT_TOKEN_FILE_PATH) - if os.path.isfile(filename): - with open(filename, encoding="utf-8") as file: - content = file.read() - try: - self.tokens = self.tokens.merge_with(RefreshTokens.from_json(content)) - except json.decoder.JSONDecodeError: - # If the file is not valid JSON, it is an old refresh token. We convert it to - # the new JSON format after reading. - self.tokens = self.tokens.merge_with( - RefreshTokens.from_host_user_token(self.backend, self.user, content) - ) - self.write_refresh_token() - - def write_refresh_token(self) -> None: - """Write the refresh token to the user’s home directory.""" - filename = os.path.expanduser(DEFAULT_TOKEN_FILE_PATH) - with open(filename, "w", encoding="utf-8") as file: - file.write(self.tokens.to_json()) - - def get_access_token(self) -> bool: - """Get an access token from Auth0 using a refresh token.""" - if self.refresh_token == "" or self.reauthenticate: - self.reauthenticate = False - return False - - url = f"https://{self.auth0}/oauth/token" - data = { - "grant_type": "refresh_token", - "client_id": self.client_id, - "refresh_token": self.refresh_token, - } - response = requests.post(url, data=data, timeout=10) - if response.status_code == 200: - parsed = response.json() - self.access_token = parsed["access_token"] - return True - return False - - def get_tokens(self) -> bool: - """Get refresh and access tokens from Auth0 using an authorization code.""" - if self.authorization_code == "": - return False - - url = f"https://{self.auth0}/oauth/token" - data = { - "grant_type": "authorization_code", - "client_id": self.client_id, - "code": self.authorization_code, - "redirect_uri": self.redirect_uri, - } - response = requests.post(url, data=data, timeout=10) - if response.status_code == 200: - parsed = response.json() - self.access_token = parsed["access_token"] - self.tokens = self.tokens.merge_with( - RefreshTokens.from_host_user_token(self.backend, self.user, parsed["refresh_token"]) - ) - self.write_refresh_token() - return True - return False - - def get_authorization_code(self) -> None: - """ - Get an authorization code by opening a browser to Auth0 login. - """ - self.state = uuid.uuid4().hex - httpd = self.start_http_server() - self.redirect_uri = f"http://localhost:{httpd.server_port}/callback" - url = ( - f"https://{self.auth0}/authorize" - f"?audience=https://{self.backend}/" - "&scope=offline_access" - "&response_type=code" - f"&client_id={self.client_id}" - f"&redirect_uri={self.redirect_uri}" - f"&state={self.state}" - # Auth0 remembers which user you logged in as last time. This parameter forces Auth0 to - # show the login screen again. - "&prompt=login" - ) - if self.user != DEFAULT_USER: - url += f"&login_hint={quote_plus(self.user)}" - webbrowser.open(url) - self.callback_received.wait() - httpd.shutdown() - - def log_in(self) -> bool: - """ - Get an access token, asking the user to log in if necessary. - - Returns whether the user successfully logged in. - """ - self.read_refresh_token() - if not self.get_access_token(): - # None or expired refresh token - self.get_authorization_code() - self.get_tokens() - logged_in = self.access_token is not None - if logged_in: - payload = self.access_token.split(".")[1] - # base64.urlsafe_b64decode requires padding, but will truncate any unnecessary ones - parsed = json.loads(base64.urlsafe_b64decode(payload + "===")) - self.expires = datetime.utcfromtimestamp(parsed["exp"]) - self.verify_logged_in_user() - return logged_in - - def verify_logged_in_user(self) -> None: - """Verify the logged in user is the same as the one provided to the instance.""" - if self.user != DEFAULT_USER: - parsed = requests.get( - get_current_user_url(host=self.backend), headers=self.auth_headers, timeout=10 - ).json() - logged_in_user = parsed["email"] - if self.user != logged_in_user: - # Delete the refresh token for the wrong user to force reauthentication on next - # login attempt. - self.tokens = self.tokens.merge_with( - RefreshTokens.from_host_user(self.backend, self.user) - ) - self.write_refresh_token() - raise ValueError( - f"Logged in as {logged_in_user}, but expected to be logged in as {self.user}." - ) - - @property - def is_expired(self) -> bool: - """Whether the current access token has expired.""" - return self.expires < datetime.utcnow() - - @property - def auth_headers(self) -> Dict[str, str]: - """The authentication headers for HTTPS requests to the Exabel API.""" - return {"Authorization": f"Bearer {self.access_token}"} - - def get_auth_headers(self) -> Dict[str, str]: - """Log in and get the authentication headers for HTTPS requests to the Exabel API.""" - if not self.log_in(): - raise ValueError("Failed to log in.") - return self.auth_headers - - -class TokenHandler(BaseHTTPRequestHandler): - """HTTP reponse handler for Auth0 callbacks.""" - - def __init__(self, login: UserLogin, *args: Any, **kwargs: Any): - self.login = login - # BaseHTTPRequestHandler calls do_GET **inside** __init__, - # so we have to call super().__init__ after setting attributes. - super().__init__(*args, **kwargs) - - def log_message(self, format: str, *args: Any) -> None: # pylint: disable=redefined-builtin - return - - def do_GET(self) -> None: # pylint: disable=invalid-name - """Handle a GET request.""" - req = urlparse(self.path) - if req.path == "/callback": - query = parse_qs(req.query) - if "state" in query: - if query["state"][0] != self.login.state: - self.send_error(500) - if "code" in query: - self.login.authorization_code = query["code"][0] - - self.send_response(200) - self.send_header("content-type", "text/plain") - self.end_headers() - self.wfile.write( - bytes( - "The Python SDK is now logged in. Please close this window.", - "utf-8", - ) - ) - else: - self.send_error(400) - self.login.callback_received.set() - return - self.send_response(404) - - -def get_current_user_url(host: str) -> str: - """Get the URL to get the current user.""" - return GET_CURRENT_USER_URL_TEMPLATE.format(host=host) diff --git a/exabel_data_sdk/query/column.py b/exabel_data_sdk/query/column.py index e2aa29a1..a5ec4da7 100644 --- a/exabel_data_sdk/query/column.py +++ b/exabel_data_sdk/query/column.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Optional from exabel_data_sdk.query.literal import Literal, escape from exabel_data_sdk.query.predicate import Comparison, InPredicate @@ -17,7 +16,7 @@ class Column: """ name: str - expression: Optional[str] = None + expression: str | None = None def sql(self) -> str: """Returns the SQL representation of this column as used in the SELECT part.""" diff --git a/exabel_data_sdk/query/dashboard.py b/exabel_data_sdk/query/dashboard.py index 807cf639..8349ac5e 100644 --- a/exabel_data_sdk/query/dashboard.py +++ b/exabel_data_sdk/query/dashboard.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence, Union +from typing import Sequence from exabel_data_sdk.query.column import Column from exabel_data_sdk.query.query import Query @@ -19,9 +19,9 @@ class Dashboard: @staticmethod def query( - dashboard: Union[str, int], - columns: Optional[Sequence[str]] = None, - widget: Optional[Union[str, int]] = None, + dashboard: str | int, + columns: Sequence[str] | None = None, + widget: str | int | None = None, ) -> Query: """ Build a query for the dashboard table. diff --git a/exabel_data_sdk/query/literal.py b/exabel_data_sdk/query/literal.py index 4467b270..f4630348 100644 --- a/exabel_data_sdk/query/literal.py +++ b/exabel_data_sdk/query/literal.py @@ -1,8 +1,8 @@ -from typing import Union +from typing import TypeAlias import pandas as pd -Literal = Union[str, int, float, pd.Timestamp] +Literal: TypeAlias = str | int | float | pd.Timestamp def to_sql(literal: Literal) -> str: diff --git a/exabel_data_sdk/query/signals.py b/exabel_data_sdk/query/signals.py index f83ca359..39bf78ee 100644 --- a/exabel_data_sdk/query/signals.py +++ b/exabel_data_sdk/query/signals.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence, Union +from typing import Sequence import pandas as pd @@ -32,10 +32,10 @@ class Signals: @staticmethod def query( - columns: Sequence[Union[str, Column]], - tag: Optional[Union[str, Sequence[str]]] = None, - start_time: Optional[Union[str, pd.Timestamp]] = None, - end_time: Optional[Union[str, pd.Timestamp]] = None, + columns: Sequence[str | Column], + tag: str | Sequence[str] | None = None, + start_time: str | pd.Timestamp | None = None, + end_time: str | pd.Timestamp | None = None, predicates: Sequence[Predicate] = (), ) -> Query: """ diff --git a/exabel_data_sdk/scripts/actions.py b/exabel_data_sdk/scripts/actions.py index 2a6cc697..d8040b89 100644 --- a/exabel_data_sdk/scripts/actions.py +++ b/exabel_data_sdk/scripts/actions.py @@ -1,6 +1,6 @@ import argparse import warnings -from typing import Sequence, Union +from typing import Sequence from exabel_data_sdk.util.warnings import ExabelDeprecationWarning @@ -44,7 +44,7 @@ def __call__(self, parser, namespace, values, option_string=None) -> None: # ty setattr(namespace, self.dest, values) -def case_insensitive_argument(values: Union[str, Sequence, None]) -> Union[str, Sequence, None]: +def case_insensitive_argument(values: str | Sequence | None) -> str | Sequence | None: """ Lowercase all argument values that are type `str`. """ diff --git a/exabel_data_sdk/scripts/base_script.py b/exabel_data_sdk/scripts/base_script.py index 670516f6..bf0a8a0c 100644 --- a/exabel_data_sdk/scripts/base_script.py +++ b/exabel_data_sdk/scripts/base_script.py @@ -2,7 +2,7 @@ import argparse import os import urllib.parse -from typing import Optional, Sequence, Tuple +from typing import Sequence from exabel_data_sdk import ExabelClient from exabel_data_sdk.scripts.command_line_script import CommandLineScript @@ -51,6 +51,12 @@ def __init__( default="management.api.exabel.com", help="Management API host on format 'hostname[:port]'", ) + self.parser.add_argument( + "--exabel-export-api-host", + required=False, + default="export.api.exabel.com", + help="Export API host on format 'hostname[:port]'", + ) def run(self) -> None: args = self.parse_arguments() @@ -82,6 +88,7 @@ def run(self) -> None: management_api_host, management_api_port = self.parse_host_and_port( args.exabel_management_api_host ) + export_api_host, export_api_port = self.parse_host_and_port(args.exabel_export_api_host) client = ExabelClient( data_api_host=data_api_host, data_api_port=data_api_port, @@ -89,9 +96,12 @@ def run(self) -> None: analytics_api_port=analytics_api_port, management_api_host=management_api_host, management_api_port=management_api_port, + export_api_host=export_api_host, + export_api_port=export_api_port, api_key=api_key, access_token=access_token, extra_headers=extra_headers, + retries=getattr(args, "retries", None), ) self.run_script(client, args) @@ -106,7 +116,7 @@ def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: """ @staticmethod - def parse_host_and_port(host_and_port: str) -> Tuple[str, Optional[int]]: + def parse_host_and_port(host_and_port: str) -> tuple[str, int | None]: """ Parses a string on format 'hostname[:port]'. """ diff --git a/exabel_data_sdk/scripts/check_company_identifiers_in_csv.py b/exabel_data_sdk/scripts/check_company_identifiers_in_csv.py index 5d164d66..dea19662 100644 --- a/exabel_data_sdk/scripts/check_company_identifiers_in_csv.py +++ b/exabel_data_sdk/scripts/check_company_identifiers_in_csv.py @@ -1,6 +1,6 @@ import argparse import sys -from typing import Optional, Sequence +from typing import Sequence import pandas as pd @@ -54,7 +54,7 @@ def __init__(self, argv: Sequence[str]): ) def _get_identifier_type( - self, identifier_column: str, identifier_type: Optional[str] = None + self, identifier_column: str, identifier_type: str | None = None ) -> str: _identifier_type = identifier_type or identifier_column given_explicit_identifier_type = identifier_type is not None @@ -113,7 +113,7 @@ def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: ): print(checked_data_frame.to_string(index=False)) print( - f"{len(identifiers) - len(checked_data_frame[checked_data_frame['warning']!=''])} " + f"{len(identifiers) - len(checked_data_frame[checked_data_frame['warning'] != ''])} " f"identifiers successfully mapped to companies out of {len(identifiers)} " "identifiers in total" ) diff --git a/exabel_data_sdk/scripts/command_line_script.py b/exabel_data_sdk/scripts/command_line_script.py index fa117fb5..545f26c5 100644 --- a/exabel_data_sdk/scripts/command_line_script.py +++ b/exabel_data_sdk/scripts/command_line_script.py @@ -18,7 +18,7 @@ def parse_arguments(self) -> argparse.Namespace: def setup_logging( self, - format: str = "%(message)s", # pylint: disable=redefined-builtin + format: str = "%(message)s", level: int = logging.INFO, stream: TextIO = sys.stdout, ) -> None: diff --git a/exabel_data_sdk/scripts/create_entity_mapping_from_csv.py b/exabel_data_sdk/scripts/create_entity_mapping_from_csv.py index 2be06526..77db1a37 100644 --- a/exabel_data_sdk/scripts/create_entity_mapping_from_csv.py +++ b/exabel_data_sdk/scripts/create_entity_mapping_from_csv.py @@ -1,6 +1,6 @@ import argparse import sys -from typing import List, Sequence +from typing import Sequence import pandas as pd @@ -208,7 +208,7 @@ def _get_entity_mapping_by_id( return mapping_output - def get_markets(self, market: str) -> List[str]: + def get_markets(self, market: str) -> list[str]: """ Find the list of MICs we will use in entity search for a given market. diff --git a/exabel_data_sdk/scripts/csv_script.py b/exabel_data_sdk/scripts/csv_script.py index 9f8be624..c908ff22 100644 --- a/exabel_data_sdk/scripts/csv_script.py +++ b/exabel_data_sdk/scripts/csv_script.py @@ -1,7 +1,7 @@ import argparse import logging import os -from typing import Collection, Mapping, Optional, Sequence, Union +from typing import Collection, Mapping, Sequence import pandas as pd @@ -88,10 +88,10 @@ def __init__(self, argv: Sequence[str], description: str): ) def read_csv( - self, args: argparse.Namespace, string_columns: Optional[Collection[Union[str, int]]] = None + self, args: argparse.Namespace, string_columns: Collection[str | int] | None = None ) -> pd.DataFrame: """Read the CSV file from disk with the filename specified by command line argument.""" - dtype: Optional[Mapping[Union[str, int], type]] = None + dtype: Mapping[str | int, type] | None = None if string_columns: dtype = {column: str for column in string_columns} return pd.read_csv(args.filename, header=0, sep=args.sep, dtype=dtype) diff --git a/exabel_data_sdk/scripts/export_data.py b/exabel_data_sdk/scripts/export_data.py index eca89f32..b54fa17c 100644 --- a/exabel_data_sdk/scripts/export_data.py +++ b/exabel_data_sdk/scripts/export_data.py @@ -1,122 +1,49 @@ import argparse -import os import sys -from typing import Optional, Sequence +from typing import Sequence -from exabel_data_sdk.client.api.export_api import ExportApi -from exabel_data_sdk.client.user_login import UserLogin -from exabel_data_sdk.scripts.command_line_script import CommandLineScript +from exabel_data_sdk import ExabelClient +from exabel_data_sdk.scripts.base_script import BaseScript -class ExportData(CommandLineScript): +class ExportData(BaseScript): """Script for exporting data from the Exabel API with a user-provided query string.""" - def __init__(self, argv: Sequence[str], description: str = "Export data from Exabel"): + def __init__(self, argv: Sequence[str], description: str): super().__init__(argv, description) self.argv = argv - - def parse_arguments(self) -> argparse.Namespace: - """Parse the command-line input arguments.""" - parser = argparse.ArgumentParser(description="Export data") - parser.add_argument( + self.parser.add_argument( "--query", required=True, type=str, help="The query to execute", ) - parser.add_argument( + self.parser.add_argument( "--filename", required=True, type=str, help="The filename where the exported data should be saved", ) - parser.add_argument( + self.parser.add_argument( "--format", required=True, type=str, help="The format", ) - parser.add_argument( + self.parser.add_argument( "--retries", + required=False, type=int, - help="The number of times to retry each request.", default=0, + help="The maximum number of retries to make for each failed request. Defaults to 0.", ) - auth_group = parser.add_mutually_exclusive_group() - auth_group.add_argument( - "--reauthenticate", - action="store_true", - help="Reauthenticate the default user, for example to login to a different tenant " - "[deprecated - use --access-token]", - ) - auth_group.add_argument( - "--api-key", - type=str, - help="Authenticate using an API key", - ) - auth_group.add_argument( - "--access-token", - type=str, - help="Authenticate using an access token", - ) - auth_group.add_argument( - "--user", - type=str, - help="The Exabel user to log in as, e.g. `my_user@enterprise.com` [deprecated - use " - "--access-token]", - ) - parser.add_argument( - "--use-test-backend", - action="store_true", - help=argparse.SUPPRESS, - ) - return parser.parse_args(self.argv[1:]) - @staticmethod - def get_export_api( - api_key: Optional[str] = None, - reauthenticate: bool = False, - use_test_backend: bool = False, - user: Optional[str] = None, - retries: int = 0, - access_token: Optional[str] = None, - ) -> ExportApi: - """Get an `ExportApi` from an API key, access token, or user authentication.""" - # Command line argument takes precedence over environment variables. - if not api_key and not access_token and not user: - api_key = os.getenv("EXABEL_API_KEY") - access_token = os.getenv("EXABEL_ACCESS_TOKEN") - if api_key and access_token: - raise ValueError("Only one of EXABEL_API_KEY and EXABEL_ACCESS_TOKEN can be set.") - if api_key: - return ExportApi.from_api_key(api_key, use_test_backend, retries=retries) - if access_token: - return ExportApi.from_access_token(access_token, use_test_backend, retries=retries) - - sys.stderr.write( - "Username/password authentication is deprecated. Change to --access-token.\n" - ) - - login = UserLogin(reauthenticate, use_test_backend, user) - headers = login.get_auth_headers() - return ExportApi(auth_headers=headers, backend=login.backend, retries=retries) - - def run(self) -> None: + def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: """Download data from the Exabel API and store it to file.""" - args = self.parse_arguments() - self.setup_logging() - export_api = ExportData.get_export_api( - args.api_key, - args.reauthenticate, - args.use_test_backend, - args.user, - args.retries, - args.access_token, - ) - content = export_api.run_query_bytes(query=args.query, file_format=args.format) + content = client.export_api.run_query_bytes(query=args.query, file_format=args.format) with open(args.filename, "wb") as file: file.write(content) if __name__ == "__main__": - ExportData(sys.argv).run() + ExportData(sys.argv, "Export data from Exabel").run() diff --git a/exabel_data_sdk/scripts/export_signals.py b/exabel_data_sdk/scripts/export_signals.py index 1d0b9b20..ea0b29cf 100644 --- a/exabel_data_sdk/scripts/export_signals.py +++ b/exabel_data_sdk/scripts/export_signals.py @@ -1,15 +1,13 @@ import argparse import logging -import os import sys from datetime import timedelta from time import time -from typing import List, Sequence, Set +from typing import Sequence import pandas as pd from exabel_data_sdk import ExabelClient -from exabel_data_sdk.client.api.export_api import ExportApi from exabel_data_sdk.scripts.base_script import BaseScript from exabel_data_sdk.scripts.file_utils import ( supported_formats_message, @@ -70,12 +68,6 @@ def __init__(self, argv: Sequence[str], description: str): help="The number of entities to evaluate in each batch.", default=100, ) - self.parser.add_argument( - "--retries", - type=int, - help="The number of times to retry each request.", - default=3, - ) self.parser.add_argument( "--show-progress", required=False, @@ -83,25 +75,18 @@ def __init__(self, argv: Sequence[str], description: str): default=False, help="Show progress bar", ) - - @staticmethod - def get_api_key(args: argparse.Namespace) -> str: - """ - Get the API key to use, either from the command line arguments or the environment. - Raises SystemExit if there is no API key provided. - """ - api_key = args.api_key or os.getenv("EXABEL_API_KEY") - if not api_key: - print("No API key specified.") - print("Use the --api-key command line argument or EXABEL_API_KEY environment variable.") - sys.exit(1) - return api_key + self.parser.add_argument( + "--retries", + required=False, + type=int, + default=0, + help="The maximum number of retries to make for each failed request. Defaults to 0.", + ) def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: """Download data from the Exabel API and store it to file.""" - api_key = self.get_api_key(args) start_time = time() - tag_results: List[Set[str]] = [] + tag_results: list[set[str]] = [] for tag in args.tag: if not tag.startswith("tags/"): tag = f"tags/{tag}" @@ -112,11 +97,10 @@ def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: if len(tag_results) > 1: entities = entities.union(*tag_results[1:]) print("In total", len(entities), "entities") - export_api = ExportApi.from_api_key(api_key, retries=args.retries) signals = args.signal print("Downloading signal(s):", ", ".join(signals)) logging.getLogger("exabel_data_sdk.client.api.export_api").setLevel(logging.WARNING) - data = export_api.batched_signal_query( + data = client.export_api.batched_signal_query( batch_size=args.batch_size, signal=signals, resource_name=list(entities), diff --git a/exabel_data_sdk/scripts/get_time_series.py b/exabel_data_sdk/scripts/get_time_series.py index adffc4ad..ecd3c3d3 100644 --- a/exabel_data_sdk/scripts/get_time_series.py +++ b/exabel_data_sdk/scripts/get_time_series.py @@ -72,9 +72,7 @@ def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: ) pd.set_option("display.max_rows", None) - pd.set_option( - "display.float_format", "{:}".format # pylint: disable=consider-using-f-string - ) + pd.set_option("display.float_format", "{:}".format) print(result) diff --git a/exabel_data_sdk/scripts/list_entities.py b/exabel_data_sdk/scripts/list_entities.py index c26e119b..83ef3f0f 100644 --- a/exabel_data_sdk/scripts/list_entities.py +++ b/exabel_data_sdk/scripts/list_entities.py @@ -1,7 +1,7 @@ import argparse import sys from math import ceil -from typing import List, Optional, Sequence +from typing import Sequence from exabel_data_sdk import ExabelClient from exabel_data_sdk.client.api.data_classes.entity import Entity @@ -49,7 +49,7 @@ def _list_entities( self, client: ExabelClient, entity_type: str, - limit: Optional[int] = None, + limit: int | None = None, show_progress: bool = False, exclude_read_only: bool = False, ) -> Sequence[Entity]: @@ -64,7 +64,7 @@ def _list_entities( print(f"Limiting to {limit} entities") num_entities = min(num_entities, limit) - all_entities: List[Entity] = [] + all_entities: list[Entity] = [] for _ in conditional_progress_bar( range(1, ceil(num_entities / PAGE_SIZE) + 1), show_progress=show_progress, diff --git a/exabel_data_sdk/scripts/list_relationship_types.py b/exabel_data_sdk/scripts/list_relationship_types.py index 00bceb01..9ed1df31 100644 --- a/exabel_data_sdk/scripts/list_relationship_types.py +++ b/exabel_data_sdk/scripts/list_relationship_types.py @@ -1,6 +1,5 @@ import argparse import sys -from typing import List from exabel_data_sdk import ExabelClient from exabel_data_sdk.client.api.data_classes.relationship_type import RelationshipType @@ -15,7 +14,7 @@ class ListRelationshipTypes(BaseScript): def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: page_token = None - all_relationship_types: List[RelationshipType] = [] + all_relationship_types: list[RelationshipType] = [] while True: result = client.relationship_api.list_relationship_types( page_size=PAGE_SIZE, page_token=page_token diff --git a/exabel_data_sdk/scripts/list_time_series.py b/exabel_data_sdk/scripts/list_time_series.py index 6b72df43..617efb97 100644 --- a/exabel_data_sdk/scripts/list_time_series.py +++ b/exabel_data_sdk/scripts/list_time_series.py @@ -2,7 +2,7 @@ import sys from concurrent.futures import ThreadPoolExecutor from math import ceil -from typing import List, Optional, Sequence +from typing import Sequence from exabel_data_sdk import ExabelClient from exabel_data_sdk.client.api.data_classes.entity import Entity @@ -49,7 +49,7 @@ def __init__(self, argv: Sequence[str], description: str): ) def _filter_ts_list( - self, ts_list: Sequence[str], ts_filter: Optional[str] = None + self, ts_list: Sequence[str], ts_filter: str | None = None ) -> Sequence[str]: if ts_filter: return [ts for ts in ts_list if ts_filter in ts] @@ -58,9 +58,9 @@ def _filter_ts_list( def _list_time_series( self, client: ExabelClient, - entity: Optional[str] = None, - signal: Optional[str] = None, - entity_type: Optional[str] = None, + entity: str | None = None, + signal: str | None = None, + entity_type: str | None = None, show_progress: bool = False, ) -> Sequence[str]: if (signal is None) == (entity is None) == (entity_type is None): @@ -69,8 +69,8 @@ def _list_time_series( "only signal, entity-type or entity, but not all three." ) - all_time_series: List[str] = [] - all_entities: List[Entity] = [] + all_time_series: list[str] = [] + all_entities: list[Entity] = [] if entity: if signal: @@ -134,7 +134,7 @@ def _list_time_series( def _get_entity_time_series(entity: Entity) -> Sequence[str]: page_token = None - ts_list: List[str] = [] + ts_list: list[str] = [] while True: result = client.time_series_api.get_entity_time_series( entity.name, page_size=PAGE_SIZE, page_token=page_token diff --git a/exabel_data_sdk/scripts/login.py b/exabel_data_sdk/scripts/login.py deleted file mode 100644 index fb8d630c..00000000 --- a/exabel_data_sdk/scripts/login.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Script for logging in to Auth0 in order to get an access token.""" - -import argparse -import sys -from typing import List - -from exabel_data_sdk.client.user_login import UserLogin - - -def main(argv: List[str]) -> None: - """Set up the environment and login process.""" - parser = argparse.ArgumentParser() - parser.add_argument( - "--reauthenticate", - action="store_true", - help="Reauthenticate the user, for example to login to a different tenant", - ) - parser.add_argument( - "--use-test-backend", - action="store_true", - help=argparse.SUPPRESS, - ) - args = parser.parse_args(argv[1:]) - - login = UserLogin(args.reauthenticate, args.use_test_backend) - success = login.log_in() - if success: - print("Successfully logged in.") - else: - print("Failed to log in.") - - -if __name__ == "__main__": - main(sys.argv) diff --git a/exabel_data_sdk/scripts/sql/read_snowflake.py b/exabel_data_sdk/scripts/sql/read_snowflake.py index e6b4d615..e00b21c9 100644 --- a/exabel_data_sdk/scripts/sql/read_snowflake.py +++ b/exabel_data_sdk/scripts/sql/read_snowflake.py @@ -1,7 +1,7 @@ import sys from getpass import getpass from os import path -from typing import Optional, Sequence +from typing import Sequence from exabel_data_sdk.scripts.sql.sql_script import SqlScript from exabel_data_sdk.services.sql.snowflake_reader import SnowflakeReader @@ -58,7 +58,7 @@ def __init__(self, argv: Sequence[str]): help="The role to use. Required if no default is set for the user.", ) - def read_key(self, file: str, passphrase: Optional[str]) -> bytes: + def read_key(self, file: str, passphrase: str | None) -> bytes: """Read the key from given file. Use the provided passphrase to decrypt the file. If not passphrase is provided, promt the user to enter one. Provide an empty string as passphrase for unencrypted keys.""" diff --git a/exabel_data_sdk/scripts/sql/sql_script.py b/exabel_data_sdk/scripts/sql/sql_script.py index 033ed468..449d83d1 100644 --- a/exabel_data_sdk/scripts/sql/sql_script.py +++ b/exabel_data_sdk/scripts/sql/sql_script.py @@ -1,5 +1,5 @@ import abc -from typing import Sequence, Type +from typing import Sequence from exabel_data_sdk.scripts.command_line_script import CommandLineScript from exabel_data_sdk.services.sql.sql_reader_configuration import SqlReaderConfiguration @@ -16,7 +16,7 @@ def __init__( self, argv: Sequence[str], description: str, - reader_configuration_class: Type[SqlReaderConfiguration], + reader_configuration_class: type[SqlReaderConfiguration], ): super().__init__(argv, description) self.reader_configuration_class = reader_configuration_class diff --git a/exabel_data_sdk/scripts/update_data_set.py b/exabel_data_sdk/scripts/update_data_set.py index edbd1d70..188e1a77 100644 --- a/exabel_data_sdk/scripts/update_data_set.py +++ b/exabel_data_sdk/scripts/update_data_set.py @@ -1,6 +1,6 @@ import argparse import sys -from typing import List, Sequence +from typing import Sequence from google.protobuf.field_mask_pb2 import FieldMask @@ -64,8 +64,7 @@ def __init__(self, argv: Sequence[str], description: str): required=False, action="store_true", help=( - "Whether signals should be removed or replace the existing signals of the data " - "set." + "Whether signals should be removed or replace the existing signals of the data set." ), ) @@ -82,7 +81,7 @@ def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: if args.add_signals and not args.signals and not args.signals_file: raise ValueError("Signals must be given when --add-signals is set.") - signals: List[str] = [] + signals: list[str] = [] if args.signals: signals = args.signals elif args.signals_file: diff --git a/exabel_data_sdk/scripts/utils.py b/exabel_data_sdk/scripts/utils.py index c991d20f..b3ea6807 100644 --- a/exabel_data_sdk/scripts/utils.py +++ b/exabel_data_sdk/scripts/utils.py @@ -1,5 +1,5 @@ import argparse -from typing import Iterable, Sequence, Union +from typing import Iterable, Sequence from tqdm import tqdm @@ -21,7 +21,7 @@ def conditional_progress_bar( - iterable: Iterable, show_progress: bool = False, **kwargs: Union[str, int] + iterable: Iterable, show_progress: bool = False, **kwargs: str | int ) -> Iterable: """ Returns a tqdm progress bar if show_progress is True, otherwise returns the iterable unchanged. diff --git a/exabel_data_sdk/services/csv_entity_loader.py b/exabel_data_sdk/services/csv_entity_loader.py index ab09a558..ad06ae45 100644 --- a/exabel_data_sdk/services/csv_entity_loader.py +++ b/exabel_data_sdk/services/csv_entity_loader.py @@ -1,5 +1,5 @@ import logging -from typing import Mapping, Optional, Set, Union +from typing import Mapping from pandas import DataFrame, Index from pandas.core.arrays import ExtensionArray @@ -38,23 +38,23 @@ def load_entities( *, filename: str, separator: str = ",", - entity_type: Optional[str] = None, - entity_column: Optional[str] = None, - display_name_column: Optional[str] = None, - description_column: Optional[str] = None, - property_columns: Optional[Mapping[str, type]] = None, + entity_type: str | None = None, + entity_column: str | None = None, + display_name_column: str | None = None, + description_column: str | None = None, + property_columns: Mapping[str, type] | None = None, threads: int = DEFAULT_NUMBER_OF_THREADS, upsert: bool = False, dry_run: bool = False, error_on_any_failure: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, - batch_size: Optional[int] = None, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, + batch_size: int | None = None, return_results: bool = True, - total_rows: Optional[int] = None, + total_rows: int | None = None, # Deprecated arguments - name_column: Optional[str] = None, # pylint: disable=unused-argument - namespace: Optional[str] = None, # pylint: disable=unused-argument + name_column: str | None = None, + namespace: str | None = None, ) -> FileLoadingResult: """ Load a CSV file and upload the entities specified therein to the Exabel Data API. @@ -95,7 +95,7 @@ def load_entities( filename, separator, string_columns=[], keep_default_na=False, nrows=0 ) - string_columns: Set[Union[str, int]] = set() + string_columns: set[str | int] = set() name_col_ref = entity_column or 0 display_name_col_ref = ( display_name_column or preview_df.columns[1] @@ -173,17 +173,17 @@ def _load_entities( self, *, data_frame: DataFrame, - entity_type: Optional[str], + entity_type: str | None, name_col: str, display_name_col: str, - description_col: Optional[str], + description_col: str | None, property_columns: Mapping[str, type], threads: int = DEFAULT_NUMBER_OF_THREADS, upsert: bool = False, dry_run: bool = False, error_on_any_failure: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, ) -> FileLoadingResult: # Entity types are reversed because we want to match the first entity type returned by the # API lexicographically. @@ -252,7 +252,7 @@ def _load_entities( @staticmethod def detect_duplicate_entities( entities_dataframe: DataFrame, - name_col: Union[str, None, ExtensionArray, Index], + name_col: str | None | ExtensionArray | Index, ) -> None: """Detects duplicate entities""" duplicated_original_names = entities_dataframe[name_col][ @@ -266,8 +266,8 @@ def detect_duplicate_entities( raise ValueError("Duplicate entities detected") def _get_description_column( - self, columns: Index, property_columns: Optional[Mapping[str, type]] - ) -> Optional[str]: + self, columns: Index, property_columns: Mapping[str, type] | None + ) -> str | None: """Get the third column if it exists and it is not a property column""" if len(columns) > 2 and (property_columns is None or columns[2] not in property_columns): return columns[2] diff --git a/exabel_data_sdk/services/csv_exception.py b/exabel_data_sdk/services/csv_exception.py index 82e5ed0c..46d7e8b4 100644 --- a/exabel_data_sdk/services/csv_exception.py +++ b/exabel_data_sdk/services/csv_exception.py @@ -1,6 +1,5 @@ """This file aliases an import for backwards compatibility after an exception object was renamed.""" -# pylint: disable=unused-import -from exabel_data_sdk.services.file_loading_exception import ( +from exabel_data_sdk.services.file_loading_exception import ( # noqa: F401 FileLoadingException as CsvLoadingException, ) diff --git a/exabel_data_sdk/services/csv_loading_result.py b/exabel_data_sdk/services/csv_loading_result.py index ec3fd251..1594cbd1 100644 --- a/exabel_data_sdk/services/csv_loading_result.py +++ b/exabel_data_sdk/services/csv_loading_result.py @@ -1,4 +1,5 @@ """This file aliases an import for backwards compatibility after the result object was renamed.""" -# pylint: disable=unused-import -from exabel_data_sdk.services.file_loading_result import FileLoadingResult as CsvLoadingResult +from exabel_data_sdk.services.file_loading_result import ( + FileLoadingResult as CsvLoadingResult, # noqa: F401 +) diff --git a/exabel_data_sdk/services/csv_reader.py b/exabel_data_sdk/services/csv_reader.py index 5ac2f059..fe35df31 100644 --- a/exabel_data_sdk/services/csv_reader.py +++ b/exabel_data_sdk/services/csv_reader.py @@ -1,4 +1,4 @@ -from typing import Iterable, Iterator, Mapping, Optional, Union, overload +from typing import Iterable, Iterator, Mapping, overload import pandas as pd @@ -10,19 +10,19 @@ class CsvReader: @staticmethod def read_file( filename: str, - separator: Optional[str], - string_columns: Iterable[Union[str, int]], + separator: str | None, + string_columns: Iterable[str | int], *, keep_default_na: bool, - nrows: Optional[int], + nrows: int | None, ) -> pd.DataFrame: ... @overload @staticmethod def read_file( filename: str, - separator: Optional[str], - string_columns: Iterable[Union[str, int]], + separator: str | None, + string_columns: Iterable[str | int], *, keep_default_na: bool, ) -> pd.DataFrame: ... @@ -31,8 +31,8 @@ def read_file( @staticmethod def read_file( filename: str, - separator: Optional[str], - string_columns: Iterable[Union[str, int]], + separator: str | None, + string_columns: Iterable[str | int], *, keep_default_na: bool, chunksize: int, @@ -42,23 +42,23 @@ def read_file( @staticmethod def read_file( filename: str, - separator: Optional[str], - string_columns: Iterable[Union[str, int]], + separator: str | None, + string_columns: Iterable[str | int], *, keep_default_na: bool, - chunksize: Optional[int], - ) -> Union[pd.DataFrame, Iterator[pd.DataFrame]]: ... + chunksize: int | None, + ) -> pd.DataFrame | Iterator[pd.DataFrame]: ... @staticmethod def read_file( filename: str, - separator: Optional[str], - string_columns: Iterable[Union[str, int]], + separator: str | None, + string_columns: Iterable[str | int], *, keep_default_na: bool, - nrows: Optional[int] = None, - chunksize: Optional[int] = None, - ) -> Union[pd.DataFrame, Iterator[pd.DataFrame]]: + nrows: int | None = None, + chunksize: int | None = None, + ) -> pd.DataFrame | Iterator[pd.DataFrame]: """ Read the given file and return the content as a pandas DataFrame. @@ -69,7 +69,7 @@ def read_file( keep_default_na: whether to parse default nan values as nan nrows: how many rows to parse """ - dtype: Optional[Mapping[Union[str, int], type]] = None + dtype: Mapping[str | int, type] | None = None if string_columns: dtype = {column: str for column in string_columns} return pd.read_csv( diff --git a/exabel_data_sdk/services/csv_relationship_loader.py b/exabel_data_sdk/services/csv_relationship_loader.py index 6aac8088..08a9a2b3 100644 --- a/exabel_data_sdk/services/csv_relationship_loader.py +++ b/exabel_data_sdk/services/csv_relationship_loader.py @@ -1,7 +1,7 @@ import logging from dataclasses import dataclass from itertools import chain -from typing import Mapping, Optional, Sequence, Set, Tuple, Union +from typing import Mapping, Sequence from pandas import DataFrame @@ -44,13 +44,13 @@ class EntityColumnConfiguration: index: The index of the column. """ - entity_type: Optional[str] = None - identifier_type: Optional[str] = None - name: Optional[str] = None - index: Optional[int] = None + entity_type: str | None = None + identifier_type: str | None = None + name: str | None = None + index: int | None = None @property - def reference(self) -> Union[str, int]: + def reference(self) -> str | int: """ The column reference, either the column name or the column index. """ @@ -85,12 +85,12 @@ class RelationshipLoaderColumnConfiguration: @staticmethod def _validate_argument_combination( - from_entity_type: Optional[str] = None, - from_identifier_type: Optional[str] = None, - from_entity_column: Optional[str] = None, - to_entity_type: Optional[str] = None, - to_identifier_type: Optional[str] = None, - to_entity_column: Optional[str] = None, + from_entity_type: str | None = None, + from_identifier_type: str | None = None, + from_entity_column: str | None = None, + to_entity_type: str | None = None, + to_identifier_type: str | None = None, + to_entity_column: str | None = None, ) -> None: """ Validate that the argument combination is valid. @@ -135,8 +135,7 @@ def _validate_argument_combination( if to_identifier_type: if to_entity_type is None: raise ValueError( - "to_identifier_type can only be specified in combination with " - "to_entity_type." + "to_identifier_type can only be specified in combination with to_entity_type." ) if to_entity_type == "company": if to_identifier_type not in COMPANY_SEARCH_TERM_FIELDS: @@ -163,11 +162,11 @@ def _from_entity_types( cls, *, from_entity_type: str, - from_identifier_type: Optional[str] = None, - from_entity_column: Optional[str] = None, + from_identifier_type: str | None = None, + from_entity_column: str | None = None, to_entity_type: str, - to_identifier_type: Optional[str] = None, - to_entity_column: Optional[str] = None, + to_identifier_type: str | None = None, + to_entity_column: str | None = None, ) -> "RelationshipLoaderColumnConfiguration": """Create configuration from specified entity types.""" logger.debug("Creating column configuration from entity types.") @@ -210,12 +209,12 @@ def _from_specified_columns( @classmethod def from_arguments( cls, - from_entity_type: Optional[str] = None, - from_identifier_type: Optional[str] = None, - from_entity_column: Optional[str] = None, - to_entity_type: Optional[str] = None, - to_identifier_type: Optional[str] = None, - to_entity_column: Optional[str] = None, + from_entity_type: str | None = None, + from_identifier_type: str | None = None, + from_entity_column: str | None = None, + to_entity_type: str | None = None, + to_identifier_type: str | None = None, + to_entity_column: str | None = None, ) -> "RelationshipLoaderColumnConfiguration": """ Create a RelationshipLoaderConfiguration from the given arguments. @@ -254,7 +253,7 @@ def from_arguments( return cls._from_default_values() raise ValueError("Cannot determine entity types and columns from provided arguments.") - def get_from_and_to_column_names(self, columns: Sequence[str]) -> Tuple[str, str]: + def get_from_and_to_column_names(self, columns: Sequence[str]) -> tuple[str, str]: """ Get the from and to column names from the given columns. """ @@ -286,30 +285,30 @@ def load_relationships( self, *, filename: str, - entity_mapping_filename: Optional[str] = None, + entity_mapping_filename: str | None = None, separator: str = ",", relationship_type: str, - from_entity_type: Optional[str] = None, - from_identifier_type: Optional[str] = None, - from_entity_column: Optional[str] = None, - to_entity_type: Optional[str] = None, - to_identifier_type: Optional[str] = None, - to_entity_column: Optional[str] = None, - description_column: Optional[str] = None, - property_columns: Optional[Mapping[str, type]] = None, + from_entity_type: str | None = None, + from_identifier_type: str | None = None, + from_entity_column: str | None = None, + to_entity_type: str | None = None, + to_identifier_type: str | None = None, + to_entity_column: str | None = None, + description_column: str | None = None, + property_columns: Mapping[str, type] | None = None, threads: int = DEFAULT_NUMBER_OF_THREADS, upsert: bool = False, dry_run: bool = False, error_on_any_failure: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, - batch_size: Optional[int] = None, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, + batch_size: int | None = None, return_results: bool = True, - total_rows: Optional[int] = None, + total_rows: int | None = None, # Deprecated arguments: - entity_from_column: Optional[str] = None, # pylint: disable=unused-argument - entity_to_column: Optional[str] = None, # pylint: disable=unused-argument - namespace: Optional[str] = None, # pylint: disable=unused-argument + entity_from_column: str | None = None, + entity_to_column: str | None = None, + namespace: str | None = None, ) -> FileLoadingResult: """ Load a CSV file and upload the relationships specified therein to the Exabel Data API. @@ -368,7 +367,7 @@ def load_relationships( preview_df = CsvReader.read_file( filename, separator, string_columns=[], keep_default_na=False, nrows=0 ) - string_columns: Set[Union[str, int]] = set() + string_columns: set[str | int] = set() string_columns.update( ( get_case_insensitive_column(config.from_column.reference, preview_df.columns), @@ -430,17 +429,17 @@ def load_relationships( def _load_relationships( self, data_frame: DataFrame, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]], + entity_mapping: Mapping[str, Mapping[str, str]] | None, relationship_type_name: str, config: RelationshipLoaderColumnConfiguration, - description_column: Optional[str], + description_column: str | None, property_columns: Mapping[str, type], threads: int = DEFAULT_NUMBER_OF_THREADS, upsert: bool = False, dry_run: bool = False, error_on_any_failure: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, ) -> FileLoadingResult: from_entity_column_name, to_entity_column_name = config.get_from_and_to_column_names( data_frame.columns diff --git a/exabel_data_sdk/services/csv_time_series_loader.py b/exabel_data_sdk/services/csv_time_series_loader.py index bcda9bd0..6da37d4b 100644 --- a/exabel_data_sdk/services/csv_time_series_loader.py +++ b/exabel_data_sdk/services/csv_time_series_loader.py @@ -1,5 +1,3 @@ -from typing import Optional - from exabel_data_sdk import ExabelClient from exabel_data_sdk.services.csv_loading_constants import ( DEFAULT_ABORT_THRESHOLD, @@ -24,23 +22,23 @@ def load_time_series( self, *, filename: str, - entity_mapping_filename: Optional[str] = None, + entity_mapping_filename: str | None = None, separator: str = ",", pit_current_time: bool = False, - pit_offset: Optional[int] = None, + pit_offset: int | None = None, create_missing_signals: bool = False, create_library_signal: bool = True, - global_time_series: Optional[bool] = None, + global_time_series: bool | None = None, threads: int = DEFAULT_NUMBER_OF_THREADS_FOR_IMPORT, dry_run: bool = False, error_on_any_failure: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, skip_validation: bool = False, case_sensitive_signals: bool = False, # Deprecated arguments - create_tag: Optional[bool] = None, # pylint: disable=unused-argument - namespace: Optional[str] = None, # pylint: disable=unused-argument + create_tag: bool | None = None, + namespace: str | None = None, ) -> FileLoadingResult: """ Load a CSV file and upload the time series to the Exabel Data API diff --git a/exabel_data_sdk/services/csv_writer.py b/exabel_data_sdk/services/csv_writer.py index 0bea0393..8a78d618 100644 --- a/exabel_data_sdk/services/csv_writer.py +++ b/exabel_data_sdk/services/csv_writer.py @@ -1,6 +1,6 @@ import logging import os -from typing import Iterable, Union +from typing import Iterable import pandas as pd @@ -13,9 +13,7 @@ class CsvWriter(FileWriter): """Stores a DataFrame in a CSV file.""" @staticmethod - def write_file( - df: Union[pd.DataFrame, Iterable[pd.DataFrame]], filepath: str - ) -> FileWritingResult: + def write_file(df: pd.DataFrame | Iterable[pd.DataFrame], filepath: str) -> FileWritingResult: rows = 0 if isinstance(df, pd.DataFrame): df.to_csv(filepath, index=False) diff --git a/exabel_data_sdk/services/entity_mapping_file_reader.py b/exabel_data_sdk/services/entity_mapping_file_reader.py index a30f5c99..3bd20193 100644 --- a/exabel_data_sdk/services/entity_mapping_file_reader.py +++ b/exabel_data_sdk/services/entity_mapping_file_reader.py @@ -1,5 +1,5 @@ import json -from typing import Mapping, Optional +from typing import Mapping import pandas as pd @@ -11,8 +11,8 @@ class EntityMappingFileReader: @staticmethod def read_entity_mapping_file( - filename: Optional[str], *, separator: str = "," - ) -> Optional[Mapping[str, Mapping[str, str]]]: + filename: str | None, *, separator: str = "," + ) -> Mapping[str, Mapping[str, str]] | None: """ Read the entity mapping file from disk with the filename specified by command line argument. Only supports *.json and *.csv file extensions. @@ -24,19 +24,17 @@ def read_entity_mapping_file( if filename.endswith(".csv"): return EntityMappingFileReader._read_csv(filename, separator=separator) raise FileLoadingException( - "Expected the entity mapping file to be a *.json or *.csv file, " - f"but got: '{filename}'." + f"Expected the entity mapping file to be a *.json or *.csv file, but got: '{filename}'." ) @staticmethod - def _read_json(filename: str) -> Optional[Mapping[str, Mapping[str, str]]]: + def _read_json(filename: str) -> Mapping[str, Mapping[str, str]] | None: with open(filename, "r", encoding="utf-8") as f: mappings = json.load(f) # validate the mapping is a dictionary (and not a list) if not isinstance(mappings, dict): raise FileLoadingException( - "Expected entity mapping file to be a JSON key-value object, " - f"but got: {mappings}" + f"Expected entity mapping file to be a JSON key-value object, but got: {mappings}" ) for value in mappings.values(): if not isinstance(value, dict): @@ -57,7 +55,7 @@ def _read_json(filename: str) -> Optional[Mapping[str, Mapping[str, str]]]: return mappings @staticmethod - def _read_csv(filename: str, separator: str) -> Optional[Mapping[str, Mapping[str, str]]]: + def _read_csv(filename: str, separator: str) -> Mapping[str, Mapping[str, str]] | None: csv_data_frame = pd.read_csv(filename, header=0, sep=separator, dtype="str") identifier_columns = [col for col in csv_data_frame.columns if not col.endswith("_entity")] entity_columns = [col for col in csv_data_frame.columns if col.endswith("_entity")] @@ -79,7 +77,7 @@ def _read_csv(filename: str, separator: str) -> Optional[Mapping[str, Mapping[st if invalid_entities: raise FileLoadingException( "The entity mapping CSV file is missing one or more identifier columns: " - f"{[entity[:-len('_entity')] for entity in invalid_entities]}" + f"{[entity[: -len('_entity')] for entity in invalid_entities]}" ) mappings = {} diff --git a/exabel_data_sdk/services/excel_writer.py b/exabel_data_sdk/services/excel_writer.py index c3f175ed..e0fcae52 100644 --- a/exabel_data_sdk/services/excel_writer.py +++ b/exabel_data_sdk/services/excel_writer.py @@ -1,4 +1,4 @@ -from typing import Iterable, Union +from typing import Iterable import pandas as pd @@ -9,9 +9,7 @@ class ExcelWriter(FileWriter): """Stores a DataFrame in an Excel file.""" @staticmethod - def write_file( - df: Union[pd.DataFrame, Iterable[pd.DataFrame]], filepath: str - ) -> FileWritingResult: + def write_file(df: pd.DataFrame | Iterable[pd.DataFrame], filepath: str) -> FileWritingResult: if isinstance(df, pd.DataFrame): df.to_excel(filepath, index=False) return FileWritingResult(len(df)) diff --git a/exabel_data_sdk/services/feather_writer.py b/exabel_data_sdk/services/feather_writer.py index 71c64675..5f72419e 100644 --- a/exabel_data_sdk/services/feather_writer.py +++ b/exabel_data_sdk/services/feather_writer.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import Iterable, Union +from typing import Iterable import pandas as pd @@ -13,9 +13,7 @@ class FeatherWriter(FileWriter): """Stores a DataFrame in a Feather file.""" @staticmethod - def write_file( - df: Union[pd.DataFrame, Iterable[pd.DataFrame]], filepath: str - ) -> FileWritingResult: + def write_file(df: pd.DataFrame | Iterable[pd.DataFrame], filepath: str) -> FileWritingResult: rows = 0 if isinstance(df, pd.DataFrame): df.to_feather(filepath) diff --git a/exabel_data_sdk/services/file_loading_exception.py b/exabel_data_sdk/services/file_loading_exception.py index e959cf83..563846d8 100644 --- a/exabel_data_sdk/services/file_loading_exception.py +++ b/exabel_data_sdk/services/file_loading_exception.py @@ -1,4 +1,4 @@ -from typing import Generic, Optional, Sequence +from typing import Generic, Sequence from exabel_data_sdk.client.api.resource_creation_result import ResourceCreationResult from exabel_data_sdk.services.file_loading_result import ResourceT @@ -11,8 +11,8 @@ def __init__( self, message: str, *, - failures: Optional[Sequence[ResourceCreationResult[ResourceT]]] = None, + failures: Sequence[ResourceCreationResult[ResourceT]] | None = None, ): super().__init__(message) self.message = message - self.failures: Optional[Sequence[ResourceCreationResult[ResourceT]]] = failures + self.failures: Sequence[ResourceCreationResult[ResourceT]] | None = failures diff --git a/exabel_data_sdk/services/file_loading_result.py b/exabel_data_sdk/services/file_loading_result.py index 0fe17a2b..a7f89542 100644 --- a/exabel_data_sdk/services/file_loading_result.py +++ b/exabel_data_sdk/services/file_loading_result.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Generic, Mapping, Optional, Sequence, TypeVar +from typing import Generic, Mapping, Sequence, TypeVar import pandas as pd @@ -43,13 +43,13 @@ class FileLoadingResult(Generic[ResourceT]): def __init__( self, - results: Optional[ResourceCreationResults[ResourceT]] = None, + results: ResourceCreationResults[ResourceT] | None = None, *, - warnings: Optional[Sequence[str]] = None, + warnings: Sequence[str] | None = None, aborted: bool = False, - processed_rows: Optional[int] = None, + processed_rows: int | None = None, ): - self.results: Optional[ResourceCreationResults[ResourceT]] = results + self.results: ResourceCreationResults[ResourceT] | None = results self.warnings = warnings or [] self.aborted = aborted self.processed_rows = processed_rows @@ -90,17 +90,17 @@ class TimeSeriesFileLoadingResult(FileLoadingResult[pd.Series]): def __init__( self, - results: Optional[ResourceCreationResults[pd.Series]] = None, + results: ResourceCreationResults[pd.Series] | None = None, *, - warnings: Optional[Sequence[str]] = None, + warnings: Sequence[str] | None = None, aborted: bool = False, - processed_rows: Optional[int] = None, - entity_mapping_result: Optional[EntityMappingResult] = None, - created_data_signals: Optional[Sequence[str]] = None, - dry_run_results: Optional[Sequence[str]] = None, - sheet_name: Optional[str] = None, + processed_rows: int | None = None, + entity_mapping_result: EntityMappingResult | None = None, + created_data_signals: Sequence[str] | None = None, + dry_run_results: Sequence[str] | None = None, + sheet_name: str | None = None, has_known_time: bool = False, - replaced: Optional[Sequence[str]] = None, + replaced: Sequence[str] | None = None, ): super().__init__(results, warnings=warnings, aborted=aborted, processed_rows=processed_rows) if entity_mapping_result is None: diff --git a/exabel_data_sdk/services/file_time_series_loader.py b/exabel_data_sdk/services/file_time_series_loader.py index 61719da3..aa0c1985 100644 --- a/exabel_data_sdk/services/file_time_series_loader.py +++ b/exabel_data_sdk/services/file_time_series_loader.py @@ -1,5 +1,5 @@ import logging -from typing import List, Mapping, MutableSequence, Optional, Sequence, Tuple, Type +from typing import Mapping, MutableSequence, Sequence from google.protobuf.duration_pb2 import Duration from google.protobuf.field_mask_pb2 import FieldMask @@ -52,39 +52,39 @@ class FileTimeSeriesLoader: def __init__(self, client: ExabelClient): self._client = client - self._time_series_parser: Optional[Type[ParsedTimeSeriesFile]] = None + self._time_series_parser: type[ParsedTimeSeriesFile] | None = None @deprecate_arguments(namespace=None, create_tag=None) def load_time_series( self, *, filename: str, - entity_mapping_filename: Optional[str] = None, + entity_mapping_filename: str | None = None, separator: str = ",", - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, - pit_current_time: Optional[bool] = False, - pit_offset: Optional[int] = None, + entity_type: str | None = None, + identifier_type: str | None = None, + pit_current_time: bool | None = False, + pit_offset: int | None = None, create_missing_signals: bool = False, create_library_signal: bool = True, - global_time_series: Optional[bool] = None, + global_time_series: bool | None = None, threads: int = DEFAULT_NUMBER_OF_THREADS_FOR_IMPORT, dry_run: bool = False, error_on_any_failure: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, - batch_size: Optional[int] = None, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, + batch_size: int | None = None, skip_validation: bool = False, case_sensitive_signals: bool = False, replace_existing_time_series: bool = False, replace_existing_data_points: bool = False, - should_optimise: Optional[bool] = None, + should_optimise: bool | None = None, return_results: bool = True, processed_rows: int = 0, - total_rows: Optional[int] = None, + total_rows: int | None = None, # Deprecated arguments - create_tag: Optional[bool] = None, # pylint: disable=unused-argument - namespace: Optional[str] = None, # pylint: disable=unused-argument + create_tag: bool | None = None, + namespace: str | None = None, ) -> Sequence[TimeSeriesFileLoadingResult]: """ Load a file and upload the time series to the Exabel Data API @@ -144,7 +144,7 @@ def load_time_series( entity_mapping_filename, separator=separator ) results = [] - replaced_time_series: List[str] = [] + replaced_time_series: list[str] = [] for batch_no, parser in enumerate( TimeSeriesFileParser.from_file(filename, separator, batch_size), 1 ): @@ -192,25 +192,25 @@ def _load_time_series( self, *, parser: TimeSeriesFileParser, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, - pit_current_time: Optional[bool] = False, - pit_offset: Optional[int] = None, + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, + identifier_type: str | None = None, + pit_current_time: bool | None = False, + pit_offset: int | None = None, create_missing_signals: bool = False, create_library_signal: bool = True, - global_time_series: Optional[bool] = None, + global_time_series: bool | None = None, threads: int = DEFAULT_NUMBER_OF_THREADS, dry_run: bool = False, error_on_any_failure: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, skip_validation: bool = False, case_sensitive_signals: bool = False, replace_existing_time_series: bool = False, replace_existing_data_points: bool = False, - replaced_time_series: Optional[Sequence[str]] = None, - should_optimise: Optional[bool] = None, + replaced_time_series: Sequence[str] | None = None, + should_optimise: bool | None = None, ) -> TimeSeriesFileLoadingResult: """ Load time series from a parser. @@ -248,7 +248,7 @@ def _load_time_series( elif pit_current_time is None and pit_offset is None: default_known_time = DefaultKnownTime(current_time=True) - candidate_parsers: MutableSequence[Type[ParsedTimeSeriesFile]] = [] + candidate_parsers: MutableSequence[type[ParsedTimeSeriesFile]] = [] if parser.data_frame is None: candidate_parsers.append(EntitiesInColumns) candidate_parsers += [ @@ -427,21 +427,21 @@ def batch_delete_time_series_points( self, *, filename: str, - entity_mapping_filename: Optional[str] = None, + entity_mapping_filename: str | None = None, separator: str = ",", - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, - global_time_series: Optional[bool] = None, + entity_type: str | None = None, + identifier_type: str | None = None, + global_time_series: bool | None = None, threads: int = DEFAULT_NUMBER_OF_THREADS_FOR_IMPORT, dry_run: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, - batch_size: Optional[int] = None, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, + batch_size: int | None = None, skip_validation: bool = False, case_sensitive_signals: bool = False, return_results: bool = True, processed_rows: int = 0, - total_rows: Optional[int] = None, + total_rows: int | None = None, ) -> Sequence[FileLoadingResult]: """ Load a CSV file to delete the time series data points represented in it. @@ -475,14 +475,14 @@ def batch_delete_time_series_points( entity_mapping = EntityMappingFileReader.read_entity_mapping_file( entity_mapping_filename, separator=separator ) - results: List[FileLoadingResult] = [] + results: list[FileLoadingResult] = [] for batch_no, parser in enumerate( TimeSeriesFileParser.from_file(filename, separator, batch_size), 1 ): if batch_size is not None: logger.info("Processing batch no: %d", batch_no) - candidate_parsers: MutableSequence[Type[ParsedTimeSeriesFile]] = [] + candidate_parsers: MutableSequence[type[ParsedTimeSeriesFile]] = [] if parser.data_frame is None: candidate_parsers.append(EntitiesInColumns) candidate_parsers += [ @@ -575,24 +575,24 @@ def load_time_series_metadata( self, *, filename: str, - entity_mapping_filename: Optional[str] = None, + entity_mapping_filename: str | None = None, separator: str = ",", - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, + entity_type: str | None = None, + identifier_type: str | None = None, create_missing_signals: bool = False, create_library_signal: bool = True, - global_time_series: Optional[bool] = None, + global_time_series: bool | None = None, threads: int = DEFAULT_NUMBER_OF_THREADS, dry_run: bool = False, error_on_any_failure: bool = False, retries: int = DEFAULT_NUMBER_OF_RETRIES, - abort_threshold: Optional[float] = DEFAULT_ABORT_THRESHOLD, - batch_size: Optional[int] = None, + abort_threshold: float | None = DEFAULT_ABORT_THRESHOLD, + batch_size: int | None = None, skip_validation: bool = False, case_sensitive_signals: bool = False, return_results: bool = True, processed_rows: int = 0, - total_rows: Optional[int] = None, + total_rows: int | None = None, ) -> Sequence[TimeSeriesFileLoadingResult]: """ Load a file and upload the time series metadata to the Exabel Data API @@ -768,7 +768,7 @@ def _get_signal_prefix(self) -> str: def _check_signals_to_rename( self, signals: Sequence[str], case_sensitive_signals: bool, signals_in_rows: bool - ) -> Tuple[Sequence[str], dict]: + ) -> tuple[Sequence[str], dict]: """ Check which signals are missing and which signals can be renamed according to case sensitivity rules. @@ -805,7 +805,7 @@ def _handle_signals( dry_run: bool, case_sensitive_signals: bool, parsed_file: ParsedTimeSeriesFile, - ) -> Tuple[ParsedTimeSeriesFile, Sequence[str]]: + ) -> tuple[ParsedTimeSeriesFile, Sequence[str]]: """ Handle signals in a time series file by: 1. Checking for missing signals @@ -846,9 +846,7 @@ def _handle_signals( return (parsed_file, missing_signals) -def check_global_time_series( - entity_names: Sequence[str], global_time_series: Optional[bool] -) -> None: +def check_global_time_series(entity_names: Sequence[str], global_time_series: bool | None) -> None: """ Perform checks related to global entities and time series. """ @@ -875,8 +873,8 @@ def check_signal_name_errors(signals: Sequence[str]) -> None: """ Perform checks on signals in a time series file. """ - missing_headers: List[str] = [] - invalid_signals: List[str] = [] + missing_headers: list[str] = [] + invalid_signals: list[str] = [] for signal in signals: try: validate_signal_name(signal) diff --git a/exabel_data_sdk/services/file_time_series_parser.py b/exabel_data_sdk/services/file_time_series_parser.py index 46b0f3ca..1cc5d020 100644 --- a/exabel_data_sdk/services/file_time_series_parser.py +++ b/exabel_data_sdk/services/file_time_series_parser.py @@ -11,11 +11,7 @@ Iterator, Mapping, NamedTuple, - Optional, Sequence, - Set, - Tuple, - Union, ) import numpy as np @@ -66,16 +62,16 @@ class TimeSeriesFileParser: """ filename: str - separator: Optional[str] - worksheet: Optional[str] - data_frame: Optional[pd.DataFrame] + separator: str | None + worksheet: str | None + data_frame: pd.DataFrame | None def __post_init__(self) -> None: - self._preview: Optional[pd.DataFrame] = None + self._preview: pd.DataFrame | None = None @classmethod def from_file( - cls, filename: str, separator: Optional[str] = None, batch_size: Optional[int] = None + cls, filename: str, separator: str | None = None, batch_size: int | None = None ) -> Iterator["TimeSeriesFileParser"]: """ Construct an iterator of parsers from a file. @@ -112,7 +108,7 @@ def from_file( logger.info("Reading input data in batches of %d rows. ", batch_size) return ( TimeSeriesFileParser(filename, separator, None, df) - for df in CsvReader.read_file( # pylint: disable=all + for df in CsvReader.read_file( filename, separator, (0,), @@ -129,14 +125,14 @@ def preview(self) -> pd.DataFrame: self._preview = self.parse_file(nrows=10) return self._preview - def sheet_name(self) -> Optional[str]: + def sheet_name(self) -> str | None: """Return the name of the worksheet, when applicable.""" return str(self.worksheet) if self.worksheet is not None else None def parse_file( self, - nrows: Optional[int] = None, - header: Optional[Sequence[int]] = None, + nrows: int | None = None, + header: Sequence[int] | None = None, case_sensitive_signals: bool = False, ) -> pd.DataFrame: """Parse the file as a Pandas data frame.""" @@ -207,7 +203,7 @@ def check_columns(self) -> None: invalid.append(column) if invalid: raise FileLoadingException( - f"Parsing failed, invalid column(s) found: " f"{', '.join(invalid)}." + f"Parsing failed, invalid column(s) found: {', '.join(invalid)}." ) if "date" in self.preview.columns and _has_duplicate_columns(self.preview.columns): raise FileLoadingException( @@ -228,10 +224,8 @@ class ParsedTimeSeriesFile(abc.ABC): class ValidatedTimeSeries(NamedTuple): """A tuple of valid time series and failures that did not pass validation.""" - valid_series: Sequence[Union[pd.Series, TimeSeries]] - failures: Sequence[ - Union[ResourceCreationResult[pd.Series], ResourceCreationResult[TimeSeries]] - ] + valid_series: Sequence[pd.Series | TimeSeries] + failures: Sequence[ResourceCreationResult[pd.Series] | ResourceCreationResult[TimeSeries]] def __init__(self, data: pd.DataFrame, entity_lookup_result: EntityResourceNames): self._data = data @@ -253,10 +247,10 @@ def from_file( file_parser: TimeSeriesFileParser, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, case_sensitive_signals: bool = False, - identifier_type: Optional[str] = None, + identifier_type: str | None = None, ) -> "ParsedTimeSeriesFile": """Read a file and construct a parsed file from the contents.""" data = file_parser.parse_file(case_sensitive_signals=case_sensitive_signals) @@ -271,9 +265,9 @@ def from_data_frame( data: pd.DataFrame, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, + identifier_type: str | None = None, ) -> "ParsedTimeSeriesFile": """Construct a new parsed file from a data frame.""" data = cls._set_index(data) @@ -385,10 +379,10 @@ def _map_entities( data: pd.DataFrame, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, - ) -> Tuple[pd.DataFrame, EntityResourceNames]: + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, + identifier_type: str | None = None, + ) -> tuple[pd.DataFrame, EntityResourceNames]: """Map the entities of the data, and return any warnings.""" @staticmethod @@ -428,14 +422,14 @@ def _lookup_entities( identifiers: pd.Series, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, preserve_namespace: bool = False, - entity_type: Optional[str] = None, + entity_type: str | None = None, ) -> EntityResourceNames: """Look up the entity identifier, and throw an exception if no entities are found.""" if not all(isinstance(i, str) for i in identifiers): example = next(i for i in identifiers if not isinstance(i, str)) - if example is np.nan: + if np.isnan(example): raise FileLoadingException("A cell in the entity column was empty.") raise FileLoadingException( "Entity identifiers were not strings. If there are entity identifiers which are " @@ -568,10 +562,10 @@ def _map_entities( data: pd.DataFrame, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, - ) -> Tuple[pd.DataFrame, EntityResourceNames]: + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, + identifier_type: str | None = None, + ) -> tuple[pd.DataFrame, EntityResourceNames]: if not any( _is_valid_entity_column(column, cls.RESERVED_COLUMNS) for column in data.columns ): @@ -673,10 +667,10 @@ def _map_entities( data: pd.DataFrame, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, - ) -> Tuple[pd.DataFrame, EntityResourceNames]: + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, + identifier_type: str | None = None, + ) -> tuple[pd.DataFrame, EntityResourceNames]: entity_column = data.columns[0] identifiers = data[entity_column] identifiers.name = identifier_type or entity_column @@ -738,10 +732,10 @@ def _map_entities( data: pd.DataFrame, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, - ) -> Tuple[pd.DataFrame, EntityResourceNames]: + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, + identifier_type: str | None = None, + ) -> tuple[pd.DataFrame, EntityResourceNames]: if entity_type is not None and entity_type != file_constants.GLOBAL_ENTITY_TYPE: raise FileLoadingException( "Entity type must be set to 'global' when loading time series to the global entity." @@ -787,10 +781,10 @@ def from_file( file_parser: TimeSeriesFileParser, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, case_sensitive_signals: bool = False, - identifier_type: Optional[str] = None, + identifier_type: str | None = None, ) -> "ParsedTimeSeriesFile": """Read a file and construct a new parser from the contents of that file.""" if entity_type is not None: @@ -873,10 +867,10 @@ def _map_entities( data: pd.DataFrame, entity_api: EntityApi, namespace: str, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, - ) -> Tuple[pd.DataFrame, EntityResourceNames]: + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, + entity_type: str | None = None, + identifier_type: str | None = None, + ) -> tuple[pd.DataFrame, EntityResourceNames]: identifiers = pd.Series(data.columns.get_level_values(1)) identifiers.name = identifier_type or entity_type lookup_result = cls._lookup_entities( @@ -1040,7 +1034,7 @@ def _get_series_with_potential_duplicates(self, prefix: str) -> Sequence[TimeSer @staticmethod def _get_deduplicated_series( series: Sequence[TimeSeries], - ) -> Tuple[Sequence[TimeSeries], Sequence[TimeSeries]]: + ) -> tuple[Sequence[TimeSeries], Sequence[TimeSeries]]: """Get the deduplicated series and series with duplicate names""" series_dict = defaultdict(list) for ts in series: @@ -1115,7 +1109,7 @@ def _is_int(element: Any) -> bool: return False -def _is_valid_entity_column(column: str, invalid: Set[str]) -> bool: +def _is_valid_entity_column(column: str, invalid: set[str]) -> bool: if column in invalid: return False if len(column) == 0: @@ -1127,7 +1121,7 @@ def _is_valid_entity_column(column: str, invalid: Set[str]) -> bool: return True -def _is_valid_signal_name(column: str, invalid: Set[str], allow_duplicates: bool = False) -> bool: +def _is_valid_signal_name(column: str, invalid: set[str], allow_duplicates: bool = False) -> bool: # Pandas adds a `.x` to duplicate columns, for example `col`, `col.1`, `col.2`. if allow_duplicates: column = _remove_dot_int(column) diff --git a/exabel_data_sdk/services/file_writer.py b/exabel_data_sdk/services/file_writer.py index 117fb988..e0df3562 100644 --- a/exabel_data_sdk/services/file_writer.py +++ b/exabel_data_sdk/services/file_writer.py @@ -1,6 +1,6 @@ import abc from dataclasses import dataclass -from typing import Iterable, Optional, Union +from typing import Iterable import pandas as pd @@ -14,7 +14,7 @@ class FileWritingResult: rows: Number of rows written to a file or a set of files. """ - rows: Optional[int] = None + rows: int | None = None class FileWriter(abc.ABC): @@ -22,7 +22,5 @@ class FileWriter(abc.ABC): @staticmethod @abc.abstractmethod - def write_file( - df: Union[pd.DataFrame, Iterable[pd.DataFrame]], filepath: str - ) -> FileWritingResult: + def write_file(df: pd.DataFrame | Iterable[pd.DataFrame], filepath: str) -> FileWritingResult: """Write the DataFrame or iterable of DataFrames to a file.""" diff --git a/exabel_data_sdk/services/file_writer_provider.py b/exabel_data_sdk/services/file_writer_provider.py index cbace399..b24e1075 100644 --- a/exabel_data_sdk/services/file_writer_provider.py +++ b/exabel_data_sdk/services/file_writer_provider.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import Type from exabel_data_sdk.services.csv_writer import CsvWriter from exabel_data_sdk.services.excel_writer import ExcelWriter @@ -22,7 +21,7 @@ def get_file_extension(filepath: str) -> str: return "".join(suffixes) @classmethod - def get_file_writer(cls, filepath: str) -> Type[FileWriter]: + def get_file_writer(cls, filepath: str) -> type[FileWriter]: """Return the file writer for the given filepath.""" extension = cls.get_file_extension(filepath) if extension in FULL_CSV_EXTENSIONS: diff --git a/exabel_data_sdk/services/sql/athena_reader_configuration.py b/exabel_data_sdk/services/sql/athena_reader_configuration.py index b911b021..da2fbe68 100644 --- a/exabel_data_sdk/services/sql/athena_reader_configuration.py +++ b/exabel_data_sdk/services/sql/athena_reader_configuration.py @@ -1,7 +1,7 @@ import argparse import urllib.parse from dataclasses import dataclass -from typing import MutableMapping, NewType, Optional +from typing import MutableMapping, NewType from exabel_data_sdk.services.sql.sql_reader_configuration import ( ConnectionString, @@ -44,12 +44,12 @@ class AthenaReaderConfiguration(SqlReaderConfiguration): region: Region s3_staging_dir: S3StagingDir - workgroup: Optional[Workgroup] = None - catalog: Optional[Catalog] = None - schema: Optional[Schema] = None - aws_access_key_id: Optional[AwsAccessKeyId] = None - aws_secret_access_key: Optional[AwsSecretAccessKey] = None - profile: Optional[Profile] = None + workgroup: Workgroup | None = None + catalog: Catalog | None = None + schema: Schema | None = None + aws_access_key_id: AwsAccessKeyId | None = None + aws_secret_access_key: AwsSecretAccessKey | None = None + profile: Profile | None = None def __post_init__(self) -> None: if self.profile is not None and ( diff --git a/exabel_data_sdk/services/sql/bigquery_reader_configuration.py b/exabel_data_sdk/services/sql/bigquery_reader_configuration.py index e3725824..7c28d65e 100644 --- a/exabel_data_sdk/services/sql/bigquery_reader_configuration.py +++ b/exabel_data_sdk/services/sql/bigquery_reader_configuration.py @@ -2,7 +2,7 @@ import json import urllib.parse from dataclasses import dataclass -from typing import MutableMapping, NewType, Optional +from typing import MutableMapping, NewType from exabel_data_sdk.services.sql.exceptions import InvalidServiceAccountCredentialsError from exabel_data_sdk.services.sql.sql_reader_configuration import ( @@ -27,10 +27,10 @@ class BigQueryReaderConfiguration(SqlReaderConfiguration): """SQL configuration for BigQuery.""" - project: Optional[Project] = None - credentials_path: Optional[CredentialsPath] = None - dataset: Optional[Dataset] = None - credentials: Optional[Credentials] = None + project: Project | None = None + credentials_path: CredentialsPath | None = None + dataset: Dataset | None = None + credentials: Credentials | None = None def __post_init__(self) -> None: if self.credentials_path and self.credentials: diff --git a/exabel_data_sdk/services/sql/snowflake_reader.py b/exabel_data_sdk/services/sql/snowflake_reader.py index ac1aa2b1..80e74190 100644 --- a/exabel_data_sdk/services/sql/snowflake_reader.py +++ b/exabel_data_sdk/services/sql/snowflake_reader.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Iterator, Mapping, Optional +from typing import Any, Iterator, Mapping import pandas as pd @@ -18,7 +18,7 @@ class SnowflakeReader(SqlReader): """Reader of SQL queries from Snowflake.""" def __init__( - self, connection_args: Mapping[str, Any], *, kwargs: Optional[Mapping[str, Any]] = None + self, connection_args: Mapping[str, Any], *, kwargs: Mapping[str, Any] | None = None ) -> None: self.kwargs = kwargs or {} self.connection_args = connection_args diff --git a/exabel_data_sdk/services/sql/snowflake_reader_configuration.py b/exabel_data_sdk/services/sql/snowflake_reader_configuration.py index c1708fc7..f2f7065e 100644 --- a/exabel_data_sdk/services/sql/snowflake_reader_configuration.py +++ b/exabel_data_sdk/services/sql/snowflake_reader_configuration.py @@ -1,6 +1,6 @@ import argparse from dataclasses import asdict, dataclass -from typing import NewType, Optional +from typing import NewType from exabel_data_sdk.services.sql.sql_reader_configuration import ( ConnectionString, @@ -30,12 +30,12 @@ class SnowflakeReaderConfiguration(SqlReaderConfiguration): account: Account user: Username - password: Optional[Password] = None - private_key: Optional[PrivateKey] = None - warehouse: Optional[Warehouse] = None - database: Optional[Database] = None - schema: Optional[Schema] = None - role: Optional[Role] = None + password: Password | None = None + private_key: PrivateKey | None = None + warehouse: Warehouse | None = None + database: Database | None = None + schema: Schema | None = None + role: Role | None = None login_timeout: LoginTimeout = LoginTimeout(15) def __post_init__(self) -> None: diff --git a/exabel_data_sdk/services/sql/sql_reader.py b/exabel_data_sdk/services/sql/sql_reader.py index d30a139d..0579cd53 100644 --- a/exabel_data_sdk/services/sql/sql_reader.py +++ b/exabel_data_sdk/services/sql/sql_reader.py @@ -1,6 +1,6 @@ import abc import logging -from typing import Iterable, Iterator, NewType, Optional, Union +from typing import Iterable, Iterator, NewType import pandas as pd @@ -22,7 +22,7 @@ class SqlReader(abc.ABC): """ @staticmethod - def get_data_frame(df: Union[pd.DataFrame, Iterable[pd.DataFrame]]) -> pd.DataFrame: + def get_data_frame(df: pd.DataFrame | Iterable[pd.DataFrame]) -> pd.DataFrame: """Return the first `DataFrame` if given an iterable, or the given `DataFrame`.""" if isinstance(df, pd.DataFrame): return df @@ -46,10 +46,10 @@ def read_sql_query_in_batches( def read_sql_query_and_write_result( self, query: Query, - output_file: Optional[OutputFile] = None, + output_file: OutputFile | None = None, *, - batch_size: Optional[BatchSize] = None, - ) -> Optional[FileWritingResult]: + batch_size: BatchSize | None = None, + ) -> FileWritingResult | None: """ Execute the given query and write the result to the given output file. If no output file is given, print a sample instead. diff --git a/exabel_data_sdk/services/sql/sqlalchemy_reader.py b/exabel_data_sdk/services/sql/sqlalchemy_reader.py index b5d31ff0..19ebf181 100644 --- a/exabel_data_sdk/services/sql/sqlalchemy_reader.py +++ b/exabel_data_sdk/services/sql/sqlalchemy_reader.py @@ -1,6 +1,6 @@ import logging import time -from typing import Any, Iterator, Mapping, Optional +from typing import Any, Iterator, Mapping import pandas as pd @@ -18,7 +18,7 @@ class SQLAlchemyReader(SqlReader): """Reader of SQL queries using SQLAlchemy.""" def __init__( - self, connection_string: ConnectionString, *, kwargs: Optional[Mapping[str, Any]] = None + self, connection_string: ConnectionString, *, kwargs: Mapping[str, Any] | None = None ) -> None: kwargs = kwargs or {} self.engine = create_engine(connection_string, **kwargs) diff --git a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_mapping_messages_pb2.py b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_mapping_messages_pb2.py index 9a0af463..7dc70432 100644 --- a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_mapping_messages_pb2.py +++ b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_mapping_messages_pb2.py @@ -10,8 +10,9 @@ from .....exabel.api.analytics.v1 import derived_signal_messages_pb2 as exabel_dot_api_dot_analytics_dot_v1_dot_derived__signal__messages__pb2 from .....exabel.api.analytics.v1 import kpi_messages_pb2 as exabel_dot_api_dot_analytics_dot_v1_dot_kpi__messages__pb2 from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 from .....protoc_gen_openapiv2.options import annotations_pb2 as protoc__gen__openapiv2_dot_options_dot_annotations__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n2exabel/api/analytics/v1/kpi_mapping_messages.proto\x12\x17exabel.api.analytics.v1\x1a-exabel/api/analytics/v1/common_messages.proto\x1a5exabel/api/analytics/v1/derived_signal_messages.proto\x1a*exabel/api/analytics/v1/kpi_messages.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a.protoc_gen_openapiv2/options/annotations.proto"\x9c\x03\n\x0fKpiMappingGroup\x12I\n\x04name\x18\x01 \x01(\tB;\x92A5J\x1a"kpiMappings/123/groups/4"\xca>\x16\xfa\x02\x13kpiMappingGroupName\xe0A\x05\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\t\x12)\n\x03kpi\x18\x03 \x01(\x0b2\x1c.exabel.api.analytics.v1.Kpi\x12<\n\x0cproxy_signal\x18\x04 \x01(\x0b2&.exabel.api.analytics.v1.DerivedSignal\x12?\n\x04type\x18\x05 \x01(\x0e2,.exabel.api.analytics.v1.KpiMappingGroupTypeB\x03\xe0A\x05\x126\n\nentity_set\x18\x06 \x01(\x0b2".exabel.api.analytics.v1.EntitySet\x12F\n\x15proxy_resample_method\x18\x07 \x01(\x0e2\'.exabel.api.analytics.v1.ResampleMethod*]\n\x13KpiMappingGroupType\x12&\n"KPI_MAPPING_GROUP_TYPE_UNSPECIFIED\x10\x00\x12\x08\n\x04BULK\x10\x01\x12\x14\n\x10COMPANY_SPECIFIC\x10\x02*\xad\x01\n\x0eResampleMethod\x12\x1f\n\x1bRESAMPLE_METHOD_UNSPECIFIED\x10\x00\x12\x0f\n\x0bNO_RESAMPLE\x10\x01\x12\x11\n\rRESAMPLE_MEAN\x10\x02\x12\x10\n\x0cRESAMPLE_SUM\x10\x03\x12\x13\n\x0fRESAMPLE_MEDIAN\x10\x04\x12\x1c\n\x18RESAMPLE_MEAN_TIMES_DAYS\x10\x05\x12\x11\n\rRESAMPLE_LAST\x10\x06BZ\n\x1bcom.exabel.api.analytics.v1B\x1cKpiMappingGroupMessagesProtoP\x01Z\x1bexabel.com/api/analytics/v1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n2exabel/api/analytics/v1/kpi_mapping_messages.proto\x12\x17exabel.api.analytics.v1\x1a-exabel/api/analytics/v1/common_messages.proto\x1a5exabel/api/analytics/v1/derived_signal_messages.proto\x1a*exabel/api/analytics/v1/kpi_messages.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a.protoc_gen_openapiv2/options/annotations.proto"\xa4\x04\n\x0fKpiMappingGroup\x12I\n\x04name\x18\x01 \x01(\tB;\x92A5J\x1a"kpiMappings/123/groups/4"\xca>\x16\xfa\x02\x13kpiMappingGroupName\xe0A\x05\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\t\x12)\n\x03kpi\x18\x03 \x01(\x0b2\x1c.exabel.api.analytics.v1.Kpi\x12<\n\x0cproxy_signal\x18\x04 \x01(\x0b2&.exabel.api.analytics.v1.DerivedSignal\x12?\n\x04type\x18\x05 \x01(\x0e2,.exabel.api.analytics.v1.KpiMappingGroupTypeB\x03\xe0A\x05\x126\n\nentity_set\x18\x06 \x01(\x0b2".exabel.api.analytics.v1.EntitySet\x12F\n\x15proxy_resample_method\x18\x07 \x01(\x0e2\'.exabel.api.analytics.v1.ResampleMethod\x12H\n\x13forecasting_options\x18\x08 \x01(\x0b2+.exabel.api.analytics.v1.ForecastingOptions\x12<\n\rmodel_options\x18\t \x01(\x0b2%.exabel.api.analytics.v1.ModelOptions"\xc4\x01\n\x12ForecastingOptions\x12U\n\nmodel_type\x18\x01 \x01(\tBA\x92A>\xf2\x02\x04auto\xf2\x02\x07prophet\xf2\x02\x06sarima\xf2\x02\x05theta\xf2\x02\nunobserved\xf2\x02\x0cholt_winters\x12+\n\nparameters\x18\x02 \x01(\x0b2\x17.google.protobuf.Struct\x12\x18\n\x10country_holidays\x18\x03 \x01(\t\x12\x10\n\x08holidays\x18\x04 \x01(\t"\xa0\x02\n\x0cModelOptions\x12\xca\x01\n\nmodel_type\x18\x01 \x01(\tB\xb5\x01\x92A\xb1\x01\xf2\x02\x0eard_regression\xf2\x02\x0belastic_net\xf2\x02\x0eelastic_net_cv\xf2\x02\x10huber_regression\xf2\x02\x11linear_regression\xf2\x02\x10ratio_prediction\xf2\x02\x13ratio_prediction_ml\xf2\x02\x07sarimax\xf2\x02\x0cspread_model\xf2\x02\x15unobserved_components\x12+\n\nparameters\x18\x02 \x01(\x0b2\x17.google.protobuf.Struct\x12\x16\n\x0eyear_over_year\x18\x03 \x01(\x08*]\n\x13KpiMappingGroupType\x12&\n"KPI_MAPPING_GROUP_TYPE_UNSPECIFIED\x10\x00\x12\x08\n\x04BULK\x10\x01\x12\x14\n\x10COMPANY_SPECIFIC\x10\x02*\xad\x01\n\x0eResampleMethod\x12\x1f\n\x1bRESAMPLE_METHOD_UNSPECIFIED\x10\x00\x12\x0f\n\x0bNO_RESAMPLE\x10\x01\x12\x11\n\rRESAMPLE_MEAN\x10\x02\x12\x10\n\x0cRESAMPLE_SUM\x10\x03\x12\x13\n\x0fRESAMPLE_MEDIAN\x10\x04\x12\x1c\n\x18RESAMPLE_MEAN_TIMES_DAYS\x10\x05\x12\x11\n\rRESAMPLE_LAST\x10\x06BZ\n\x1bcom.exabel.api.analytics.v1B\x1cKpiMappingGroupMessagesProtoP\x01Z\x1bexabel.com/api/analytics/v1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'exabel.api.analytics.v1.kpi_mapping_messages_pb2', _globals) @@ -22,9 +23,17 @@ _globals['_KPIMAPPINGGROUP'].fields_by_name['name']._serialized_options = b'\x92A5J\x1a"kpiMappings/123/groups/4"\xca>\x16\xfa\x02\x13kpiMappingGroupName\xe0A\x05' _globals['_KPIMAPPINGGROUP'].fields_by_name['type']._loaded_options = None _globals['_KPIMAPPINGGROUP'].fields_by_name['type']._serialized_options = b'\xe0A\x05' - _globals['_KPIMAPPINGGROUPTYPE']._serialized_start = 721 - _globals['_KPIMAPPINGGROUPTYPE']._serialized_end = 814 - _globals['_RESAMPLEMETHOD']._serialized_start = 817 - _globals['_RESAMPLEMETHOD']._serialized_end = 990 - _globals['_KPIMAPPINGGROUP']._serialized_start = 307 - _globals['_KPIMAPPINGGROUP']._serialized_end = 719 \ No newline at end of file + _globals['_FORECASTINGOPTIONS'].fields_by_name['model_type']._loaded_options = None + _globals['_FORECASTINGOPTIONS'].fields_by_name['model_type']._serialized_options = b'\x92A>\xf2\x02\x04auto\xf2\x02\x07prophet\xf2\x02\x06sarima\xf2\x02\x05theta\xf2\x02\nunobserved\xf2\x02\x0cholt_winters' + _globals['_MODELOPTIONS'].fields_by_name['model_type']._loaded_options = None + _globals['_MODELOPTIONS'].fields_by_name['model_type']._serialized_options = b'\x92A\xb1\x01\xf2\x02\x0eard_regression\xf2\x02\x0belastic_net\xf2\x02\x0eelastic_net_cv\xf2\x02\x10huber_regression\xf2\x02\x11linear_regression\xf2\x02\x10ratio_prediction\xf2\x02\x13ratio_prediction_ml\xf2\x02\x07sarimax\xf2\x02\x0cspread_model\xf2\x02\x15unobserved_components' + _globals['_KPIMAPPINGGROUPTYPE']._serialized_start = 1377 + _globals['_KPIMAPPINGGROUPTYPE']._serialized_end = 1470 + _globals['_RESAMPLEMETHOD']._serialized_start = 1473 + _globals['_RESAMPLEMETHOD']._serialized_end = 1646 + _globals['_KPIMAPPINGGROUP']._serialized_start = 337 + _globals['_KPIMAPPINGGROUP']._serialized_end = 885 + _globals['_FORECASTINGOPTIONS']._serialized_start = 888 + _globals['_FORECASTINGOPTIONS']._serialized_end = 1084 + _globals['_MODELOPTIONS']._serialized_start = 1087 + _globals['_MODELOPTIONS']._serialized_end = 1375 \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_mapping_messages_pb2.pyi b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_mapping_messages_pb2.pyi index f47a8d67..30e624e4 100644 --- a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_mapping_messages_pb2.pyi +++ b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_mapping_messages_pb2.pyi @@ -7,6 +7,7 @@ from ..... import exabel import google.protobuf.descriptor import google.protobuf.internal.enum_type_wrapper import google.protobuf.message +import google.protobuf.struct_pb2 import sys import typing if sys.version_info >= (3, 10): @@ -88,6 +89,8 @@ class KpiMappingGroup(google.protobuf.message.Message): TYPE_FIELD_NUMBER: builtins.int ENTITY_SET_FIELD_NUMBER: builtins.int PROXY_RESAMPLE_METHOD_FIELD_NUMBER: builtins.int + FORECASTING_OPTIONS_FIELD_NUMBER: builtins.int + MODEL_OPTIONS_FIELD_NUMBER: builtins.int name: builtins.str 'Resource name. E.g "kpiMappings/123/groups/4".' display_name: builtins.str @@ -111,12 +114,75 @@ class KpiMappingGroup(google.protobuf.message.Message): All the entities must be companies. """ - def __init__(self, *, name: builtins.str | None=..., display_name: builtins.str | None=..., kpi: exabel.api.analytics.v1.kpi_messages_pb2.Kpi | None=..., proxy_signal: exabel.api.analytics.v1.derived_signal_messages_pb2.DerivedSignal | None=..., type: global___KpiMappingGroupType.ValueType | None=..., entity_set: exabel.api.analytics.v1.common_messages_pb2.EntitySet | None=..., proxy_resample_method: global___ResampleMethod.ValueType | None=...) -> None: + @property + def forecasting_options(self) -> global___ForecastingOptions: + """Specifies how proxy signal forecasting should be performed.""" + + @property + def model_options(self) -> global___ModelOptions: + """Configuration for the single-predictor models that are built for this group.""" + + def __init__(self, *, name: builtins.str | None=..., display_name: builtins.str | None=..., kpi: exabel.api.analytics.v1.kpi_messages_pb2.Kpi | None=..., proxy_signal: exabel.api.analytics.v1.derived_signal_messages_pb2.DerivedSignal | None=..., type: global___KpiMappingGroupType.ValueType | None=..., entity_set: exabel.api.analytics.v1.common_messages_pb2.EntitySet | None=..., proxy_resample_method: global___ResampleMethod.ValueType | None=..., forecasting_options: global___ForecastingOptions | None=..., model_options: global___ModelOptions | None=...) -> None: + ... + + def HasField(self, field_name: typing.Literal['entity_set', b'entity_set', 'forecasting_options', b'forecasting_options', 'kpi', b'kpi', 'model_options', b'model_options', 'proxy_signal', b'proxy_signal']) -> builtins.bool: + ... + + def ClearField(self, field_name: typing.Literal['display_name', b'display_name', 'entity_set', b'entity_set', 'forecasting_options', b'forecasting_options', 'kpi', b'kpi', 'model_options', b'model_options', 'name', b'name', 'proxy_resample_method', b'proxy_resample_method', 'proxy_signal', b'proxy_signal', 'type', b'type']) -> None: + ... +global___KpiMappingGroup = KpiMappingGroup + +@typing.final +class ForecastingOptions(google.protobuf.message.Message): + """Forecasting options for Prophet and other forecasting models.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + MODEL_TYPE_FIELD_NUMBER: builtins.int + PARAMETERS_FIELD_NUMBER: builtins.int + COUNTRY_HOLIDAYS_FIELD_NUMBER: builtins.int + HOLIDAYS_FIELD_NUMBER: builtins.int + model_type: builtins.str + 'Forecasting model type.\n\n Supported values: `auto`, `prophet`, `sarima`, `theta`, `unobserved`, `holt_winters`.\n\n Default: `auto`.\n\n For information about forecasting, see https://doc.exabel.com/dsl/modelling/forecasting.html\n ' + country_holidays: builtins.str + "Country code for standard country holidays (e.g., 'US', 'UK').\n Only used when model_type='prophet'.\n " + holidays: builtins.str + 'Resource name of the holiday specification to use for Prophet forecasting.\n Only used when model_type=\'prophet\'.\n Example: "holidaySpecifications/123"\n ' + + @property + def parameters(self) -> google.protobuf.struct_pb2.Struct: + """Model-specific parameters passed to the forecasting function.""" + + def __init__(self, *, model_type: builtins.str | None=..., parameters: google.protobuf.struct_pb2.Struct | None=..., country_holidays: builtins.str | None=..., holidays: builtins.str | None=...) -> None: + ... + + def HasField(self, field_name: typing.Literal['parameters', b'parameters']) -> builtins.bool: + ... + + def ClearField(self, field_name: typing.Literal['country_holidays', b'country_holidays', 'holidays', b'holidays', 'model_type', b'model_type', 'parameters', b'parameters']) -> None: + ... +global___ForecastingOptions = ForecastingOptions + +@typing.final +class ModelOptions(google.protobuf.message.Message): + """Options for the KPI prediction models.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + MODEL_TYPE_FIELD_NUMBER: builtins.int + PARAMETERS_FIELD_NUMBER: builtins.int + YEAR_OVER_YEAR_FIELD_NUMBER: builtins.int + model_type: builtins.str + 'The type of the model.\n\n Supported values: `ard_regression`, `elastic_net`, `elastic_net_cv`, `huber_regression`,\n `linear_regression`, `ratio_prediction`, `ratio_prediction_ml`, `sarimax`, `spread_model`,\n and `unobserved_components`.\n\n Default: `sarimax` is used for ratio KPIs and `ratio_prediction` for other KPIs.\n\n For information about model types, see https://doc.exabel.com/dsl/modelling/models.html\n ' + year_over_year: builtins.bool + 'Whether to apply year-over-year transformation.\n When enabled, both predictors and targets are transformed to year-over-year changes during training,\n and predictions are transformed back to absolute values during inference.\n ' + + @property + def parameters(self) -> google.protobuf.struct_pb2.Struct: + """Model-specific parameters. Only takes effect if `model_type` is set.""" + + def __init__(self, *, model_type: builtins.str | None=..., parameters: google.protobuf.struct_pb2.Struct | None=..., year_over_year: builtins.bool | None=...) -> None: ... - def HasField(self, field_name: typing.Literal['entity_set', b'entity_set', 'kpi', b'kpi', 'proxy_signal', b'proxy_signal']) -> builtins.bool: + def HasField(self, field_name: typing.Literal['parameters', b'parameters']) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal['display_name', b'display_name', 'entity_set', b'entity_set', 'kpi', b'kpi', 'name', b'name', 'proxy_resample_method', b'proxy_resample_method', 'proxy_signal', b'proxy_signal', 'type', b'type']) -> None: + def ClearField(self, field_name: typing.Literal['model_type', b'model_type', 'parameters', b'parameters', 'year_over_year', b'year_over_year']) -> None: ... -global___KpiMappingGroup = KpiMappingGroup \ No newline at end of file +global___ModelOptions = ModelOptions \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_messages_pb2.py b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_messages_pb2.py index 7775a129..8ba1b42d 100644 --- a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_messages_pb2.py +++ b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_messages_pb2.py @@ -8,7 +8,7 @@ _sym_db = _symbol_database.Default() from .....exabel.api.time import date_pb2 as exabel_dot_api_dot_time_dot_date__pb2 from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n*exabel/api/analytics/v1/kpi_messages.proto\x12\x17exabel.api.analytics.v1\x1a\x1aexabel/api/time/date.proto\x1a\x1fgoogle/protobuf/timestamp.proto"\x9a\x01\n\x18CompanyKpiMappingResults\x120\n\x06entity\x18\x01 \x01(\x0b2 .exabel.api.analytics.v1.Company\x12L\n\x0bkpi_results\x18\x02 \x03(\x0b27.exabel.api.analytics.v1.SingleCompanyKpiMappingResults"G\n\x07Company\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\t\x12\x18\n\x10bloomberg_ticker\x18\x03 \x01(\t"\x88\x01\n\x1eSingleCompanyKpiMappingResults\x12)\n\x03kpi\x18\x01 \x01(\x0b2\x1c.exabel.api.analytics.v1.Kpi\x12;\n\x04data\x18\x02 \x03(\x0b2-.exabel.api.analytics.v1.KpiMappingResultData"\x87\x01\n\x03Kpi\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x12\n\x05value\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0cdisplay_name\x18\x03 \x01(\t\x12\x11\n\x04freq\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x15\n\x08is_ratio\x18\x05 \x01(\x08H\x02\x88\x01\x01B\x08\n\x06_valueB\x07\n\x05_freqB\x0b\n\t_is_ratio"\x8b\x07\n\x14KpiMappingResultData\x12A\n\x06source\x18\x01 \x01(\x0b21.exabel.api.analytics.v1.KpiMappingGroupReference\x12\x17\n\nmodel_mape\x18\x02 \x01(\x01H\x00\x88\x01\x01\x12\x16\n\tmodel_mae\x18\x03 \x01(\x01H\x01\x88\x01\x01\x12\x1b\n\x0emodel_hit_rate\x18\x04 \x01(\x01H\x02\x88\x01\x01\x12<\n\rmodel_quality\x18\x0f \x01(\x0e2%.exabel.api.analytics.v1.ModelQuality\x12.\n\x0flast_value_date\x18\x05 \x01(\x0b2\x15.exabel.api.time.Date\x12"\n\x15number_of_data_points\x18\x06 \x01(\x05H\x03\x88\x01\x01\x12#\n\x16period_over_period_mae\x18\x07 \x01(\x01H\x04\x88\x01\x01\x12\x1f\n\x12year_over_year_mae\x18\x08 \x01(\x01H\x05\x88\x01\x01\x12!\n\x14absolute_correlation\x18\t \x01(\x01H\x06\x88\x01\x01\x12+\n\x1eperiod_over_period_correlation\x18\n \x01(\x01H\x07\x88\x01\x01\x12\'\n\x1ayear_over_year_correlation\x18\x0b \x01(\x01H\x08\x88\x01\x01\x12\x1d\n\x10absolute_p_value\x18\x0c \x01(\x01H\t\x88\x01\x01\x12\'\n\x1aperiod_over_period_p_value\x18\r \x01(\x01H\n\x88\x01\x01\x12#\n\x16year_over_year_p_value\x18\x0e \x01(\x01H\x0b\x88\x01\x01B\r\n\x0b_model_mapeB\x0c\n\n_model_maeB\x11\n\x0f_model_hit_rateB\x18\n\x16_number_of_data_pointsB\x19\n\x17_period_over_period_maeB\x15\n\x13_year_over_year_maeB\x17\n\x15_absolute_correlationB!\n\x1f_period_over_period_correlationB\x1d\n\x1b_year_over_year_correlationB\x13\n\x11_absolute_p_valueB\x1d\n\x1b_period_over_period_p_valueB\x19\n\x17_year_over_year_p_value"[\n\x18KpiMappingGroupReference\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\t\x12\x1b\n\x13vendor_display_name\x18\x03 \x01(\t"\xcb\x01\n\x15CompanyKpiModelResult\x12)\n\x03kpi\x18\x01 \x01(\x0b2\x1c.exabel.api.analytics.v1.Kpi\x120\n\x05model\x18\x02 \x01(\x0b2!.exabel.api.analytics.v1.KpiModel\x12!\n\x19accessible_mappings_count\x18\x03 \x01(\x05\x12\x1c\n\x14total_mappings_count\x18\x04 \x01(\x05\x12\x14\n\x0cmodels_count\x18\x05 \x01(\x05"\xc6\x02\n\x08KpiModel\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x14\n\x0cdisplay_name\x18\x03 \x01(\t\x123\n\x04data\x18\x04 \x01(\x0b2%.exabel.api.analytics.v1.KpiModelData\x12>\n\x07weights\x18\x05 \x01(\x0b2-.exabel.api.analytics.v1.KpiModelWeightGroups\x129\n\nmodel_runs\x18\x06 \x01(\x0b2%.exabel.api.analytics.v1.KpiModelRuns\x12Z\n\x1dhierarchical_model_kpi_source\x18\x07 \x01(\x0b23.exabel.api.analytics.v1.HierarchicalModelKpiSource"\xed\x01\n\x1aHierarchicalModelKpiSource\x12`\n\x04type\x18\x01 \x01(\x0e2R.exabel.api.analytics.v1.HierarchicalModelKpiSource.HierarchicalModelKpiSourceType\x12\r\n\x05model\x18\x02 \x01(\t"^\n\x1eHierarchicalModelKpiSourceType\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\t\n\x05MODEL\x10\x01\x12\r\n\tCONSENSUS\x10\x02\x12\x11\n\rFREE_VARIABLE\x10\x03"\xc2\x01\n\x0cKpiModelRuns\x129\n\x0binitial_run\x18\x01 \x01(\x0b2$.exabel.api.analytics.v1.KpiModelRun\x127\n\tdaily_run\x18\x02 \x01(\x0b2$.exabel.api.analytics.v1.KpiModelRun\x12>\n\x10pit_backtest_run\x18\x03 \x01(\x0b2$.exabel.api.analytics.v1.KpiModelRun"\xbc\x01\n\x0bKpiModelRun\x12.\n\ncreated_at\x18\x01 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12.\n\nstarted_at\x18\x02 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12/\n\x0bfinished_at\x18\x03 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12\x12\n\x05error\x18\x04 \x01(\tH\x00\x88\x01\x01B\x08\n\x06_error"\x93\x01\n\x0fKpiMappingModel\x12A\n\x06source\x18\x01 \x01(\x0b21.exabel.api.analytics.v1.KpiMappingGroupReference\x12=\n\x0ekpi_model_data\x18\x02 \x01(\x0b2%.exabel.api.analytics.v1.KpiModelData"\x8a\x07\n\x0cKpiModelData\x12\x17\n\nprediction\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x1f\n\x12prediction_yoy_rel\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x1f\n\x12prediction_yoy_abs\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x16\n\tconsensus\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x1e\n\x11consensus_yoy_rel\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x1e\n\x11consensus_yoy_abs\x18\x06 \x01(\x01H\x05\x88\x01\x01\x12\x16\n\tdelta_abs\x18\x07 \x01(\x01H\x06\x88\x01\x01\x12\x16\n\tdelta_rel\x18\x08 \x01(\x01H\x07\x88\x01\x01\x12\x1b\n\x0edelta_by_error\x18\t \x01(\x01H\x08\x88\x01\x01\x12<\n\rmodel_quality\x18\n \x01(\x0e2%.exabel.api.analytics.v1.ModelQuality\x12\x11\n\x04mape\x18\x0b \x01(\x01H\t\x88\x01\x01\x12\x15\n\x08mape_pit\x18\x0c \x01(\x01H\n\x88\x01\x01\x12\x10\n\x03mae\x18\r \x01(\x01H\x0b\x88\x01\x01\x12\x14\n\x07mae_pit\x18\x0e \x01(\x01H\x0c\x88\x01\x01\x12\x18\n\x0berror_count\x18\x13 \x01(\x05H\r\x88\x01\x01\x12\x15\n\x08hit_rate\x18\x0f \x01(\x01H\x0e\x88\x01\x01\x12\x1b\n\x0ehit_rate_count\x18\x14 \x01(\x05H\x0f\x88\x01\x01\x12\x1c\n\x0frevision_1_week\x18\x10 \x01(\x01H\x10\x88\x01\x01\x12\x1d\n\x10revision_1_month\x18\x11 \x01(\x01H\x11\x88\x01\x01\x12\'\n\x04date\x18\x12 \x01(\x0b2\x15.exabel.api.time.DateB\x02\x18\x01\x12\r\n\x05error\x18d \x01(\tB\r\n\x0b_predictionB\x15\n\x13_prediction_yoy_relB\x15\n\x13_prediction_yoy_absB\x0c\n\n_consensusB\x14\n\x12_consensus_yoy_relB\x14\n\x12_consensus_yoy_absB\x0c\n\n_delta_absB\x0c\n\n_delta_relB\x11\n\x0f_delta_by_errorB\x07\n\x05_mapeB\x0b\n\t_mape_pitB\x06\n\x04_maeB\n\n\x08_mae_pitB\x0e\n\x0c_error_countB\x0b\n\t_hit_rateB\x11\n\x0f_hit_rate_countB\x12\n\x10_revision_1_weekB\x13\n\x11_revision_1_month"t\n\x14KpiModelWeightGroups\x12C\n\rweight_groups\x18\x01 \x03(\x0b2,.exabel.api.analytics.v1.KpiModelWeightGroup\x12\x17\n\x0fis_coefficients\x18\x02 \x01(\x08"\xb6\x01\n\x13KpiModelWeightGroup\x12\x14\n\x0cdisplay_name\x18\x01 \x01(\t\x12@\n\x05group\x18\x02 \x01(\x0b21.exabel.api.analytics.v1.KpiMappingGroupReference\x12G\n\x0ffeature_weights\x18\x03 \x03(\x0b2..exabel.api.analytics.v1.KpiModelFeatureWeight"M\n\x15KpiModelFeatureWeight\x12\x13\n\x06weight\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\tB\t\n\x07_weight"U\n\x0cFiscalPeriod\x12\'\n\x08end_date\x18\x01 \x01(\x0b2\x15.exabel.api.time.Date\x12\x12\n\x05label\x18\x02 \x01(\tH\x00\x88\x01\x01B\x08\n\x06_label"\xa1\x01\n\x14FiscalPeriodSelector\x12P\n\x11relative_selector\x18\x01 \x01(\x0e25.exabel.api.analytics.v1.RelativeFiscalPeriodSelector\x12)\n\nperiod_end\x18\x02 \x01(\x0b2\x15.exabel.api.time.Date\x12\x0c\n\x04freq\x18\x03 \x01(\t"d\n\x0cKpiHierarchy\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04freq\x18\x02 \x01(\t\x128\n\tbreakdown\x18\x03 \x01(\x0b2%.exabel.api.analytics.v1.KpiBreakdown"G\n\x0cKpiBreakdown\x127\n\x04kpis\x18\x01 \x03(\x0b2).exabel.api.analytics.v1.KpiBreakdownNode"\x99\x01\n\x10KpiBreakdownNode\x12+\n\x03kpi\x18\x01 \x01(\x0b2\x1c.exabel.api.analytics.v1.KpiH\x00\x12\x10\n\x06header\x18\x02 \x01(\tH\x00\x12;\n\x08children\x18\x03 \x03(\x0b2).exabel.api.analytics.v1.KpiBreakdownNodeB\t\n\x07content*x\n\tKpiSource\x12\x1a\n\x16KPI_SOURCE_UNSPECIFIED\x10\x00\x12\x1c\n\x18KPI_SOURCE_VISIBLE_ALPHA\x10\x01\x12\x16\n\x12KPI_SOURCE_FACTSET\x10\x02\x12\x19\n\x15KPI_SOURCE_CUSTOM_KPI\x10\x03*\x93\x01\n\x0cModelQuality\x12\x1d\n\x19MODEL_QUALITY_UNSPECIFIED\x10\x00\x12\x15\n\x11MODEL_QUALITY_LOW\x10\n\x12\x18\n\x14MODEL_QUALITY_MEDIUM\x10\x14\x12\x16\n\x12MODEL_QUALITY_HIGH\x10\x1e\x12\x1b\n\x17MODEL_QUALITY_VERY_HIGH\x10(*t\n\x1cRelativeFiscalPeriodSelector\x12/\n+RELATIVE_FISCAL_PERIOD_SELECTOR_UNSPECIFIED\x10\x00\x12\x0c\n\x08PREVIOUS\x10\x01\x12\x0b\n\x07CURRENT\x10\x02\x12\x08\n\x04NEXT\x10\x03BN\n\x1bcom.exabel.api.analytics.v1B\x10KpiMessagesProtoP\x01Z\x1bexabel.com/api/analytics/v1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n*exabel/api/analytics/v1/kpi_messages.proto\x12\x17exabel.api.analytics.v1\x1a\x1aexabel/api/time/date.proto\x1a\x1fgoogle/protobuf/timestamp.proto"\x9a\x01\n\x18CompanyKpiMappingResults\x120\n\x06entity\x18\x01 \x01(\x0b2 .exabel.api.analytics.v1.Company\x12L\n\x0bkpi_results\x18\x02 \x03(\x0b27.exabel.api.analytics.v1.SingleCompanyKpiMappingResults"G\n\x07Company\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\t\x12\x18\n\x10bloomberg_ticker\x18\x03 \x01(\t"\x88\x01\n\x1eSingleCompanyKpiMappingResults\x12)\n\x03kpi\x18\x01 \x01(\x0b2\x1c.exabel.api.analytics.v1.Kpi\x12;\n\x04data\x18\x02 \x03(\x0b2-.exabel.api.analytics.v1.KpiMappingResultData"\x87\x01\n\x03Kpi\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x12\n\x05value\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0cdisplay_name\x18\x03 \x01(\t\x12\x11\n\x04freq\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x15\n\x08is_ratio\x18\x05 \x01(\x08H\x02\x88\x01\x01B\x08\n\x06_valueB\x07\n\x05_freqB\x0b\n\t_is_ratio"\x8b\x07\n\x14KpiMappingResultData\x12A\n\x06source\x18\x01 \x01(\x0b21.exabel.api.analytics.v1.KpiMappingGroupReference\x12\x17\n\nmodel_mape\x18\x02 \x01(\x01H\x00\x88\x01\x01\x12\x16\n\tmodel_mae\x18\x03 \x01(\x01H\x01\x88\x01\x01\x12\x1b\n\x0emodel_hit_rate\x18\x04 \x01(\x01H\x02\x88\x01\x01\x12<\n\rmodel_quality\x18\x0f \x01(\x0e2%.exabel.api.analytics.v1.ModelQuality\x12.\n\x0flast_value_date\x18\x05 \x01(\x0b2\x15.exabel.api.time.Date\x12"\n\x15number_of_data_points\x18\x06 \x01(\x05H\x03\x88\x01\x01\x12#\n\x16period_over_period_mae\x18\x07 \x01(\x01H\x04\x88\x01\x01\x12\x1f\n\x12year_over_year_mae\x18\x08 \x01(\x01H\x05\x88\x01\x01\x12!\n\x14absolute_correlation\x18\t \x01(\x01H\x06\x88\x01\x01\x12+\n\x1eperiod_over_period_correlation\x18\n \x01(\x01H\x07\x88\x01\x01\x12\'\n\x1ayear_over_year_correlation\x18\x0b \x01(\x01H\x08\x88\x01\x01\x12\x1d\n\x10absolute_p_value\x18\x0c \x01(\x01H\t\x88\x01\x01\x12\'\n\x1aperiod_over_period_p_value\x18\r \x01(\x01H\n\x88\x01\x01\x12#\n\x16year_over_year_p_value\x18\x0e \x01(\x01H\x0b\x88\x01\x01B\r\n\x0b_model_mapeB\x0c\n\n_model_maeB\x11\n\x0f_model_hit_rateB\x18\n\x16_number_of_data_pointsB\x19\n\x17_period_over_period_maeB\x15\n\x13_year_over_year_maeB\x17\n\x15_absolute_correlationB!\n\x1f_period_over_period_correlationB\x1d\n\x1b_year_over_year_correlationB\x13\n\x11_absolute_p_valueB\x1d\n\x1b_period_over_period_p_valueB\x19\n\x17_year_over_year_p_value"[\n\x18KpiMappingGroupReference\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\t\x12\x1b\n\x13vendor_display_name\x18\x03 \x01(\t"\xcb\x01\n\x15CompanyKpiModelResult\x12)\n\x03kpi\x18\x01 \x01(\x0b2\x1c.exabel.api.analytics.v1.Kpi\x120\n\x05model\x18\x02 \x01(\x0b2!.exabel.api.analytics.v1.KpiModel\x12!\n\x19accessible_mappings_count\x18\x03 \x01(\x05\x12\x1c\n\x14total_mappings_count\x18\x04 \x01(\x05\x12\x14\n\x0cmodels_count\x18\x05 \x01(\x05"\xc6\x02\n\x08KpiModel\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x14\n\x0cdisplay_name\x18\x03 \x01(\t\x123\n\x04data\x18\x04 \x01(\x0b2%.exabel.api.analytics.v1.KpiModelData\x12>\n\x07weights\x18\x05 \x01(\x0b2-.exabel.api.analytics.v1.KpiModelWeightGroups\x129\n\nmodel_runs\x18\x06 \x01(\x0b2%.exabel.api.analytics.v1.KpiModelRuns\x12Z\n\x1dhierarchical_model_kpi_source\x18\x07 \x01(\x0b23.exabel.api.analytics.v1.HierarchicalModelKpiSource"\xed\x01\n\x1aHierarchicalModelKpiSource\x12`\n\x04type\x18\x01 \x01(\x0e2R.exabel.api.analytics.v1.HierarchicalModelKpiSource.HierarchicalModelKpiSourceType\x12\r\n\x05model\x18\x02 \x01(\t"^\n\x1eHierarchicalModelKpiSourceType\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\t\n\x05MODEL\x10\x01\x12\r\n\tCONSENSUS\x10\x02\x12\x11\n\rFREE_VARIABLE\x10\x03"\xc2\x01\n\x0cKpiModelRuns\x129\n\x0binitial_run\x18\x01 \x01(\x0b2$.exabel.api.analytics.v1.KpiModelRun\x127\n\tdaily_run\x18\x02 \x01(\x0b2$.exabel.api.analytics.v1.KpiModelRun\x12>\n\x10pit_backtest_run\x18\x03 \x01(\x0b2$.exabel.api.analytics.v1.KpiModelRun"\xbc\x01\n\x0bKpiModelRun\x12.\n\ncreated_at\x18\x01 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12.\n\nstarted_at\x18\x02 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12/\n\x0bfinished_at\x18\x03 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12\x12\n\x05error\x18\x04 \x01(\tH\x00\x88\x01\x01B\x08\n\x06_error"\x93\x01\n\x0fKpiMappingModel\x12A\n\x06source\x18\x01 \x01(\x0b21.exabel.api.analytics.v1.KpiMappingGroupReference\x12=\n\x0ekpi_model_data\x18\x02 \x01(\x0b2%.exabel.api.analytics.v1.KpiModelData"\x8a\x07\n\x0cKpiModelData\x12\x17\n\nprediction\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x1f\n\x12prediction_yoy_rel\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x1f\n\x12prediction_yoy_abs\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x16\n\tconsensus\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x1e\n\x11consensus_yoy_rel\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x1e\n\x11consensus_yoy_abs\x18\x06 \x01(\x01H\x05\x88\x01\x01\x12\x16\n\tdelta_abs\x18\x07 \x01(\x01H\x06\x88\x01\x01\x12\x16\n\tdelta_rel\x18\x08 \x01(\x01H\x07\x88\x01\x01\x12\x1b\n\x0edelta_by_error\x18\t \x01(\x01H\x08\x88\x01\x01\x12<\n\rmodel_quality\x18\n \x01(\x0e2%.exabel.api.analytics.v1.ModelQuality\x12\x11\n\x04mape\x18\x0b \x01(\x01H\t\x88\x01\x01\x12\x15\n\x08mape_pit\x18\x0c \x01(\x01H\n\x88\x01\x01\x12\x10\n\x03mae\x18\r \x01(\x01H\x0b\x88\x01\x01\x12\x14\n\x07mae_pit\x18\x0e \x01(\x01H\x0c\x88\x01\x01\x12\x18\n\x0berror_count\x18\x13 \x01(\x05H\r\x88\x01\x01\x12\x15\n\x08hit_rate\x18\x0f \x01(\x01H\x0e\x88\x01\x01\x12\x1b\n\x0ehit_rate_count\x18\x14 \x01(\x05H\x0f\x88\x01\x01\x12\x1c\n\x0frevision_1_week\x18\x10 \x01(\x01H\x10\x88\x01\x01\x12\x1d\n\x10revision_1_month\x18\x11 \x01(\x01H\x11\x88\x01\x01\x12\'\n\x04date\x18\x12 \x01(\x0b2\x15.exabel.api.time.DateB\x02\x18\x01\x12\r\n\x05error\x18d \x01(\tB\r\n\x0b_predictionB\x15\n\x13_prediction_yoy_relB\x15\n\x13_prediction_yoy_absB\x0c\n\n_consensusB\x14\n\x12_consensus_yoy_relB\x14\n\x12_consensus_yoy_absB\x0c\n\n_delta_absB\x0c\n\n_delta_relB\x11\n\x0f_delta_by_errorB\x07\n\x05_mapeB\x0b\n\t_mape_pitB\x06\n\x04_maeB\n\n\x08_mae_pitB\x0e\n\x0c_error_countB\x0b\n\t_hit_rateB\x11\n\x0f_hit_rate_countB\x12\n\x10_revision_1_weekB\x13\n\x11_revision_1_month"t\n\x14KpiModelWeightGroups\x12C\n\rweight_groups\x18\x01 \x03(\x0b2,.exabel.api.analytics.v1.KpiModelWeightGroup\x12\x17\n\x0fis_coefficients\x18\x02 \x01(\x08"\xb6\x01\n\x13KpiModelWeightGroup\x12\x14\n\x0cdisplay_name\x18\x01 \x01(\t\x12@\n\x05group\x18\x02 \x01(\x0b21.exabel.api.analytics.v1.KpiMappingGroupReference\x12G\n\x0ffeature_weights\x18\x03 \x03(\x0b2..exabel.api.analytics.v1.KpiModelFeatureWeight"M\n\x15KpiModelFeatureWeight\x12\x13\n\x06weight\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\tB\t\n\x07_weight"\x81\x01\n\x0cFiscalPeriod\x12\'\n\x08end_date\x18\x01 \x01(\x0b2\x15.exabel.api.time.Date\x12\x12\n\x05label\x18\x02 \x01(\tH\x00\x88\x01\x01\x12*\n\x0breport_date\x18\x03 \x01(\x0b2\x15.exabel.api.time.DateB\x08\n\x06_label"\xa1\x01\n\x14FiscalPeriodSelector\x12P\n\x11relative_selector\x18\x01 \x01(\x0e25.exabel.api.analytics.v1.RelativeFiscalPeriodSelector\x12)\n\nperiod_end\x18\x02 \x01(\x0b2\x15.exabel.api.time.Date\x12\x0c\n\x04freq\x18\x03 \x01(\t"d\n\x0cKpiHierarchy\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04freq\x18\x02 \x01(\t\x128\n\tbreakdown\x18\x03 \x01(\x0b2%.exabel.api.analytics.v1.KpiBreakdown"G\n\x0cKpiBreakdown\x127\n\x04kpis\x18\x01 \x03(\x0b2).exabel.api.analytics.v1.KpiBreakdownNode"\x99\x01\n\x10KpiBreakdownNode\x12+\n\x03kpi\x18\x01 \x01(\x0b2\x1c.exabel.api.analytics.v1.KpiH\x00\x12\x10\n\x06header\x18\x02 \x01(\tH\x00\x12;\n\x08children\x18\x03 \x03(\x0b2).exabel.api.analytics.v1.KpiBreakdownNodeB\t\n\x07content"\xca\x01\n\x16KpiScreenCompanyResult\x121\n\x07company\x18\x01 \x01(\x0b2 .exabel.api.analytics.v1.Company\x12<\n\rfiscal_period\x18\x02 \x01(\x0b2%.exabel.api.analytics.v1.FiscalPeriod\x12?\n\x07results\x18\x03 \x03(\x0b2..exabel.api.analytics.v1.CompanyKpiModelResult*x\n\tKpiSource\x12\x1a\n\x16KPI_SOURCE_UNSPECIFIED\x10\x00\x12\x1c\n\x18KPI_SOURCE_VISIBLE_ALPHA\x10\x01\x12\x16\n\x12KPI_SOURCE_FACTSET\x10\x02\x12\x19\n\x15KPI_SOURCE_CUSTOM_KPI\x10\x03*\x93\x01\n\x0cModelQuality\x12\x1d\n\x19MODEL_QUALITY_UNSPECIFIED\x10\x00\x12\x15\n\x11MODEL_QUALITY_LOW\x10\n\x12\x18\n\x14MODEL_QUALITY_MEDIUM\x10\x14\x12\x16\n\x12MODEL_QUALITY_HIGH\x10\x1e\x12\x1b\n\x17MODEL_QUALITY_VERY_HIGH\x10(*t\n\x1cRelativeFiscalPeriodSelector\x12/\n+RELATIVE_FISCAL_PERIOD_SELECTOR_UNSPECIFIED\x10\x00\x12\x0c\n\x08PREVIOUS\x10\x01\x12\x0b\n\x07CURRENT\x10\x02\x12\x08\n\x04NEXT\x10\x03BN\n\x1bcom.exabel.api.analytics.v1B\x10KpiMessagesProtoP\x01Z\x1bexabel.com/api/analytics/v1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'exabel.api.analytics.v1.kpi_messages_pb2', _globals) @@ -17,12 +17,12 @@ _globals['DESCRIPTOR']._serialized_options = b'\n\x1bcom.exabel.api.analytics.v1B\x10KpiMessagesProtoP\x01Z\x1bexabel.com/api/analytics/v1' _globals['_KPIMODELDATA'].fields_by_name['date']._loaded_options = None _globals['_KPIMODELDATA'].fields_by_name['date']._serialized_options = b'\x18\x01' - _globals['_KPISOURCE']._serialized_start = 4828 - _globals['_KPISOURCE']._serialized_end = 4948 - _globals['_MODELQUALITY']._serialized_start = 4951 - _globals['_MODELQUALITY']._serialized_end = 5098 - _globals['_RELATIVEFISCALPERIODSELECTOR']._serialized_start = 5100 - _globals['_RELATIVEFISCALPERIODSELECTOR']._serialized_end = 5216 + _globals['_KPISOURCE']._serialized_start = 5078 + _globals['_KPISOURCE']._serialized_end = 5198 + _globals['_MODELQUALITY']._serialized_start = 5201 + _globals['_MODELQUALITY']._serialized_end = 5348 + _globals['_RELATIVEFISCALPERIODSELECTOR']._serialized_start = 5350 + _globals['_RELATIVEFISCALPERIODSELECTOR']._serialized_end = 5466 _globals['_COMPANYKPIMAPPINGRESULTS']._serialized_start = 133 _globals['_COMPANYKPIMAPPINGRESULTS']._serialized_end = 287 _globals['_COMPANY']._serialized_start = 289 @@ -57,13 +57,15 @@ _globals['_KPIMODELWEIGHTGROUP']._serialized_end = 4165 _globals['_KPIMODELFEATUREWEIGHT']._serialized_start = 4167 _globals['_KPIMODELFEATUREWEIGHT']._serialized_end = 4244 - _globals['_FISCALPERIOD']._serialized_start = 4246 - _globals['_FISCALPERIOD']._serialized_end = 4331 - _globals['_FISCALPERIODSELECTOR']._serialized_start = 4334 - _globals['_FISCALPERIODSELECTOR']._serialized_end = 4495 - _globals['_KPIHIERARCHY']._serialized_start = 4497 - _globals['_KPIHIERARCHY']._serialized_end = 4597 - _globals['_KPIBREAKDOWN']._serialized_start = 4599 - _globals['_KPIBREAKDOWN']._serialized_end = 4670 - _globals['_KPIBREAKDOWNNODE']._serialized_start = 4673 - _globals['_KPIBREAKDOWNNODE']._serialized_end = 4826 \ No newline at end of file + _globals['_FISCALPERIOD']._serialized_start = 4247 + _globals['_FISCALPERIOD']._serialized_end = 4376 + _globals['_FISCALPERIODSELECTOR']._serialized_start = 4379 + _globals['_FISCALPERIODSELECTOR']._serialized_end = 4540 + _globals['_KPIHIERARCHY']._serialized_start = 4542 + _globals['_KPIHIERARCHY']._serialized_end = 4642 + _globals['_KPIBREAKDOWN']._serialized_start = 4644 + _globals['_KPIBREAKDOWN']._serialized_end = 4715 + _globals['_KPIBREAKDOWNNODE']._serialized_start = 4718 + _globals['_KPIBREAKDOWNNODE']._serialized_end = 4871 + _globals['_KPISCREENCOMPANYRESULT']._serialized_start = 4874 + _globals['_KPISCREENCOMPANYRESULT']._serialized_end = 5076 \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_messages_pb2.pyi b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_messages_pb2.pyi index 2a55c6b4..d2dd11b9 100644 --- a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_messages_pb2.pyi +++ b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_messages_pb2.pyi @@ -806,6 +806,7 @@ class FiscalPeriod(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor END_DATE_FIELD_NUMBER: builtins.int LABEL_FIELD_NUMBER: builtins.int + REPORT_DATE_FIELD_NUMBER: builtins.int label: builtins.str 'The fiscal period label. Examples: 1Q-2003, 2H-1997, FY-2029.' @@ -813,13 +814,17 @@ class FiscalPeriod(google.protobuf.message.Message): def end_date(self) -> exabel.api.time.date_pb2.Date: """The last date of the fiscal period.""" - def __init__(self, *, end_date: exabel.api.time.date_pb2.Date | None=..., label: builtins.str | None=...) -> None: + @property + def report_date(self) -> exabel.api.time.date_pb2.Date: + """The report date of the fiscal period.""" + + def __init__(self, *, end_date: exabel.api.time.date_pb2.Date | None=..., label: builtins.str | None=..., report_date: exabel.api.time.date_pb2.Date | None=...) -> None: ... - def HasField(self, field_name: typing.Literal['_label', b'_label', 'end_date', b'end_date', 'label', b'label']) -> builtins.bool: + def HasField(self, field_name: typing.Literal['_label', b'_label', 'end_date', b'end_date', 'label', b'label', 'report_date', b'report_date']) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal['_label', b'_label', 'end_date', b'end_date', 'label', b'label']) -> None: + def ClearField(self, field_name: typing.Literal['_label', b'_label', 'end_date', b'end_date', 'label', b'label', 'report_date', b'report_date']) -> None: ... def WhichOneof(self, oneof_group: typing.Literal['_label', b'_label']) -> typing.Literal['label'] | None: @@ -926,4 +931,34 @@ class KpiBreakdownNode(google.protobuf.message.Message): def WhichOneof(self, oneof_group: typing.Literal['content', b'content']) -> typing.Literal['kpi', 'header'] | None: ... -global___KpiBreakdownNode = KpiBreakdownNode \ No newline at end of file +global___KpiBreakdownNode = KpiBreakdownNode + +@typing.final +class KpiScreenCompanyResult(google.protobuf.message.Message): + """KPI screen results for a single company.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + COMPANY_FIELD_NUMBER: builtins.int + FISCAL_PERIOD_FIELD_NUMBER: builtins.int + RESULTS_FIELD_NUMBER: builtins.int + + @property + def company(self) -> global___Company: + """The company.""" + + @property + def fiscal_period(self) -> global___FiscalPeriod: + """The fiscal period for this result.""" + + @property + def results(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___CompanyKpiModelResult]: + """KPI model results for this company.""" + + def __init__(self, *, company: global___Company | None=..., fiscal_period: global___FiscalPeriod | None=..., results: collections.abc.Iterable[global___CompanyKpiModelResult] | None=...) -> None: + ... + + def HasField(self, field_name: typing.Literal['company', b'company', 'fiscal_period', b'fiscal_period']) -> builtins.bool: + ... + + def ClearField(self, field_name: typing.Literal['company', b'company', 'fiscal_period', b'fiscal_period', 'results', b'results']) -> None: + ... +global___KpiScreenCompanyResult = KpiScreenCompanyResult \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2.py b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2.py index 8c5931fa..5e3c12e9 100644 --- a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2.py +++ b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2.py @@ -10,7 +10,7 @@ from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 from .....protoc_gen_openapiv2.options import annotations_pb2 as protoc__gen__openapiv2_dot_options_dot_annotations__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)exabel/api/analytics/v1/kpi_service.proto\x12\x17exabel.api.analytics.v1\x1a*exabel/api/analytics/v1/kpi_messages.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a.protoc_gen_openapiv2/options/annotations.proto"\x9e\x01\n\x1cListKpiMappingResultsRequest\x12*\n\x06parent\x18\x01 \x01(\tB\x1a\x92A\x14\xca>\x11\xfa\x02\x0ekpiMappingName\xe0A\x02\x12>\n\tpage_size\x18\x02 \x01(\x05B+\x92A(2&Maximum number of companies to return.\x12\x12\n\npage_token\x18\x03 \x01(\t"\x90\x01\n\x1dListKpiMappingResultsResponse\x12B\n\x07results\x18\x01 \x03(\x0b21.exabel.api.analytics.v1.CompanyKpiMappingResults\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\x12\x12\n\ntotal_size\x18\x03 \x01(\x05"\xc4\x01\n"ListCompanyBaseModelResultsRequest\x12\'\n\x06parent\x18\x01 \x01(\tB\x17\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02\x12=\n\x06period\x18\x02 \x01(\x0b2-.exabel.api.analytics.v1.FiscalPeriodSelector\x126\n\nkpi_source\x18\x03 \x01(\x0e2".exabel.api.analytics.v1.KpiSource"\x9d\x01\n#ListCompanyBaseModelResultsResponse\x12?\n\x07results\x18\x01 \x03(\x0b2..exabel.api.analytics.v1.CompanyKpiModelResult\x125\n\x06period\x18\x02 \x01(\x0b2%.exabel.api.analytics.v1.FiscalPeriod"\xcc\x01\n*ListCompanyHierarchicalModelResultsRequest\x12\'\n\x06parent\x18\x01 \x01(\tB\x17\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02\x12=\n\x06period\x18\x02 \x01(\x0b2-.exabel.api.analytics.v1.FiscalPeriodSelector\x126\n\nkpi_source\x18\x03 \x01(\x0e2".exabel.api.analytics.v1.KpiSource"\xe3\x01\n+ListCompanyHierarchicalModelResultsResponse\x12?\n\x07results\x18\x01 \x03(\x0b2..exabel.api.analytics.v1.CompanyKpiModelResult\x12<\n\rkpi_hierarchy\x18\x02 \x01(\x0b2%.exabel.api.analytics.v1.KpiHierarchy\x125\n\x06period\x18\x03 \x01(\x0b2%.exabel.api.analytics.v1.FiscalPeriod"~\n#ListCompanyKpiMappingResultsRequest\x12\'\n\x06parent\x18\x01 \x01(\tB\x17\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02\x12.\n\x03kpi\x18\x02 \x01(\x0b2\x1c.exabel.api.analytics.v1.KpiB\x03\xe0A\x02"f\n$ListCompanyKpiMappingResultsResponse\x12>\n\x07results\x18\x01 \x03(\x0b2-.exabel.api.analytics.v1.KpiMappingResultData"|\n!ListCompanyKpiModelResultsRequest\x12\'\n\x06parent\x18\x01 \x01(\tB\x17\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02\x12.\n\x03kpi\x18\x02 \x01(\x0b2\x1c.exabel.api.analytics.v1.KpiB\x03\xe0A\x02"\x9c\x02\n"ListCompanyKpiModelResultsResponse\x127\n\x0cexabel_model\x18\x01 \x01(\x0b2!.exabel.api.analytics.v1.KpiModel\x12=\n\x12hierarchical_model\x18\x02 \x01(\x0b2!.exabel.api.analytics.v1.KpiModel\x128\n\rcustom_models\x18\x03 \x03(\x0b2!.exabel.api.analytics.v1.KpiModel\x12D\n\x12kpi_mapping_models\x18\x04 \x03(\x0b2(.exabel.api.analytics.v1.KpiMappingModel2\x9a\n\n\nKpiService\x12\xcf\x01\n\x15ListKpiMappingResults\x125.exabel.api.analytics.v1.ListKpiMappingResultsRequest\x1a6.exabel.api.analytics.v1.ListKpiMappingResultsResponse"G\x92A\x1a\x12\x18List KPI mapping results\x82\xd3\xe4\x93\x02$\x12"/v1/{parent=kpiMappings/*}/results\x12\x82\x02\n\x1bListCompanyBaseModelResults\x12;.exabel.api.analytics.v1.ListCompanyBaseModelResultsRequest\x1a<.exabel.api.analytics.v1.ListCompanyBaseModelResultsResponse"h\x92A!\x12\x1fList company base model results\x82\xd3\xe4\x93\x02>\x12\x11\xfa\x02\x0ekpiMappingName\xe0A\x02\x12>\n\tpage_size\x18\x02 \x01(\x05B+\x92A(2&Maximum number of companies to return.\x12\x12\n\npage_token\x18\x03 \x01(\t"\x90\x01\n\x1dListKpiMappingResultsResponse\x12B\n\x07results\x18\x01 \x03(\x0b21.exabel.api.analytics.v1.CompanyKpiMappingResults\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\x12\x12\n\ntotal_size\x18\x03 \x01(\x05"\xc8\x01\n"ListCompanyBaseModelResultsRequest\x12\'\n\x06parent\x18\x01 \x01(\tB\x17\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02\x12=\n\x06period\x18\x02 \x01(\x0b2-.exabel.api.analytics.v1.FiscalPeriodSelector\x12:\n\nkpi_source\x18\x03 \x01(\x0e2".exabel.api.analytics.v1.KpiSourceB\x02\x18\x01"\x9d\x01\n#ListCompanyBaseModelResultsResponse\x12?\n\x07results\x18\x01 \x03(\x0b2..exabel.api.analytics.v1.CompanyKpiModelResult\x125\n\x06period\x18\x02 \x01(\x0b2%.exabel.api.analytics.v1.FiscalPeriod"\xd0\x01\n*ListCompanyHierarchicalModelResultsRequest\x12\'\n\x06parent\x18\x01 \x01(\tB\x17\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02\x12=\n\x06period\x18\x02 \x01(\x0b2-.exabel.api.analytics.v1.FiscalPeriodSelector\x12:\n\nkpi_source\x18\x03 \x01(\x0e2".exabel.api.analytics.v1.KpiSourceB\x02\x18\x01"\xe3\x01\n+ListCompanyHierarchicalModelResultsResponse\x12?\n\x07results\x18\x01 \x03(\x0b2..exabel.api.analytics.v1.CompanyKpiModelResult\x12<\n\rkpi_hierarchy\x18\x02 \x01(\x0b2%.exabel.api.analytics.v1.KpiHierarchy\x125\n\x06period\x18\x03 \x01(\x0b2%.exabel.api.analytics.v1.FiscalPeriod"~\n#ListCompanyKpiMappingResultsRequest\x12\'\n\x06parent\x18\x01 \x01(\tB\x17\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02\x12.\n\x03kpi\x18\x02 \x01(\x0b2\x1c.exabel.api.analytics.v1.KpiB\x03\xe0A\x02"f\n$ListCompanyKpiMappingResultsResponse\x12>\n\x07results\x18\x01 \x03(\x0b2-.exabel.api.analytics.v1.KpiMappingResultData"|\n!ListCompanyKpiModelResultsRequest\x12\'\n\x06parent\x18\x01 \x01(\tB\x17\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02\x12.\n\x03kpi\x18\x02 \x01(\x0b2\x1c.exabel.api.analytics.v1.KpiB\x03\xe0A\x02"\x9c\x02\n"ListCompanyKpiModelResultsResponse\x127\n\x0cexabel_model\x18\x01 \x01(\x0b2!.exabel.api.analytics.v1.KpiModel\x12=\n\x12hierarchical_model\x18\x02 \x01(\x0b2!.exabel.api.analytics.v1.KpiModel\x128\n\rcustom_models\x18\x03 \x03(\x0b2!.exabel.api.analytics.v1.KpiModel\x12D\n\x12kpi_mapping_models\x18\x04 \x03(\x0b2(.exabel.api.analytics.v1.KpiMappingModel"\x9a\x01\n\x1bListKpiScreenResultsRequest\x12\'\n\x04name\x18\x01 \x01(\tB\x19\x92A\x13\xca>\x10\xfa\x02\rkpiScreenName\xe0A\x02\x12>\n\tpage_size\x18\x02 \x01(\x05B+\x92A(2&Maximum number of companies to return.\x12\x12\n\npage_token\x18\x03 \x01(\t"\x8d\x01\n\x1cListKpiScreenResultsResponse\x12@\n\x07results\x18\x01 \x03(\x0b2/.exabel.api.analytics.v1.KpiScreenCompanyResult\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\x12\x12\n\ntotal_size\x18\x03 \x01(\x052\xe5\x0b\n\nKpiService\x12\xcf\x01\n\x15ListKpiMappingResults\x125.exabel.api.analytics.v1.ListKpiMappingResultsRequest\x1a6.exabel.api.analytics.v1.ListKpiMappingResultsResponse"G\x92A\x1a\x12\x18List KPI mapping results\x82\xd3\xe4\x93\x02$\x12"/v1/{parent=kpiMappings/*}/results\x12\x82\x02\n\x1bListCompanyBaseModelResults\x12;.exabel.api.analytics.v1.ListCompanyBaseModelResultsRequest\x1a<.exabel.api.analytics.v1.ListCompanyBaseModelResultsResponse"h\x92A!\x12\x1fList company base model results\x82\xd3\xe4\x93\x02>\x12\x0e\xfa\x02\x0bcompanyName\xe0A\x02' + _globals['_LISTCOMPANYBASEMODELRESULTSREQUEST'].fields_by_name['kpi_source']._loaded_options = None + _globals['_LISTCOMPANYBASEMODELRESULTSREQUEST'].fields_by_name['kpi_source']._serialized_options = b'\x18\x01' _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSREQUEST'].fields_by_name['parent']._loaded_options = None _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSREQUEST'].fields_by_name['parent']._serialized_options = b'\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02' + _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSREQUEST'].fields_by_name['kpi_source']._loaded_options = None + _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSREQUEST'].fields_by_name['kpi_source']._serialized_options = b'\x18\x01' _globals['_LISTCOMPANYKPIMAPPINGRESULTSREQUEST'].fields_by_name['parent']._loaded_options = None _globals['_LISTCOMPANYKPIMAPPINGRESULTSREQUEST'].fields_by_name['parent']._serialized_options = b'\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02' _globals['_LISTCOMPANYKPIMAPPINGRESULTSREQUEST'].fields_by_name['kpi']._loaded_options = None @@ -33,6 +37,10 @@ _globals['_LISTCOMPANYKPIMODELRESULTSREQUEST'].fields_by_name['parent']._serialized_options = b'\x92A\x11\xca>\x0e\xfa\x02\x0bcompanyName\xe0A\x02' _globals['_LISTCOMPANYKPIMODELRESULTSREQUEST'].fields_by_name['kpi']._loaded_options = None _globals['_LISTCOMPANYKPIMODELRESULTSREQUEST'].fields_by_name['kpi']._serialized_options = b'\xe0A\x02' + _globals['_LISTKPISCREENRESULTSREQUEST'].fields_by_name['name']._loaded_options = None + _globals['_LISTKPISCREENRESULTSREQUEST'].fields_by_name['name']._serialized_options = b'\x92A\x13\xca>\x10\xfa\x02\rkpiScreenName\xe0A\x02' + _globals['_LISTKPISCREENRESULTSREQUEST'].fields_by_name['page_size']._loaded_options = None + _globals['_LISTKPISCREENRESULTSREQUEST'].fields_by_name['page_size']._serialized_options = b'\x92A(2&Maximum number of companies to return.' _globals['_KPISERVICE'].methods_by_name['ListKpiMappingResults']._loaded_options = None _globals['_KPISERVICE'].methods_by_name['ListKpiMappingResults']._serialized_options = b'\x92A\x1a\x12\x18List KPI mapping results\x82\xd3\xe4\x93\x02$\x12"/v1/{parent=kpiMappings/*}/results' _globals['_KPISERVICE'].methods_by_name['ListCompanyBaseModelResults']._loaded_options = None @@ -43,25 +51,31 @@ _globals['_KPISERVICE'].methods_by_name['ListCompanyKpiMappingResults']._serialized_options = b'\x92A"\x12 List company KPI mapping results\x82\xd3\xe4\x93\x02?\x12=/v1/{parent=entityTypes/company/entities/*}/kpiMappingResults' _globals['_KPISERVICE'].methods_by_name['ListCompanyKpiModelResults']._loaded_options = None _globals['_KPISERVICE'].methods_by_name['ListCompanyKpiModelResults']._serialized_options = b'\x92A \x12\x1eList company KPI model results\x82\xd3\xe4\x93\x02=\x12;/v1/{parent=entityTypes/company/entities/*}/kpiModelResults' + _globals['_KPISERVICE'].methods_by_name['ListKpiScreenResults']._loaded_options = None + _globals['_KPISERVICE'].methods_by_name['ListKpiScreenResults']._serialized_options = b'\x92A\x19\x12\x17List KPI screen results\x82\xd3\xe4\x93\x02!\x12\x1f/v1/{name=kpiScreens/*}/results' _globals['_LISTKPIMAPPINGRESULTSREQUEST']._serialized_start = 226 _globals['_LISTKPIMAPPINGRESULTSREQUEST']._serialized_end = 384 _globals['_LISTKPIMAPPINGRESULTSRESPONSE']._serialized_start = 387 _globals['_LISTKPIMAPPINGRESULTSRESPONSE']._serialized_end = 531 _globals['_LISTCOMPANYBASEMODELRESULTSREQUEST']._serialized_start = 534 - _globals['_LISTCOMPANYBASEMODELRESULTSREQUEST']._serialized_end = 730 - _globals['_LISTCOMPANYBASEMODELRESULTSRESPONSE']._serialized_start = 733 - _globals['_LISTCOMPANYBASEMODELRESULTSRESPONSE']._serialized_end = 890 - _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSREQUEST']._serialized_start = 893 - _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSREQUEST']._serialized_end = 1097 - _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSRESPONSE']._serialized_start = 1100 - _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSRESPONSE']._serialized_end = 1327 - _globals['_LISTCOMPANYKPIMAPPINGRESULTSREQUEST']._serialized_start = 1329 - _globals['_LISTCOMPANYKPIMAPPINGRESULTSREQUEST']._serialized_end = 1455 - _globals['_LISTCOMPANYKPIMAPPINGRESULTSRESPONSE']._serialized_start = 1457 - _globals['_LISTCOMPANYKPIMAPPINGRESULTSRESPONSE']._serialized_end = 1559 - _globals['_LISTCOMPANYKPIMODELRESULTSREQUEST']._serialized_start = 1561 - _globals['_LISTCOMPANYKPIMODELRESULTSREQUEST']._serialized_end = 1685 - _globals['_LISTCOMPANYKPIMODELRESULTSRESPONSE']._serialized_start = 1688 - _globals['_LISTCOMPANYKPIMODELRESULTSRESPONSE']._serialized_end = 1972 - _globals['_KPISERVICE']._serialized_start = 1975 - _globals['_KPISERVICE']._serialized_end = 3281 \ No newline at end of file + _globals['_LISTCOMPANYBASEMODELRESULTSREQUEST']._serialized_end = 734 + _globals['_LISTCOMPANYBASEMODELRESULTSRESPONSE']._serialized_start = 737 + _globals['_LISTCOMPANYBASEMODELRESULTSRESPONSE']._serialized_end = 894 + _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSREQUEST']._serialized_start = 897 + _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSREQUEST']._serialized_end = 1105 + _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSRESPONSE']._serialized_start = 1108 + _globals['_LISTCOMPANYHIERARCHICALMODELRESULTSRESPONSE']._serialized_end = 1335 + _globals['_LISTCOMPANYKPIMAPPINGRESULTSREQUEST']._serialized_start = 1337 + _globals['_LISTCOMPANYKPIMAPPINGRESULTSREQUEST']._serialized_end = 1463 + _globals['_LISTCOMPANYKPIMAPPINGRESULTSRESPONSE']._serialized_start = 1465 + _globals['_LISTCOMPANYKPIMAPPINGRESULTSRESPONSE']._serialized_end = 1567 + _globals['_LISTCOMPANYKPIMODELRESULTSREQUEST']._serialized_start = 1569 + _globals['_LISTCOMPANYKPIMODELRESULTSREQUEST']._serialized_end = 1693 + _globals['_LISTCOMPANYKPIMODELRESULTSRESPONSE']._serialized_start = 1696 + _globals['_LISTCOMPANYKPIMODELRESULTSRESPONSE']._serialized_end = 1980 + _globals['_LISTKPISCREENRESULTSREQUEST']._serialized_start = 1983 + _globals['_LISTKPISCREENRESULTSREQUEST']._serialized_end = 2137 + _globals['_LISTKPISCREENRESULTSRESPONSE']._serialized_start = 2140 + _globals['_LISTKPISCREENRESULTSRESPONSE']._serialized_end = 2281 + _globals['_KPISERVICE']._serialized_start = 2284 + _globals['_KPISERVICE']._serialized_end = 3793 \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2.pyi b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2.pyi index 4df8700b..12ef60ca 100644 --- a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2.pyi +++ b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2.pyi @@ -40,13 +40,13 @@ class ListKpiMappingResultsResponse(google.protobuf.message.Message): NEXT_PAGE_TOKEN_FIELD_NUMBER: builtins.int TOTAL_SIZE_FIELD_NUMBER: builtins.int next_page_token: builtins.str - 'Token for the next page of results, which can be sent to a subsequent list query.' + 'Token for the next page of results, which can be sent to a subsequent list query.\n The end of the list is reached when the token is empty.\n ' total_size: builtins.int 'Total number of rows, irrespective of paging.' @property def results(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[exabel.api.analytics.v1.kpi_messages_pb2.CompanyKpiMappingResults]: - """List of results, one for each company. The end of the list is reached when the token is empty.""" + """List of results, one for each company.""" def __init__(self, *, results: collections.abc.Iterable[exabel.api.analytics.v1.kpi_messages_pb2.CompanyKpiMappingResults] | None=..., next_page_token: builtins.str | None=..., total_size: builtins.int | None=...) -> None: ... @@ -65,7 +65,7 @@ class ListCompanyBaseModelResultsRequest(google.protobuf.message.Message): parent: builtins.str 'Resource name of the company get results for.' kpi_source: exabel.api.analytics.v1.kpi_messages_pb2.KpiSource.ValueType - 'Optional KPI source.\n If not specified, all KPIs are returned.\n ' + 'This field is no longer in use.' @property def period(self) -> exabel.api.analytics.v1.kpi_messages_pb2.FiscalPeriodSelector: @@ -120,7 +120,7 @@ class ListCompanyHierarchicalModelResultsRequest(google.protobuf.message.Message parent: builtins.str 'Resource name of the company get results for.' kpi_source: exabel.api.analytics.v1.kpi_messages_pb2.KpiSource.ValueType - 'Optional KPI source.\n If not specified, all KPIs are returned.\n ' + 'This field is no longer in use.' @property def period(self) -> exabel.api.analytics.v1.kpi_messages_pb2.FiscalPeriodSelector: @@ -270,4 +270,48 @@ class ListCompanyKpiModelResultsResponse(google.protobuf.message.Message): def ClearField(self, field_name: typing.Literal['custom_models', b'custom_models', 'exabel_model', b'exabel_model', 'hierarchical_model', b'hierarchical_model', 'kpi_mapping_models', b'kpi_mapping_models']) -> None: ... -global___ListCompanyKpiModelResultsResponse = ListCompanyKpiModelResultsResponse \ No newline at end of file +global___ListCompanyKpiModelResultsResponse = ListCompanyKpiModelResultsResponse + +@typing.final +class ListKpiScreenResultsRequest(google.protobuf.message.Message): + """Request to ListKpiScreenResults.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + NAME_FIELD_NUMBER: builtins.int + PAGE_SIZE_FIELD_NUMBER: builtins.int + PAGE_TOKEN_FIELD_NUMBER: builtins.int + name: builtins.str + 'Resource name of the KPI screen, e.g. `kpiScreens/123`.' + page_size: builtins.int + 'Maximum number of companies to return. Defaults to 20, and the maximum allowed value is 100.' + page_token: builtins.str + 'Token for a specific page of results, as returned from a previous list request with the same\n query parameters.\n ' + + def __init__(self, *, name: builtins.str | None=..., page_size: builtins.int | None=..., page_token: builtins.str | None=...) -> None: + ... + + def ClearField(self, field_name: typing.Literal['name', b'name', 'page_size', b'page_size', 'page_token', b'page_token']) -> None: + ... +global___ListKpiScreenResultsRequest = ListKpiScreenResultsRequest + +@typing.final +class ListKpiScreenResultsResponse(google.protobuf.message.Message): + """Response from ListKpiScreenResults.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RESULTS_FIELD_NUMBER: builtins.int + NEXT_PAGE_TOKEN_FIELD_NUMBER: builtins.int + TOTAL_SIZE_FIELD_NUMBER: builtins.int + next_page_token: builtins.str + 'Token for the next page of results, which can be sent to a subsequent list query.\n The end of the list is reached when the token is empty.\n ' + total_size: builtins.int + 'Total number of rows, irrespective of paging.' + + @property + def results(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[exabel.api.analytics.v1.kpi_messages_pb2.KpiScreenCompanyResult]: + """List of results, one for each company.""" + + def __init__(self, *, results: collections.abc.Iterable[exabel.api.analytics.v1.kpi_messages_pb2.KpiScreenCompanyResult] | None=..., next_page_token: builtins.str | None=..., total_size: builtins.int | None=...) -> None: + ... + + def ClearField(self, field_name: typing.Literal['next_page_token', b'next_page_token', 'results', b'results', 'total_size', b'total_size']) -> None: + ... +global___ListKpiScreenResultsResponse = ListKpiScreenResultsResponse \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2_grpc.py b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2_grpc.py index b23d19bf..281aa562 100644 --- a/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2_grpc.py +++ b/exabel_data_sdk/stubs/exabel/api/analytics/v1/kpi_service_pb2_grpc.py @@ -28,6 +28,7 @@ def __init__(self, channel): self.ListCompanyHierarchicalModelResults = channel.unary_unary('/exabel.api.analytics.v1.KpiService/ListCompanyHierarchicalModelResults', request_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyHierarchicalModelResultsRequest.SerializeToString, response_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyHierarchicalModelResultsResponse.FromString, _registered_method=True) self.ListCompanyKpiMappingResults = channel.unary_unary('/exabel.api.analytics.v1.KpiService/ListCompanyKpiMappingResults', request_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiMappingResultsRequest.SerializeToString, response_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiMappingResultsResponse.FromString, _registered_method=True) self.ListCompanyKpiModelResults = channel.unary_unary('/exabel.api.analytics.v1.KpiService/ListCompanyKpiModelResults', request_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsRequest.SerializeToString, response_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsResponse.FromString, _registered_method=True) + self.ListKpiScreenResults = channel.unary_unary('/exabel.api.analytics.v1.KpiService/ListKpiScreenResults', request_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiScreenResultsRequest.SerializeToString, response_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiScreenResultsResponse.FromString, _registered_method=True) class KpiServiceServicer(object): """Service to retrieve KPI results. @@ -42,6 +43,15 @@ def ListKpiMappingResults(self, request, context): def ListCompanyBaseModelResults(self, request, context): """List base model results for a company. + + Returns results for all modeled KPIs for the given company. This is provided as a list of KPIs, + with the latest model predictions and metadata for each KPI. + + Base models work by modeling each KPI independently, using the vendor alternative data + available to your customer/user, and combining the best signals for that KPI. + + The default "Exabel Model" that you see for all KPIs is a base model. If you have trained a + custom model and set that as your primary model for the KPI, you may see that returned instead. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -49,6 +59,16 @@ def ListCompanyBaseModelResults(self, request, context): def ListCompanyHierarchicalModelResults(self, request, context): """List hierarchical model results for a company. + + Returns results for all modeled KPIs for the given company. This is provided as a list of KPIs, + with the latest model predictions and metadata for each KPI. + + Hierarchical models build on top of the base models by harmonizing their predictions with + knowledge of each company's financial model, e.g. Total revenue = Segment A + Segment B + ... + This produces more accurate predictions that are coherent across all segments. Note that + consensus may be used for KPIs for which you have no alternative data; this is denoted in the + metadata. Hierarchical models are only available for some companies - contact + support@exabel.com with requests. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -56,6 +76,10 @@ def ListCompanyHierarchicalModelResults(self, request, context): def ListCompanyKpiMappingResults(self, request, context): """List KPI mapping results for a single company KPI. + + Returns the statistical test results of all KPI mappings for a given company KPI. This is + provided as a list of KPI mappings, each with the model backtest MAPE/MAE, correlation scores, + MAEs, and p-values. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -63,13 +87,32 @@ def ListCompanyKpiMappingResults(self, request, context): def ListCompanyKpiModelResults(self, request, context): """List model results for a single company KPI. + + Returns all models for a given company KPI. This includes: all single-predictor models derived + from each individual KPI mapping; the Exabel model that is an automatic ensemble of the best + single-predictor models; any custom models you train; and the hierarchical model (if available) + that harmonizes model predictions across a company's KPIs as the final modeling layer. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListKpiScreenResults(self, request, context): + """List KPI screen results. + + Returns results from a saved KPI screen. This is provided as a list of companies and their KPIs + that meet the screen criteria, with the latest model predictions and metadata for each KPI. + + As KPI screens are private to each user, you should authenticate with a user-level access + token, rather than the customer-level API key (which will not have access to individual users' + KPI screens). """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_KpiServiceServicer_to_server(servicer, server): - rpc_method_handlers = {'ListKpiMappingResults': grpc.unary_unary_rpc_method_handler(servicer.ListKpiMappingResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiMappingResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiMappingResultsResponse.SerializeToString), 'ListCompanyBaseModelResults': grpc.unary_unary_rpc_method_handler(servicer.ListCompanyBaseModelResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyBaseModelResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyBaseModelResultsResponse.SerializeToString), 'ListCompanyHierarchicalModelResults': grpc.unary_unary_rpc_method_handler(servicer.ListCompanyHierarchicalModelResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyHierarchicalModelResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyHierarchicalModelResultsResponse.SerializeToString), 'ListCompanyKpiMappingResults': grpc.unary_unary_rpc_method_handler(servicer.ListCompanyKpiMappingResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiMappingResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiMappingResultsResponse.SerializeToString), 'ListCompanyKpiModelResults': grpc.unary_unary_rpc_method_handler(servicer.ListCompanyKpiModelResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsResponse.SerializeToString)} + rpc_method_handlers = {'ListKpiMappingResults': grpc.unary_unary_rpc_method_handler(servicer.ListKpiMappingResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiMappingResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiMappingResultsResponse.SerializeToString), 'ListCompanyBaseModelResults': grpc.unary_unary_rpc_method_handler(servicer.ListCompanyBaseModelResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyBaseModelResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyBaseModelResultsResponse.SerializeToString), 'ListCompanyHierarchicalModelResults': grpc.unary_unary_rpc_method_handler(servicer.ListCompanyHierarchicalModelResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyHierarchicalModelResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyHierarchicalModelResultsResponse.SerializeToString), 'ListCompanyKpiMappingResults': grpc.unary_unary_rpc_method_handler(servicer.ListCompanyKpiMappingResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiMappingResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiMappingResultsResponse.SerializeToString), 'ListCompanyKpiModelResults': grpc.unary_unary_rpc_method_handler(servicer.ListCompanyKpiModelResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsResponse.SerializeToString), 'ListKpiScreenResults': grpc.unary_unary_rpc_method_handler(servicer.ListKpiScreenResults, request_deserializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiScreenResultsRequest.FromString, response_serializer=exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiScreenResultsResponse.SerializeToString)} generic_handler = grpc.method_handlers_generic_handler('exabel.api.analytics.v1.KpiService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) server.add_registered_method_handlers('exabel.api.analytics.v1.KpiService', rpc_method_handlers) @@ -96,4 +139,8 @@ def ListCompanyKpiMappingResults(request, target, options=(), channel_credential @staticmethod def ListCompanyKpiModelResults(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/exabel.api.analytics.v1.KpiService/ListCompanyKpiModelResults', exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsRequest.SerializeToString, exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata, _registered_method=True) \ No newline at end of file + return grpc.experimental.unary_unary(request, target, '/exabel.api.analytics.v1.KpiService/ListCompanyKpiModelResults', exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsRequest.SerializeToString, exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListCompanyKpiModelResultsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata, _registered_method=True) + + @staticmethod + def ListKpiScreenResults(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): + return grpc.experimental.unary_unary(request, target, '/exabel.api.analytics.v1.KpiService/ListKpiScreenResults', exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiScreenResultsRequest.SerializeToString, exabel_dot_api_dot_analytics_dot_v1_dot_kpi__service__pb2.ListKpiScreenResultsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata, _registered_method=True) \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/__init__.pyi b/exabel_data_sdk/stubs/exabel/api/data/v1/__init__.pyi index 1cae8f00..3dc9246b 100644 --- a/exabel_data_sdk/stubs/exabel/api/data/v1/__init__.pyi +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/__init__.pyi @@ -5,6 +5,8 @@ from . import data_set_messages_pb2 from . import data_set_service_pb2 from . import entity_messages_pb2 from . import entity_service_pb2 +from . import holiday_messages_pb2 +from . import holiday_service_pb2 from . import import_job_service_pb2 from . import namespace_service_pb2 from . import namespaces_messages_pb2 diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/all_pb2.py b/exabel_data_sdk/stubs/exabel/api/data/v1/all_pb2.py index 5a3af2a7..650f6dc5 100644 --- a/exabel_data_sdk/stubs/exabel/api/data/v1/all_pb2.py +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/all_pb2.py @@ -8,6 +8,8 @@ from .data_set_service_pb2 import * from .entity_messages_pb2 import * from .entity_service_pb2 import * +from .holiday_messages_pb2 import * +from .holiday_service_pb2 import * from .import_job_service_pb2 import * from .namespace_service_pb2 import * from .namespaces_messages_pb2 import * diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/all_pb2_grpc.py b/exabel_data_sdk/stubs/exabel/api/data/v1/all_pb2_grpc.py index c00932eb..9d510149 100644 --- a/exabel_data_sdk/stubs/exabel/api/data/v1/all_pb2_grpc.py +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/all_pb2_grpc.py @@ -8,6 +8,8 @@ from .data_set_service_pb2_grpc import * from .entity_messages_pb2_grpc import * from .entity_service_pb2_grpc import * +from .holiday_messages_pb2_grpc import * +from .holiday_service_pb2_grpc import * from .import_job_service_pb2_grpc import * from .namespace_service_pb2_grpc import * from .namespaces_messages_pb2_grpc import * diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/entity_service_pb2_grpc.py b/exabel_data_sdk/stubs/exabel/api/data/v1/entity_service_pb2_grpc.py index 880fb044..cebf06e9 100644 --- a/exabel_data_sdk/stubs/exabel/api/data/v1/entity_service_pb2_grpc.py +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/entity_service_pb2_grpc.py @@ -47,8 +47,8 @@ class EntityServiceServicer(object): def ListEntityTypes(self, request, context): """Lists all known entity types. - Lists all entity types available to your customer, including those created by you, in the - global catalog, and from data sets you are subscribed to. + Lists all entity types available to your customer, including those created by you, and in the + global catalog. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2.py b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2.py new file mode 100644 index 00000000..740356b3 --- /dev/null +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2.py @@ -0,0 +1,20 @@ +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion(_runtime_version.Domain.PUBLIC, 5, 29, 0, '', 'exabel/api/data/v1/holiday_messages.proto') +_sym_db = _symbol_database.Default() +from .....exabel.api.time import date_pb2 as exabel_dot_api_dot_time_dot_date__pb2 +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)exabel/api/data/v1/holiday_messages.proto\x12\x12exabel.api.data.v1\x1a\x1aexabel/api/time/date.proto"i\n\x14HolidaySpecification\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0cdisplay_name\x18\x02 \x01(\t\x12-\n\x08holidays\x18\x03 \x03(\x0b2\x1b.exabel.api.data.v1.Holiday"\x7f\n\x07Holiday\x12\r\n\x05label\x18\x01 \x01(\t\x12\x14\n\x0clower_window\x18\x02 \x01(\x05\x12\x14\n\x0cupper_window\x18\x03 \x01(\x05\x12\x13\n\x0bprior_scale\x18\x04 \x01(\x01\x12$\n\x05dates\x18\x05 \x03(\x0b2\x15.exabel.api.time.DateBH\n\x16com.exabel.api.data.v1B\x14HolidayMessagesProtoP\x01Z\x16exabel.com/api/data/v1b\x06proto3') +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'exabel.api.data.v1.holiday_messages_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\x16com.exabel.api.data.v1B\x14HolidayMessagesProtoP\x01Z\x16exabel.com/api/data/v1' + _globals['_HOLIDAYSPECIFICATION']._serialized_start = 93 + _globals['_HOLIDAYSPECIFICATION']._serialized_end = 198 + _globals['_HOLIDAY']._serialized_start = 200 + _globals['_HOLIDAY']._serialized_end = 327 \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2.pyi b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2.pyi new file mode 100644 index 00000000..ddf26026 --- /dev/null +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2.pyi @@ -0,0 +1,70 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +Copyright (c) 2025 Exabel AS. All rights reserved.""" +import builtins +import collections.abc +from ..... import exabel +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.message +import typing +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class HolidaySpecification(google.protobuf.message.Message): + """A holiday specification defining a set of holidays with their properties. + Used for Prophet forecasting models. + """ + DESCRIPTOR: google.protobuf.descriptor.Descriptor + NAME_FIELD_NUMBER: builtins.int + DISPLAY_NAME_FIELD_NUMBER: builtins.int + HOLIDAYS_FIELD_NUMBER: builtins.int + name: builtins.str + 'Resource name, e.g. "holidaySpecifications/123".' + display_name: builtins.str + 'Display name for the holiday specification.' + + @property + def holidays(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Holiday]: + """List of holidays in this specification.""" + + def __init__(self, *, name: builtins.str | None=..., display_name: builtins.str | None=..., holidays: collections.abc.Iterable[global___Holiday] | None=...) -> None: + ... + + def ClearField(self, field_name: typing.Literal['display_name', b'display_name', 'holidays', b'holidays', 'name', b'name']) -> None: + ... +global___HolidaySpecification = HolidaySpecification + +@typing.final +class Holiday(google.protobuf.message.Message): + """A single holiday with its occurrences and parameters. + Parameters follow Facebook Prophet's holiday specification format. + """ + DESCRIPTOR: google.protobuf.descriptor.Descriptor + LABEL_FIELD_NUMBER: builtins.int + LOWER_WINDOW_FIELD_NUMBER: builtins.int + UPPER_WINDOW_FIELD_NUMBER: builtins.int + PRIOR_SCALE_FIELD_NUMBER: builtins.int + DATES_FIELD_NUMBER: builtins.int + label: builtins.str + 'Label for the holiday.' + lower_window: builtins.int + 'Number of days before the holiday to include in the window.\n Can be negative to extend the window backwards.\n ' + upper_window: builtins.int + 'Number of days after the holiday to include in the window.\n Can be negative to shorten the window.\n ' + prior_scale: builtins.float + 'Regularization parameter controlling the magnitude of the holiday effect.' + + @property + def dates(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[exabel.api.time.date_pb2.Date]: + """List of dates when this holiday occurs. + Must contain at least one date. + """ + + def __init__(self, *, label: builtins.str | None=..., lower_window: builtins.int | None=..., upper_window: builtins.int | None=..., prior_scale: builtins.float | None=..., dates: collections.abc.Iterable[exabel.api.time.date_pb2.Date] | None=...) -> None: + ... + + def ClearField(self, field_name: typing.Literal['dates', b'dates', 'label', b'label', 'lower_window', b'lower_window', 'prior_scale', b'prior_scale', 'upper_window', b'upper_window']) -> None: + ... +global___Holiday = Holiday \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2_grpc.py b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2_grpc.py new file mode 100644 index 00000000..da16375d --- /dev/null +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_messages_pb2_grpc.py @@ -0,0 +1,13 @@ +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings +GRPC_GENERATED_VERSION = '1.71.2' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True +if _version_not_supported: + raise RuntimeError(f'The grpc package installed is at version {GRPC_VERSION},' + f' but the generated code in exabel/api/data/v1/holiday_messages_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.') \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2.py b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2.py new file mode 100644 index 00000000..738991be --- /dev/null +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2.py @@ -0,0 +1,52 @@ +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion(_runtime_version.Domain.PUBLIC, 5, 29, 0, '', 'exabel/api/data/v1/holiday_service.proto') +_sym_db = _symbol_database.Default() +from .....exabel.api.data.v1 import holiday_messages_pb2 as exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2 +from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 +from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 +from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 +from .....protoc_gen_openapiv2.options import annotations_pb2 as protoc__gen__openapiv2_dot_options_dot_annotations__pb2 +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(exabel/api/data/v1/holiday_service.proto\x12\x12exabel.api.data.v1\x1a)exabel/api/data/v1/holiday_messages.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a.protoc_gen_openapiv2/options/annotations.proto"I\n ListHolidaySpecificationsRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t"\x86\x01\n!ListHolidaySpecificationsResponse\x12H\n\x16holiday_specifications\x18\x01 \x03(\x0b2(.exabel.api.data.v1.HolidaySpecification\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t"q\n\x1eGetHolidaySpecificationRequest\x12O\n\x04name\x18\x01 \x01(\tBA\x92A;J\x1b"holidaySpecifications/123"\xca>\x1b\xfa\x02\x18holidaySpecificationName\xe0A\x02"q\n!CreateHolidaySpecificationRequest\x12L\n\x15holiday_specification\x18\x01 \x01(\x0b2(.exabel.api.data.v1.HolidaySpecificationB\x03\xe0A\x02"q\n!UpdateHolidaySpecificationRequest\x12L\n\x15holiday_specification\x18\x01 \x01(\x0b2(.exabel.api.data.v1.HolidaySpecificationB\x03\xe0A\x02"t\n!DeleteHolidaySpecificationRequest\x12O\n\x04name\x18\x01 \x01(\tBA\x92A;J\x1b"holidaySpecifications/123"\xca>\x1b\xfa\x02\x18holidaySpecificationName\xe0A\x022\xb2\x08\n\x0eHolidayService\x12\xcb\x01\n\x19ListHolidaySpecifications\x124.exabel.api.data.v1.ListHolidaySpecificationsRequest\x1a5.exabel.api.data.v1.ListHolidaySpecificationsResponse"A\x92A\x1d\x12\x1bList holiday specifications\x82\xd3\xe4\x93\x02\x1b\x12\x19/v1/holidaySpecifications\x12\xc1\x01\n\x17GetHolidaySpecification\x122.exabel.api.data.v1.GetHolidaySpecificationRequest\x1a(.exabel.api.data.v1.HolidaySpecification"H\x92A\x1b\x12\x19Get holiday specification\x82\xd3\xe4\x93\x02$\x12"/v1/{name=holidaySpecifications/*}\x12\xd8\x01\n\x1aCreateHolidaySpecification\x125.exabel.api.data.v1.CreateHolidaySpecificationRequest\x1a(.exabel.api.data.v1.HolidaySpecification"Y\x92A\x1e\x12\x1cCreate holiday specification\x82\xd3\xe4\x93\x022"\x19/v1/holidaySpecifications:\x15holiday_specification\x12\xf7\x01\n\x1aUpdateHolidaySpecification\x125.exabel.api.data.v1.UpdateHolidaySpecificationRequest\x1a(.exabel.api.data.v1.HolidaySpecification"x\x92A\x1e\x12\x1cUpdate holiday specification\x82\xd3\xe4\x93\x02Q28/v1/{holiday_specification.name=holidaySpecifications/*}:\x15holiday_specification\x12\xb8\x01\n\x1aDeleteHolidaySpecification\x125.exabel.api.data.v1.DeleteHolidaySpecificationRequest\x1a\x16.google.protobuf.Empty"K\x92A\x1e\x12\x1cDelete holiday specification\x82\xd3\xe4\x93\x02$*"/v1/{name=holidaySpecifications/*}BG\n\x16com.exabel.api.data.v1B\x13HolidayServiceProtoP\x01Z\x16exabel.com/api/data/v1b\x06proto3') +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'exabel.api.data.v1.holiday_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\x16com.exabel.api.data.v1B\x13HolidayServiceProtoP\x01Z\x16exabel.com/api/data/v1' + _globals['_GETHOLIDAYSPECIFICATIONREQUEST'].fields_by_name['name']._loaded_options = None + _globals['_GETHOLIDAYSPECIFICATIONREQUEST'].fields_by_name['name']._serialized_options = b'\x92A;J\x1b"holidaySpecifications/123"\xca>\x1b\xfa\x02\x18holidaySpecificationName\xe0A\x02' + _globals['_CREATEHOLIDAYSPECIFICATIONREQUEST'].fields_by_name['holiday_specification']._loaded_options = None + _globals['_CREATEHOLIDAYSPECIFICATIONREQUEST'].fields_by_name['holiday_specification']._serialized_options = b'\xe0A\x02' + _globals['_UPDATEHOLIDAYSPECIFICATIONREQUEST'].fields_by_name['holiday_specification']._loaded_options = None + _globals['_UPDATEHOLIDAYSPECIFICATIONREQUEST'].fields_by_name['holiday_specification']._serialized_options = b'\xe0A\x02' + _globals['_DELETEHOLIDAYSPECIFICATIONREQUEST'].fields_by_name['name']._loaded_options = None + _globals['_DELETEHOLIDAYSPECIFICATIONREQUEST'].fields_by_name['name']._serialized_options = b'\x92A;J\x1b"holidaySpecifications/123"\xca>\x1b\xfa\x02\x18holidaySpecificationName\xe0A\x02' + _globals['_HOLIDAYSERVICE'].methods_by_name['ListHolidaySpecifications']._loaded_options = None + _globals['_HOLIDAYSERVICE'].methods_by_name['ListHolidaySpecifications']._serialized_options = b'\x92A\x1d\x12\x1bList holiday specifications\x82\xd3\xe4\x93\x02\x1b\x12\x19/v1/holidaySpecifications' + _globals['_HOLIDAYSERVICE'].methods_by_name['GetHolidaySpecification']._loaded_options = None + _globals['_HOLIDAYSERVICE'].methods_by_name['GetHolidaySpecification']._serialized_options = b'\x92A\x1b\x12\x19Get holiday specification\x82\xd3\xe4\x93\x02$\x12"/v1/{name=holidaySpecifications/*}' + _globals['_HOLIDAYSERVICE'].methods_by_name['CreateHolidaySpecification']._loaded_options = None + _globals['_HOLIDAYSERVICE'].methods_by_name['CreateHolidaySpecification']._serialized_options = b'\x92A\x1e\x12\x1cCreate holiday specification\x82\xd3\xe4\x93\x022"\x19/v1/holidaySpecifications:\x15holiday_specification' + _globals['_HOLIDAYSERVICE'].methods_by_name['UpdateHolidaySpecification']._loaded_options = None + _globals['_HOLIDAYSERVICE'].methods_by_name['UpdateHolidaySpecification']._serialized_options = b'\x92A\x1e\x12\x1cUpdate holiday specification\x82\xd3\xe4\x93\x02Q28/v1/{holiday_specification.name=holidaySpecifications/*}:\x15holiday_specification' + _globals['_HOLIDAYSERVICE'].methods_by_name['DeleteHolidaySpecification']._loaded_options = None + _globals['_HOLIDAYSERVICE'].methods_by_name['DeleteHolidaySpecification']._serialized_options = b'\x92A\x1e\x12\x1cDelete holiday specification\x82\xd3\xe4\x93\x02$*"/v1/{name=holidaySpecifications/*}' + _globals['_LISTHOLIDAYSPECIFICATIONSREQUEST']._serialized_start = 247 + _globals['_LISTHOLIDAYSPECIFICATIONSREQUEST']._serialized_end = 320 + _globals['_LISTHOLIDAYSPECIFICATIONSRESPONSE']._serialized_start = 323 + _globals['_LISTHOLIDAYSPECIFICATIONSRESPONSE']._serialized_end = 457 + _globals['_GETHOLIDAYSPECIFICATIONREQUEST']._serialized_start = 459 + _globals['_GETHOLIDAYSPECIFICATIONREQUEST']._serialized_end = 572 + _globals['_CREATEHOLIDAYSPECIFICATIONREQUEST']._serialized_start = 574 + _globals['_CREATEHOLIDAYSPECIFICATIONREQUEST']._serialized_end = 687 + _globals['_UPDATEHOLIDAYSPECIFICATIONREQUEST']._serialized_start = 689 + _globals['_UPDATEHOLIDAYSPECIFICATIONREQUEST']._serialized_end = 802 + _globals['_DELETEHOLIDAYSPECIFICATIONREQUEST']._serialized_start = 804 + _globals['_DELETEHOLIDAYSPECIFICATIONREQUEST']._serialized_end = 920 + _globals['_HOLIDAYSERVICE']._serialized_start = 923 + _globals['_HOLIDAYSERVICE']._serialized_end = 1997 \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2.pyi b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2.pyi new file mode 100644 index 00000000..511697e1 --- /dev/null +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2.pyi @@ -0,0 +1,124 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +Copyright (c) 2025 Exabel AS. All rights reserved.""" +import builtins +import collections.abc +from ..... import exabel +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.message +import typing +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class ListHolidaySpecificationsRequest(google.protobuf.message.Message): + """Request to list holiday specifications.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + PAGE_SIZE_FIELD_NUMBER: builtins.int + PAGE_TOKEN_FIELD_NUMBER: builtins.int + page_size: builtins.int + 'Maximum number of results to return. If not set, the server will pick an appropriate default.' + page_token: builtins.str + 'Page token for pagination.' + + def __init__(self, *, page_size: builtins.int | None=..., page_token: builtins.str | None=...) -> None: + ... + + def ClearField(self, field_name: typing.Literal['page_size', b'page_size', 'page_token', b'page_token']) -> None: + ... +global___ListHolidaySpecificationsRequest = ListHolidaySpecificationsRequest + +@typing.final +class ListHolidaySpecificationsResponse(google.protobuf.message.Message): + """Response from listing holiday specifications.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + HOLIDAY_SPECIFICATIONS_FIELD_NUMBER: builtins.int + NEXT_PAGE_TOKEN_FIELD_NUMBER: builtins.int + next_page_token: builtins.str + 'Token to retrieve the next page of results, or empty if there are no more results.' + + @property + def holiday_specifications(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[exabel.api.data.v1.holiday_messages_pb2.HolidaySpecification]: + """List of holiday specifications.""" + + def __init__(self, *, holiday_specifications: collections.abc.Iterable[exabel.api.data.v1.holiday_messages_pb2.HolidaySpecification] | None=..., next_page_token: builtins.str | None=...) -> None: + ... + + def ClearField(self, field_name: typing.Literal['holiday_specifications', b'holiday_specifications', 'next_page_token', b'next_page_token']) -> None: + ... +global___ListHolidaySpecificationsResponse = ListHolidaySpecificationsResponse + +@typing.final +class GetHolidaySpecificationRequest(google.protobuf.message.Message): + """Request to get a holiday specification.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + NAME_FIELD_NUMBER: builtins.int + name: builtins.str + 'Resource name of the holiday specification.\n Format: "holidaySpecifications/123"\n ' + + def __init__(self, *, name: builtins.str | None=...) -> None: + ... + + def ClearField(self, field_name: typing.Literal['name', b'name']) -> None: + ... +global___GetHolidaySpecificationRequest = GetHolidaySpecificationRequest + +@typing.final +class CreateHolidaySpecificationRequest(google.protobuf.message.Message): + """Request to create a holiday specification.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + HOLIDAY_SPECIFICATION_FIELD_NUMBER: builtins.int + + @property + def holiday_specification(self) -> exabel.api.data.v1.holiday_messages_pb2.HolidaySpecification: + """The holiday specification to create. + The name field will be ignored and a new resource name will be assigned. + """ + + def __init__(self, *, holiday_specification: exabel.api.data.v1.holiday_messages_pb2.HolidaySpecification | None=...) -> None: + ... + + def HasField(self, field_name: typing.Literal['holiday_specification', b'holiday_specification']) -> builtins.bool: + ... + + def ClearField(self, field_name: typing.Literal['holiday_specification', b'holiday_specification']) -> None: + ... +global___CreateHolidaySpecificationRequest = CreateHolidaySpecificationRequest + +@typing.final +class UpdateHolidaySpecificationRequest(google.protobuf.message.Message): + """Request to update a holiday specification.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + HOLIDAY_SPECIFICATION_FIELD_NUMBER: builtins.int + + @property + def holiday_specification(self) -> exabel.api.data.v1.holiday_messages_pb2.HolidaySpecification: + """The holiday specification to update. + The name field must be set and must match an existing holiday specification. + """ + + def __init__(self, *, holiday_specification: exabel.api.data.v1.holiday_messages_pb2.HolidaySpecification | None=...) -> None: + ... + + def HasField(self, field_name: typing.Literal['holiday_specification', b'holiday_specification']) -> builtins.bool: + ... + + def ClearField(self, field_name: typing.Literal['holiday_specification', b'holiday_specification']) -> None: + ... +global___UpdateHolidaySpecificationRequest = UpdateHolidaySpecificationRequest + +@typing.final +class DeleteHolidaySpecificationRequest(google.protobuf.message.Message): + """Request to delete a holiday specification.""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + NAME_FIELD_NUMBER: builtins.int + name: builtins.str + 'Resource name of the holiday specification to delete.\n Format: "holidaySpecifications/123"\n ' + + def __init__(self, *, name: builtins.str | None=...) -> None: + ... + + def ClearField(self, field_name: typing.Literal['name', b'name']) -> None: + ... +global___DeleteHolidaySpecificationRequest = DeleteHolidaySpecificationRequest \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2_grpc.py b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2_grpc.py new file mode 100644 index 00000000..01421781 --- /dev/null +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/holiday_service_pb2_grpc.py @@ -0,0 +1,113 @@ +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings +from .....exabel.api.data.v1 import holiday_messages_pb2 as exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2 +from .....exabel.api.data.v1 import holiday_service_pb2 as exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2 +from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 +GRPC_GENERATED_VERSION = '1.71.2' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True +if _version_not_supported: + raise RuntimeError(f'The grpc package installed is at version {GRPC_VERSION},' + f' but the generated code in exabel/api/data/v1/holiday_service_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.') + +class HolidayServiceStub(object): + """Service for managing custom holiday specifications for Prophet forecasting. + + Holiday specifications define sets of holidays with their properties (dates, windows, weights) + that can be used when forecasting with the Prophet model. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListHolidaySpecifications = channel.unary_unary('/exabel.api.data.v1.HolidayService/ListHolidaySpecifications', request_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.ListHolidaySpecificationsRequest.SerializeToString, response_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.ListHolidaySpecificationsResponse.FromString, _registered_method=True) + self.GetHolidaySpecification = channel.unary_unary('/exabel.api.data.v1.HolidayService/GetHolidaySpecification', request_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.GetHolidaySpecificationRequest.SerializeToString, response_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.FromString, _registered_method=True) + self.CreateHolidaySpecification = channel.unary_unary('/exabel.api.data.v1.HolidayService/CreateHolidaySpecification', request_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.CreateHolidaySpecificationRequest.SerializeToString, response_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.FromString, _registered_method=True) + self.UpdateHolidaySpecification = channel.unary_unary('/exabel.api.data.v1.HolidayService/UpdateHolidaySpecification', request_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.UpdateHolidaySpecificationRequest.SerializeToString, response_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.FromString, _registered_method=True) + self.DeleteHolidaySpecification = channel.unary_unary('/exabel.api.data.v1.HolidayService/DeleteHolidaySpecification', request_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.DeleteHolidaySpecificationRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) + +class HolidayServiceServicer(object): + """Service for managing custom holiday specifications for Prophet forecasting. + + Holiday specifications define sets of holidays with their properties (dates, windows, weights) + that can be used when forecasting with the Prophet model. + """ + + def ListHolidaySpecifications(self, request, context): + """Lists all holiday specifications. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetHolidaySpecification(self, request, context): + """Gets one holiday specification. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateHolidaySpecification(self, request, context): + """Creates a new holiday specification. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateHolidaySpecification(self, request, context): + """Updates an existing holiday specification. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteHolidaySpecification(self, request, context): + """Deletes a holiday specification. + + Note: Deleting a holiday specification that is referenced by KPI mapping groups + will cause forecasting to fail for those groups until the reference is removed or updated. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + +def add_HolidayServiceServicer_to_server(servicer, server): + rpc_method_handlers = {'ListHolidaySpecifications': grpc.unary_unary_rpc_method_handler(servicer.ListHolidaySpecifications, request_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.ListHolidaySpecificationsRequest.FromString, response_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.ListHolidaySpecificationsResponse.SerializeToString), 'GetHolidaySpecification': grpc.unary_unary_rpc_method_handler(servicer.GetHolidaySpecification, request_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.GetHolidaySpecificationRequest.FromString, response_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.SerializeToString), 'CreateHolidaySpecification': grpc.unary_unary_rpc_method_handler(servicer.CreateHolidaySpecification, request_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.CreateHolidaySpecificationRequest.FromString, response_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.SerializeToString), 'UpdateHolidaySpecification': grpc.unary_unary_rpc_method_handler(servicer.UpdateHolidaySpecification, request_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.UpdateHolidaySpecificationRequest.FromString, response_serializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.SerializeToString), 'DeleteHolidaySpecification': grpc.unary_unary_rpc_method_handler(servicer.DeleteHolidaySpecification, request_deserializer=exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.DeleteHolidaySpecificationRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString)} + generic_handler = grpc.method_handlers_generic_handler('exabel.api.data.v1.HolidayService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('exabel.api.data.v1.HolidayService', rpc_method_handlers) + +class HolidayService(object): + """Service for managing custom holiday specifications for Prophet forecasting. + + Holiday specifications define sets of holidays with their properties (dates, windows, weights) + that can be used when forecasting with the Prophet model. + """ + + @staticmethod + def ListHolidaySpecifications(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): + return grpc.experimental.unary_unary(request, target, '/exabel.api.data.v1.HolidayService/ListHolidaySpecifications', exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.ListHolidaySpecificationsRequest.SerializeToString, exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.ListHolidaySpecificationsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata, _registered_method=True) + + @staticmethod + def GetHolidaySpecification(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): + return grpc.experimental.unary_unary(request, target, '/exabel.api.data.v1.HolidayService/GetHolidaySpecification', exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.GetHolidaySpecificationRequest.SerializeToString, exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata, _registered_method=True) + + @staticmethod + def CreateHolidaySpecification(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): + return grpc.experimental.unary_unary(request, target, '/exabel.api.data.v1.HolidayService/CreateHolidaySpecification', exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.CreateHolidaySpecificationRequest.SerializeToString, exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata, _registered_method=True) + + @staticmethod + def UpdateHolidaySpecification(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): + return grpc.experimental.unary_unary(request, target, '/exabel.api.data.v1.HolidayService/UpdateHolidaySpecification', exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.UpdateHolidaySpecificationRequest.SerializeToString, exabel_dot_api_dot_data_dot_v1_dot_holiday__messages__pb2.HolidaySpecification.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata, _registered_method=True) + + @staticmethod + def DeleteHolidaySpecification(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): + return grpc.experimental.unary_unary(request, target, '/exabel.api.data.v1.HolidayService/DeleteHolidaySpecification', exabel_dot_api_dot_data_dot_v1_dot_holiday__service__pb2.DeleteHolidaySpecificationRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata, _registered_method=True) \ No newline at end of file diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/relationship_service_pb2_grpc.py b/exabel_data_sdk/stubs/exabel/api/data/v1/relationship_service_pb2_grpc.py index db0d04cf..b44de63a 100644 --- a/exabel_data_sdk/stubs/exabel/api/data/v1/relationship_service_pb2_grpc.py +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/relationship_service_pb2_grpc.py @@ -47,8 +47,8 @@ class RelationshipServiceServicer(object): def ListRelationshipTypes(self, request, context): """List all relationship types from a common catalog. - Lists all relationship types available to your customer, including those created by you, in - the global catalog, and from data sets you are subscribed to. + Lists all relationship types available to your customer, including those created by you, and in + the global catalog. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/signal_service_pb2_grpc.py b/exabel_data_sdk/stubs/exabel/api/data/v1/signal_service_pb2_grpc.py index 7562a30b..62d39423 100644 --- a/exabel_data_sdk/stubs/exabel/api/data/v1/signal_service_pb2_grpc.py +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/signal_service_pb2_grpc.py @@ -43,8 +43,8 @@ class SignalServiceServicer(object): def ListSignals(self, request, context): """Lists all known signals. - Lists all raw data signals available to your customer, including those created by you, in the - global catalog, and from data sets you are subscribed to. + Lists all raw data signals available to your customer, including those created by you, and in + the global catalog. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') diff --git a/exabel_data_sdk/stubs/exabel/api/data/v1/time_series_service_pb2.pyi b/exabel_data_sdk/stubs/exabel/api/data/v1/time_series_service_pb2.pyi index 1789d010..c1ecace9 100644 --- a/exabel_data_sdk/stubs/exabel/api/data/v1/time_series_service_pb2.pyi +++ b/exabel_data_sdk/stubs/exabel/api/data/v1/time_series_service_pb2.pyi @@ -230,7 +230,7 @@ class UpdateTimeSeriesRequest(google.protobuf.message.Message): @property def update_options(self) -> global___UpdateOptions: - """Update uptions for this request.""" + """Update options for this request.""" @property def default_known_time(self) -> exabel.api.data.v1.time_series_messages_pb2.DefaultKnownTime: @@ -291,7 +291,7 @@ class ImportTimeSeriesRequest(google.protobuf.message.Message): @property def update_options(self) -> global___UpdateOptions: - """Update uptions for this request.""" + """Update options for this request.""" @property def default_known_time(self) -> exabel.api.data.v1.time_series_messages_pb2.DefaultKnownTime: diff --git a/exabel_data_sdk/stubs/exabel/api/management/v1/library_service_pb2.pyi b/exabel_data_sdk/stubs/exabel/api/management/v1/library_service_pb2.pyi index 9240f14f..58431350 100644 --- a/exabel_data_sdk/stubs/exabel/api/management/v1/library_service_pb2.pyi +++ b/exabel_data_sdk/stubs/exabel/api/management/v1/library_service_pb2.pyi @@ -44,6 +44,7 @@ global___ListFoldersResponse = ListFoldersResponse @typing.final class GetFolderRequest(google.protobuf.message.Message): + """Request to GetFolder.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor NAME_FIELD_NUMBER: builtins.int name: builtins.str @@ -58,6 +59,7 @@ global___GetFolderRequest = GetFolderRequest @typing.final class CreateFolderRequest(google.protobuf.message.Message): + """Request to CreateFolder.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor FOLDER_FIELD_NUMBER: builtins.int @@ -77,6 +79,7 @@ global___CreateFolderRequest = CreateFolderRequest @typing.final class UpdateFolderRequest(google.protobuf.message.Message): + """Request to UpdateFolder.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor FOLDER_FIELD_NUMBER: builtins.int UPDATE_MASK_FIELD_NUMBER: builtins.int @@ -111,6 +114,7 @@ global___UpdateFolderRequest = UpdateFolderRequest @typing.final class DeleteFolderRequest(google.protobuf.message.Message): + """Request to DeleteFolder.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor NAME_FIELD_NUMBER: builtins.int name: builtins.str @@ -125,6 +129,7 @@ global___DeleteFolderRequest = DeleteFolderRequest @typing.final class ListItemsRequest(google.protobuf.message.Message): + """Request to ListItems.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor PARENT_FIELD_NUMBER: builtins.int ITEM_TYPE_FIELD_NUMBER: builtins.int @@ -142,6 +147,7 @@ global___ListItemsRequest = ListItemsRequest @typing.final class ListItemsResponse(google.protobuf.message.Message): + """Response from ListItems.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor ITEMS_FIELD_NUMBER: builtins.int @@ -158,6 +164,7 @@ global___ListItemsResponse = ListItemsResponse @typing.final class MoveItemsRequest(google.protobuf.message.Message): + """Request to MoveItems.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor ITEMS_FIELD_NUMBER: builtins.int TARGET_FOLDER_FIELD_NUMBER: builtins.int @@ -177,6 +184,7 @@ global___MoveItemsRequest = MoveItemsRequest @typing.final class MoveItemsResponse(google.protobuf.message.Message): + """Response from MoveItems.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor def __init__(self) -> None: @@ -185,6 +193,7 @@ global___MoveItemsResponse = MoveItemsResponse @typing.final class ListFolderAccessorsRequest(google.protobuf.message.Message): + """Request to ListFolderAccessors.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor NAME_FIELD_NUMBER: builtins.int name: builtins.str @@ -199,6 +208,7 @@ global___ListFolderAccessorsRequest = ListFolderAccessorsRequest @typing.final class ListFolderAccessorsResponse(google.protobuf.message.Message): + """Response from ListFolderAccessors.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor FOLDER_ACCESSORS_FIELD_NUMBER: builtins.int @@ -217,6 +227,7 @@ global___ListFolderAccessorsResponse = ListFolderAccessorsResponse @typing.final class ShareFolderRequest(google.protobuf.message.Message): + """Request to ShareFolder.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor FOLDER_FIELD_NUMBER: builtins.int GROUP_FIELD_NUMBER: builtins.int @@ -237,6 +248,7 @@ global___ShareFolderRequest = ShareFolderRequest @typing.final class UnshareFolderRequest(google.protobuf.message.Message): + """Request to UnshareFolder.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor FOLDER_FIELD_NUMBER: builtins.int GROUP_FIELD_NUMBER: builtins.int @@ -254,6 +266,7 @@ global___UnshareFolderRequest = UnshareFolderRequest @typing.final class SearchItemsRequest(google.protobuf.message.Message): + """Request to SearchItems.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor FOLDER_FIELD_NUMBER: builtins.int QUERY_FIELD_NUMBER: builtins.int @@ -280,6 +293,7 @@ global___SearchItemsRequest = SearchItemsRequest @typing.final class SearchItemsResponse(google.protobuf.message.Message): + """Response from SearchItems.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor RESULTS_FIELD_NUMBER: builtins.int NEXT_PAGE_TOKEN_FIELD_NUMBER: builtins.int diff --git a/exabel_data_sdk/tests/client/api/data_classes/test_time_series.py b/exabel_data_sdk/tests/client/api/data_classes/test_time_series.py index 7c8f380d..50a2c69e 100644 --- a/exabel_data_sdk/tests/client/api/data_classes/test_time_series.py +++ b/exabel_data_sdk/tests/client/api/data_classes/test_time_series.py @@ -115,7 +115,7 @@ def test_proto_conversion__no_known_time(self): def test_time_series_name(self): time_series = TimeSeries( - series=pd.Series([]), + series=pd.Series([], dtype=float), ) time_series.name = "entityTypes/country/entities/no/signals/customerA.revenue" self.assertEqual( diff --git a/exabel_data_sdk/tests/client/api/mock_entity_api.py b/exabel_data_sdk/tests/client/api/mock_entity_api.py index e2d5892f..914bbfac 100644 --- a/exabel_data_sdk/tests/client/api/mock_entity_api.py +++ b/exabel_data_sdk/tests/client/api/mock_entity_api.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence +from typing import Sequence from unittest import mock from google.protobuf.field_mask_pb2 import FieldMask @@ -11,7 +11,6 @@ from exabel_data_sdk.tests.client.api.mock_resource_store import MockResourceStore -# pylint: disable=super-init-not-called class MockEntityApi(EntityApi): """ Mock of the EntityApi class for CRUD operations on entities and entity types. @@ -28,29 +27,29 @@ def _insert_standard_entity_types(self): self.types.create(EntityType("entityTypes/" + entity_type, entity_type, "")) def list_entity_types( - self, page_size: int = 1000, page_token: Optional[str] = None + self, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[EntityType]: return self.types.list() - def get_entity_type(self, name: str) -> Optional[EntityType]: + def get_entity_type(self, name: str) -> EntityType | None: return self.types.get(name) def create_entity_type(self, entity_type: EntityType) -> EntityType: return self.types.create(entity_type) def list_entities( - self, entity_type: str, page_size: int = 1000, page_token: Optional[str] = None + self, entity_type: str, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[Entity]: return self.entities.list(lambda x: x.get_entity_type() == entity_type) - def get_entity(self, name: str) -> Optional[Entity]: + def get_entity(self, name: str) -> Entity | None: return self.entities.get(name) def create_entity(self, entity: Entity, entity_type: str) -> Entity: return self.entities.create(entity) def update_entity( - self, entity: Entity, update_mask: Optional[FieldMask] = None, allow_missing: bool = False + self, entity: Entity, update_mask: FieldMask | None = None, allow_missing: bool = False ) -> Entity: # Note: The mock implementation ignores update_mask return self.entities.update(entity, allow_missing=allow_missing) diff --git a/exabel_data_sdk/tests/client/api/mock_relationship_api.py b/exabel_data_sdk/tests/client/api/mock_relationship_api.py index 3af6e0a7..031ab14d 100644 --- a/exabel_data_sdk/tests/client/api/mock_relationship_api.py +++ b/exabel_data_sdk/tests/client/api/mock_relationship_api.py @@ -1,5 +1,3 @@ -from typing import Optional - from google.protobuf.field_mask_pb2 import FieldMask from exabel_data_sdk.client.api.data_classes.paging_result import PagingResult @@ -9,7 +7,6 @@ from exabel_data_sdk.tests.client.api.mock_resource_store import MockResourceStore -# pylint: disable=super-init-not-called class MockRelationshipApi(RelationshipApi): """ Mock of the RelationshipApi class for CRUD operations on relationships and relationship types. @@ -29,11 +26,11 @@ def _key(relationship: Relationship) -> object: return (relationship.relationship_type, relationship.from_entity, relationship.to_entity) def list_relationship_types( - self, page_size: int = 1000, page_token: Optional[str] = None + self, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[RelationshipType]: return self.types.list() - def get_relationship_type(self, name: str) -> Optional[RelationshipType]: + def get_relationship_type(self, name: str) -> RelationshipType | None: return self.types.get(name) def create_relationship_type(self, relationship_type: RelationshipType) -> RelationshipType: @@ -42,8 +39,8 @@ def create_relationship_type(self, relationship_type: RelationshipType) -> Relat def update_relationship_type( self, relationship_type: RelationshipType, - update_mask: Optional[FieldMask] = None, - allow_missing: Optional[bool] = None, + update_mask: FieldMask | None = None, + allow_missing: bool | None = None, ) -> RelationshipType: raise NotImplementedError() @@ -55,7 +52,7 @@ def get_relationships_from_entity( relationship_type: str, from_entity: str, page_size: int = 1000, - page_token: Optional[str] = None, + page_token: str | None = None, ) -> PagingResult[Relationship]: # Note: This implementation ignores page_size and page_token return self.relationships.list( @@ -69,7 +66,7 @@ def get_relationships_to_entity( relationship_type: str, to_entity: str, page_size: int = 1000, - page_token: Optional[str] = None, + page_token: str | None = None, ) -> PagingResult[Relationship]: # Note: This implementation ignores page_size and page_token return self.relationships.list( @@ -80,7 +77,7 @@ def get_relationships_to_entity( def get_relationship( self, relationship_type: str, from_entity: str, to_entity: str - ) -> Optional[Relationship]: + ) -> Relationship | None: return self.relationships.get((relationship_type, from_entity, to_entity)) def create_relationship(self, relationship: Relationship) -> Relationship: @@ -89,8 +86,8 @@ def create_relationship(self, relationship: Relationship) -> Relationship: def update_relationship( self, relationship: Relationship, - update_mask: Optional[FieldMask] = None, - allow_missing: Optional[bool] = None, + update_mask: FieldMask | None = None, + allow_missing: bool | None = None, ) -> Relationship: # Note: The mock implementation ignores update_mask return self.relationships.update(relationship, self._key(relationship), allow_missing) @@ -102,7 +99,7 @@ def list_relationships( self, relationship_type: str, page_size: int = 1000, - page_token: Optional[str] = None, + page_token: str | None = None, ) -> PagingResult[Relationship]: # Note: This implementation ignores page_size and page_token return self.relationships.list(predicate=lambda r: r.relationship_type == relationship_type) diff --git a/exabel_data_sdk/tests/client/api/mock_resource_store.py b/exabel_data_sdk/tests/client/api/mock_resource_store.py index 325aec64..20b5988f 100644 --- a/exabel_data_sdk/tests/client/api/mock_resource_store.py +++ b/exabel_data_sdk/tests/client/api/mock_resource_store.py @@ -1,5 +1,5 @@ from random import random -from typing import Callable, Dict, Generic, Optional, TypeVar +from typing import Callable, Generic, TypeVar from exabel_data_sdk.client.api.data_classes.paging_result import PagingResult from exabel_data_sdk.client.api.data_classes.request_error import ErrorType, RequestError @@ -25,17 +25,15 @@ class MockResourceStore(Generic[ResourceT]): """In-memory resource store. Only intended for tests.""" def __init__(self): - self.resources: Dict[object, ResourceT] = {} + self.resources: dict[object, ResourceT] = {} # The failure rate, as a fraction (0.0-1.0) of calls that should fail self.failure_rate = 0.0 - def get(self, key: object) -> Optional[ResourceT]: + def get(self, key: object) -> ResourceT | None: """Get the resource with the given key if present, otherwise returns None.""" return self.resources.get(key, None) - def list( - self, predicate: Optional[Callable[[ResourceT], bool]] = None - ) -> PagingResult[ResourceT]: + def list(self, predicate: Callable[[ResourceT], bool] | None = None) -> PagingResult[ResourceT]: """List all resources in the store.""" resources = list(self.resources.values()) if predicate: @@ -47,7 +45,7 @@ def list( ) @failure_prone - def create(self, resource: ResourceT, key: Optional[object] = None) -> ResourceT: + def create(self, resource: ResourceT, key: object | None = None) -> ResourceT: """ Create the given resource in the store. @@ -70,8 +68,8 @@ def create(self, resource: ResourceT, key: Optional[object] = None) -> ResourceT def update( self, resource: ResourceT, - key: Optional[object] = None, - allow_missing: Optional[bool] = None, + key: object | None = None, + allow_missing: bool | None = None, ) -> ResourceT: """ Update the given resource in the store. diff --git a/exabel_data_sdk/tests/client/api/mock_signal_api.py b/exabel_data_sdk/tests/client/api/mock_signal_api.py index 94584f0d..3f921112 100644 --- a/exabel_data_sdk/tests/client/api/mock_signal_api.py +++ b/exabel_data_sdk/tests/client/api/mock_signal_api.py @@ -1,5 +1,3 @@ -from typing import Optional - from google.protobuf.field_mask_pb2 import FieldMask from exabel_data_sdk.client.api.data_classes.paging_result import PagingResult @@ -8,7 +6,6 @@ from exabel_data_sdk.tests.client.api.mock_resource_store import MockResourceStore -# pylint: disable=super-init-not-called class MockSignalApi(SignalApi): """ Mock of the EntityApi class for CRUD operations on entities and entity types. @@ -19,11 +16,11 @@ def __init__(self): self.created_library_signals = [] def list_signals( - self, page_size: int = 1000, page_token: Optional[str] = None + self, page_size: int = 1000, page_token: str | None = None ) -> PagingResult[Signal]: return self.signals.list() - def get_signal(self, name: str) -> Optional[Signal]: + def get_signal(self, name: str) -> Signal | None: return self.signals.get(name) def create_signal(self, signal: Signal, create_library_signal: bool = False) -> Signal: @@ -34,7 +31,7 @@ def create_signal(self, signal: Signal, create_library_signal: bool = False) -> def update_signal( self, signal: Signal, - update_mask: Optional[FieldMask] = None, + update_mask: FieldMask | None = None, allow_missing: bool = False, create_library_signal: bool = False, ) -> Signal: diff --git a/exabel_data_sdk/tests/client/api/test_bulk_insert_import.py b/exabel_data_sdk/tests/client/api/test_bulk_insert_import.py index a26d244a..69d6dc23 100644 --- a/exabel_data_sdk/tests/client/api/test_bulk_insert_import.py +++ b/exabel_data_sdk/tests/client/api/test_bulk_insert_import.py @@ -20,8 +20,6 @@ logger = logging.getLogger(__name__) -# pylint: disable=protected-access - def _get_ts_resources_map() -> Mapping[str, pd.Series]: resource_map = { diff --git a/exabel_data_sdk/tests/client/api/test_export_api.py b/exabel_data_sdk/tests/client/api/test_export_api.py index 3607cfb0..ccbd1de5 100644 --- a/exabel_data_sdk/tests/client/api/test_export_api.py +++ b/exabel_data_sdk/tests/client/api/test_export_api.py @@ -1,17 +1,18 @@ import re import unittest -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock import pandas as pd from exabel_data_sdk.client.api.export_api import ExportApi +from exabel_data_sdk.client.client_config import ClientConfig from exabel_data_sdk.query.column import Column from exabel_data_sdk.query.signals import Signals class TestExportApi(unittest.TestCase): def test_signal_query(self): - export_api = ExportApi(auth_headers={}) + export_api = ExportApi(ClientConfig(api_key="api-key")) mock = MagicMock(name="run_query") export_api.run_query = mock @@ -104,7 +105,7 @@ def side_effect(query: str): self.assertLessEqual(length, batch_size) return data.query(f"name in {names}") - export_api = ExportApi(auth_headers={}) + export_api = ExportApi(ClientConfig(api_key="api-key")) export_api.run_query = MagicMock(name="run_query", side_effect=side_effect) for batch_size in range(1, 12): result = export_api.batched_signal_query( @@ -113,7 +114,7 @@ def side_effect(query: str): pd.testing.assert_series_equal(series, result) def test_batched_signal_query_error(self): - export_api = ExportApi(auth_headers={}) + export_api = ExportApi(ClientConfig(api_key="api-key")) self.assertRaisesRegex( ValueError, "Need to specify an identification method", @@ -130,25 +131,3 @@ def test_batched_signal_query_error(self): bloomberg_ticker=["A", "B"], factset_id=["C", "D"], ) - - @patch("exabel_data_sdk.client.api.export_api.ExportApi") - def test_from_api_key(self, mock_api): - mock_api.return_value = None - api_key = "api-key" - ExportApi.from_api_key(api_key=api_key) - mock_api.assert_called_with( - auth_headers={"x-api-key": api_key}, backend="export.api.exabel.com", retries=0 - ) - ExportApi.from_api_key(api_key=api_key, use_test_backend=True, retries=3) - mock_api.assert_called_with( - auth_headers={"x-api-key": api_key}, backend="export.api-test.exabel.com", retries=3 - ) - - def test_retries(self): - # pylint: disable=protected-access - export_api = ExportApi(auth_headers={}) - adapter = export_api._session.get_adapter("https://export.api.exabel.com") - self.assertEqual(0, adapter.max_retries.total) - export_api = ExportApi(auth_headers={}, retries=3) - adapter = export_api._session.get_adapter("https://export.api.exabel.com") - self.assertEqual(3, adapter.max_retries.total) diff --git a/exabel_data_sdk/tests/client/api/test_pageable_resource.py b/exabel_data_sdk/tests/client/api/test_pageable_resource.py index 078d814b..ee616015 100644 --- a/exabel_data_sdk/tests/client/api/test_pageable_resource.py +++ b/exabel_data_sdk/tests/client/api/test_pageable_resource.py @@ -1,12 +1,12 @@ import unittest -from typing import Iterator, Optional, Sequence +from typing import Iterator, Sequence from exabel_data_sdk.client.api.data_classes.paging_result import PagingResult from exabel_data_sdk.client.api.pageable_resource import PageableResourceMixin class _PageableApiMock(PageableResourceMixin): - def __init__(self, resources: Sequence[str], total_sizes: Optional[Sequence[int]] = None): + def __init__(self, resources: Sequence[str], total_sizes: Sequence[int] | None = None): self.resources = resources if total_sizes is not None: self.total_sizes = total_sizes @@ -14,7 +14,7 @@ def __init__(self, resources: Sequence[str], total_sizes: Optional[Sequence[int] self.total_sizes = [len(resources)] * len(resources) def get_resource_page( - self, page_size: Optional[int] = None, page_token: Optional[str] = None + self, page_size: int | None = None, page_token: str | None = None ) -> PagingResult[str]: """Return a page of resources.""" assert page_size == 1 diff --git a/exabel_data_sdk/tests/client/api/test_resource_creation_result.py b/exabel_data_sdk/tests/client/api/test_resource_creation_result.py index 84c1ea15..c7b23403 100644 --- a/exabel_data_sdk/tests/client/api/test_resource_creation_result.py +++ b/exabel_data_sdk/tests/client/api/test_resource_creation_result.py @@ -12,8 +12,6 @@ get_resource_name, ) -# pylint: disable=protected-access - class TestResourceCreationResult(unittest.TestCase): def test_get_resource_name(self): diff --git a/exabel_data_sdk/tests/client/api/test_time_series_api.py b/exabel_data_sdk/tests/client/api/test_time_series_api.py index cc251b6b..c9b71bce 100644 --- a/exabel_data_sdk/tests/client/api/test_time_series_api.py +++ b/exabel_data_sdk/tests/client/api/test_time_series_api.py @@ -10,13 +10,13 @@ from exabel_data_sdk.client.api.data_classes.time_series import TimeSeries from exabel_data_sdk.client.api.time_series_api import TimeSeriesApi from exabel_data_sdk.client.client_config import ClientConfig -from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import ImportTimeSeriesRequest +from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import ( + ImportTimeSeriesRequest, + TimeSeriesPoint, +) from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import TimeSeries as ProtoTimeSeries -from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import TimeSeriesPoint from exabel_data_sdk.util.import_ import estimate_size, get_batches_for_import -# pylint: disable=protected-access - class TestTimeSeriesApi(unittest.TestCase): def test_time_series_conversion(self): diff --git a/exabel_data_sdk/tests/client/exabel_mock_client.py b/exabel_data_sdk/tests/client/exabel_mock_client.py index 64bb2de0..e8828850 100644 --- a/exabel_data_sdk/tests/client/exabel_mock_client.py +++ b/exabel_data_sdk/tests/client/exabel_mock_client.py @@ -13,7 +13,7 @@ class ExabelMockClient(ExabelClient): which only store objects in memory. """ - def __init__(self, namespace: str = "test"): # pylint: disable=super-init-not-called + def __init__(self, namespace: str = "test"): self.entity_api = MockEntityApi() self.relationship_api = MockRelationshipApi() self.signal_api = MockSignalApi() diff --git a/exabel_data_sdk/tests/client/test_user_login.py b/exabel_data_sdk/tests/client/test_user_login.py deleted file mode 100644 index b127beb2..00000000 --- a/exabel_data_sdk/tests/client/test_user_login.py +++ /dev/null @@ -1,111 +0,0 @@ -import json -import unittest -from unittest import mock - -from exabel_data_sdk.client.user_login import RefreshTokens, UserLogin - - -class TestUserLogin(unittest.TestCase): - def test_reauthenticate(self): - user_login = UserLogin(reauthenticate=True) - self.assertTrue(user_login.reauthenticate) - user_login.get_access_token() - self.assertFalse(user_login.reauthenticate) - - @mock.patch("os.path.isfile", return_value=True) - def test_convert_old_refresh_token_file(self, _): - user_login = UserLogin() - with mock.patch("builtins.open", mock.mock_open(read_data="token")) as mock_open: - user_login.read_refresh_token() - self.assertEqual( - user_login.tokens, - RefreshTokens({"endpoints.exabel.com": {"__default__": "token"}}), - ) - write_arg = mock_open.return_value.__enter__.return_value.write.call_args[0][0] - self.assertEqual( - {"endpoints.exabel.com": {"__default__": "token"}}, - json.loads(write_arg), - ) - - @mock.patch("os.path.isfile", return_value=True) - def test_refresh_token(self, _): - user_login = UserLogin(user="user") - token_file_content = json.dumps({"endpoints.exabel.com": {"user": "token"}}) - with mock.patch("builtins.open", mock.mock_open(read_data=token_file_content)): - user_login.read_refresh_token() - self.assertEqual( - user_login.refresh_token, - "token", - ) - - @mock.patch("os.path.isfile", return_value=True) - def test_refresh_token__default_user(self, _): - user_login = UserLogin() - token_file_content = json.dumps({"endpoints.exabel.com": {"__default__": "token"}}) - with mock.patch("builtins.open", mock.mock_open(read_data=token_file_content)): - user_login.read_refresh_token() - self.assertEqual( - user_login.refresh_token, - "token", - ) - - -class TestRefreshTokens(unittest.TestCase): - def test_refresh_token_file(self): - host_tokens = {"host1": {"user1": "token1"}, "host2": {"user2": "token2"}} - tokens = RefreshTokens(host_tokens) - self.assertEqual("token1", tokens.get_refresh_token("host1", "user1")) - self.assertEqual("token2", tokens.get_refresh_token("host2", "user2")) - self.assertEqual("", tokens.get_refresh_token("unknown_host", "anything")) - self.assertEqual("", tokens.get_refresh_token("host1", "unknown_user")) - - def test_invalid_host_tokens_should_fail(self): - with self.assertRaises(ValueError): - RefreshTokens([]) - with self.assertRaises(ValueError): - RefreshTokens({"host": "token"}) - with self.assertRaises(ValueError): - RefreshTokens({"host": {"user": 1}}) - with self.assertRaises(ValueError): - RefreshTokens({"host": []}) - - def test_merge_with(self): - host_tokens = {"host1": {"user1": "token1"}, "host2": {"user2": "token2"}} - tokens = RefreshTokens(host_tokens) - merged_tokens = tokens.merge_with( - RefreshTokens({"host1": {"user3": "token3"}, "host3": {"user4": "token4"}}) - ) - self.assertEqual("token1", merged_tokens.get_refresh_token("host1", "user1")) - self.assertEqual("token3", merged_tokens.get_refresh_token("host1", "user3")) - self.assertEqual("token2", merged_tokens.get_refresh_token("host2", "user2")) - self.assertEqual("token4", merged_tokens.get_refresh_token("host3", "user4")) - - def test_merge_with__overwrites(self): - host_tokens = {"host": {"user": "token"}} - tokens = RefreshTokens(host_tokens) - merged_token_file = tokens.merge_with(RefreshTokens({"host": {"user": "new_token"}})) - self.assertEqual("new_token", merged_token_file.get_refresh_token("host", "user")) - - def test_from_token(self): - tokens = RefreshTokens.from_host_user_token("host", "user", "token") - self.assertEqual("token", tokens.get_refresh_token("host", "user")) - - def test_from_host_and_user(self): - tokens = RefreshTokens.from_host_user("host", "user") - self.assertEqual("", tokens.get_refresh_token("host", "user")) - - def test_to_json(self): - host_tokens = {"host1": {"user1": "token1"}, "host2": {"user2": "token2"}} - tokens = RefreshTokens(host_tokens) - self.assertEqual( - json.dumps(host_tokens, indent=4), - tokens.to_json(), - ) - - def test_from_json(self): - host_tokens = {"host1": {"user1": "token1"}, "host2": {"user2": "token2"}} - tokens = RefreshTokens(host_tokens) - self.assertEqual( - tokens, - RefreshTokens.from_json(tokens.to_json()), - ) diff --git a/exabel_data_sdk/tests/decorators.py b/exabel_data_sdk/tests/decorators.py index 848e32ae..4bd79323 100644 --- a/exabel_data_sdk/tests/decorators.py +++ b/exabel_data_sdk/tests/decorators.py @@ -1,18 +1,18 @@ import importlib.util import unittest -from typing import Callable, Type, TypeVar +from typing import Callable, TypeVar T = TypeVar("T") -def requires_modules(*modules: str) -> Callable[..., Type[T]]: +def requires_modules(*modules: str) -> Callable[..., type[T]]: """ Decorator for a class containing tests that require optional dependencies. If the modules are not importable, the tests are skipped. """ - def wrapper(original_class: Type[T]) -> Type[T]: + def wrapper(original_class: type[T]) -> type[T]: not_importable = [] for module in modules: if not _is_importable(module): diff --git a/exabel_data_sdk/tests/scripts/common_utils.py b/exabel_data_sdk/tests/scripts/common_utils.py index 6a54bfae..eb5a0156 100644 --- a/exabel_data_sdk/tests/scripts/common_utils.py +++ b/exabel_data_sdk/tests/scripts/common_utils.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence, Type +from typing import Sequence from exabel_data_sdk.client.exabel_client import ExabelClient from exabel_data_sdk.scripts.csv_script import CsvScript @@ -6,9 +6,9 @@ def load_test_data_from_csv( - csv_script: Type[CsvScript], + csv_script: type[CsvScript], args: Sequence[str], - client: Optional[ExabelClient] = None, + client: ExabelClient | None = None, namespace: str = "test", ) -> ExabelClient: """Loads resources to an ExabelMockClient using CsvScript""" diff --git a/exabel_data_sdk/tests/scripts/test_create_entity_mapping_from_csv.py b/exabel_data_sdk/tests/scripts/test_create_entity_mapping_from_csv.py index d0f3513a..531f898a 100644 --- a/exabel_data_sdk/tests/scripts/test_create_entity_mapping_from_csv.py +++ b/exabel_data_sdk/tests/scripts/test_create_entity_mapping_from_csv.py @@ -11,7 +11,7 @@ class TestCreateEntityMappingFromCsv(unittest.TestCase): def setUp(self): self.client = mock.create_autospec(ExabelClient) self.client.entity_api = mock.create_autospec(EntityApi) - self.temp_file = tempfile.NamedTemporaryFile() # pylint: disable=consider-using-with + self.temp_file = tempfile.NamedTemporaryFile() def tearDown(self): self.temp_file.close() diff --git a/exabel_data_sdk/tests/scripts/test_export_data.py b/exabel_data_sdk/tests/scripts/test_export_data.py index 4de348d9..4bdbeea2 100644 --- a/exabel_data_sdk/tests/scripts/test_export_data.py +++ b/exabel_data_sdk/tests/scripts/test_export_data.py @@ -1,6 +1,5 @@ import argparse import unittest -from unittest import mock from exabel_data_sdk.scripts.export_data import ExportData @@ -15,72 +14,37 @@ def setUp(self): "filename", "--format", "format", - "--use-test-backend", + "--retries", + "2", ] def _assert_common_args(self, args: argparse.Namespace): self.assertEqual(args.query, "query") self.assertEqual(args.filename, "filename") self.assertEqual(args.format, "format") - self.assertTrue(args.use_test_backend) + self.assertEqual(args.retries, 2) def test_args__api_key(self): - script = ExportData(self.common_args + ["--api-key", "api-key"]) + script = ExportData(self.common_args + ["--api-key", "api-key"], "Export") args = script.parse_arguments() self._assert_common_args(args) self.assertEqual(args.api_key, "api-key") def test_args__access_token(self): - script = ExportData(self.common_args + ["--access-token", "access-token"]) + script = ExportData(self.common_args + ["--access-token", "access-token"], "Export") args = script.parse_arguments() self._assert_common_args(args) self.assertEqual(args.access_token, "access-token") - def test_args__reauthenticate(self): - script = ExportData(self.common_args + ["--reauthenticate"]) - args = script.parse_arguments() - self._assert_common_args(args) - self.assertTrue(args.reauthenticate) - - def test_args__defaults(self): - script = ExportData(self.common_args) - args = script.parse_arguments() - self._assert_common_args(args) - self.assertFalse(args.reauthenticate) - - def test_description_optional(self): - # To maintain backwards compatibility - script = ExportData(self.common_args) - self.assertIsNotNone(script.parser.description) - - def test_description(self): + def test_args__neither_api_key_nor_access_token_should_fail(self): script = ExportData(self.common_args, "Export") - self.assertEqual(script.parser.description, "Export") - - def test_args__both_api_key_and_reauthenticate_should_fail(self): - script = ExportData(self.common_args + ["--api-key", "api-key", "--reauthenticate"]) with self.assertRaises(SystemExit): script.parse_arguments() def test_args__both_api_key_and_access_token_should_fail(self): script = ExportData( - self.common_args + ["--api-key", "api-key", "--access-token", "access-token"] + self.common_args + ["--api-key", "api-key", "--access-token", "access-token"], + "Export", ) with self.assertRaises(SystemExit): script.parse_arguments() - - @mock.patch("exabel_data_sdk.client.api.export_api.ExportApi.from_api_key") - def test_get_export_api_from_api_key(self, from_api_key_mock): - """Factory method ExportApi.use_api_key should be called is api-key not None.""" - ExportData.get_export_api(api_key="api-key", retries=2) - from_api_key_mock.assert_called_with("api-key", False, retries=2) - ExportData.get_export_api(api_key="api-key", use_test_backend=True) - from_api_key_mock.assert_called_with("api-key", True, retries=0) - - @mock.patch("exabel_data_sdk.client.api.export_api.ExportApi.from_access_token") - def test_get_export_api_from_access_token(self, from_access_token_mock): - """Factory method ExportApi.use_api_key should be called is api-key not None.""" - ExportData.get_export_api(access_token="access-token", retries=2) - from_access_token_mock.assert_called_with("access-token", False, retries=2) - ExportData.get_export_api(access_token="access-token", use_test_backend=True) - from_access_token_mock.assert_called_with("access-token", True, retries=0) diff --git a/exabel_data_sdk/tests/scripts/test_export_signals.py b/exabel_data_sdk/tests/scripts/test_export_signals.py index 8eb4697e..8254ed55 100644 --- a/exabel_data_sdk/tests/scripts/test_export_signals.py +++ b/exabel_data_sdk/tests/scripts/test_export_signals.py @@ -37,7 +37,6 @@ def test_args_api_key(self): self._assert_common_args(args) self.assertEqual(args.filename, "foo.csv") self.assertEqual(args.api_key, "api-key") - self.assertEqual(script.get_api_key(args), "api-key") def test_args_env_variable(self): os.environ["EXABEL_API_KEY"] = "env_key" @@ -48,7 +47,6 @@ def test_args_env_variable(self): self._assert_common_args(args) self.assertIsNone(args.api_key) self.assertEqual(args.filename, "foo.csv") - self.assertEqual(script.get_api_key(args), "env_key") finally: del os.environ["EXABEL_API_KEY"] diff --git a/exabel_data_sdk/tests/scripts/test_load_scripts.py b/exabel_data_sdk/tests/scripts/test_load_scripts.py index 3d7e06bb..81b4c279 100644 --- a/exabel_data_sdk/tests/scripts/test_load_scripts.py +++ b/exabel_data_sdk/tests/scripts/test_load_scripts.py @@ -1,5 +1,4 @@ -# pylint: disable=unused-import - +# ruff: noqa: F401 from exabel_data_sdk.scripts.create_relationship_type import CreateRelationshipType from exabel_data_sdk.scripts.create_signal import CreateSignal from exabel_data_sdk.scripts.delete_entities import DeleteEntities diff --git a/exabel_data_sdk/tests/scripts/test_load_time_series_from_csv.py b/exabel_data_sdk/tests/scripts/test_load_time_series_from_csv.py index 4982b248..66c09bf9 100644 --- a/exabel_data_sdk/tests/scripts/test_load_time_series_from_csv.py +++ b/exabel_data_sdk/tests/scripts/test_load_time_series_from_csv.py @@ -29,9 +29,6 @@ common_args = ["script-name", "--sep", ";", "--api-key", "123"] -# pylint: disable=protected-access - - class TestUploadTimeSeries(unittest.TestCase): def setUp(self) -> None: self.client = mock.create_autospec(ExabelClient) diff --git a/exabel_data_sdk/tests/scripts/test_load_time_series_from_excel.py b/exabel_data_sdk/tests/scripts/test_load_time_series_from_excel.py index f3a6ee72..ec126908 100644 --- a/exabel_data_sdk/tests/scripts/test_load_time_series_from_excel.py +++ b/exabel_data_sdk/tests/scripts/test_load_time_series_from_excel.py @@ -1,6 +1,5 @@ import math import unittest -from typing import Optional import pandas as pd from dateutil import tz @@ -25,8 +24,8 @@ def check_import( filename, *expected_calls, pit_from_file: bool = False, - entity_type: Optional[str] = None, - identifier_type: Optional[str] = None, + entity_type: str | None = None, + identifier_type: str | None = None, ): """Check that the file can be imported and that it produces the given time series.""" args = common_args + [ diff --git a/exabel_data_sdk/tests/services/test_csv_reader.py b/exabel_data_sdk/tests/services/test_csv_reader.py index 242b2d83..5e69ac5e 100644 --- a/exabel_data_sdk/tests/services/test_csv_reader.py +++ b/exabel_data_sdk/tests/services/test_csv_reader.py @@ -1,6 +1,6 @@ import tempfile import unittest -from typing import Iterable, Optional, Union +from typing import Iterable import pandas as pd @@ -11,8 +11,8 @@ class TestCsvReader(unittest.TestCase): def _read_csv( self, content: str, - string_columns: Iterable[Union[str, int]], - chunksize: Optional[int] = None, + string_columns: Iterable[str | int], + chunksize: int | None = None, ): with tempfile.TemporaryDirectory() as tmp: file = f"{tmp}/file.csv" diff --git a/exabel_data_sdk/tests/services/test_csv_relationship_loader.py b/exabel_data_sdk/tests/services/test_csv_relationship_loader.py index d9f68459..d859aadc 100644 --- a/exabel_data_sdk/tests/services/test_csv_relationship_loader.py +++ b/exabel_data_sdk/tests/services/test_csv_relationship_loader.py @@ -26,8 +26,6 @@ from exabel_data_sdk.stubs.exabel.api.data.v1.entity_messages_pb2 import Entity from exabel_data_sdk.tests.client.exabel_mock_client import ExabelMockClient -# pylint: disable=protected-access - class TestRelationshipLoaderColumnConfiguration(unittest.TestCase): def test_validate_argument_combination(self): @@ -522,9 +520,7 @@ def test_load_relationships__with_entity_types__to_entity_type_in_accessible_nam expected_relationships = [ Relationship( relationship_type="relationshipTypes/ns.HAS_BRAND", - from_entity=( - "entityTypes/otherns.accessible_entity_type/" "entities/otherns.entity" - ), + from_entity=("entityTypes/otherns.accessible_entity_type/entities/otherns.entity"), to_entity="entityTypes/ns.brand/entities/ns.brand", ) ] diff --git a/exabel_data_sdk/tests/services/test_file_time_series_parser.py b/exabel_data_sdk/tests/services/test_file_time_series_parser.py index 0fe9edd9..e0d4e0d1 100644 --- a/exabel_data_sdk/tests/services/test_file_time_series_parser.py +++ b/exabel_data_sdk/tests/services/test_file_time_series_parser.py @@ -30,8 +30,6 @@ EntitySearchResultWarning, ) -# pylint: disable=protected-access - BLOOMBERG_TICKER_MAPPING = {"AAPL US": "F_000C7F-E", "MSFT US": "F_000Q07-E"} diff --git a/exabel_data_sdk/tests/util/test_deprecate_arguments.py b/exabel_data_sdk/tests/util/test_deprecate_arguments.py index 544d056d..523a7117 100644 --- a/exabel_data_sdk/tests/util/test_deprecate_arguments.py +++ b/exabel_data_sdk/tests/util/test_deprecate_arguments.py @@ -1,25 +1,22 @@ import unittest -from typing import Optional from exabel_data_sdk.util.deprecate_arguments import deprecate_argument_value, deprecate_arguments from exabel_data_sdk.util.warnings import ExabelDeprecationWarning @deprecate_arguments(old_arg="new_arg") -def _test_func(*, new_arg: Optional[str] = None, old_arg: Optional[str] = None) -> Optional[str]: +def _test_func(*, new_arg: str | None = None, old_arg: str | None = None) -> str | None: return new_arg or old_arg @deprecate_argument_value(old_arg="illegal") -def _test_func_2(*, new_arg: Optional[str] = None, old_arg: Optional[str] = None) -> Optional[str]: +def _test_func_2(*, new_arg: str | None = None, old_arg: str | None = None) -> str | None: return new_arg or old_arg class TestDeprecateArguments(unittest.TestCase): @deprecate_arguments(old_arg="new_arg") - def _test_method( - self, *, new_arg: Optional[str] = None, old_arg: Optional[str] = None - ) -> Optional[str]: + def _test_method(self, *, new_arg: str | None = None, old_arg: str | None = None) -> str | None: return new_arg or old_arg def test_deprecate_arguments(self): @@ -62,15 +59,13 @@ def _no_deprecation() -> None: ... def test_deprecate_argument__removed_argument(self): @deprecate_arguments(deprecated_arg=None) - def _test_func(*, deprecated_arg: Optional[str] = None) -> Optional[str]: + def _test_func(*, deprecated_arg: str | None = None) -> str | None: return deprecated_arg self.assertIsNone(_test_func(deprecated_arg="test")) def test_deprecate_argument__with_function_as_arg(self): - def _test_func( - new_arg: Optional[str] = None, old_arg: Optional[str] = None - ) -> Optional[str]: + def _test_func(new_arg: str | None = None, old_arg: str | None = None) -> str | None: return new_arg or old_arg wrapped_local_func = deprecate_arguments(_test_func, old_arg="new_arg") @@ -79,9 +74,7 @@ def _test_func( class TestDeprecateArgumentValue(unittest.TestCase): @deprecate_argument_value(old_arg="illegal") - def _test_method( - self, *, new_arg: Optional[str] = None, old_arg: Optional[str] = None - ) -> Optional[str]: + def _test_method(self, *, new_arg: str | None = None, old_arg: str | None = None) -> str | None: return new_arg or old_arg def test_deprecate_argument_value(self): diff --git a/exabel_data_sdk/tests/util/test_handle_missing_imports.py b/exabel_data_sdk/tests/util/test_handle_missing_imports.py index 503c613d..9cdca48b 100644 --- a/exabel_data_sdk/tests/util/test_handle_missing_imports.py +++ b/exabel_data_sdk/tests/util/test_handle_missing_imports.py @@ -2,12 +2,14 @@ from exabel_data_sdk.util.handle_missing_imports import handle_missing_imports +# ruff: noqa: F401 + class TestHandleMissingImports(unittest.TestCase): def test_handle_missing_imports(self): with self.assertWarns(UserWarning) as cm: with handle_missing_imports({"exabel_data_sdk.might_not_exist": "library-name"}): - import exabel_data_sdk.might_not_exist # pylint: disable=unused-import + import exabel_data_sdk.might_not_exist self.assertTrue(str(cm.warning).startswith("Module 'exabel_data_sdk.might_not_exist'")) def test_handle_missing_imports_custom_warning(self): @@ -15,7 +17,7 @@ def test_handle_missing_imports_custom_warning(self): with handle_missing_imports( {"exabel_data_sdk.might_not_exist": "library-name"}, warning="custom warning" ): - import exabel_data_sdk.might_not_exist # pylint: disable=unused-import + import exabel_data_sdk.might_not_exist self.assertTrue(str(cm.warning).startswith("custom warning")) def test_handle_missing_imports_reraise(self): @@ -25,11 +27,11 @@ def test_handle_missing_imports_reraise(self): warning="custom exception", reraise=True, ): - import exabel_data_sdk.might_not_exist # pylint: disable=unused-import + import exabel_data_sdk.might_not_exist self.assertTrue(str(cm.exception).startswith("custom exception")) def test_handle_missing_imports_should_fail(self): with self.assertRaises(ImportError) as cm: with handle_missing_imports({}): - import exabel_data_sdk.does_not_exist # pylint: disable=unused-import + import exabel_data_sdk.does_not_exist self.assertEqual("exabel_data_sdk.does_not_exist", str(cm.exception.name)) diff --git a/exabel_data_sdk/tests/util/test_logging_thread_pool_executor.py b/exabel_data_sdk/tests/util/test_logging_thread_pool_executor.py index 6484fb03..72d8cc0d 100644 --- a/exabel_data_sdk/tests/util/test_logging_thread_pool_executor.py +++ b/exabel_data_sdk/tests/util/test_logging_thread_pool_executor.py @@ -20,7 +20,7 @@ def task(): futures = [executor.submit(task) for _ in range(2)] for _ in range(2): - start_semaphore.acquire() # pylint: disable=consider-using-with + start_semaphore.acquire() self.assertEqual(executor.running_threads, 2) start_barrier.wait() diff --git a/exabel_data_sdk/util/case_insensitive_column.py b/exabel_data_sdk/util/case_insensitive_column.py index 49fb33c0..1cedce2d 100644 --- a/exabel_data_sdk/util/case_insensitive_column.py +++ b/exabel_data_sdk/util/case_insensitive_column.py @@ -1,7 +1,7 @@ -from typing import Sequence, Union +from typing import Sequence -def get_case_insensitive_column(column: Union[str, int], columns: Sequence[str]) -> Union[str, int]: +def get_case_insensitive_column(column: str | int, columns: Sequence[str]) -> str | int: """ Search for a column name in a sequence of column names and if found, return the lexicographically first case-insensitive match among the column names. If no match is found diff --git a/exabel_data_sdk/util/deprecate_arguments.py b/exabel_data_sdk/util/deprecate_arguments.py index dd64dc85..93b79b3c 100644 --- a/exabel_data_sdk/util/deprecate_arguments.py +++ b/exabel_data_sdk/util/deprecate_arguments.py @@ -1,6 +1,6 @@ import functools import warnings -from typing import Any, Callable, Optional, TypeVar, overload +from typing import Any, Callable, TypeVar, overload from exabel_data_sdk.util.warnings import ExabelDeprecationWarning @@ -9,29 +9,29 @@ @overload def deprecate_arguments( - **deprecation_replacements: Optional[str], + **deprecation_replacements: str | None, ) -> Callable[[FunctionT], FunctionT]: ... @overload def deprecate_arguments( - __func: None, # pylint: disable=invalid-name - **deprecation_replacements: Optional[str], + __func: None, + **deprecation_replacements: str | None, ) -> Callable[[FunctionT], FunctionT]: ... @overload def deprecate_arguments( - __func: FunctionT, # pylint: disable=invalid-name - **deprecation_replacements: Optional[str], + __func: FunctionT, + **deprecation_replacements: str | None, ) -> FunctionT: ... # Pylint flags '__func' as an invalid argument name, but we want the '__' prefix to make Mypy # interpret it as a positional-only argument. Therefore, we disable the check for this argument. def deprecate_arguments( - __func: Optional[FunctionT] = None, # pylint: disable=invalid-name - **deprecation_replacements: Optional[str], + __func: FunctionT | None = None, + **deprecation_replacements: str | None, ) -> FunctionT: """ Decorator for warning about and replacing deprecated arguments in a function that will be @@ -95,20 +95,20 @@ def deprecate_argument_value( @overload def deprecate_argument_value( - __func: None, # pylint: disable=invalid-name + __func: None, **deprecated_values: object, ) -> Callable[[FunctionT], FunctionT]: ... @overload def deprecate_argument_value( - __func: FunctionT, # pylint: disable=invalid-name + __func: FunctionT, **deprecated_values: object, ) -> FunctionT: ... def deprecate_argument_value( - __func: Optional[FunctionT] = None, # pylint: disable=invalid-name + __func: FunctionT | None = None, **deprecated_values: object, ) -> FunctionT: """ diff --git a/exabel_data_sdk/util/handle_missing_imports.py b/exabel_data_sdk/util/handle_missing_imports.py index 5e86760d..f851e40f 100644 --- a/exabel_data_sdk/util/handle_missing_imports.py +++ b/exabel_data_sdk/util/handle_missing_imports.py @@ -1,7 +1,7 @@ import sys import warnings from contextlib import contextmanager -from typing import Iterator, Mapping, Optional +from typing import Iterator, Mapping _OPTIONAL_DEPENDENCIES = { "snowflake": "snowflake-connector-python", @@ -16,8 +16,8 @@ @contextmanager def handle_missing_imports( - module_library_map: Optional[Mapping[str, str]] = None, - warning: Optional[str] = None, + module_library_map: Mapping[str, str] | None = None, + warning: str | None = None, reraise: bool = False, ) -> Iterator: """ @@ -32,7 +32,7 @@ def handle_missing_imports( # Get the caller's module name. Second frame is `contextlib` because of the decorator. # Third frame is the caller of this function. if not warning: - caller_name = sys._getframe(2).f_locals.get( # pylint: disable=protected-access + caller_name = sys._getframe(2).f_locals.get( # noqa: SLF001 "__name__" ) warning = ( diff --git a/exabel_data_sdk/util/logging_thread_pool_executor.py b/exabel_data_sdk/util/logging_thread_pool_executor.py index a1272843..2309d313 100644 --- a/exabel_data_sdk/util/logging_thread_pool_executor.py +++ b/exabel_data_sdk/util/logging_thread_pool_executor.py @@ -34,7 +34,7 @@ def wrapped(*args, **kwargs): # type: ignore return wrapped - def submit( # type: ignore[override] # pylint: disable=arguments-differ + def submit( # type: ignore[override] self, function: Callable[..., Any], *args: Any, **kwargs: Any ) -> Future: return super().submit(self.active_threads_counter(function), *args, **kwargs) diff --git a/exabel_data_sdk/util/resource_name_normalization.py b/exabel_data_sdk/util/resource_name_normalization.py index f9e23fc0..9812bf1b 100644 --- a/exabel_data_sdk/util/resource_name_normalization.py +++ b/exabel_data_sdk/util/resource_name_normalization.py @@ -3,7 +3,7 @@ import warnings from collections import defaultdict, deque from dataclasses import dataclass -from typing import Iterator, Mapping, MutableMapping, MutableSequence, Optional, Sequence, Tuple +from typing import Iterator, Mapping, MutableMapping, MutableSequence, Sequence import pandas as pd @@ -133,7 +133,7 @@ def get_duplicates(self) -> Mapping[str, Sequence[str]]: return {key: values for key, values in result.items() if len(values) > 1} -def get_namespace_from_resource_identifier(resource_identifier: str) -> Optional[str]: +def get_namespace_from_resource_identifier(resource_identifier: str) -> str | None: """Get the namespace from a resource identifier.""" resource_identifier_parts = resource_identifier.split(".") if len(resource_identifier_parts) == 1: @@ -166,7 +166,7 @@ def _search_entities( identifier_type: str, identifiers: Sequence[str], entity_type: str, -) -> Tuple[Mapping[str, str], Sequence[EntitySearchResultWarning]]: +) -> tuple[Mapping[str, str], Sequence[EntitySearchResultWarning]]: logger.info("Looking up %d %ss...", len(identifiers), identifier_type) # Skip empty identifiers non_empty_identifiers: Iterator[str] = ( @@ -217,7 +217,7 @@ def _get_resource_names( entity_api: EntityApi, identifiers: Sequence[str], entity_type: str, - namespace: Optional[str] = None, + namespace: str | None = None, check_entity_types: bool = True, preserve_namespace: bool = False, ) -> Mapping[str, str]: @@ -271,11 +271,11 @@ def _get_resource_names( def to_entity_resource_names( entity_api: EntityApi, identifiers: pd.Series, - namespace: Optional[str] = None, - entity_mapping: Optional[Mapping[str, Mapping[str, str]]] = None, + namespace: str | None = None, + entity_mapping: Mapping[str, Mapping[str, str]] | None = None, check_entity_types: bool = True, preserve_namespace: bool = False, - entity_type: Optional[str] = None, + entity_type: str | None = None, ) -> EntityResourceNames: """ Turns the given identifiers into entity resource names. diff --git a/exabel_data_sdk/util/type_converter.py b/exabel_data_sdk/util/type_converter.py index 64372f6f..376377ab 100644 --- a/exabel_data_sdk/util/type_converter.py +++ b/exabel_data_sdk/util/type_converter.py @@ -1,9 +1,7 @@ -from typing import Union - from exabel_data_sdk.util.exceptions import TypeConversionError -def type_converter(value: str, type_: type) -> Union[str, int, float, bool]: +def type_converter(value: str, type_: type) -> str | int | float | bool: """ Convert a string value to the given type. """ diff --git a/publish.sh b/publish.sh index 51ea6edb..7680ce49 100755 --- a/publish.sh +++ b/publish.sh @@ -38,8 +38,8 @@ bash build.sh if [ "${UPLOAD_TO_REAL_PYPI}" == true ]; then echo "" echo "=== Publishing package to the real PyPI ===" - python3 -m twine upload dist/* + uv publish else echo "Publishing to test" - python3 -m twine upload --repository testpypi dist/* + uv publish --publish-url https://test.pypi.org/legacy/ fi diff --git a/pyproject.toml b/pyproject.toml index 7121e0e7..ba2ee409 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,87 +1,72 @@ -[tool.black] -line-length = 100 +[project] +name = "exabel-data-sdk" +description = "Python SDK for the Exabel Data API" +version = "7.0.0" +license = "MIT" +license-files = ["LICENSE"] +readme = "README.md" +authors = [ + { name = "Exabel", email = "support@exabel.com" } +] +requires-python = ">=3.10" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Financial and Insurance Industry", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dependencies = [ + "google-api-core>1.31.3", + "googleapis-common-protos>=1.56.0", + "grpcio", + "openpyxl", + "pandas", + "protobuf>=4", + "requests", + "tqdm", +] -[tool.pylint.MASTER] -ignore = "stubs" -# pylint on mac is reporting c-extension-no-member errors without this. -extension-pkg-allow-list = ["math", "resource"] -jobs = 2 - -[tool.pylint."MESSAGES CONTROL"] -disable = [ - # fails on any TODO - "fixme", - - # many dubious positives, such as similar __init__ methods and tests - "duplicate-code", - - # when this is run on Jenkins, the dependencies are not available - "import-error", - - # Checks fail on protobufs. - "no-member", - "no-name-in-module", - - # Does not support typing correctly. - "isinstance-second-argument-not-valid-type", - - # When an import is placed inside a function, it is generally as a work-around due to cyclic - # imports; emitting a warning which has to be ignored, does not seem helpful. - "cyclic-import", - - # Since we usually follow the pattern of one class per module, the module docstring is - # superfluous. - "missing-module-docstring", - - # Imports are handled by 'isort'. - "wrong-import-order", - "wrong-import-position", - "ungrouped-imports", - "import-outside-toplevel", - - # Mypy is better at checking this. - # Pylint has false positives for methods called with resource contexts. - "arguments-differ", - "arguments-renamed", - "too-many-function-args", - - # The checks with 'too-few' or 'too-many' are generally reasonable programming guidelines, - # but when enforced they tend to have little effect beyond causing the code to be sprinkled - # by ignore statements. - "too-few-public-methods", - "too-many-ancestors", - "too-many-arguments", - "too-many-boolean-expressions", - "too-many-branches", - "too-many-instance-attributes", - "too-many-lines", - "too-many-locals", - "too-many-nested-blocks", - "too-many-public-methods", - "too-many-return-statements", - "too-many-statements", - "too-many-positional-arguments", +[dependency-groups] +dev = [ + "ruff", + "mypy", + "pytest", + "types-protobuf", + "types-python-dateutil", + "types-requests", +] + +[project.optional-dependencies] +snowflake = ["snowflake-connector-python[pandas]", "snowflake-sqlalchemy"] +bigquery = ["google-cloud-bigquery", "sqlalchemy-bigquery"] +athena = ["pyathena", "pyarrow", "sqlalchemy"] + +[project.urls] +Homepage = "https://github.com/Exabel/python-sdk" +Source = "https://github.com/Exabel/python-sdk" +Changelog = "https://github.com/Exabel/python-sdk/releases" + +[build-system] +requires = ["uv_build>=0.9.28,<0.10.0"] +build-backend = "uv_build" + +[tool.uv] +package = true + +[tool.uv.build-backend] +module-name = "exabel_data_sdk" +module-root = "" +source-exclude = [ + "exabel_data_sdk/tests/**", +] +wheel-exclude = [ + "exabel_data_sdk/tests/**", ] -enable="useless-suppression" - -[tool.pylint.BASIC] -no-docstring-rgx = "^_|^test_|.*Test$|^Test" - -[tool.pylint.FORMAT] -# By default, pylint enforces an argument naming convention which is different from PEP8 (stricter), -# but we have decided on PEP8 and no need to deviate from that. -argument-rgx = "[a-z][a-z0-9_]*$" -attr-rgx = "_{0,2}[A-Za-z_][A-Za-z0-9_]*$" -variable-rgx = "[a-z_][a-z0-9_]*$" -good-names=["_", "of"] - -[tool.isort] -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -line_length = 100 -ensure_newline_before_comments = true [tool.mypy] python_version = "3.10" @@ -103,7 +88,127 @@ disallow_untyped_defs = false check_untyped_defs = false disable_error_code = ["annotation-unchecked"] +[[tool.mypy.overrides]] +module = [ + "google.*", + "google.api_core.*", + "google.cloud.*", +] +ignore_errors = true +follow_imports = "skip" + [[tool.mypy.overrides]] module = "exabel_data_sdk.stubs.*" disallow_untyped_defs = false disable_error_code = ["name-defined"] + +[tool.ruff] +src = ["exabel_data_sdk"] +force-exclude = true +extend-exclude = ["stubs"] +line-length = 100 +indent-width = 4 +target-version = "py310" + +[tool.ruff.lint] +select = [ + "I", # isort + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "N", # pep8-naming + "G", # flake8-logging-format + "PL", # Pylint (PLC, PLE, PLR, PLW) + "SLF001", # private-member-access + "BLE001", # blind-except + "B006", # mutable-argument-default + "B018", # useless-expression + "B023", # function-uses-loop-variable + "S307", # suspicious-eval-usage + + # pydocstyle + "D101", # undocumented-public-class + # "D102", # undocumented-public-method + "D103", # undocumented-public-function + "D419", # empty-docstring + + # pyupgrade + "UP006", # use dict/list instead of typing.Dict/List + "UP007", # use X | Y for unions + "UP032", # f-string + "UP037", # do not need quotes for type annotations + "UP045", # use X | None instead of Optional[X] +] + +ignore = [ + "E402", # module-import-not-at-top-of-file + "E501", # line-too-long - handled by formatter + "E721", # type-comparison + "E741", # ambiguous-variable-name + + # Matches pylint disabled rules in [tool.pylint.messages_control] + "PLC0415", # import-outside-toplevel + "PLR0904", # too-many-public-methods + "PLR0911", # too-many-return-statements + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments + "PLR0914", # too-many-locals + "PLR0915", # too-many-statements + "PLR0916", # too-many-boolean-expressions + "PLR0917", # too-many-positional-arguments + "PLR1702", # too-many-nested-blocks + + "PLR6201", # using set literal for testing membership + "PLC2801", # unnecessary-dunder-call + "PLR5501", # collapsible-else-if + "PLC2401", # non-ascii-name + + # Pylint optional checkers (not enabled by default in pylint) + "PLC2701", # import-private-name + "PLR2004", # magic-value-comparison + "PLR2044", # empty-comment + "PLR6301", # no-self-use + "PLW1641", # eq-without-hash + "PLW2901", # redefined-loop-name + + "B019", # using lru_cache on methods can lead to memory leaks + + "N802", # invalid-function-name + "N803", # invalid-argument-name + "N806", # non-lowercase-variable-in-function + "N813", # camelcase-imported-as-lowercase + "N815", # mixed-case-variable-in-class-scope + "N818", # error-suffix-on-exception-name + + "F821", # undefined name + "F601", # multi-value-repeated-key-literal + + "G010", # logging-warn + "G201", # logging-exc-info - use .exception() instead of .error() with exc_info=true +] + +[tool.ruff.lint.per-file-ignores] +"{**/test_*.py,**/*_test.py,**/tests/**/*.py,**/test/**/*.py}" = [ + "F841", # unused-variable + "F811", # redefined-while-unused + "ARG001", # unused-function-argument + "ARG002", # unused-method-argument + "D", # pydocstyle + "SLF001" # private-member-access +] +"**/__init__.py" = ["F401"] # unused-import + +[tool.ruff.lint.isort] +known-first-party = ["exabel_data_sdk"] +detect-same-package = false + +[tool.ruff.lint.pep8-naming] +extend-ignore-names = ["_*", "of*", "__*", "X*"] + +[tool.ruff.lint.pydocstyle] +convention = "google" +ignore-decorators = ["typing.overload"] +ignore-var-parameters = true + +[tool.ruff.lint.flake8-unused-arguments] +ignore-variadic-names = true diff --git a/style.sh b/style.sh index f2642360..d0ff3f8b 100755 --- a/style.sh +++ b/style.sh @@ -4,13 +4,10 @@ set -euf echo "Running mypy." -mypy exabel_data_sdk setup.py +mypy exabel_data_sdk -echo "Check imports" -isort --check-only -s exabel_data_sdk/stubs exabel_data_sdk setup.py +echo "Check style with ruff linter." +ruff check exabel_data_sdk -echo "Check formatting" -black --check --exclude exabel_data_sdk/stubs exabel_data_sdk setup.py - -echo "Running pylint." -pylint exabel_data_sdk setup.py +echo "Check formatting with ruff formatter." +ruff format --check exabel_data_sdk diff --git a/test_installed.sh b/test_installed.sh index 5b5f53c4..48d2b2be 100755 --- a/test_installed.sh +++ b/test_installed.sh @@ -7,6 +7,7 @@ cp -r ./exabel_data_sdk/tests ./tests/exabel_data_sdk/ PYTHON_SITE_PACKAGE=$(pip show exabel_data_sdk|grep Location|cut -f 2 -d ' ') echo "__path__ = __import__('pkgutil').extend_path(__path__, __name__)" >> ${PYTHON_SITE_PACKAGE}/exabel_data_sdk/__init__.py echo "__path__ = __import__('pkgutil').extend_path(__path__, __name__)" > tests/__init__.py +echo "__path__ = __import__('pkgutil').extend_path(__path__, __name__)" > tests/exabel_data_sdk/__init__.py cd ./tests/ -python -m unittest discover -s ./exabel_data_sdk/tests +pytest ./exabel_data_sdk/tests diff --git a/tests.sh b/tests.sh index eef2560e..9cd9f844 100755 --- a/tests.sh +++ b/tests.sh @@ -2,4 +2,4 @@ # Runs all the tests. -python -m unittest -v +python -m pytest -v diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..d857d862 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1653 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] + +[[package]] +name = "asn1crypto" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", size = 121080, upload-time = "2022-03-15T14:46:52.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045, upload-time = "2022-03-15T14:46:51.055Z" }, +] + +[[package]] +name = "boto3" +version = "1.42.39" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/ea/b96c77da49fed28744ee0347374d8223994a2b8570e76e8380a4064a8c4a/boto3-1.42.39.tar.gz", hash = "sha256:d03f82363314759eff7f84a27b9e6428125f89d8119e4588e8c2c1d79892c956", size = 112783, upload-time = "2026-01-30T20:38:31.226Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/c4/3493b5c86e32d6dd558b30d16b55503e24a6e6cd7115714bc102b247d26e/boto3-1.42.39-py3-none-any.whl", hash = "sha256:d9d6ce11df309707b490d2f5f785b761cfddfd6d1f665385b78c9d8ed097184b", size = 140606, upload-time = "2026-01-30T20:38:28.635Z" }, +] + +[[package]] +name = "botocore" +version = "1.42.39" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/a6/3a34d1b74effc0f759f5ff4e91c77729d932bc34dd3207905e9ecbba1103/botocore-1.42.39.tar.gz", hash = "sha256:0f00355050821e91a5fe6d932f7bf220f337249b752899e3e4cf6ed54326249e", size = 14914927, upload-time = "2026-01-30T20:38:19.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/71/9a2c88abb5fe47b46168b262254d5b5d635de371eba4bd01ea5c8c109575/botocore-1.42.39-py3-none-any.whl", hash = "sha256:9e0d0fed9226449cc26fcf2bbffc0392ac698dd8378e8395ce54f3ec13f81d58", size = 14591958, upload-time = "2026-01-30T20:38:14.814Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" }, + { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" }, + { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" }, + { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" }, + { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" }, + { url = "https://files.pythonhosted.org/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" }, + { url = "https://files.pythonhosted.org/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" }, + { url = "https://files.pythonhosted.org/packages/b9/27/542b029f293a5cce59349d799d4d8484b3b1654a7b9a0585c266e974a488/cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908", size = 7116417, upload-time = "2026-01-28T00:23:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f5/ed3fcddd0a5e39321e595e144615399e47e7c153a1fb8c4862aec3151ff9/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085", size = 4926748, upload-time = "2026-01-28T00:23:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" }, + { url = "https://files.pythonhosted.org/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" }, + { url = "https://files.pythonhosted.org/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/17/b7/0f6b8c1dd0779df2b526e78978ff00462355e31c0a6f6cff8a3e99889c90/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e", size = 4891908, upload-time = "2026-01-28T00:23:46.48Z" }, + { url = "https://files.pythonhosted.org/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" }, + { url = "https://files.pythonhosted.org/packages/00/03/0de4ed43c71c31e4fe954edd50b9d28d658fef56555eba7641696370a8e2/cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061", size = 3001986, upload-time = "2026-01-28T00:23:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/5c/70/81830b59df7682917d7a10f833c4dab2a5574cd664e86d18139f2b421329/cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7", size = 3468288, upload-time = "2026-01-28T00:23:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" }, + { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" }, + { url = "https://files.pythonhosted.org/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" }, + { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" }, + { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" }, + { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" }, + { url = "https://files.pythonhosted.org/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" }, + { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" }, + { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" }, + { url = "https://files.pythonhosted.org/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" }, + { url = "https://files.pythonhosted.org/packages/59/e0/f9c6c53e1f2a1c2507f00f2faba00f01d2f334b35b0fbfe5286715da2184/cryptography-46.0.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:766330cce7416c92b5e90c3bb71b1b79521760cdcfc3a6a1a182d4c9fab23d2b", size = 3476316, upload-time = "2026-01-28T00:24:24.144Z" }, + { url = "https://files.pythonhosted.org/packages/27/7a/f8d2d13227a9a1a9fe9c7442b057efecffa41f1e3c51d8622f26b9edbe8f/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da", size = 4216693, upload-time = "2026-01-28T00:24:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/c5/de/3787054e8f7972658370198753835d9d680f6cd4a39df9f877b57f0dd69c/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80", size = 4382765, upload-time = "2026-01-28T00:24:27.577Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/60e0afb019973ba6a0b322e86b3d61edf487a4f5597618a430a2a15f2d22/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822", size = 4216066, upload-time = "2026-01-28T00:24:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/81/8e/bf4a0de294f147fee66f879d9bae6f8e8d61515558e3d12785dd90eca0be/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947", size = 4382025, upload-time = "2026-01-28T00:24:30.681Z" }, + { url = "https://files.pythonhosted.org/packages/79/f4/9ceb90cfd6a3847069b0b0b353fd3075dc69b49defc70182d8af0c4ca390/cryptography-46.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be8c01a7d5a55f9a47d1888162b76c8f49d62b234d88f0ff91a9fbebe32ffbc3", size = 3406043, upload-time = "2026-01-28T00:24:32.236Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "exabel-data-sdk" +version = "7.0.0" +source = { editable = "." } +dependencies = [ + { name = "google-api-core" }, + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "protobuf" }, + { name = "requests" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +athena = [ + { name = "pyarrow" }, + { name = "pyathena" }, + { name = "sqlalchemy" }, +] +bigquery = [ + { name = "google-cloud-bigquery" }, + { name = "sqlalchemy-bigquery" }, +] +snowflake = [ + { name = "snowflake-connector-python", extra = ["pandas"] }, + { name = "snowflake-sqlalchemy" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "types-protobuf" }, + { name = "types-python-dateutil" }, + { name = "types-requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "google-api-core", specifier = ">1.31.3" }, + { name = "google-cloud-bigquery", marker = "extra == 'bigquery'" }, + { name = "googleapis-common-protos", specifier = ">=1.56.0" }, + { name = "grpcio" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "protobuf", specifier = ">=4" }, + { name = "pyarrow", marker = "extra == 'athena'" }, + { name = "pyathena", marker = "extra == 'athena'" }, + { name = "requests" }, + { name = "snowflake-connector-python", extras = ["pandas"], marker = "extra == 'snowflake'" }, + { name = "snowflake-sqlalchemy", marker = "extra == 'snowflake'" }, + { name = "sqlalchemy", marker = "extra == 'athena'" }, + { name = "sqlalchemy-bigquery", marker = "extra == 'bigquery'" }, + { name = "tqdm" }, +] +provides-extras = ["snowflake", "bigquery", "athena"] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "types-protobuf" }, + { name = "types-python-dateutil" }, + { name = "types-requests" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/7d/5df2650c57d47c57232af5ef4b4fdbff182070421e405e0d62c6cdbfaa87/fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b", size = 310496, upload-time = "2026-01-09T15:21:35.562Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/10/05572d33273292bac49c2d1785925f7bc3ff2fe50e3044cf1062c1dde32e/google_api_core-2.29.0.tar.gz", hash = "sha256:84181be0f8e6b04006df75ddfe728f24489f0af57c96a529ff7cf45bc28797f7", size = 177828, upload-time = "2026-01-08T22:21:39.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/b6/85c4d21067220b9a78cfb81f516f9725ea6befc1544ec9bd2c1acd97c324/google_api_core-2.29.0-py3-none-any.whl", hash = "sha256:d30bc60980daa36e314b5d5a3e5958b0200cb44ca8fa1be2b614e932b75a3ea9", size = 173906, upload-time = "2026-01-08T22:21:36.093Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, +] + +[[package]] +name = "google-cloud-bigquery" +version = "3.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-resumable-media" }, + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/0a/62438ca138a095945468968696d9cca75a4cfd059e810402e70b0236d8ba/google_cloud_bigquery-3.40.0.tar.gz", hash = "sha256:b3ccb11caf0029f15b29569518f667553fe08f6f1459b959020c83fbbd8f2e68", size = 509287, upload-time = "2026-01-08T01:07:26.065Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/6a/90a04270dd60cc70259b73744f6e610ae9a158b21ab50fb695cca0056a3d/google_cloud_bigquery-3.40.0-py3-none-any.whl", hash = "sha256:0469bcf9e3dad3cab65b67cce98180c8c0aacf3253d47f0f8e976f299b49b5ab", size = 261335, upload-time = "2026-01-08T01:07:23.761Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/03/ef0bc99d0e0faf4fdbe67ac445e18cdaa74824fd93cd069e7bb6548cb52d/google_cloud_core-2.5.0.tar.gz", hash = "sha256:7c1b7ef5c92311717bd05301aa1a91ffbc565673d3b0b4163a52d8413a186963", size = 36027, upload-time = "2025-10-29T23:17:39.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl", hash = "sha256:67d977b41ae6c7211ee830c7912e41003ea8194bff15ae7d72fd6f51e57acabc", size = 29469, upload-time = "2025-10-29T23:17:38.548Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/ac/6f7bc93886a823ab545948c2dd48143027b2355ad1944c7cf852b338dc91/google_crc32c-1.8.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0470b8c3d73b5f4e3300165498e4cf25221c7eb37f1159e221d1825b6df8a7ff", size = 31296, upload-time = "2025-12-16T00:19:07.261Z" }, + { url = "https://files.pythonhosted.org/packages/f7/97/a5accde175dee985311d949cfcb1249dcbb290f5ec83c994ea733311948f/google_crc32c-1.8.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:119fcd90c57c89f30040b47c211acee231b25a45d225e3225294386f5d258288", size = 30870, upload-time = "2025-12-16T00:29:17.669Z" }, + { url = "https://files.pythonhosted.org/packages/3d/63/bec827e70b7a0d4094e7476f863c0dbd6b5f0f1f91d9c9b32b76dcdfeb4e/google_crc32c-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f35aaffc8ccd81ba3162443fabb920e65b1f20ab1952a31b13173a67811467d", size = 33214, upload-time = "2025-12-16T00:40:19.618Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/11b70614df04c289128d782efc084b9035ef8466b3d0a8757c1b6f5cf7ac/google_crc32c-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:864abafe7d6e2c4c66395c1eb0fe12dc891879769b52a3d56499612ca93b6092", size = 33589, upload-time = "2025-12-16T00:40:20.7Z" }, + { url = "https://files.pythonhosted.org/packages/3e/00/a08a4bc24f1261cc5b0f47312d8aebfbe4b53c2e6307f1b595605eed246b/google_crc32c-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:db3fe8eaf0612fc8b20fa21a5f25bd785bc3cd5be69f8f3412b0ac2ffd49e733", size = 34437, upload-time = "2025-12-16T00:35:19.437Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, + { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301, upload-time = "2025-12-16T00:24:48.527Z" }, + { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868, upload-time = "2025-12-16T00:48:12.163Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381, upload-time = "2025-12-16T00:40:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734, upload-time = "2025-12-16T00:40:27.028Z" }, + { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878, upload-time = "2025-12-16T00:35:23.142Z" }, + { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/d7/520b62a35b23038ff005e334dba3ffc75fcf583bee26723f1fd8fd4b6919/google_resumable_media-2.8.0.tar.gz", hash = "sha256:f1157ed8b46994d60a1bc432544db62352043113684d4e030ee02e77ebe9a1ae", size = 2163265, upload-time = "2025-11-17T15:38:06.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl", hash = "sha256:dd14a116af303845a8d932ddae161a26e86cc229645bc98b39f026f9b1717582", size = 81340, upload-time = "2025-11-17T15:38:05.594Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, +] + +[[package]] +name = "greenlet" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/65/5b235b40581ad75ab97dcd8b4218022ae8e3ab77c13c919f1a1dfe9171fd/greenlet-3.3.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:04bee4775f40ecefcdaa9d115ab44736cd4b9c5fba733575bfe9379419582e13", size = 273723, upload-time = "2026-01-23T15:30:37.521Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ad/eb4729b85cba2d29499e0a04ca6fbdd8f540afd7be142fd571eea43d712f/greenlet-3.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e1457f4fed12a50e427988a07f0f9df53cf0ee8da23fab16e6732c2ec909d4", size = 574874, upload-time = "2026-01-23T16:00:54.551Z" }, + { url = "https://files.pythonhosted.org/packages/87/32/57cad7fe4c8b82fdaa098c89498ef85ad92dfbb09d5eb713adedfc2ae1f5/greenlet-3.3.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:070472cd156f0656f86f92e954591644e158fd65aa415ffbe2d44ca77656a8f5", size = 586309, upload-time = "2026-01-23T16:05:25.18Z" }, + { url = "https://files.pythonhosted.org/packages/87/eb/8a1ec2da4d55824f160594a75a9d8354a5fe0a300fb1c48e7944265217e1/greenlet-3.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a300354f27dd86bae5fbf7002e6dd2b3255cd372e9242c933faf5e859b703fe", size = 586985, upload-time = "2026-01-23T15:32:47.968Z" }, + { url = "https://files.pythonhosted.org/packages/15/1c/0621dd4321dd8c351372ee8f9308136acb628600658a49be1b7504208738/greenlet-3.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e84b51cbebf9ae573b5fbd15df88887815e3253fc000a7d0ff95170e8f7e9729", size = 1547271, upload-time = "2026-01-23T16:04:18.977Z" }, + { url = "https://files.pythonhosted.org/packages/9d/53/24047f8924c83bea7a59c8678d9571209c6bfe5f4c17c94a78c06024e9f2/greenlet-3.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0093bd1a06d899892427217f0ff2a3c8f306182b8c754336d32e2d587c131b4", size = 1613427, upload-time = "2026-01-23T15:33:44.428Z" }, + { url = "https://files.pythonhosted.org/packages/ff/07/ac9bf1ec008916d1a3373cae212884c1dcff4a4ba0d41127ce81a8deb4e9/greenlet-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:7932f5f57609b6a3b82cc11877709aa7a98e3308983ed93552a1c377069b20c8", size = 226100, upload-time = "2026-01-23T15:30:56.957Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e8/2e1462c8fdbe0f210feb5ac7ad2d9029af8be3bf45bd9fa39765f821642f/greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c", size = 274974, upload-time = "2026-01-23T15:31:02.891Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a8/530a401419a6b302af59f67aaf0b9ba1015855ea7e56c036b5928793c5bd/greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd", size = 577175, upload-time = "2026-01-23T16:00:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/8e/89/7e812bb9c05e1aaef9b597ac1d0962b9021d2c6269354966451e885c4e6b/greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5", size = 590401, upload-time = "2026-01-23T16:05:26.365Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ae/8d472e1f5ac5efe55c563f3eabb38c98a44b832602e12910750a7c025802/greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2", size = 590272, upload-time = "2026-01-23T15:32:49.411Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/0fde34bebfcadc833550717eade64e35ec8738e6b097d5d248274a01258b/greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9", size = 1550729, upload-time = "2026-01-23T16:04:20.867Z" }, + { url = "https://files.pythonhosted.org/packages/16/c9/2fb47bee83b25b119d5a35d580807bb8b92480a54b68fef009a02945629f/greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f", size = 1615552, upload-time = "2026-01-23T15:33:45.743Z" }, + { url = "https://files.pythonhosted.org/packages/1f/54/dcf9f737b96606f82f8dd05becfb8d238db0633dd7397d542a296fe9cad3/greenlet-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:32e4ca9777c5addcbf42ff3915d99030d8e00173a56f80001fb3875998fe410b", size = 226462, upload-time = "2026-01-23T15:36:50.422Z" }, + { url = "https://files.pythonhosted.org/packages/91/37/61e1015cf944ddd2337447d8e97fb423ac9bc21f9963fb5f206b53d65649/greenlet-3.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:da19609432f353fed186cc1b85e9440db93d489f198b4bdf42ae19cc9d9ac9b4", size = 225715, upload-time = "2026-01-23T15:33:17.298Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, + { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, + { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/2f/5e0e41f33c69655300a5e54aeb637cf8ff57f1786a3aba374eacc0228c1d/greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a", size = 227156, upload-time = "2026-01-23T15:34:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ab/717c58343cf02c5265b531384b248787e04d8160b8afe53d9eec053d7b44/greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1", size = 226403, upload-time = "2026-01-23T15:31:39.372Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, + { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/c9c23a6478b3bcc91f979ce4ca50879e4d0b2bd7b9a53d8ecded719b92e2/greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", size = 227042, upload-time = "2026-01-23T15:33:58.216Z" }, + { url = "https://files.pythonhosted.org/packages/90/e7/824beda656097edee36ab15809fd063447b200cc03a7f6a24c34d520bc88/greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", size = 226294, upload-time = "2026-01-23T15:30:52.73Z" }, + { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, + { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/17/ff4795dc9a34b6aee6ec379f1b66438a3789cd1315aac0cbab60d92f74b3/grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc", size = 5840037, upload-time = "2025-10-21T16:20:25.069Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ff/35f9b96e3fa2f12e1dcd58a4513a2e2294a001d64dec81677361b7040c9a/grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde", size = 11836482, upload-time = "2025-10-21T16:20:30.113Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1c/8374990f9545e99462caacea5413ed783014b3b66ace49e35c533f07507b/grpcio-1.76.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3", size = 6407178, upload-time = "2025-10-21T16:20:32.733Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/36fd7d7c75a6c12542c90a6d647a27935a1ecaad03e0ffdb7c42db6b04d2/grpcio-1.76.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990", size = 7075684, upload-time = "2025-10-21T16:20:35.435Z" }, + { url = "https://files.pythonhosted.org/packages/38/f7/e3cdb252492278e004722306c5a8935eae91e64ea11f0af3437a7de2e2b7/grpcio-1.76.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af", size = 6611133, upload-time = "2025-10-21T16:20:37.541Z" }, + { url = "https://files.pythonhosted.org/packages/7e/20/340db7af162ccd20a0893b5f3c4a5d676af7b71105517e62279b5b61d95a/grpcio-1.76.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2", size = 7195507, upload-time = "2025-10-21T16:20:39.643Z" }, + { url = "https://files.pythonhosted.org/packages/10/f0/b2160addc1487bd8fa4810857a27132fb4ce35c1b330c2f3ac45d697b106/grpcio-1.76.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6", size = 8160651, upload-time = "2025-10-21T16:20:42.492Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2c/ac6f98aa113c6ef111b3f347854e99ebb7fb9d8f7bb3af1491d438f62af4/grpcio-1.76.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3", size = 7620568, upload-time = "2025-10-21T16:20:45.995Z" }, + { url = "https://files.pythonhosted.org/packages/90/84/7852f7e087285e3ac17a2703bc4129fafee52d77c6c82af97d905566857e/grpcio-1.76.0-cp310-cp310-win32.whl", hash = "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b", size = 3998879, upload-time = "2025-10-21T16:20:48.592Z" }, + { url = "https://files.pythonhosted.org/packages/10/30/d3d2adcbb6dd3ff59d6ac3df6ef830e02b437fb5c90990429fd180e52f30/grpcio-1.76.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b", size = 4706892, upload-time = "2025-10-21T16:20:50.697Z" }, + { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567, upload-time = "2025-10-21T16:20:52.829Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", size = 11848017, upload-time = "2025-10-21T16:20:56.705Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", size = 6412027, upload-time = "2025-10-21T16:20:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", size = 7075913, upload-time = "2025-10-21T16:21:01.645Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", size = 6615417, upload-time = "2025-10-21T16:21:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", size = 7199683, upload-time = "2025-10-21T16:21:06.195Z" }, + { url = "https://files.pythonhosted.org/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", size = 8163109, upload-time = "2025-10-21T16:21:08.498Z" }, + { url = "https://files.pythonhosted.org/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", size = 7626676, upload-time = "2025-10-21T16:21:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688, upload-time = "2025-10-21T16:21:12.746Z" }, + { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315, upload-time = "2025-10-21T16:21:15.26Z" }, + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, + { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, + { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, + { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, + { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, + { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, + { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417, upload-time = "2025-10-21T16:22:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219, upload-time = "2025-10-21T16:22:17.954Z" }, + { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826, upload-time = "2025-10-21T16:22:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550, upload-time = "2025-10-21T16:22:23.637Z" }, + { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564, upload-time = "2025-10-21T16:22:26.016Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236, upload-time = "2025-10-21T16:22:28.362Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795, upload-time = "2025-10-21T16:22:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214, upload-time = "2025-10-21T16:22:33.831Z" }, + { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961, upload-time = "2025-10-21T16:22:36.468Z" }, + { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462, upload-time = "2025-10-21T16:22:39.772Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/46/e9f19d5be65e8423f886813a2a9d0056ba94757b0c5007aa59aed1a961fa/grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd", size = 13679, upload-time = "2025-10-21T16:28:52.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/cc/27ba60ad5a5f2067963e6a858743500df408eb5855e98be778eaef8c9b02/grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18", size = 14425, upload-time = "2025-10-21T16:28:40.853Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "librt" +version = "0.7.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/24/5f3646ff414285e0f7708fa4e946b9bf538345a41d1c375c439467721a5e/librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", size = 148323, upload-time = "2026-01-14T12:56:16.876Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/13/57b06758a13550c5f09563893b004f98e9537ee6ec67b7df85c3571c8832/librt-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d", size = 56521, upload-time = "2026-01-14T12:54:40.066Z" }, + { url = "https://files.pythonhosted.org/packages/c2/24/bbea34d1452a10612fb45ac8356f95351ba40c2517e429602160a49d1fd0/librt-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b", size = 58456, upload-time = "2026-01-14T12:54:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/04/72/a168808f92253ec3a810beb1eceebc465701197dbc7e865a1c9ceb3c22c7/librt-0.7.8-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d", size = 164392, upload-time = "2026-01-14T12:54:42.843Z" }, + { url = "https://files.pythonhosted.org/packages/14/5c/4c0d406f1b02735c2e7af8ff1ff03a6577b1369b91aa934a9fa2cc42c7ce/librt-0.7.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d", size = 172959, upload-time = "2026-01-14T12:54:44.602Z" }, + { url = "https://files.pythonhosted.org/packages/82/5f/3e85351c523f73ad8d938989e9a58c7f59fb9c17f761b9981b43f0025ce7/librt-0.7.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c", size = 186717, upload-time = "2026-01-14T12:54:45.986Z" }, + { url = "https://files.pythonhosted.org/packages/08/f8/18bfe092e402d00fe00d33aa1e01dda1bd583ca100b393b4373847eade6d/librt-0.7.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c", size = 184585, upload-time = "2026-01-14T12:54:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/4e/fc/f43972ff56fd790a9fa55028a52ccea1875100edbb856b705bd393b601e3/librt-0.7.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d", size = 180497, upload-time = "2026-01-14T12:54:48.946Z" }, + { url = "https://files.pythonhosted.org/packages/e1/3a/25e36030315a410d3ad0b7d0f19f5f188e88d1613d7d3fd8150523ea1093/librt-0.7.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0", size = 200052, upload-time = "2026-01-14T12:54:50.382Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b8/f3a5a1931ae2a6ad92bf6893b9ef44325b88641d58723529e2c2935e8abe/librt-0.7.8-cp310-cp310-win32.whl", hash = "sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85", size = 43477, upload-time = "2026-01-14T12:54:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/fe/91/c4202779366bc19f871b4ad25db10fcfa1e313c7893feb942f32668e8597/librt-0.7.8-cp310-cp310-win_amd64.whl", hash = "sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c", size = 49806, upload-time = "2026-01-14T12:54:53.149Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a3/87ea9c1049f2c781177496ebee29430e4631f439b8553a4969c88747d5d8/librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f", size = 56507, upload-time = "2026-01-14T12:54:54.156Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4a/23bcef149f37f771ad30203d561fcfd45b02bc54947b91f7a9ac34815747/librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac", size = 58455, upload-time = "2026-01-14T12:54:55.978Z" }, + { url = "https://files.pythonhosted.org/packages/22/6e/46eb9b85c1b9761e0f42b6e6311e1cc544843ac897457062b9d5d0b21df4/librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c", size = 164956, upload-time = "2026-01-14T12:54:57.311Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3f/aa7c7f6829fb83989feb7ba9aa11c662b34b4bd4bd5b262f2876ba3db58d/librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8", size = 174364, upload-time = "2026-01-14T12:54:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/2d/d57d154b40b11f2cb851c4df0d4c4456bacd9b1ccc4ecb593ddec56c1a8b/librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff", size = 188034, upload-time = "2026-01-14T12:55:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/59/f9/36c4dad00925c16cd69d744b87f7001792691857d3b79187e7a673e812fb/librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3", size = 186295, upload-time = "2026-01-14T12:55:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/23/9b/8a9889d3df5efb67695a67785028ccd58e661c3018237b73ad081691d0cb/librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75", size = 181470, upload-time = "2026-01-14T12:55:02.492Z" }, + { url = "https://files.pythonhosted.org/packages/43/64/54d6ef11afca01fef8af78c230726a9394759f2addfbf7afc5e3cc032a45/librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873", size = 201713, upload-time = "2026-01-14T12:55:03.919Z" }, + { url = "https://files.pythonhosted.org/packages/2d/29/73e7ed2991330b28919387656f54109139b49e19cd72902f466bd44415fd/librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7", size = 43803, upload-time = "2026-01-14T12:55:04.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/de/66766ff48ed02b4d78deea30392ae200bcbd99ae61ba2418b49fd50a4831/librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c", size = 50080, upload-time = "2026-01-14T12:55:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e3/33450438ff3a8c581d4ed7f798a70b07c3206d298cf0b87d3806e72e3ed8/librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232", size = 43383, upload-time = "2026-01-14T12:55:07.49Z" }, + { url = "https://files.pythonhosted.org/packages/56/04/79d8fcb43cae376c7adbab7b2b9f65e48432c9eced62ac96703bcc16e09b/librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63", size = 57472, upload-time = "2026-01-14T12:55:08.528Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ba/60b96e93043d3d659da91752689023a73981336446ae82078cddf706249e/librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93", size = 58986, upload-time = "2026-01-14T12:55:09.466Z" }, + { url = "https://files.pythonhosted.org/packages/7c/26/5215e4cdcc26e7be7eee21955a7e13cbf1f6d7d7311461a6014544596fac/librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592", size = 168422, upload-time = "2026-01-14T12:55:10.499Z" }, + { url = "https://files.pythonhosted.org/packages/0f/84/e8d1bc86fa0159bfc24f3d798d92cafd3897e84c7fea7fe61b3220915d76/librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850", size = 177478, upload-time = "2026-01-14T12:55:11.577Z" }, + { url = "https://files.pythonhosted.org/packages/57/11/d0268c4b94717a18aa91df1100e767b010f87b7ae444dafaa5a2d80f33a6/librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62", size = 192439, upload-time = "2026-01-14T12:55:12.7Z" }, + { url = "https://files.pythonhosted.org/packages/8d/56/1e8e833b95fe684f80f8894ae4d8b7d36acc9203e60478fcae599120a975/librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b", size = 191483, upload-time = "2026-01-14T12:55:13.838Z" }, + { url = "https://files.pythonhosted.org/packages/17/48/f11cf28a2cb6c31f282009e2208312aa84a5ee2732859f7856ee306176d5/librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714", size = 185376, upload-time = "2026-01-14T12:55:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6a/d7c116c6da561b9155b184354a60a3d5cdbf08fc7f3678d09c95679d13d9/librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449", size = 206234, upload-time = "2026-01-14T12:55:16.571Z" }, + { url = "https://files.pythonhosted.org/packages/61/de/1975200bb0285fc921c5981d9978ce6ce11ae6d797df815add94a5a848a3/librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac", size = 44057, upload-time = "2026-01-14T12:55:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/8e/cd/724f2d0b3461426730d4877754b65d39f06a41ac9d0a92d5c6840f72b9ae/librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708", size = 50293, upload-time = "2026-01-14T12:55:19.179Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cf/7e899acd9ee5727ad8160fdcc9994954e79fab371c66535c60e13b968ffc/librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0", size = 43574, upload-time = "2026-01-14T12:55:20.185Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fe/b1f9de2829cf7fc7649c1dcd202cfd873837c5cc2fc9e526b0e7f716c3d2/librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc", size = 57500, upload-time = "2026-01-14T12:55:21.219Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d4/4a60fbe2e53b825f5d9a77325071d61cd8af8506255067bf0c8527530745/librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2", size = 59019, upload-time = "2026-01-14T12:55:22.256Z" }, + { url = "https://files.pythonhosted.org/packages/6a/37/61ff80341ba5159afa524445f2d984c30e2821f31f7c73cf166dcafa5564/librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3", size = 169015, upload-time = "2026-01-14T12:55:23.24Z" }, + { url = "https://files.pythonhosted.org/packages/1c/86/13d4f2d6a93f181ebf2fc953868826653ede494559da8268023fe567fca3/librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6", size = 178161, upload-time = "2026-01-14T12:55:24.826Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/e24ef01305954fc4d771f1f09f3dd682f9eb610e1bec188ffb719374d26e/librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d", size = 193015, upload-time = "2026-01-14T12:55:26.04Z" }, + { url = "https://files.pythonhosted.org/packages/88/a0/92b6bd060e720d7a31ed474d046a69bd55334ec05e9c446d228c4b806ae3/librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e", size = 192038, upload-time = "2026-01-14T12:55:27.208Z" }, + { url = "https://files.pythonhosted.org/packages/06/bb/6f4c650253704279c3a214dad188101d1b5ea23be0606628bc6739456624/librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca", size = 186006, upload-time = "2026-01-14T12:55:28.594Z" }, + { url = "https://files.pythonhosted.org/packages/dc/00/1c409618248d43240cadf45f3efb866837fa77e9a12a71481912135eb481/librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93", size = 206888, upload-time = "2026-01-14T12:55:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/d9/83/b2cfe8e76ff5c1c77f8a53da3d5de62d04b5ebf7cf913e37f8bca43b5d07/librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951", size = 44126, upload-time = "2026-01-14T12:55:31.44Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0b/c59d45de56a51bd2d3a401fc63449c0ac163e4ef7f523ea8b0c0dee86ec5/librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34", size = 50262, upload-time = "2026-01-14T12:55:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b9/973455cec0a1ec592395250c474164c4a58ebf3e0651ee920fef1a2623f1/librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09", size = 43600, upload-time = "2026-01-14T12:55:34.054Z" }, + { url = "https://files.pythonhosted.org/packages/1a/73/fa8814c6ce2d49c3827829cadaa1589b0bf4391660bd4510899393a23ebc/librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418", size = 57049, upload-time = "2026-01-14T12:55:35.056Z" }, + { url = "https://files.pythonhosted.org/packages/53/fe/f6c70956da23ea235fd2e3cc16f4f0b4ebdfd72252b02d1164dd58b4e6c3/librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611", size = 58689, upload-time = "2026-01-14T12:55:36.078Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4d/7a2481444ac5fba63050d9abe823e6bc16896f575bfc9c1e5068d516cdce/librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758", size = 166808, upload-time = "2026-01-14T12:55:37.595Z" }, + { url = "https://files.pythonhosted.org/packages/ac/3c/10901d9e18639f8953f57c8986796cfbf4c1c514844a41c9197cf87cb707/librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea", size = 175614, upload-time = "2026-01-14T12:55:38.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/01/5cbdde0951a5090a80e5ba44e6357d375048123c572a23eecfb9326993a7/librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac", size = 189955, upload-time = "2026-01-14T12:55:39.939Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b4/e80528d2f4b7eaf1d437fcbd6fc6ba4cbeb3e2a0cb9ed5a79f47c7318706/librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398", size = 189370, upload-time = "2026-01-14T12:55:41.057Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ab/938368f8ce31a9787ecd4becb1e795954782e4312095daf8fd22420227c8/librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81", size = 183224, upload-time = "2026-01-14T12:55:42.328Z" }, + { url = "https://files.pythonhosted.org/packages/3c/10/559c310e7a6e4014ac44867d359ef8238465fb499e7eb31b6bfe3e3f86f5/librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83", size = 203541, upload-time = "2026-01-14T12:55:43.501Z" }, + { url = "https://files.pythonhosted.org/packages/f8/db/a0db7acdb6290c215f343835c6efda5b491bb05c3ddc675af558f50fdba3/librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d", size = 40657, upload-time = "2026-01-14T12:55:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/72/e0/4f9bdc2a98a798511e81edcd6b54fe82767a715e05d1921115ac70717f6f/librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44", size = 46835, upload-time = "2026-01-14T12:55:45.655Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3d/59c6402e3dec2719655a41ad027a7371f8e2334aa794ed11533ad5f34969/librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce", size = 39885, upload-time = "2026-01-14T12:55:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9c/2481d80950b83085fb14ba3c595db56330d21bbc7d88a19f20165f3538db/librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f", size = 59161, upload-time = "2026-01-14T12:55:48.45Z" }, + { url = "https://files.pythonhosted.org/packages/96/79/108df2cfc4e672336765d54e3ff887294c1cc36ea4335c73588875775527/librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde", size = 61008, upload-time = "2026-01-14T12:55:49.527Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/30179898f9994a5637459d6e169b6abdc982012c0a4b2d4c26f50c06f911/librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e", size = 187199, upload-time = "2026-01-14T12:55:50.587Z" }, + { url = "https://files.pythonhosted.org/packages/b4/da/f7563db55cebdc884f518ba3791ad033becc25ff68eb70902b1747dc0d70/librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b", size = 198317, upload-time = "2026-01-14T12:55:51.991Z" }, + { url = "https://files.pythonhosted.org/packages/b3/6c/4289acf076ad371471fa86718c30ae353e690d3de6167f7db36f429272f1/librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666", size = 210334, upload-time = "2026-01-14T12:55:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7f/377521ac25b78ac0a5ff44127a0360ee6d5ddd3ce7327949876a30533daa/librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581", size = 211031, upload-time = "2026-01-14T12:55:54.827Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/e1e96c3e20b23d00cf90f4aad48f0deb4cdfec2f0ed8380d0d85acf98bbf/librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a", size = 204581, upload-time = "2026-01-14T12:55:56.811Z" }, + { url = "https://files.pythonhosted.org/packages/43/71/0f5d010e92ed9747e14bef35e91b6580533510f1e36a8a09eb79ee70b2f0/librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca", size = 224731, upload-time = "2026-01-14T12:55:58.175Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/07fb6ab5c39a4ca9af3e37554f9d42f25c464829254d72e4ebbd81da351c/librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", size = 41173, upload-time = "2026-01-14T12:55:59.315Z" }, + { url = "https://files.pythonhosted.org/packages/24/d4/7e4be20993dc6a782639625bd2f97f3c66125c7aa80c82426956811cfccf/librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", size = 47668, upload-time = "2026-01-14T12:56:00.261Z" }, + { url = "https://files.pythonhosted.org/packages/fc/85/69f92b2a7b3c0f88ffe107c86b952b397004b5b8ea5a81da3d9c04c04422/librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", size = 40550, upload-time = "2026-01-14T12:56:01.542Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/34/2b1bc18424f3ad9af577f6ce23600319968a70575bd7db31ce66731bbef9/numpy-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5", size = 16944563, upload-time = "2026-01-10T06:42:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/2c/57/26e5f97d075aef3794045a6ca9eada6a4ed70eb9a40e7a4a93f9ac80d704/numpy-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:899d2c18024984814ac7e83f8f49d8e8180e2fbe1b2e252f2e7f1d06bea92425", size = 12645658, upload-time = "2026-01-10T06:42:17.298Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ba/80fc0b1e3cb2fd5c6143f00f42eb67762aa043eaa05ca924ecc3222a7849/numpy-2.4.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:09aa8a87e45b55a1c2c205d42e2808849ece5c484b2aab11fecabec3841cafba", size = 5474132, upload-time = "2026-01-10T06:42:19.637Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/0a5b9a397f0e865ec171187c78d9b57e5588afc439a04ba9cab1ebb2c945/numpy-2.4.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:edee228f76ee2dab4579fad6f51f6a305de09d444280109e0f75df247ff21501", size = 6804159, upload-time = "2026-01-10T06:42:21.44Z" }, + { url = "https://files.pythonhosted.org/packages/86/9c/841c15e691c7085caa6fd162f063eff494099c8327aeccd509d1ab1e36ab/numpy-2.4.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a92f227dbcdc9e4c3e193add1a189a9909947d4f8504c576f4a732fd0b54240a", size = 14708058, upload-time = "2026-01-10T06:42:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9d/7862db06743f489e6a502a3b93136d73aea27d97b2cf91504f70a27501d6/numpy-2.4.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:538bf4ec353709c765ff75ae616c34d3c3dca1a68312727e8f2676ea644f8509", size = 16651501, upload-time = "2026-01-10T06:42:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9c/6fc34ebcbd4015c6e5f0c0ce38264010ce8a546cb6beacb457b84a75dfc8/numpy-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ac08c63cb7779b85e9d5318e6c3518b424bc1f364ac4cb2c6136f12e5ff2dccc", size = 16492627, upload-time = "2026-01-10T06:42:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/aa/63/2494a8597502dacda439f61b3c0db4da59928150e62be0e99395c3ad23c5/numpy-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f9c360ecef085e5841c539a9a12b883dff005fbd7ce46722f5e9cef52634d82", size = 18585052, upload-time = "2026-01-10T06:42:31.312Z" }, + { url = "https://files.pythonhosted.org/packages/6a/93/098e1162ae7522fc9b618d6272b77404c4656c72432ecee3abc029aa3de0/numpy-2.4.1-cp311-cp311-win32.whl", hash = "sha256:0f118ce6b972080ba0758c6087c3617b5ba243d806268623dc34216d69099ba0", size = 6236575, upload-time = "2026-01-10T06:42:33.872Z" }, + { url = "https://files.pythonhosted.org/packages/8c/de/f5e79650d23d9e12f38a7bc6b03ea0835b9575494f8ec94c11c6e773b1b1/numpy-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:18e14c4d09d55eef39a6ab5b08406e84bc6869c1e34eef45564804f90b7e0574", size = 12604479, upload-time = "2026-01-10T06:42:35.778Z" }, + { url = "https://files.pythonhosted.org/packages/dd/65/e1097a7047cff12ce3369bd003811516b20ba1078dbdec135e1cd7c16c56/numpy-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:6461de5113088b399d655d45c3897fa188766415d0f568f175ab071c8873bd73", size = 10578325, upload-time = "2026-01-10T06:42:38.518Z" }, + { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ee/34b7930eb61e79feb4478800a4b95b46566969d837546aa7c034c742ef98/numpy-2.4.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0", size = 6549459, upload-time = "2026-01-10T06:42:48.152Z" }, + { url = "https://files.pythonhosted.org/packages/79/e3/5f115fae982565771be994867c89bcd8d7208dbfe9469185497d70de5ddf/numpy-2.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c", size = 14404859, upload-time = "2026-01-10T06:42:49.947Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7d/9c8a781c88933725445a859cac5d01b5871588a15969ee6aeb618ba99eee/numpy-2.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02", size = 16371419, upload-time = "2026-01-10T06:42:52.409Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d2/8aa084818554543f17cf4162c42f162acbd3bb42688aefdba6628a859f77/numpy-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162", size = 16182131, upload-time = "2026-01-10T06:42:54.694Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/0425216684297c58a8df35f3284ef56ec4a043e6d283f8a59c53562caf1b/numpy-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9", size = 18295342, upload-time = "2026-01-10T06:42:56.991Z" }, + { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" }, + { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" }, + { url = "https://files.pythonhosted.org/packages/04/68/732d4b7811c00775f3bd522a21e8dd5a23f77eb11acdeb663e4a4ebf0ef4/numpy-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d797454e37570cfd61143b73b8debd623c3c0952959adb817dd310a483d58a1b", size = 16652495, upload-time = "2026-01-10T06:43:06.283Z" }, + { url = "https://files.pythonhosted.org/packages/20/ca/857722353421a27f1465652b2c66813eeeccea9d76d5f7b74b99f298e60e/numpy-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c55962006156aeef1629b953fd359064aa47e4d82cfc8e67f0918f7da3344f", size = 12368657, upload-time = "2026-01-10T06:43:09.094Z" }, + { url = "https://files.pythonhosted.org/packages/81/0d/2377c917513449cc6240031a79d30eb9a163d32a91e79e0da47c43f2c0c8/numpy-2.4.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:71abbea030f2cfc3092a0ff9f8c8fdefdc5e0bf7d9d9c99663538bb0ecdac0b9", size = 5197256, upload-time = "2026-01-10T06:43:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/17/39/569452228de3f5de9064ac75137082c6214be1f5c532016549a7923ab4b5/numpy-2.4.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5b55aa56165b17aaf15520beb9cbd33c9039810e0d9643dd4379e44294c7303e", size = 6545212, upload-time = "2026-01-10T06:43:15.661Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/77333f4d1e4dac4395385482557aeecf4826e6ff517e32ca48e1dafbe42a/numpy-2.4.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0faba4a331195bfa96f93dd9dfaa10b2c7aa8cda3a02b7fd635e588fe821bf5", size = 14402871, upload-time = "2026-01-10T06:43:17.324Z" }, + { url = "https://files.pythonhosted.org/packages/ba/87/d341e519956273b39d8d47969dd1eaa1af740615394fe67d06f1efa68773/numpy-2.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e3087f53e2b4428766b54932644d148613c5a595150533ae7f00dab2f319a8", size = 16359305, upload-time = "2026-01-10T06:43:19.376Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/789132c6666288eaa20ae8066bb99eba1939362e8f1a534949a215246e97/numpy-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:49e792ec351315e16da54b543db06ca8a86985ab682602d90c60ef4ff4db2a9c", size = 16181909, upload-time = "2026-01-10T06:43:21.808Z" }, + { url = "https://files.pythonhosted.org/packages/cf/b8/090b8bd27b82a844bb22ff8fdf7935cb1980b48d6e439ae116f53cdc2143/numpy-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79e9e06c4c2379db47f3f6fc7a8652e7498251789bf8ff5bd43bf478ef314ca2", size = 18284380, upload-time = "2026-01-10T06:43:23.957Z" }, + { url = "https://files.pythonhosted.org/packages/67/78/722b62bd31842ff029412271556a1a27a98f45359dea78b1548a3a9996aa/numpy-2.4.1-cp313-cp313-win32.whl", hash = "sha256:3d1a100e48cb266090a031397863ff8a30050ceefd798f686ff92c67a486753d", size = 5957089, upload-time = "2026-01-10T06:43:27.535Z" }, + { url = "https://files.pythonhosted.org/packages/da/a6/cf32198b0b6e18d4fbfa9a21a992a7fca535b9bb2b0cdd217d4a3445b5ca/numpy-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:92a0e65272fd60bfa0d9278e0484c2f52fe03b97aedc02b357f33fe752c52ffb", size = 12307230, upload-time = "2026-01-10T06:43:29.298Z" }, + { url = "https://files.pythonhosted.org/packages/44/6c/534d692bfb7d0afe30611320c5fb713659dcb5104d7cc182aff2aea092f5/numpy-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:20d4649c773f66cc2fc36f663e091f57c3b7655f936a4c681b4250855d1da8f5", size = 10313125, upload-time = "2026-01-10T06:43:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/da/a1/354583ac5c4caa566de6ddfbc42744409b515039e085fab6e0ff942e0df5/numpy-2.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f93bc6892fe7b0663e5ffa83b61aab510aacffd58c16e012bb9352d489d90cb7", size = 12496156, upload-time = "2026-01-10T06:43:34.237Z" }, + { url = "https://files.pythonhosted.org/packages/51/b0/42807c6e8cce58c00127b1dc24d365305189991f2a7917aa694a109c8d7d/numpy-2.4.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:178de8f87948163d98a4c9ab5bee4ce6519ca918926ec8df195af582de28544d", size = 5324663, upload-time = "2026-01-10T06:43:36.211Z" }, + { url = "https://files.pythonhosted.org/packages/fe/55/7a621694010d92375ed82f312b2f28017694ed784775269115323e37f5e2/numpy-2.4.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:98b35775e03ab7f868908b524fc0a84d38932d8daf7b7e1c3c3a1b6c7a2c9f15", size = 6645224, upload-time = "2026-01-10T06:43:37.884Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/9fa8635ed9d7c847d87e30c834f7109fac5e88549d79ef3324ab5c20919f/numpy-2.4.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941c2a93313d030f219f3a71fd3d91a728b82979a5e8034eb2e60d394a2b83f9", size = 14462352, upload-time = "2026-01-10T06:43:39.479Z" }, + { url = "https://files.pythonhosted.org/packages/03/d1/8cf62d8bb2062da4fb82dd5d49e47c923f9c0738032f054e0a75342faba7/numpy-2.4.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:529050522e983e00a6c1c6b67411083630de8b57f65e853d7b03d9281b8694d2", size = 16407279, upload-time = "2026-01-10T06:43:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/86/1c/95c86e17c6b0b31ce6ef219da00f71113b220bcb14938c8d9a05cee0ff53/numpy-2.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2302dc0224c1cbc49bb94f7064f3f923a971bfae45c33870dcbff63a2a550505", size = 16248316, upload-time = "2026-01-10T06:43:44.121Z" }, + { url = "https://files.pythonhosted.org/packages/30/b4/e7f5ff8697274c9d0fa82398b6a372a27e5cef069b37df6355ccb1f1db1a/numpy-2.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9171a42fcad32dcf3fa86f0a4faa5e9f8facefdb276f54b8b390d90447cff4e2", size = 18329884, upload-time = "2026-01-10T06:43:46.613Z" }, + { url = "https://files.pythonhosted.org/packages/37/a4/b073f3e9d77f9aec8debe8ca7f9f6a09e888ad1ba7488f0c3b36a94c03ac/numpy-2.4.1-cp313-cp313t-win32.whl", hash = "sha256:382ad67d99ef49024f11d1ce5dcb5ad8432446e4246a4b014418ba3a1175a1f4", size = 6081138, upload-time = "2026-01-10T06:43:48.854Z" }, + { url = "https://files.pythonhosted.org/packages/16/16/af42337b53844e67752a092481ab869c0523bc95c4e5c98e4dac4e9581ac/numpy-2.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:62fea415f83ad8fdb6c20840578e5fbaf5ddd65e0ec6c3c47eda0f69da172510", size = 12447478, upload-time = "2026-01-10T06:43:50.476Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f8/fa85b2eac68ec631d0b631abc448552cb17d39afd17ec53dcbcc3537681a/numpy-2.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a7870e8c5fc11aef57d6fea4b4085e537a3a60ad2cdd14322ed531fdca68d261", size = 10382981, upload-time = "2026-01-10T06:43:52.575Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a7/ef08d25698e0e4b4efbad8d55251d20fe2a15f6d9aa7c9b30cd03c165e6f/numpy-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3869ea1ee1a1edc16c29bbe3a2f2a4e515cc3a44d43903ad41e0cacdbaf733dc", size = 16652046, upload-time = "2026-01-10T06:43:54.797Z" }, + { url = "https://files.pythonhosted.org/packages/8f/39/e378b3e3ca13477e5ac70293ec027c438d1927f18637e396fe90b1addd72/numpy-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e867df947d427cdd7a60e3e271729090b0f0df80f5f10ab7dd436f40811699c3", size = 12378858, upload-time = "2026-01-10T06:43:57.099Z" }, + { url = "https://files.pythonhosted.org/packages/c3/74/7ec6154f0006910ed1fdbb7591cf4432307033102b8a22041599935f8969/numpy-2.4.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e3bd2cb07841166420d2fa7146c96ce00cb3410664cbc1a6be028e456c4ee220", size = 5207417, upload-time = "2026-01-10T06:43:59.037Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b7/053ac11820d84e42f8feea5cb81cc4fcd1091499b45b1ed8c7415b1bf831/numpy-2.4.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:f0a90aba7d521e6954670550e561a4cb925713bd944445dbe9e729b71f6cabee", size = 6542643, upload-time = "2026-01-10T06:44:01.852Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c4/2e7908915c0e32ca636b92e4e4a3bdec4cb1e7eb0f8aedf1ed3c68a0d8cd/numpy-2.4.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d558123217a83b2d1ba316b986e9248a1ed1971ad495963d555ccd75dcb1556", size = 14418963, upload-time = "2026-01-10T06:44:04.047Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/3ed5083d94e7ffd7c404e54619c088e11f2e1939a9544f5397f4adb1b8ba/numpy-2.4.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f44de05659b67d20499cbc96d49f2650769afcb398b79b324bb6e297bfe3844", size = 16363811, upload-time = "2026-01-10T06:44:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/0e/68/42b66f1852bf525050a67315a4fb94586ab7e9eaa541b1bef530fab0c5dd/numpy-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:69e7419c9012c4aaf695109564e3387f1259f001b4326dfa55907b098af082d3", size = 16197643, upload-time = "2026-01-10T06:44:08.33Z" }, + { url = "https://files.pythonhosted.org/packages/d2/40/e8714fc933d85f82c6bfc7b998a0649ad9769a32f3494ba86598aaf18a48/numpy-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffd257026eb1b34352e749d7cc1678b5eeec3e329ad8c9965a797e08ccba205", size = 18289601, upload-time = "2026-01-10T06:44:10.841Z" }, + { url = "https://files.pythonhosted.org/packages/80/9a/0d44b468cad50315127e884802351723daca7cf1c98d102929468c81d439/numpy-2.4.1-cp314-cp314-win32.whl", hash = "sha256:727c6c3275ddefa0dc078524a85e064c057b4f4e71ca5ca29a19163c607be745", size = 6005722, upload-time = "2026-01-10T06:44:13.332Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bb/c6513edcce5a831810e2dddc0d3452ce84d208af92405a0c2e58fd8e7881/numpy-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:7d5d7999df434a038d75a748275cd6c0094b0ecdb0837342b332a82defc4dc4d", size = 12438590, upload-time = "2026-01-10T06:44:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/e9/da/a598d5cb260780cf4d255102deba35c1d072dc028c4547832f45dd3323a8/numpy-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:ce9ce141a505053b3c7bce3216071f3bf5c182b8b28930f14cd24d43932cd2df", size = 10596180, upload-time = "2026-01-10T06:44:17.386Z" }, + { url = "https://files.pythonhosted.org/packages/de/bc/ea3f2c96fcb382311827231f911723aeff596364eb6e1b6d1d91128aa29b/numpy-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e53170557d37ae404bf8d542ca5b7c629d6efa1117dac6a83e394142ea0a43f", size = 12498774, upload-time = "2026-01-10T06:44:19.467Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ab/ef9d939fe4a812648c7a712610b2ca6140b0853c5efea361301006c02ae5/numpy-2.4.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:a73044b752f5d34d4232f25f18160a1cc418ea4507f5f11e299d8ac36875f8a0", size = 5327274, upload-time = "2026-01-10T06:44:23.189Z" }, + { url = "https://files.pythonhosted.org/packages/bd/31/d381368e2a95c3b08b8cf7faac6004849e960f4a042d920337f71cef0cae/numpy-2.4.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:fb1461c99de4d040666ca0444057b06541e5642f800b71c56e6ea92d6a853a0c", size = 6648306, upload-time = "2026-01-10T06:44:25.012Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e5/0989b44ade47430be6323d05c23207636d67d7362a1796ccbccac6773dd2/numpy-2.4.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423797bdab2eeefbe608d7c1ec7b2b4fd3c58d51460f1ee26c7500a1d9c9ee93", size = 14464653, upload-time = "2026-01-10T06:44:26.706Z" }, + { url = "https://files.pythonhosted.org/packages/10/a7/cfbe475c35371cae1358e61f20c5f075badc18c4797ab4354140e1d283cf/numpy-2.4.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52b5f61bdb323b566b528899cc7db2ba5d1015bda7ea811a8bcf3c89c331fa42", size = 16405144, upload-time = "2026-01-10T06:44:29.378Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a3/0c63fe66b534888fa5177cc7cef061541064dbe2b4b60dcc60ffaf0d2157/numpy-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42d7dd5fa36d16d52a84f821eb96031836fd405ee6955dd732f2023724d0aa01", size = 16247425, upload-time = "2026-01-10T06:44:31.721Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2b/55d980cfa2c93bd40ff4c290bf824d792bd41d2fe3487b07707559071760/numpy-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7b6b5e28bbd47b7532698e5db2fe1db693d84b58c254e4389d99a27bb9b8f6b", size = 18330053, upload-time = "2026-01-10T06:44:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/23/12/8b5fc6b9c487a09a7957188e0943c9ff08432c65e34567cabc1623b03a51/numpy-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:5de60946f14ebe15e713a6f22850c2372fa72f4ff9a432ab44aa90edcadaa65a", size = 6152482, upload-time = "2026-01-10T06:44:36.798Z" }, + { url = "https://files.pythonhosted.org/packages/00/a5/9f8ca5856b8940492fc24fbe13c1bc34d65ddf4079097cf9e53164d094e1/numpy-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f085da926c0d491ffff3096f91078cc97ea67e7e6b65e490bc8dcda65663be2", size = 12627117, upload-time = "2026-01-10T06:44:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0d/eca3d962f9eef265f01a8e0d20085c6dd1f443cbffc11b6dede81fd82356/numpy-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6436cffb4f2bf26c974344439439c95e152c9a527013f26b3577be6c2ca64295", size = 10667121, upload-time = "2026-01-10T06:44:41.644Z" }, + { url = "https://files.pythonhosted.org/packages/1e/48/d86f97919e79314a1cdee4c832178763e6e98e623e123d0bada19e92c15a/numpy-2.4.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ad35f20be147a204e28b6a0575fbf3540c5e5f802634d4258d55b1ff5facce1", size = 16822202, upload-time = "2026-01-10T06:44:43.738Z" }, + { url = "https://files.pythonhosted.org/packages/51/e9/1e62a7f77e0f37dcfb0ad6a9744e65df00242b6ea37dfafb55debcbf5b55/numpy-2.4.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8097529164c0f3e32bb89412a0905d9100bf434d9692d9fc275e18dcf53c9344", size = 12569985, upload-time = "2026-01-10T06:44:45.945Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7e/914d54f0c801342306fdcdce3e994a56476f1b818c46c47fc21ae968088c/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ea66d2b41ca4a1630aae5507ee0a71647d3124d1741980138aa8f28f44dac36e", size = 5398484, upload-time = "2026-01-10T06:44:48.012Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d8/9570b68584e293a33474e7b5a77ca404f1dcc655e40050a600dee81d27fb/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d3f8f0df9f4b8be57b3bf74a1d087fec68f927a2fab68231fdb442bf2c12e426", size = 6713216, upload-time = "2026-01-10T06:44:49.725Z" }, + { url = "https://files.pythonhosted.org/packages/33/9b/9dd6e2db8d49eb24f86acaaa5258e5f4c8ed38209a4ee9de2d1a0ca25045/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2023ef86243690c2791fd6353e5b4848eedaa88ca8a2d129f462049f6d484696", size = 14538937, upload-time = "2026-01-10T06:44:51.498Z" }, + { url = "https://files.pythonhosted.org/packages/53/87/d5bd995b0f798a37105b876350d346eea5838bd8f77ea3d7a48392f3812b/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8361ea4220d763e54cff2fbe7d8c93526b744f7cd9ddab47afeff7e14e8503be", size = 16479830, upload-time = "2026-01-10T06:44:53.931Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c7/b801bf98514b6ae6475e941ac05c58e6411dd863ea92916bfd6d510b08c1/numpy-2.4.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4f1b68ff47680c2925f8063402a693ede215f0257f02596b1318ecdfb1d79e33", size = 12492579, upload-time = "2026-01-10T06:44:57.094Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/89/9cbe2f4bba860e149108b683bc2efec21f14d5f7ed6e25562ad86acbc373/proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4", size = 56158, upload-time = "2025-12-16T13:46:25.729Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/24/3b7a0818484df9c28172857af32c2397b6d8fcd99d9468bd4684f98ebf0a/proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82", size = 50205, upload-time = "2025-12-16T13:46:24.76Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, +] + +[[package]] +name = "pyarrow" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/33/ffd9c3eb087fa41dd79c3cf20c4c0ae3cdb877c4f8e1107a446006344924/pyarrow-23.0.0.tar.gz", hash = "sha256:180e3150e7edfcd182d3d9afba72f7cf19839a497cc76555a8dce998a8f67615", size = 1167185, upload-time = "2026-01-18T16:19:42.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/2f/23e042a5aa99bcb15e794e14030e8d065e00827e846e53a66faec73c7cd6/pyarrow-23.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cbdc2bf5947aa4d462adcf8453cf04aee2f7932653cb67a27acd96e5e8528a67", size = 34281861, upload-time = "2026-01-18T16:13:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/8b/65/1651933f504b335ec9cd8f99463718421eb08d883ed84f0abd2835a16cad/pyarrow-23.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:4d38c836930ce15cd31dce20114b21ba082da231c884bdc0a7b53e1477fe7f07", size = 35825067, upload-time = "2026-01-18T16:13:42.549Z" }, + { url = "https://files.pythonhosted.org/packages/84/ec/d6fceaec050c893f4e35c0556b77d4cc9973fcc24b0a358a5781b1234582/pyarrow-23.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4222ff8f76919ecf6c716175a0e5fddb5599faeed4c56d9ea41a2c42be4998b2", size = 44458539, upload-time = "2026-01-18T16:13:52.975Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/369f134d652b21db62fe3ec1c5c2357e695f79eb67394b8a93f3a2b2cffa/pyarrow-23.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:87f06159cbe38125852657716889296c83c37b4d09a5e58f3d10245fd1f69795", size = 47535889, upload-time = "2026-01-18T16:14:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/a3/95/f37b6a252fdbf247a67a78fb3f61a529fe0600e304c4d07741763d3522b1/pyarrow-23.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1675c374570d8b91ea6d4edd4608fa55951acd44e0c31bd146e091b4005de24f", size = 48157777, upload-time = "2026-01-18T16:14:12.483Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/fb94923108c9c6415dab677cf1f066d3307798eafc03f9a65ab4abc61056/pyarrow-23.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:247374428fde4f668f138b04031a7e7077ba5fa0b5b1722fdf89a017bf0b7ee0", size = 50580441, upload-time = "2026-01-18T16:14:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/ae/78/897ba6337b517fc8e914891e1bd918da1c4eb8e936a553e95862e67b80f6/pyarrow-23.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:de53b1bd3b88a2ee93c9af412c903e57e738c083be4f6392288294513cd8b2c1", size = 27530028, upload-time = "2026-01-18T16:14:27.353Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c0/57fe251102ca834fee0ef69a84ad33cc0ff9d5dfc50f50b466846356ecd7/pyarrow-23.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5574d541923efcbfdf1294a2746ae3b8c2498a2dc6cd477882f6f4e7b1ac08d3", size = 34276762, upload-time = "2026-01-18T16:14:34.128Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4e/24130286548a5bc250cbed0b6bbf289a2775378a6e0e6f086ae8c68fc098/pyarrow-23.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:2ef0075c2488932e9d3c2eb3482f9459c4be629aa673b725d5e3cf18f777f8e4", size = 35821420, upload-time = "2026-01-18T16:14:40.699Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/a869e8529d487aa2e842d6c8865eb1e2c9ec33ce2786eb91104d2c3e3f10/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:65666fc269669af1ef1c14478c52222a2aa5c907f28b68fb50a203c777e4f60c", size = 44457412, upload-time = "2026-01-18T16:14:49.051Z" }, + { url = "https://files.pythonhosted.org/packages/36/81/1de4f0edfa9a483bbdf0082a05790bd6a20ed2169ea12a65039753be3a01/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4d85cb6177198f3812db4788e394b757223f60d9a9f5ad6634b3e32be1525803", size = 47534285, upload-time = "2026-01-18T16:14:56.748Z" }, + { url = "https://files.pythonhosted.org/packages/f2/04/464a052d673b5ece074518f27377861662449f3c1fdb39ce740d646fd098/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1a9ff6fa4141c24a03a1a434c63c8fa97ce70f8f36bccabc18ebba905ddf0f17", size = 48157913, upload-time = "2026-01-18T16:15:05.114Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1b/32a4de9856ee6688c670ca2def588382e573cce45241a965af04c2f61687/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:84839d060a54ae734eb60a756aeacb62885244aaa282f3c968f5972ecc7b1ecc", size = 50582529, upload-time = "2026-01-18T16:15:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/d6581f03e9b9e44ea60b52d1750ee1a7678c484c06f939f45365a45f7eef/pyarrow-23.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a149a647dbfe928ce8830a713612aa0b16e22c64feac9d1761529778e4d4eaa5", size = 27542646, upload-time = "2026-01-18T16:15:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bd/c861d020831ee57609b73ea721a617985ece817684dc82415b0bc3e03ac3/pyarrow-23.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5961a9f646c232697c24f54d3419e69b4261ba8a8b66b0ac54a1851faffcbab8", size = 34189116, upload-time = "2026-01-18T16:15:28.054Z" }, + { url = "https://files.pythonhosted.org/packages/8c/23/7725ad6cdcbaf6346221391e7b3eecd113684c805b0a95f32014e6fa0736/pyarrow-23.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:632b3e7c3d232f41d64e1a4a043fb82d44f8a349f339a1188c6a0dd9d2d47d8a", size = 35803831, upload-time = "2026-01-18T16:15:33.798Z" }, + { url = "https://files.pythonhosted.org/packages/57/06/684a421543455cdc2944d6a0c2cc3425b028a4c6b90e34b35580c4899743/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:76242c846db1411f1d6c2cc3823be6b86b40567ee24493344f8226ba34a81333", size = 44436452, upload-time = "2026-01-18T16:15:41.598Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6f/8f9eb40c2328d66e8b097777ddcf38494115ff9f1b5bc9754ba46991191e/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b73519f8b52ae28127000986bf228fda781e81d3095cd2d3ece76eb5cf760e1b", size = 47557396, upload-time = "2026-01-18T16:15:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/10/6e/f08075f1472e5159553501fde2cc7bc6700944bdabe49a03f8a035ee6ccd/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:068701f6823449b1b6469120f399a1239766b117d211c5d2519d4ed5861f75de", size = 48147129, upload-time = "2026-01-18T16:16:00.299Z" }, + { url = "https://files.pythonhosted.org/packages/7d/82/d5a680cd507deed62d141cc7f07f7944a6766fc51019f7f118e4d8ad0fb8/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1801ba947015d10e23bca9dd6ef5d0e9064a81569a89b6e9a63b59224fd060df", size = 50596642, upload-time = "2026-01-18T16:16:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/a9/26/4f29c61b3dce9fa7780303b86895ec6a0917c9af927101daaaf118fbe462/pyarrow-23.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:52265266201ec25b6839bf6bd4ea918ca6d50f31d13e1cf200b4261cd11dc25c", size = 27660628, upload-time = "2026-01-18T16:16:15.28Z" }, + { url = "https://files.pythonhosted.org/packages/66/34/564db447d083ec7ff93e0a883a597d2f214e552823bfc178a2d0b1f2c257/pyarrow-23.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:ad96a597547af7827342ffb3c503c8316e5043bb09b47a84885ce39394c96e00", size = 34184630, upload-time = "2026-01-18T16:16:22.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3a/3999daebcb5e6119690c92a621c4d78eef2ffba7a0a1b56386d2875fcd77/pyarrow-23.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:b9edf990df77c2901e79608f08c13fbde60202334a4fcadb15c1f57bf7afee43", size = 35796820, upload-time = "2026-01-18T16:16:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/39195233056c6a8d0976d7d1ac1cd4fe21fb0ec534eca76bc23ef3f60e11/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:36d1b5bc6ddcaff0083ceec7e2561ed61a51f49cce8be079ee8ed406acb6fdef", size = 44438735, upload-time = "2026-01-18T16:16:38.79Z" }, + { url = "https://files.pythonhosted.org/packages/2c/41/6a7328ee493527e7afc0c88d105ecca69a3580e29f2faaeac29308369fd7/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4292b889cd224f403304ddda8b63a36e60f92911f89927ec8d98021845ea21be", size = 47557263, upload-time = "2026-01-18T16:16:46.248Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ee/34e95b21ee84db494eae60083ddb4383477b31fb1fd19fd866d794881696/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dfd9e133e60eaa847fd80530a1b89a052f09f695d0b9c34c235ea6b2e0924cf7", size = 48153529, upload-time = "2026-01-18T16:16:53.412Z" }, + { url = "https://files.pythonhosted.org/packages/52/88/8a8d83cea30f4563efa1b7bf51d241331ee5cd1b185a7e063f5634eca415/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832141cc09fac6aab1cd3719951d23301396968de87080c57c9a7634e0ecd068", size = 50598851, upload-time = "2026-01-18T16:17:01.133Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4c/2929c4be88723ba025e7b3453047dc67e491c9422965c141d24bab6b5962/pyarrow-23.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:7a7d067c9a88faca655c71bcc30ee2782038d59c802d57950826a07f60d83c4c", size = 27577747, upload-time = "2026-01-18T16:18:02.413Z" }, + { url = "https://files.pythonhosted.org/packages/64/52/564a61b0b82d72bd68ec3aef1adda1e3eba776f89134b9ebcb5af4b13cb6/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ce9486e0535a843cf85d990e2ec5820a47918235183a5c7b8b97ed7e92c2d47d", size = 34446038, upload-time = "2026-01-18T16:17:07.861Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/232d4f9855fd1de0067c8a7808a363230d223c83aeee75e0fe6eab851ba9/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:075c29aeaa685fd1182992a9ed2499c66f084ee54eea47da3eb76e125e06064c", size = 35921142, upload-time = "2026-01-18T16:17:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/60af606a3748367b906bb82d41f0032e059f075444445d47e32a7ff1df62/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:799965a5379589510d888be3094c2296efd186a17ca1cef5b77703d4d5121f53", size = 44490374, upload-time = "2026-01-18T16:17:23.93Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/7731543050a678ea3a413955a2d5d80d2a642f270aa57a3cb7d5a86e3f46/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ef7cac8fe6fccd8b9e7617bfac785b0371a7fe26af59463074e4882747145d40", size = 47527896, upload-time = "2026-01-18T16:17:33.393Z" }, + { url = "https://files.pythonhosted.org/packages/5a/90/f3342553b7ac9879413aed46500f1637296f3c8222107523a43a1c08b42a/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15a414f710dc927132dd67c361f78c194447479555af57317066ee5116b90e9e", size = 48210401, upload-time = "2026-01-18T16:17:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/f3/da/9862ade205ecc46c172b6ce5038a74b5151c7401e36255f15975a45878b2/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e0d2e6915eca7d786be6a77bf227fbc06d825a75b5b5fe9bcbef121dec32685", size = 50579677, upload-time = "2026-01-18T16:17:50.241Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4c/f11f371f5d4740a5dafc2e11c76bcf42d03dfdb2d68696da97de420b6963/pyarrow-23.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4b317ea6e800b5704e5e5929acb6e2dc13e9276b708ea97a39eb8b345aa2658b", size = 27631889, upload-time = "2026-01-18T16:17:56.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/15aec78bcf43a0c004067bd33eb5352836a29a49db8581fc56f2b6ca88b7/pyarrow-23.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:20b187ed9550d233a872074159f765f52f9d92973191cd4b93f293a19efbe377", size = 34213265, upload-time = "2026-01-18T16:18:07.904Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/deb2c594bbba41c37c5d9aa82f510376998352aa69dfcb886cb4b18ad80f/pyarrow-23.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:18ec84e839b493c3886b9b5e06861962ab4adfaeb79b81c76afbd8d84c7d5fda", size = 35819211, upload-time = "2026-01-18T16:18:13.94Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/ee82af693cb7b5b2b74f6524cdfede0e6ace779d7720ebca24d68b57c36b/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e438dd3f33894e34fd02b26bd12a32d30d006f5852315f611aa4add6c7fab4bc", size = 44502313, upload-time = "2026-01-18T16:18:20.367Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/95c61ad82236495f3c31987e85135926ba3ec7f3819296b70a68d8066b49/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:a244279f240c81f135631be91146d7fa0e9e840e1dfed2aba8483eba25cd98e6", size = 47585886, upload-time = "2026-01-18T16:18:27.544Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6e/a72d901f305201802f016d015de1e05def7706fff68a1dedefef5dc7eff7/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c4692e83e42438dba512a570c6eaa42be2f8b6c0f492aea27dec54bdc495103a", size = 48207055, upload-time = "2026-01-18T16:18:35.425Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/5de029c537630ca18828db45c30e2a78da03675a70ac6c3528203c416fe3/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae7f30f898dfe44ea69654a35c93e8da4cef6606dc4c72394068fd95f8e9f54a", size = 50619812, upload-time = "2026-01-18T16:18:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/59/8d/2af846cd2412e67a087f5bda4a8e23dfd4ebd570f777db2e8686615dafc1/pyarrow-23.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:5b86bb649e4112fb0614294b7d0a175c7513738876b89655605ebb87c804f861", size = 28263851, upload-time = "2026-01-18T16:19:38.567Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7f/caab863e587041156f6786c52e64151b7386742c8c27140f637176e9230e/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ebc017d765d71d80a3f8584ca0566b53e40464586585ac64176115baa0ada7d3", size = 34463240, upload-time = "2026-01-18T16:18:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fa/3a5b8c86c958e83622b40865e11af0857c48ec763c11d472c87cd518283d/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:0800cc58a6d17d159df823f87ad66cefebf105b982493d4bad03ee7fab84b993", size = 35935712, upload-time = "2026-01-18T16:18:55.626Z" }, + { url = "https://files.pythonhosted.org/packages/c5/08/17a62078fc1a53decb34a9aa79cf9009efc74d63d2422e5ade9fed2f99e3/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3a7c68c722da9bb5b0f8c10e3eae71d9825a4b429b40b32709df5d1fa55beb3d", size = 44503523, upload-time = "2026-01-18T16:19:03.958Z" }, + { url = "https://files.pythonhosted.org/packages/cc/70/84d45c74341e798aae0323d33b7c39194e23b1abc439ceaf60a68a7a969a/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:bd5556c24622df90551063ea41f559b714aa63ca953db884cfb958559087a14e", size = 47542490, upload-time = "2026-01-18T16:19:11.208Z" }, + { url = "https://files.pythonhosted.org/packages/61/d9/d1274b0e6f19e235de17441e53224f4716574b2ca837022d55702f24d71d/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54810f6e6afc4ffee7c2e0051b61722fbea9a4961b46192dcfae8ea12fa09059", size = 48233605, upload-time = "2026-01-18T16:19:19.544Z" }, + { url = "https://files.pythonhosted.org/packages/39/07/e4e2d568cb57543d84482f61e510732820cddb0f47c4bb7df629abfed852/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:14de7d48052cf4b0ed174533eafa3cfe0711b8076ad70bede32cf59f744f0d7c", size = 50603979, upload-time = "2026-01-18T16:19:26.717Z" }, + { url = "https://files.pythonhosted.org/packages/72/9c/47693463894b610f8439b2e970b82ef81e9599c757bf2049365e40ff963c/pyarrow-23.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:427deac1f535830a744a4f04a6ac183a64fcac4341b3f618e693c41b7b98d2b0", size = 28338905, upload-time = "2026-01-18T16:19:32.93Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pyathena" +version = "3.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "botocore" }, + { name = "fsspec" }, + { name = "python-dateutil" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/42/9ce83fa64fdf607b5a792683ab5b41bec4ecfe846b132b0505f05b19054d/pyathena-3.25.0.tar.gz", hash = "sha256:60e735613454671353f88a9adaf647f10c1dc10da674919a0d5993392b05b7ab", size = 114304, upload-time = "2026-01-16T07:46:28.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/5d/4ac280ac9168d6a14c99a4324a9615cd4f19e9c76ecb2fa92a475f34b8b2/pyathena-3.25.0-py3-none-any.whl", hash = "sha256:01df17f0063cfcb7ca322cfdd0385e15f3387975cbed0836897191063c1792a2", size = 153364, upload-time = "2026-01-16T07:46:26.222Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, +] + +[[package]] +name = "pyopenssl" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "snowflake-connector-python" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asn1crypto" }, + { name = "boto3" }, + { name = "botocore" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "cryptography" }, + { name = "filelock" }, + { name = "idna" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pyjwt" }, + { name = "pyopenssl" }, + { name = "pytz" }, + { name = "requests" }, + { name = "sortedcontainers" }, + { name = "tomlkit" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/d2/4ae9fc7a0df36ad0ac06bc959757dfbfc58f160f58e1d62e7cebe9901fc7/snowflake_connector_python-4.2.0.tar.gz", hash = "sha256:74b1028caee3af4550a366ef89b33de80940bbf856844dd4d788a6b7a6511aff", size = 915327, upload-time = "2026-01-07T16:44:32.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/34/2c5c059b12db84113bb01761bd3fdab3e0c0d8d4ccc0c9631be5479960c2/snowflake_connector_python-4.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e1c60e578ddcdf99b46d7c329706aa87ea98c1c877cbe50560e034cc904231e", size = 11908869, upload-time = "2026-01-07T16:44:35.243Z" }, + { url = "https://files.pythonhosted.org/packages/c9/27/07ab3485f43d92c139fefb30b68a60498b508f2e941d9191f1ec3ac42a20/snowflake_connector_python-4.2.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:cf1805be7e124aa12bdcbb6c7f7f7bd11277aa4fe4d616cfee7633617bba9651", size = 11921560, upload-time = "2026-01-07T16:44:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/d5/12/ba6bb6cd26bc584637aa63f3e579cb929b9c3637fa830e43b77c2b2e8901/snowflake_connector_python-4.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b877cf5fc086818d86e289fc88453bc354df87a664e57f9b75d8dd7550d2df3", size = 2786595, upload-time = "2026-01-07T16:44:14.314Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/bf900ac5ddd5b60a72f0c3f7c276c9b0f29b375997c294f28bd746e9f721/snowflake_connector_python-4.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3654c3923b7ce88aab3be459bad3dba39fe4f989a4871421925a8a48f9a553ca", size = 2814560, upload-time = "2026-01-07T16:44:15.988Z" }, + { url = "https://files.pythonhosted.org/packages/8e/04/e070116ff779fcd16c5e25ef8b045afb8cc53b12b3494663457718a7d877/snowflake_connector_python-4.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cdaf91edf94d801fef6cb15c90ba321826b8342826a82375799319d509e6787a", size = 12059955, upload-time = "2026-01-07T16:45:05.556Z" }, + { url = "https://files.pythonhosted.org/packages/24/5f/2e3ac52d4b433e850c83f91b801b7c4e9935a4d1c4f2ea4fd0c3782c5a3d/snowflake_connector_python-4.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2971212e2bf38b19ed3d71d433102b09cda09ddca02fe4c813cb73f504a31e8", size = 11908767, upload-time = "2026-01-07T16:44:39.982Z" }, + { url = "https://files.pythonhosted.org/packages/31/f6/74d75623ed75244c4aad1722b83923c806a67f601b41314e8a6b30e160c0/snowflake_connector_python-4.2.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:786d9ad591439996ff5a6014c3730441bcfdc8c6d60f05d98f6576cb2cfa0f05", size = 11921016, upload-time = "2026-01-07T16:44:41.917Z" }, + { url = "https://files.pythonhosted.org/packages/31/53/ab0d2eed42f1309de2e7656651fdab6ae454032bcc485089ce5e0697b5c2/snowflake_connector_python-4.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74d3d2bcce62bbb7a8fb3adaae37dc2aaeb4e93549509db2f957fb704ce4aa18", size = 2797881, upload-time = "2026-01-07T16:44:17.319Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6f/2aa88f57107fdf0daabd113b479ba50e22d566ae36e860d4dbe68bcb6437/snowflake_connector_python-4.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cbdffcf5b12199f3060297353e69c5a4c1fc4dfacd0062acbe9a1ace7e50882", size = 2827340, upload-time = "2026-01-07T16:44:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5b/d03f1d8dfeab8c81bd1f65cad93385932789971a640db1c6369b5850cc5b/snowflake_connector_python-4.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:939e687ec4667d903b3bca3644b22946606361a2201158e137e448a6cd44605d", size = 12059905, upload-time = "2026-01-07T16:45:07.679Z" }, + { url = "https://files.pythonhosted.org/packages/3c/90/90df1e0bbc8ba22534af48518e71eb669a3bb6243989a93d59f9db9d8897/snowflake_connector_python-4.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b6e5dde4794fb190add6baee616f0f9a9b5c31502089b680a5be4441926b5173", size = 11907736, upload-time = "2026-01-07T16:44:44.598Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d1/4e9015d37a869022729a146f4c7f312f089938e1f51ac7620f6961f7ce66/snowflake_connector_python-4.2.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:f80f180092d218b578f05da145dd2640edb3c8807264d69169bc4dfb88b8b86c", size = 11919401, upload-time = "2026-01-07T16:44:47.524Z" }, + { url = "https://files.pythonhosted.org/packages/c3/5a/c65134dedd438f9d8d6eaeb7f573cb95abe4141385a4353cfe88d8c96fb1/snowflake_connector_python-4.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94a59566d3096a662b09423770aede8f99f1d06807d7b884dba8d9f767f0b2cd", size = 2854461, upload-time = "2026-01-07T16:44:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/94/6d/dd526a07042ca33ce05b8c642ef3da4a72e2cbe09e305170cb866021acd6/snowflake_connector_python-4.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11241089efc6e8d69ea1aa58bb17abe85298e66d278fed4d13381fc362f02564", size = 2887953, upload-time = "2026-01-07T16:44:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e0/d2db617da5791ec03d17bfd96db6f4c867a3498c4b4d480befc6a1854522/snowflake_connector_python-4.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:823ca257d9639b5468f53a816dc5acaea7c56991f518633c9c5f0fcf0d324721", size = 12058975, upload-time = "2026-01-07T16:45:10.293Z" }, + { url = "https://files.pythonhosted.org/packages/7a/34/cb523e85f9da46e22ee3c07a4f66a090ab935a1c6e59e4e9638cf8e7bc36/snowflake_connector_python-4.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d103ab3d9175251c1e391c4a155d99faaadd6a1e3c1c36429a711862f7ab021", size = 11908616, upload-time = "2026-01-07T16:44:49.512Z" }, + { url = "https://files.pythonhosted.org/packages/5b/eb/7a5c2a4dc275048e0b0b67b6b542b4cfdf60da158af8a315e5dd1021f443/snowflake_connector_python-4.2.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:2db02486bf72b2d4da6338bad59c58e18d0be4026b33d62b894db8cb04de403e", size = 11920460, upload-time = "2026-01-07T16:44:51.845Z" }, + { url = "https://files.pythonhosted.org/packages/37/a2/7f85a01fc13982391166c5458f4fd1078546e6f19f9e0bb184dbf6ec5f53/snowflake_connector_python-4.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b93b0195746c7734ab66889430a418ac7fd66441c11addb683bc15e364bb77c8", size = 2820920, upload-time = "2026-01-07T16:44:24.728Z" }, + { url = "https://files.pythonhosted.org/packages/aa/80/322dafc03f77f28f1ede160e4989ae758dd27dc94529e424348865bba501/snowflake_connector_python-4.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4773949e33c2503f369c20ac8fd59697e493670fed653fea7349d465ea5a0171", size = 2854097, upload-time = "2026-01-07T16:44:26.817Z" }, + { url = "https://files.pythonhosted.org/packages/06/05/64d3de8c98f783a3065e60107519b701d1ab7ef15efefa279d338f3fba64/snowflake_connector_python-4.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:3665eae47a6ccaf00ca567936cb16d5cbdd5b9f8ab3ee3a3f072bf3c4b76986c", size = 12058956, upload-time = "2026-01-07T16:45:13.063Z" }, +] + +[package.optional-dependencies] +pandas = [ + { name = "pandas" }, + { name = "pyarrow" }, +] + +[[package]] +name = "snowflake-sqlalchemy" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "snowflake-connector-python" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/0b/5e90eb28191ad6e0318254394c7e2902c4037fd566aa299dc8b5b16238f8/snowflake_sqlalchemy-1.8.2.tar.gz", hash = "sha256:91ca38719e117f94dd195ba94c22dd22f69c585b136ed129ba4e2dd93252b0c2", size = 122603, upload-time = "2025-12-10T08:33:49.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/77/c3af74a84eb00c1004a8e3c8a98627a3eecb2563f4ee01e621326c947bce/snowflake_sqlalchemy-1.8.2-py3-none-any.whl", hash = "sha256:13ad79bf51654cdaaedfbcc60d20bee417c0a128f8710eabbf4aba65b50f6d3d", size = 72726, upload-time = "2025-12-10T08:33:48.106Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/26/66ba59328dc25e523bfcb0f8db48bdebe2035e0159d600e1f01c0fc93967/sqlalchemy-2.0.46-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:895296687ad06dc9b11a024cf68e8d9d3943aa0b4964278d2553b86f1b267735", size = 2155051, upload-time = "2026-01-21T18:27:28.965Z" }, + { url = "https://files.pythonhosted.org/packages/21/cd/9336732941df972fbbfa394db9caa8bb0cf9fe03656ec728d12e9cbd6edc/sqlalchemy-2.0.46-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab65cb2885a9f80f979b85aa4e9c9165a31381ca322cbde7c638fe6eefd1ec39", size = 3234666, upload-time = "2026-01-21T18:32:28.72Z" }, + { url = "https://files.pythonhosted.org/packages/38/62/865ae8b739930ec433cd4123760bee7f8dafdc10abefd725a025604fb0de/sqlalchemy-2.0.46-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52fe29b3817bd191cc20bad564237c808967972c97fa683c04b28ec8979ae36f", size = 3232917, upload-time = "2026-01-21T18:44:54.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/38/805904b911857f2b5e00fdea44e9570df62110f834378706939825579296/sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:09168817d6c19954d3b7655da6ba87fcb3a62bb575fb396a81a8b6a9fadfe8b5", size = 3185790, upload-time = "2026-01-21T18:32:30.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/4f/3260bb53aabd2d274856337456ea52f6a7eccf6cce208e558f870cec766b/sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be6c0466b4c25b44c5d82b0426b5501de3c424d7a3220e86cd32f319ba56798e", size = 3207206, upload-time = "2026-01-21T18:44:55.93Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b3/67c432d7f9d88bb1a61909b67e29f6354d59186c168fb5d381cf438d3b73/sqlalchemy-2.0.46-cp310-cp310-win32.whl", hash = "sha256:1bc3f601f0a818d27bfe139f6766487d9c88502062a2cd3a7ee6c342e81d5047", size = 2115296, upload-time = "2026-01-21T18:33:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8c/25fb284f570f9d48e6c240f0269a50cec9cf009a7e08be4c0aaaf0654972/sqlalchemy-2.0.46-cp310-cp310-win_amd64.whl", hash = "sha256:e0c05aff5c6b1bb5fb46a87e0f9d2f733f83ef6cbbbcd5c642b6c01678268061", size = 2138540, upload-time = "2026-01-21T18:33:14.22Z" }, + { url = "https://files.pythonhosted.org/packages/69/ac/b42ad16800d0885105b59380ad69aad0cce5a65276e269ce2729a2343b6a/sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684", size = 2154851, upload-time = "2026-01-21T18:27:30.54Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/d8710068cb79f64d002ebed62a7263c00c8fd95f4ebd4b5be8f7ca93f2bc/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62", size = 3311241, upload-time = "2026-01-21T18:32:33.45Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/20c71487c7219ab3aa7421c7c62d93824c97c1460f2e8bb72404b0192d13/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f", size = 3310741, upload-time = "2026-01-21T18:44:57.887Z" }, + { url = "https://files.pythonhosted.org/packages/65/80/d26d00b3b249ae000eee4db206fcfc564bf6ca5030e4747adf451f4b5108/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01", size = 3263116, upload-time = "2026-01-21T18:32:35.044Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/74dda7506640923821340541e8e45bd3edd8df78664f1f2e0aae8077192b/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999", size = 3285327, upload-time = "2026-01-21T18:44:59.254Z" }, + { url = "https://files.pythonhosted.org/packages/9f/25/6dcf8abafff1389a21c7185364de145107b7394ecdcb05233815b236330d/sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d", size = 2114564, upload-time = "2026-01-21T18:33:15.85Z" }, + { url = "https://files.pythonhosted.org/packages/93/5f/e081490f8523adc0088f777e4ebad3cac21e498ec8a3d4067074e21447a1/sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597", size = 2139233, upload-time = "2026-01-21T18:33:17.528Z" }, + { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405, upload-time = "2026-01-21T19:05:54.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702, upload-time = "2026-01-21T18:46:45.384Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664, upload-time = "2026-01-21T18:40:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372, upload-time = "2026-01-21T18:46:47.168Z" }, + { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425, upload-time = "2026-01-21T18:40:11.548Z" }, + { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155, upload-time = "2026-01-21T18:42:49.748Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078, upload-time = "2026-01-21T18:42:51.197Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268, upload-time = "2026-01-21T19:05:56.621Z" }, + { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511, upload-time = "2026-01-21T18:46:49.022Z" }, + { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881, upload-time = "2026-01-21T18:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559, upload-time = "2026-01-21T18:46:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728, upload-time = "2026-01-21T18:40:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295, upload-time = "2026-01-21T18:42:52.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076, upload-time = "2026-01-21T18:42:53.924Z" }, + { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533, upload-time = "2026-01-21T18:33:06.636Z" }, + { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208, upload-time = "2026-01-21T18:45:08.436Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292, upload-time = "2026-01-21T18:33:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497, upload-time = "2026-01-21T18:45:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" }, + { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" }, + { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" }, + { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, + { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, + { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, +] + +[[package]] +name = "sqlalchemy-bigquery" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-bigquery" }, + { name = "packaging" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/6a/c49932b3d9c44cab9202b1866c5b36b7f0d0455d4653fbc0af4466aeaa76/sqlalchemy_bigquery-1.16.0.tar.gz", hash = "sha256:fe937a0d1f4cf7219fcf5d4995c6718805b38d4df43e29398dec5dc7b6d1987e", size = 119632, upload-time = "2025-11-06T01:35:40.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/87/11e6de00ef7949bb8ea06b55304a1a4911c329fdf0d9882b464db240c2c5/sqlalchemy_bigquery-1.16.0-py3-none-any.whl", hash = "sha256:0fe7634cd954f3e74f5e2db6d159f9e5ee87a47fbe8d52eac3cd3bb3dadb3a77", size = 40615, upload-time = "2025-11-06T01:35:39.358Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "types-protobuf" +version = "6.32.1.20251210" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/59/c743a842911887cd96d56aa8936522b0cd5f7a7f228c96e81b59fced45be/types_protobuf-6.32.1.20251210.tar.gz", hash = "sha256:c698bb3f020274b1a2798ae09dc773728ce3f75209a35187bd11916ebfde6763", size = 63900, upload-time = "2025-12-10T03:14:25.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/43/58e75bac4219cbafee83179505ff44cae3153ec279be0e30583a73b8f108/types_protobuf-6.32.1.20251210-py3-none-any.whl", hash = "sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140", size = 77921, upload-time = "2025-12-10T03:14:24.477Z" }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20260124" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/41/4f8eb1ce08688a9e3e23709ed07089ccdeaf95b93745bfb768c6da71197d/types_python_dateutil-2.9.0.20260124.tar.gz", hash = "sha256:7d2db9f860820c30e5b8152bfe78dbdf795f7d1c6176057424e8b3fdd1f581af", size = 16596, upload-time = "2026-01-24T03:18:42.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/c2/aa5e3f4103cc8b1dcf92432415dde75d70021d634ecfd95b2e913cf43e17/types_python_dateutil-2.9.0.20260124-py3-none-any.whl", hash = "sha256:f802977ae08bf2260142e7ca1ab9d4403772a254409f7bbdf652229997124951", size = 18266, upload-time = "2026-01-24T03:18:42.155Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20260107" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +]