pax_global_header00006660000000000000000000000064145741610250014517gustar00rootroot0000000000000052 comment=e90a0f0f12f30e75d0bfa18dbbac170d56431739 bachya-aionotion-e90a0f0/000077500000000000000000000000001457416102500153515ustar00rootroot00000000000000bachya-aionotion-e90a0f0/.codeclimate.yml000066400000000000000000000003401457416102500204200ustar00rootroot00000000000000--- engines: duplication: enabled: true config: languages: - python fixme: enabled: true radon: enabled: true ratings: paths: - "**.py" exclude_paths: - dist/ - docs/ - tests/ bachya-aionotion-e90a0f0/.flake8000066400000000000000000000002271457416102500165250ustar00rootroot00000000000000[flake8] ignore = E203,E266,E501,F811,W503 max-line-length = 80 max-complexity = 18 per-file-ignores = tests/*:DAR,S101 select = B,B9,LK,C,D,E,F,I,S,W bachya-aionotion-e90a0f0/.github/000077500000000000000000000000001457416102500167115ustar00rootroot00000000000000bachya-aionotion-e90a0f0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001457416102500210745ustar00rootroot00000000000000bachya-aionotion-e90a0f0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000007641457416102500235750ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. bachya-aionotion-e90a0f0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000006461457416102500246270ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Additional context** Add any other context or screenshots about the feature request here. bachya-aionotion-e90a0f0/.github/dependabot.yml000066400000000000000000000005521457416102500215430ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily time: "06:00" - package-ecosystem: pip directory: "/.github/workflows" schedule: interval: daily time: "06:00" - package-ecosystem: "github-actions" directory: "/" schedule: interval: daily time: "06:00" bachya-aionotion-e90a0f0/.github/labels.yml000066400000000000000000000024241457416102500207000ustar00rootroot00000000000000--- - name: "breaking-change" color: ee0701 description: "A breaking change for existing users" - name: "bug" color: ee0701 description: "Bugs or issues which will cause a problem for users" - name: "documentation" color: 0052cc description: "Project documentation" - name: "enhancement" color: 1d76db description: "Enhancement of the code, not introducing new features." - name: "maintenance" color: 2af79e description: "Generic library tasks" - name: "dependencies" color: 1d76db description: "Upgrade or downgrade of project dependencies" - name: "in-progress" color: fbca04 description: "Issue is currently being resolved by a developer" - name: "stale" color: fef2c0 description: "There has not been activity on this issue or PR for some time" - name: "no-stale" color: fef2c0 description: "This issue or PR is exempted from the stale bot" - name: "security" color: ee0701 description: "Marks a security issue that needs to be resolved ASAP" - name: "incomplete" color: fef2c0 description: "Marks a PR or issue that is missing information" - name: "invalid" color: fef2c0 description: "Marks a PR or issue that is missing information" - name: "help-wanted" color: 0e8a16 description: "Needs a helping hang or expertise in order to resolve" bachya-aionotion-e90a0f0/.github/pull_request_template.md000066400000000000000000000005371457416102500236570ustar00rootroot00000000000000**Describe what the PR does:** **Does this fix a specific issue?** Fixes https://github.com/bachya/aionotion/issues/ **Checklist:** - [ ] Confirm that one or more new tests are written for the new functionality. - [ ] Run tests and ensure everything passes (with 100% test coverage). - [ ] Update `README.md` with any new documentation. bachya-aionotion-e90a0f0/.github/release-drafter.yml000066400000000000000000000010051457416102500224750ustar00rootroot00000000000000--- categories: - title: "๐Ÿšจ Breaking Changes" labels: - "breaking-change" - title: "๐Ÿš€ Features" labels: - "enhancement" - title: "๐Ÿ› Bug Fixes" labels: - "bug" - title: "๐Ÿ“• Documentation" labels: - "documentation" - title: "๐Ÿงฐ Maintenance" labels: - "dependencies" - "maintenance" - "tooling" change-template: "- $TITLE (#$NUMBER)" name-template: "$NEXT_PATCH_VERSION" tag-template: "$NEXT_PATCH_VERSION" template: | $CHANGES bachya-aionotion-e90a0f0/.github/workflows/000077500000000000000000000000001457416102500207465ustar00rootroot00000000000000bachya-aionotion-e90a0f0/.github/workflows/codeql.yml000066400000000000000000000010021457416102500227310ustar00rootroot00000000000000--- name: CodeQL "on": push: branches: - dev - main pull_request: branches: - dev - main workflow_dispatch: schedule: - cron: "30 1 * * 0" jobs: codeql: name: Scanning runs-on: ubuntu-latest steps: - name: โคต๏ธ Check out code from GitHub uses: actions/checkout@v4 - name: ๐Ÿ— Initialize CodeQL uses: github/codeql-action/init@v3 - name: ๐Ÿš€ Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 bachya-aionotion-e90a0f0/.github/workflows/labels.yml000066400000000000000000000006721457416102500227400ustar00rootroot00000000000000--- name: Sync Labels "on": push: branches: - main paths: - .github/labels.yml workflow_dispatch: jobs: labels: name: โ™ป๏ธ Sync labels runs-on: ubuntu-latest steps: - name: โคต๏ธ Check out code from GitHub uses: actions/checkout@v4 - name: ๐Ÿš€ Run Label Syncer uses: micnncim/action-label-syncer@v1.3.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} bachya-aionotion-e90a0f0/.github/workflows/lock.yml000066400000000000000000000006251457416102500224240ustar00rootroot00000000000000--- name: Lock Closed Issues and PRs "on": schedule: - cron: "0 9 * * *" workflow_dispatch: jobs: lock: name: ๐Ÿ”’ Lock! runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v5.0.1 with: github-token: ${{ github.token }} issue-inactive-days: "30" issue-lock-reason: "" pr-inactive-days: "1" pr-lock-reason: "" bachya-aionotion-e90a0f0/.github/workflows/publish.yml000066400000000000000000000007751457416102500231500ustar00rootroot00000000000000--- name: Publish to PyPI "on": push: tags: - "*" jobs: publish_to_pypi: runs-on: ubuntu-latest steps: - name: โคต๏ธ Check out code from GitHub uses: actions/checkout@v4 - name: ๐Ÿ— Set up Python 3.12 id: python uses: actions/setup-python@v5 with: python-version: 3.12 - name: ๐Ÿš€ Publish to PyPi run: | pip install poetry poetry publish --build -u __token__ -p ${{ secrets.PYPI_API_KEY }} bachya-aionotion-e90a0f0/.github/workflows/release-drafter.yml000066400000000000000000000005331457416102500245370ustar00rootroot00000000000000--- name: Release Drafter "on": push: branches: - main workflow_dispatch: jobs: update_release_draft: name: โœ๏ธ Draft Release runs-on: ubuntu-latest steps: - name: ๐Ÿš€ Run Release Drafter uses: release-drafter/release-drafter@v6.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} bachya-aionotion-e90a0f0/.github/workflows/requirements.txt000066400000000000000000000000161457416102500242270ustar00rootroot00000000000000poetry==1.8.2 bachya-aionotion-e90a0f0/.github/workflows/stale.yml000066400000000000000000000023461457416102500226060ustar00rootroot00000000000000--- name: Stale "on": schedule: - cron: "0 8 * * *" workflow_dispatch: jobs: stale: name: ๐Ÿงน Clean up stale issues and PRs runs-on: ubuntu-latest steps: - name: ๐Ÿš€ Run stale uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 days-before-close: 7 remove-stale-when-updated: true stale-issue-label: "stale" exempt-issue-labels: "no-stale,help-wanted" stale-issue-message: > There hasn't been any activity on this issue recently, so it has been marked as stale. Please make sure to update to the latest version and check if that solves the issue. Let us know if that works for you by leaving a comment. This issue will be closed if no further activity occurs. Thanks! stale-pr-label: "stale" exempt-pr-labels: "no-stale" stale-pr-message: > There hasn't been any activity on this pull request recently, so it has automatically been marked as stale and will be closed if no further action occurs within 7 days. Thank you for your contributions. bachya-aionotion-e90a0f0/.github/workflows/static-analysis.yml000066400000000000000000000032471457416102500246070ustar00rootroot00000000000000--- name: Linting and Static Analysis "on": pull_request: branches: - dev - main push: branches: - dev - main jobs: lint: name: "Linting & Static Analysis" runs-on: ubuntu-latest steps: - name: โคต๏ธ Check out code from GitHub uses: actions/checkout@v4 - name: ๐Ÿ— Set up Python 3.12 id: setup-python uses: actions/setup-python@v5 with: python-version: 3.12 - name: โคต๏ธ Get pip cache directory id: pip-cache run: | echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: โคต๏ธ Establish pip cache uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: "${{ runner.os }}-pip-\ ${{ hashFiles('.github/workflows/requirements.txt') }}" restore-keys: | ${{ runner.os }}-pip- - name: ๐Ÿ— Install workflow dependencies run: | pip install -r .github/workflows/requirements.txt poetry config virtualenvs.create true poetry config virtualenvs.in-project true - name: โคต๏ธ Establish poetry cache uses: actions/cache@v4 with: path: .venv key: "venv-${{ steps.setup-python.outputs.python-version }}-\ ${{ hashFiles('poetry.lock') }}" restore-keys: | venv-${{ steps.setup-python.outputs.python-version }}- - name: ๐Ÿ— Install package dependencies run: | poetry install --no-interaction - name: ๐Ÿš€ Run pre-commit hooks uses: pre-commit/action@v3.0.1 env: SKIP: no-commit-to-branch,pytest bachya-aionotion-e90a0f0/.github/workflows/test.yml000066400000000000000000000072501457416102500224540ustar00rootroot00000000000000--- name: Tests and Coverage "on": pull_request: branches: - dev - main push: branches: - dev - main jobs: test: name: Tests runs-on: ubuntu-latest strategy: matrix: python-version: - "3.10" - "3.11" - "3.12" steps: - name: โคต๏ธ Check out code from GitHub uses: actions/checkout@v4 - name: ๐Ÿ— Set up Python id: setup-python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: โคต๏ธ Get pip cache directory id: pip-cache run: | echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: โคต๏ธ Establish pip cache uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: "${{ runner.os }}-pip-\ ${{ hashFiles('.github/workflows/requirements.txt') }}" restore-keys: | ${{ runner.os }}-pip- - name: ๐Ÿ— Install workflow dependencies run: | pip install -r .github/workflows/requirements.txt poetry config virtualenvs.create true poetry config virtualenvs.in-project true - name: โคต๏ธ Establish poetry cache uses: actions/cache@v4 with: path: .venv key: "venv-${{ steps.setup-python.outputs.python-version }}-\ ${{ hashFiles('poetry.lock') }}" restore-keys: | venv-${{ steps.setup-python.outputs.python-version }}- - name: ๐Ÿ— Install package dependencies run: | poetry install --no-interaction - name: ๐Ÿš€ Run pytest run: poetry run pytest --cov aionotion tests - name: โฌ†๏ธ Upload coverage artifact uses: actions/upload-artifact@v3 with: name: coverage-${{ matrix.python-version }} path: .coverage coverage: name: Code Coverage needs: test runs-on: ubuntu-latest steps: - name: โคต๏ธ Check out code from GitHub uses: actions/checkout@v4 - name: โฌ‡๏ธ Download coverage data uses: actions/download-artifact@v3 - name: ๐Ÿ— Set up Python 3.12 id: setup-python uses: actions/setup-python@v5 with: python-version: 3.12 - name: โคต๏ธ Get pip cache directory id: pip-cache run: | echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: โคต๏ธ Establish pip cache uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: "${{ runner.os }}-pip-\ ${{ hashFiles('.github/workflows/requirements.txt') }}" restore-keys: | ${{ runner.os }}-pip- - name: ๐Ÿ— Install workflow dependencies run: | pip install -r .github/workflows/requirements.txt poetry config virtualenvs.create true poetry config virtualenvs.in-project true - name: โคต๏ธ Establish poetry cache uses: actions/cache@v4 with: path: .venv key: "venv-${{ steps.setup-python.outputs.python-version }}-\ ${{ hashFiles('poetry.lock') }}" restore-keys: | venv-${{ steps.setup-python.outputs.python-version }}- - name: ๐Ÿ— Install package dependencies run: | poetry install --no-interaction - name: ๐Ÿš€ Process coverage results run: | poetry run coverage combine coverage*/.coverage* poetry run coverage xml -i - name: ๐Ÿ“Š Upload coverage report to codecov.io uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} bachya-aionotion-e90a0f0/.gitignore000066400000000000000000000001451457416102500173410ustar00rootroot00000000000000*.egg-info .DS_Store .coverage .mypy_cache .nox .tox .venv __pycache__ coverage.xml docs/_build tags bachya-aionotion-e90a0f0/.mise.toml000066400000000000000000000000701457416102500172560ustar00rootroot00000000000000[tools] python = { version="3.12", virtualenv=".venv" } bachya-aionotion-e90a0f0/.pre-commit-config.yaml000066400000000000000000000117221457416102500216350ustar00rootroot00000000000000--- repos: - repo: local hooks: - id: blacken-docs name: โ˜•๏ธ Format documentation using black language: system files: '\.(rst|md|markdown|py|tex)$' entry: poetry run blacken-docs require_serial: true - id: check-ast name: ๐Ÿ Checking Python AST language: system types: [python] entry: poetry run check-ast - id: check-case-conflict name: ๐Ÿ”  Checking for case conflicts language: system entry: poetry run check-case-conflict - id: check-docstring-first name: โ„น๏ธ Checking docstrings are first language: system types: [python] entry: poetry run check-docstring-first - id: check-executables-have-shebangs name: ๐Ÿง Checking that executables have shebangs language: system types: [text, executable] entry: poetry run check-executables-have-shebangs stages: [commit, push, manual] - id: check-json name: ๏ฝ› Checking JSON files language: system types: [json] entry: poetry run check-json - id: check-merge-conflict name: ๐Ÿ’ฅ Checking for merge conflicts language: system types: [text] entry: poetry run check-merge-conflict - id: check-symlinks name: ๐Ÿ”— Checking for broken symlinks language: system types: [symlink] entry: poetry run check-symlinks - id: check-toml name: โœ… Checking TOML files language: system types: [toml] entry: poetry run check-toml - id: check-yaml name: โœ… Checking YAML files language: system types: [yaml] entry: poetry run check-yaml - id: codespell name: โœ… Checking code for misspellings language: system types: [text] exclude: ^poetry\.lock$ entry: poetry run codespell - id: debug-statements name: ๐Ÿชต Checking for debug statements and imports (Python) language: system types: [python] entry: poetry run debug-statement-hook - id: detect-private-key name: ๐Ÿ•ต๏ธ Detecting private keys language: system types: [text] entry: poetry run detect-private-key - id: end-of-file-fixer name: ๐Ÿ”š Checking end of files language: system types: [text] entry: poetry run end-of-file-fixer stages: [commit, push, manual] - id: fix-byte-order-marker name: ๐Ÿš Checking UTF-8 byte order marker language: system types: [text] entry: poetry run fix-byte-order-marker - id: isort name: ๐Ÿ”€ Sorting all imports with isort language: system types: [python] entry: poetry run isort - id: format name: โ˜•๏ธ Formatting code using ruff language: system types: [python] entry: poetry run ruff format - id: mypy name: ๐Ÿ†Ž Performing static type checking using mypy language: system types: [python] entry: poetry run mypy require_serial: true exclude: ^vulture_whitelist.py$ - id: no-commit-to-branch name: ๐Ÿ›‘ Checking for commit to protected branch language: system entry: poetry run no-commit-to-branch pass_filenames: false always_run: true args: - --branch=dev - --branch=main - id: poetry name: ๐Ÿ“œ Checking pyproject with Poetry language: system entry: poetry check pass_filenames: false always_run: true - id: pylint name: ๐ŸŒŸ Starring code with pylint language: system types: [python] entry: poetry run pylint exclude: ^vulture_whitelist.py$ - id: pyupgrade name: ๐Ÿ†™ Checking for upgradable syntax with pyupgrade language: system types: [python] entry: poetry run pyupgrade args: [--py39-plus, --keep-runtime-typing] - id: ruff name: ๐Ÿ‘” Enforcing style guide with ruff language: system types: [python] entry: poetry run ruff --fix exclude: ^vulture_whitelist.py$ - id: trailing-whitespace name: โœ„ Trimming trailing whitespace language: system types: [text] entry: poetry run trailing-whitespace-fixer stages: [commit, push, manual] - id: vulture name: ๐Ÿ” Finding unused Python code with Vulture language: system types: [python] entry: poetry run vulture vulture_whitelist.py pass_filenames: false require_serial: true - id: yamllint name: ๐ŸŽ— Checking YAML files with yamllint language: system types: [yaml] entry: poetry run yamllint - repo: https://github.com/pre-commit/mirrors-prettier rev: "v3.0.0-alpha.4" hooks: - id: prettier name: ๐Ÿ’„ Ensuring files are prettier bachya-aionotion-e90a0f0/LICENSE000066400000000000000000000020601457416102500163540ustar00rootroot00000000000000MIT License Copyright (c) 2019-2024 Aaron Bach Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bachya-aionotion-e90a0f0/README.md000066400000000000000000000162471457416102500166420ustar00rootroot00000000000000# ๐Ÿ“Ÿ aionotion: a Python3, asyncio-friendly library for Notionยฎ Home Monitoring [![CI][ci-badge]][ci] [![PyPI][pypi-badge]][pypi] [![Version][version-badge]][version] [![License][license-badge]][license] [![Code Coverage][codecov-badge]][codecov] [![Maintainability][maintainability-badge]][maintainability] Buy Me A Coffee `aionotion` is a Python 3, asyncio-friendly library for interacting with [Notion][notion] home monitoring sensors. - [Installation](#installation) - [Python Versions](#python-versions) - [Usage](#usage) - [Contributing](#contributing) # Installation ```bash pip install aionotion ``` # Python Versions `aionotion` is currently supported on: - Python 3.10 - Python 3.11 - Python 3.12 # Usage ```python import asyncio from aiohttp import ClientSession from aionotion import async_get_client_with_credentials async def main() -> None: """Create the aiohttp session and run the example.""" client = await async_get_client_with_credentials( "", "", session=session ) # Get the UUID of the authenticated user: client.user_uuid # >>> xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # Get the current refresh token of the authenticated user (BE CAREFUL): client.refresh_token # >>> abcde12345 # Get all "households" associated with the account: systems = await client.system.async_all() # >>> [System(...), System(...), ...] # Get a system by ID: system = await client.system.async_get(12345) # >>> System(...) # Get all bridges associated with the account: bridges = await client.bridge.async_all() # >>> [Bridge(...), Bridge(...), ...] # Get a bridge by ID: bridge = await client.bridge.async_get(12345) # >>> Bridge(...) # Get all sensors: sensors = await client.sensor.async_all() # >>> [Sensor(...), Sensor(...), ...] # Get a sensor by ID: sensor = await client.sensor.async_get(12345) # >>> Sensor(...) # Get "listeners" (conditions that a sensor is monitoring) for all sensors: listeners = await client.listener.async_all() # >>> [Listener(...), Listener(...), ...] # Get all listener definitions supported by Notion: definitions = await client.listener.async_definitions() # >>> [ListenerDefinition(...), ListenerDefinition(...), ...] # Get user info: user_info = await client.user.async_info() # >>> User(...) # Get user preferences: user_preferences = await client.user.async_preferences() # >>> UserPreferences(...) asyncio.run(main()) ``` ## Using a Refresh Token During the normal course of operations, `aionotion` will automatically maintain a refresh token and use it when needed. At times, you may wish to manage that token yourself (so that you can use it later)โ€“`aionotion` provides a few useful capabilities there. ### Refresh Token Callbacks `aionotion` allows implementers to defining callbacks that get called when a new refresh token is generated. These callbacks accept a single string parameter (the refresh token): ```python import asyncio from aiohttp import ClientSession from aionotion import async_get_client_with_credentials async def main() -> None: """Create the aiohttp session and run the example.""" client = await async_get_client_with_credentials( "", "", session=session ) def do_somethng_with_refresh_token(refresh_token: str) -> None: """Do something interesting.""" pass # Attach the callback to the client: remove_callback = client.add_refresh_token_callback(do_somethng_with_refresh_token) # Later, if you want to remove the callback: remove_callback() asyncio.run(main()) ``` ### Getting a Client via a Refresh Token All of previous examples retrieved an authenticated client with `async_get_client_with_credentials`. However, implementers may also create an authenticated client by providing a previously retrieved user UUID and refresh token: ```python import asyncio from aiohttp import ClientSession from aionotion import async_get_client_with_refresh_token async def main() -> None: """Create the aiohttp session and run the example.""" async with ClientSession() as session: # Create a Notion API client: client = await async_get_client_with_refresh_token( "", "", session=session ) # Get to work... asyncio.run(main()) ``` ## Connection Pooling By default, the library creates a new connection to Notion with each coroutine. If you are calling a large number of coroutines (or merely want to squeeze out every second of runtime savings possible), an [`aiohttp`][aiohttp] `ClientSession` can be used for connection pooling: ```python import asyncio from aiohttp import ClientSession from aionotion import async_get_client_with_credentials async def main() -> None: """Create the aiohttp session and run the example.""" async with ClientSession() as session: # Create a Notion API client: client = await async_get_client_with_credentials( "", "", session=session ) # Get to work... asyncio.run(main()) ``` Check out the examples, the tests, and the source files themselves for method signatures and more examples. # Contributing Thanks to all of [our contributors][contributors] so far! 1. [Check for open features/bugs][issues] or [initiate a discussion on one][new-issue]. 2. [Fork the repository][fork]. 3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv` 4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate` 5. Install the dev environment: `script/setup` 6. Code your new feature or bug fix on a new branch. 7. Write tests that cover your new functionality. 8. Run tests and ensure 100% code coverage: `poetry run pytest --cov aionotion tests` 9. Update `README.md` with any new documentation. 10. Submit a pull request! [aiohttp]: https://github.com/aio-libs/aiohttp [ci-badge]: https://img.shields.io/github/actions/workflow/status/bachya/aionotion/test.yml [ci]: https://github.com/bachya/aionotion/actions [codecov-badge]: https://codecov.io/gh/bachya/aionotion/branch/dev/graph/badge.svg [codecov]: https://codecov.io/gh/bachya/aionotion [contributors]: https://github.com/bachya/aionotion/graphs/contributors [fork]: https://github.com/bachya/aionotion/fork [issues]: https://github.com/bachya/aionotion/issues [license-badge]: https://img.shields.io/pypi/l/aionotion.svg [license]: https://github.com/bachya/aionotion/blob/main/LICENSE [maintainability-badge]: https://api.codeclimate.com/v1/badges/bd79edca07c8e4529cba/maintainability [maintainability]: https://codeclimate.com/github/bachya/aionotion/maintainability [new-issue]: https://github.com/bachya/aionotion/issues/new [notion]: https://getnotion.com [pypi-badge]: https://img.shields.io/pypi/v/aionotion.svg [pypi]: https://pypi.python.org/pypi/aionotion [version-badge]: https://img.shields.io/pypi/pyversions/aionotion.svg [version]: https://pypi.python.org/pypi/aionotion bachya-aionotion-e90a0f0/aionotion/000077500000000000000000000000001457416102500173505ustar00rootroot00000000000000bachya-aionotion-e90a0f0/aionotion/__init__.py000066400000000000000000000003421457416102500214600ustar00rootroot00000000000000"""Define the aionotion package.""" from .client import async_get_client # noqa: F401 from .client import async_get_client_with_credentials # noqa: F401 from .client import async_get_client_with_refresh_token # noqa: F401 bachya-aionotion-e90a0f0/aionotion/bridge/000077500000000000000000000000001457416102500206045ustar00rootroot00000000000000bachya-aionotion-e90a0f0/aionotion/bridge/__init__.py000066400000000000000000000025011457416102500227130ustar00rootroot00000000000000"""Define endpoints for interacting with bridges.""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.bridge.models import Bridge as BridgeModel from aionotion.bridge.models import BridgeAllResponse, BridgeGetResponse if TYPE_CHECKING: from aionotion.client import Client class Bridge: """Define an object to interact with bridge endpoints.""" def __init__(self, client: Client) -> None: """Initialize. Args: client: The aionotion client """ self._client = client async def async_all(self) -> list[BridgeModel]: """Get all bridges. Returns: A validated API response payload. """ response: BridgeAllResponse = await self._client.async_request_and_validate( "get", "/base_stations", BridgeAllResponse ) return response.base_stations async def async_get(self, bridge_id: int) -> BridgeModel: """Get a bridge by ID. Args: bridge_id: The ID of the bridge to get. Returns: A validated API response payload. """ response: BridgeGetResponse = await self._client.async_request_and_validate( "get", f"/base_stations/{bridge_id}", BridgeGetResponse ) return response.base_stations bachya-aionotion-e90a0f0/aionotion/bridge/models.py000066400000000000000000000025151457416102500224440ustar00rootroot00000000000000"""Define bridge models.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime import ciso8601 from mashumaro import DataClassDictMixin @dataclass(frozen=True, kw_only=True) class FirmwareVersion(DataClassDictMixin): """Define firmware version info.""" wifi: str wifi_app: str silabs: str | None = None ti: str | None = None @dataclass(frozen=True, kw_only=True) class Bridge(DataClassDictMixin): """Define a bridge.""" id: int name: str | None mode: str hardware_id: str hardware_revision: int firmware_version: FirmwareVersion missing_at: datetime | None = field( default=None, metadata={"deserialize": ciso8601.parse_datetime} ) created_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) updated_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) system_id: int firmware: FirmwareVersion links: dict[str, int | str] @dataclass(frozen=True, kw_only=True) class BridgeAllResponse(DataClassDictMixin): """Define an API response containing all bridges.""" base_stations: list[Bridge] @dataclass(frozen=True, kw_only=True) class BridgeGetResponse(DataClassDictMixin): """Define an API response containing a single bridge.""" base_stations: Bridge bachya-aionotion-e90a0f0/aionotion/client.py000066400000000000000000000317511457416102500212070ustar00rootroot00000000000000"""Define a base client for interacting with Notion.""" from __future__ import annotations import asyncio from collections.abc import Callable from datetime import datetime from typing import Any, TypeVar, cast from uuid import uuid4 from aiohttp import ClientSession, ClientTimeout from aiohttp.client_exceptions import ClientError from mashumaro import DataClassDictMixin from mashumaro.exceptions import ( MissingField, SuitableVariantNotFoundError, UnserializableDataError, ) from aionotion.bridge import Bridge from aionotion.const import LOGGER from aionotion.errors import InvalidCredentialsError, RequestError from aionotion.listener import Listener from aionotion.sensor import Sensor from aionotion.system import System from aionotion.user import User from aionotion.user.models import ( AuthenticateViaCredentialsLegacyResponse, AuthenticateViaCredentialsResponse, AuthenticateViaRefreshTokenResponse, ) from aionotion.util.auth import decode_jwt from aionotion.util.dt import utc_from_timestamp, utcnow API_BASE = "https://api.getnotion.com/api" DEFAULT_TIMEOUT = 10 NotionBaseModelT = TypeVar("NotionBaseModelT", bound=DataClassDictMixin) RefreshTokenCallbackT = Callable[[str], None] def get_token_header_value(access_token: str, refresh_token: str | None) -> str: """Return the value for the Authorization header. The old API uses a different format for the Authorization header than the new API. We detect whether we're using the new API by checking whether a refresh token is present. Args: access_token: An access token. refresh_token: A refresh token (if it exists). Returns: The value for the Authorization header. """ if refresh_token: return f"Bearer {access_token}" return f"Token token={access_token}" class Client: """Define the API object.""" def __init__( self, *, session: ClientSession | None = None, session_name: str | None = None ) -> None: """Initialize. Args: session: An optional aiohttp ClientSession. session_name: An optional session name to use for authentication. """ self._access_token: str | None = None self._access_token_expires_at: datetime | None = None self._refresh_event = asyncio.Event() self._refresh_lock = asyncio.Lock() self._refresh_token: str | None = None self._refresh_token_callbacks: list[RefreshTokenCallbackT] = [] self._refreshing = False self._session = session self._session_name = session_name or uuid4().hex self.user_uuid: str = "" self.bridge = Bridge(self) self.listener = Listener(self) self.sensor = Sensor(self) self.system = System(self) self.user = User(self) @property def refresh_token(self) -> str | None: """Return the refresh token.""" return self._refresh_token def _save_tokens_from_auth_response( self, auth_response: AuthenticateViaCredentialsResponse | AuthenticateViaRefreshTokenResponse, ) -> None: """Save the authentication and refresh tokens from an auth response. Args: auth: An API response containing auth info. """ self._access_token = auth_response.auth.jwt self._refresh_token = auth_response.auth.refresh_token # Determine the expiration time of the access token: decoded_jwt = decode_jwt(self._access_token) self._access_token_expires_at = utc_from_timestamp(decoded_jwt["exp"]) # Call all refresh token callbacks: for callback in self._refresh_token_callbacks: callback(self._refresh_token) def add_refresh_token_callback( self, callback: RefreshTokenCallbackT ) -> Callable[[], None]: """Add a callback to be called when the refresh token is updated.""" self._refresh_token_callbacks.append(callback) def remove_callback() -> None: """Remove the callback from the list of callbacks.""" self._refresh_token_callbacks.remove(callback) return remove_callback async def async_authenticate_from_credentials( self, email: str, password: str ) -> None: """Authenticate via username and password. Args: email: The email address of a Notion account. password: The account password. """ auth_response: AuthenticateViaCredentialsResponse = ( await self.async_request_and_validate( "post", "/auth/login", AuthenticateViaCredentialsResponse, headers={"Accept-Version": "2"}, json={ "auth": { "email": email, "password": password, "session_name": self._session_name, } }, ) ) self.user_uuid = auth_response.user.uuid self._save_tokens_from_auth_response(auth_response) async def async_authenticate_from_refresh_token( self, *, refresh_token: str | None = None ) -> None: """Authenticate via a refresh token. Args: refresh_token: The refresh token to use. If not provided, the refresh token that was used to authenticate the user initially will be used. Raises: InvalidCredentialsError: If no refresh token is provided and the user has not been authenticated yet. """ if refresh_token is None and self._refresh_token is None: raise InvalidCredentialsError("No valid refresh token provided") self._refreshing = True async with self._refresh_lock: # If a refresh token is explicitly provided, use it: if refresh_token: self._refresh_token = refresh_token assert self._refresh_token is not None self._refresh_event.clear() try: auth_response: AuthenticateViaRefreshTokenResponse = ( await self.async_request_and_validate( "post", f"/auth/{self.user_uuid}/refresh", AuthenticateViaRefreshTokenResponse, refresh_request=True, headers={"Accept-Version": "2"}, json={ "auth": { "refresh_token": self._refresh_token, } }, ) ) self._save_tokens_from_auth_response(auth_response) finally: self._refreshing = False self._refresh_event.set() async def async_legacy_authenticate_from_credentials( self, email: str, password: str ) -> None: """Authenticate via username and password (via a legacy endpoint). This is kept in place for compatibility, but should be considered deprecated. Args: email: The email address of a Notion account. password: The account password. """ LOGGER.warning( "Using legacy authentication endpoint; this is deprecated and will be " "removed in a future release" ) auth_response: AuthenticateViaCredentialsLegacyResponse = ( await self.async_request_and_validate( "post", "/users/sign_in", AuthenticateViaCredentialsLegacyResponse, json={ "sessions": { "email": email, "password": password, } }, ) ) self.user_uuid = auth_response.users.uuid self._access_token = auth_response.session.authentication_token async def async_request( self, method: str, endpoint: str, *, refresh_request: bool = False, **kwargs: dict[str, Any], ) -> dict[str, Any]: """Make an API request. Args: method: An HTTP method. endpoint: A relative API endpoint. refresh_request: Whether this is a request to refresh the access token. **kwargs: Additional kwargs to send with the request. Returns: An API response payload. Raises: InvalidCredentialsError: Raised upon invalid credentials. RequestError: Raised upon an underlying HTTP error. """ if self._access_token_expires_at and utcnow() >= self._access_token_expires_at: LOGGER.debug("Access token expired, refreshing...") self._access_token = None self._access_token_expires_at = None await self.async_authenticate_from_refresh_token() # If an authenticated request arrives while we're refreshing, hold until the # refresh process is done: if not refresh_request and self._refreshing: await self._refresh_event.wait() url: str = f"{API_BASE}{endpoint}" kwargs.setdefault("headers", {}) if self._access_token: kwargs["headers"]["Authorization"] = get_token_header_value( self._access_token, self._refresh_token ) if use_running_session := self._session and not self._session.closed: session = self._session else: session = ClientSession(timeout=ClientTimeout(total=DEFAULT_TIMEOUT)) data: dict[str, Any] = {} async with session.request(method, url, **kwargs) as resp: data = await resp.json() try: resp.raise_for_status() except ClientError as err: if "401" in str(err): raise InvalidCredentialsError("Invalid credentials") from err raise RequestError(data["errors"][0]["title"]) from err if not use_running_session: await session.close() LOGGER.debug("Received data from %s: %s", endpoint, data) return data async def async_request_and_validate( self, method: str, endpoint: str, model: type[DataClassDictMixin], *, refresh_request: bool = False, **kwargs: dict[str, Any], ) -> NotionBaseModelT: """Make an API request and validate the response against a Pydantic model. Args: method: An HTTP method. endpoint: A relative API endpoint. model: A Pydantic model to validate the response against. refresh_request: Whether this is a request to refresh the access token. **kwargs: Additional kwargs to send with the request. Returns: A parsed, validated Pydantic model representing the response. """ raw_data = await self.async_request( method, endpoint, refresh_request=refresh_request, **kwargs ) try: return cast(NotionBaseModelT, model.from_dict(raw_data)) except ( MissingField, SuitableVariantNotFoundError, UnserializableDataError, ) as err: raise RequestError( f"Error while parsing response from {endpoint}: {err}" ) from err async def async_get_client_with_credentials( email: str, password: str, *, session: ClientSession | None = None, session_name: str | None = None, use_legacy_auth: bool = False, ) -> Client: """Return an authenticated API object (using username/password) Args: email: The email address of a Notion account. password: The account password. session: An optional aiohttp ClientSession. session_name: An optional session name to use for authentication. use_legacy_auth: Whether to use the legacy authentication endpoint. Returns: An authenticated Client object. """ client = Client(session=session, session_name=session_name) if use_legacy_auth: await client.async_legacy_authenticate_from_credentials(email, password) else: await client.async_authenticate_from_credentials(email, password) return client # Alias for backwards compatibility: async_get_client = async_get_client_with_credentials async def async_get_client_with_refresh_token( user_uuid: str, refresh_token: str, *, session: ClientSession | None = None, session_name: str | None = None, ) -> Client: """Return an authenticated API object (using a refresh token). Args: user_uuid: The UUID of the user. refresh_token: A refresh token. session: An optional aiohttp ClientSession. session_name: An optional session name to use for authentication. Returns: An authenticated Client object. """ client = Client(session=session, session_name=session_name) client.user_uuid = user_uuid await client.async_authenticate_from_refresh_token(refresh_token=refresh_token) return client bachya-aionotion-e90a0f0/aionotion/const.py000066400000000000000000000001311457416102500210430ustar00rootroot00000000000000"""Define package constants.""" import logging LOGGER = logging.getLogger(__package__) bachya-aionotion-e90a0f0/aionotion/errors.py000066400000000000000000000004721457416102500212410ustar00rootroot00000000000000"""Define package errors.""" class NotionError(Exception): """Define a base error.""" pass class RequestError(NotionError): """Define an error related to invalid requests.""" pass class InvalidCredentialsError(NotionError): """Define an error for unauthenticated accounts.""" pass bachya-aionotion-e90a0f0/aionotion/listener/000077500000000000000000000000001457416102500211755ustar00rootroot00000000000000bachya-aionotion-e90a0f0/aionotion/listener/__init__.py000066400000000000000000000026031457416102500233070ustar00rootroot00000000000000"""Define endpoints for interacting with listeners.""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.listener.models import Listener as ListenerModel from aionotion.listener.models import ( ListenerAllResponse, ListenerDefinition, ListenerDefinitionResponse, ) if TYPE_CHECKING: from aionotion.client import Client class Listener: """Define an object to interact with sensor endpoints.""" def __init__(self, client: Client) -> None: """Initialize. Args: client: The aionotion client """ self._client = client async def async_all(self) -> list[ListenerModel]: """Get all listeners. Returns: A validated API response payload. """ response: ListenerAllResponse = await self._client.async_request_and_validate( "get", "/sensor/listeners", ListenerAllResponse ) return response.listeners async def async_definitions(self) -> list[ListenerDefinition]: """Get all listener definitions. Returns: A validated API response payload. """ response: ListenerDefinitionResponse = ( await self._client.async_request_and_validate( "get", "/listener_definitions", ListenerDefinitionResponse ) ) return response.listener_definitions bachya-aionotion-e90a0f0/aionotion/listener/models.py000066400000000000000000000063371457416102500230430ustar00rootroot00000000000000"""Define sensor models.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime from enum import Enum from typing import Any, Literal import ciso8601 from mashumaro import DataClassDictMixin, field_options from aionotion.const import LOGGER @dataclass(frozen=True, kw_only=True) class ListenerLocalizedStatus(DataClassDictMixin): """Define a localized listener status.""" state: str description: str @dataclass(frozen=True, kw_only=True) class InsightOrigin(DataClassDictMixin): """Define an insight origin.""" id: str | None = None type: str | None = None @dataclass(frozen=True, kw_only=True) class PrimaryListenerInsight(DataClassDictMixin): """Define a primary listener insight.""" origin: InsightOrigin | None value: str | None data_received_at: datetime | None = field( default=None, metadata={"deserialize": ciso8601.parse_datetime} ) @dataclass(frozen=True, kw_only=True) class ListenerInsights(DataClassDictMixin): """Define listener insights:""" primary: PrimaryListenerInsight class ListenerKind(Enum): """Define the kinds of listener.""" BATTERY = 0 WATER_FLOW = 1 MOLD = 2 TEMPERATURE = 3 LEAK = 4 SAFE = 5 DOOR = 6 ALARM = 7 SENSOR_CONNECTION = 10 WINDOW_HINGED_VERTICAL = 12 GARAGE_DOOR = 13 WINDOW_HINGED_HORIZONTAL = 16 SYSTEM_OCCPUANCY = 23 SENSOR_FIRMWARE = 24 BRIDGE_FIRMWARE = 25 BRIDGE_CONNECTION = 26 SLIDING_DOOR_OR_WINDOW = 32 SYSTEM_USER_OCCUPANCY = 33 ESCALATION = 34 SYSTEM_STATUS = 35 UNKNOWN = 99 @dataclass(frozen=True, kw_only=True) class Listener(DataClassDictMixin): """Define a listener.""" id: str definition_id: int created_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) model_version: str sensor_id: str status_localized: ListenerLocalizedStatus insights: ListenerInsights configuration: dict[str, Any] pro_monitoring_status: Literal["eligible", "ineligible"] device_type: str = field(metadata=field_options(alias="type")) kind: ListenerKind = field(init=False) def __post_init__(self) -> None: """Perform post-init initialization.""" try: object.__setattr__(self, "kind", ListenerKind(self.definition_id)) except ValueError: LOGGER.info("Unknown listener kind: %s", self.definition_id) object.__setattr__(self, "kind", ListenerKind.UNKNOWN) @dataclass(frozen=True, kw_only=True) class ListenerAllResponse(DataClassDictMixin): """Define an API response containing all listeners.""" listeners: list[Listener] @dataclass(frozen=True, kw_only=True) class ListenerDefinition(DataClassDictMixin): """Define an API response containing all listener definitions.""" id: int name: str conflict_type: str priority: int hidden: bool conflicting_types: list[str] resources: dict | None compatible_hardware_revisions: list[int] type: str @dataclass(frozen=True, kw_only=True) class ListenerDefinitionResponse(DataClassDictMixin): """Define an API response containing all listener definitions.""" listener_definitions: list[ListenerDefinition] bachya-aionotion-e90a0f0/aionotion/py.typed000066400000000000000000000000001457416102500210350ustar00rootroot00000000000000bachya-aionotion-e90a0f0/aionotion/sensor/000077500000000000000000000000001457416102500206615ustar00rootroot00000000000000bachya-aionotion-e90a0f0/aionotion/sensor/__init__.py000066400000000000000000000024511457416102500227740ustar00rootroot00000000000000"""Define endpoints for interacting with sensors.""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.sensor.models import Sensor as SensorModel from aionotion.sensor.models import SensorAllResponse, SensorGetResponse if TYPE_CHECKING: from aionotion.client import Client class Sensor: """Define an object to interact with sensor endpoints.""" def __init__(self, client: Client) -> None: """Initialize. Args: client: The aionotion client """ self._client = client async def async_all(self) -> list[SensorModel]: """Get all sensors. Returns: A validated API response payload. """ response: SensorAllResponse = await self._client.async_request_and_validate( "get", "/sensors", SensorAllResponse ) return response.sensors async def async_get(self, sensor_id: int) -> SensorModel: """Get a sensor by ID. Args: sensor_id: The ID of the sensor to get. Returns: A validated API response payload. """ response: SensorGetResponse = await self._client.async_request_and_validate( "get", f"/sensors/{sensor_id}", SensorGetResponse ) return response.sensors bachya-aionotion-e90a0f0/aionotion/sensor/models.py000066400000000000000000000043251457416102500225220ustar00rootroot00000000000000"""Define sensor models.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime import ciso8601 from mashumaro import DataClassDictMixin @dataclass(frozen=True, kw_only=True) class Bridge(DataClassDictMixin): """Define a bridge representation.""" id: int hardware_id: str @dataclass(frozen=True, kw_only=True) class Firmware(DataClassDictMixin): """Define firmware information.""" status: str @dataclass(frozen=True, kw_only=True) class SurfaceType(DataClassDictMixin): """Define a surface type.""" id: str name: str slug: str @dataclass(frozen=True, kw_only=True) class User(DataClassDictMixin): """Define a user representation.""" id: int email: str @dataclass(frozen=True, kw_only=True) class Sensor(DataClassDictMixin): # pylint: disable=too-many-instance-attributes """Define a sensor.""" id: int uuid: str user: User bridge: Bridge last_bridge_hardware_id: str name: str location_id: int system_id: int hardware_id: str hardware_revision: int firmware_version: str device_key: str encryption_key: bool installed_at: datetime | None = field( default=None, metadata={"deserialize": ciso8601.parse_datetime} ) calibrated_at: datetime | None = field( default=None, metadata={"deserialize": ciso8601.parse_datetime} ) last_reported_at: datetime | None = field( default=None, metadata={"deserialize": ciso8601.parse_datetime} ) missing_at: datetime | None = field( default=None, metadata={"deserialize": ciso8601.parse_datetime} ) updated_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) created_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) signal_strength: int firmware: Firmware surface_type: SurfaceType | None @dataclass(frozen=True, kw_only=True) class SensorAllResponse(DataClassDictMixin): """Define an API response containing all sensors.""" sensors: list[Sensor] @dataclass(frozen=True, kw_only=True) class SensorGetResponse(DataClassDictMixin): """Define an API response containing a single sensor.""" sensors: Sensor bachya-aionotion-e90a0f0/aionotion/system/000077500000000000000000000000001457416102500206745ustar00rootroot00000000000000bachya-aionotion-e90a0f0/aionotion/system/__init__.py000066400000000000000000000024421457416102500230070ustar00rootroot00000000000000"""Define endpoints for interacting with systems (accounts).""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.system.models import System as SystemModel from aionotion.system.models import SystemAllResponse, SystemGetResponse if TYPE_CHECKING: from aionotion.client import Client class System: """Define an object to interact with system endpoints.""" def __init__(self, client: Client) -> None: """Initialize. Args: client: The aionotion client """ self._client = client async def async_all(self) -> list[SystemModel]: """Get all systems. Returns: An API response payload. """ response: SystemAllResponse = await self._client.async_request_and_validate( "get", "/systems", SystemAllResponse ) return response.systems async def async_get(self, system_id: int) -> SystemModel: """Get a system by ID. Args: system_id: The ID of the system to get. Returns: An API response payload. """ response: SystemGetResponse = await self._client.async_request_and_validate( "get", f"/systems/{system_id}", SystemGetResponse ) return response.systems bachya-aionotion-e90a0f0/aionotion/system/models.py000066400000000000000000000024601457416102500225330ustar00rootroot00000000000000"""Define system models.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime import ciso8601 from mashumaro import DataClassDictMixin @dataclass(frozen=True, kw_only=True) class System(DataClassDictMixin): """Define a system.""" uuid: str name: str mode: str partners: list[str] latitude: float longitude: float timezone_id: str created_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) updated_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) night_time_start: datetime = field( metadata={"deserialize": ciso8601.parse_datetime} ) night_time_end: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) id: int locality: str postal_code: str administrative_area: str fire_number: str police_number: str emergency_number: str address: str | None notion_pro_permit: str | None @dataclass(frozen=True, kw_only=True) class SystemAllResponse(DataClassDictMixin): """Define an API response containing all systems.""" systems: list[System] @dataclass(frozen=True, kw_only=True) class SystemGetResponse(DataClassDictMixin): """Define an API response containing a single system.""" systems: System bachya-aionotion-e90a0f0/aionotion/user/000077500000000000000000000000001457416102500203265ustar00rootroot00000000000000bachya-aionotion-e90a0f0/aionotion/user/__init__.py000066400000000000000000000030071457416102500224370ustar00rootroot00000000000000"""Define endpoints for interacting with users.""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.user.models import User as UserModel from aionotion.user.models import ( UserInformationResponse, UserPreferences, UserPreferencesResponse, ) if TYPE_CHECKING: from aionotion.client import Client class User: # pylint: disable=too-few-public-methods """Define an object to interact with user endpoints.""" def __init__(self, client: Client) -> None: """Initialize. Args: client: The aionotion client """ self._client = client async def async_info(self) -> UserModel: """Get the user's information. Returns: A validated API response payload. """ response: UserInformationResponse = ( await self._client.async_request_and_validate( "get", f"/users/{self._client.user_uuid}", UserInformationResponse, ) ) return response.users async def async_preferences(self) -> UserPreferences: """Get user preferences. Returns: A validated API response payload. """ response: UserPreferencesResponse = ( await self._client.async_request_and_validate( "get", f"/users/{self._client.user_uuid}/user_preferences", UserPreferencesResponse, ) ) return response.user_preferences bachya-aionotion-e90a0f0/aionotion/user/models.py000066400000000000000000000051721457416102500221700ustar00rootroot00000000000000"""Define user models.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime import ciso8601 from mashumaro import DataClassDictMixin @dataclass(frozen=True, kw_only=True) class AuthTokens(DataClassDictMixin): """Define auth tokens.""" jwt: str refresh_token: str @dataclass(frozen=True, kw_only=True) class LegacySession(DataClassDictMixin): """Define a legacy Notion session.""" user_id: str authentication_token: str @dataclass(frozen=True, kw_only=True) class LegacyUser(DataClassDictMixin): """Define a legacy Notion user.""" id: int uuid: str first_name: str last_name: str email: str phone_number: str | None role: str organization: str authentication_token: str created_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) updated_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) @dataclass(frozen=True, kw_only=True) class User(DataClassDictMixin): """Define a Notion user.""" id: int uuid: str first_name: str last_name: str email: str phone_number: str | None role: str organization: str created_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) updated_at: datetime = field(metadata={"deserialize": ciso8601.parse_datetime}) @dataclass(frozen=True, kw_only=True) class UserInformationResponse(DataClassDictMixin): """Define an API response containing user information.""" users: User @dataclass(frozen=True, kw_only=True) class AuthenticateViaCredentialsResponse(DataClassDictMixin): """Define an API response for authentication via credentials.""" user: User auth: AuthTokens @dataclass(frozen=True, kw_only=True) class AuthenticateViaCredentialsLegacyResponse(DataClassDictMixin): """Define an API response for authentication via credentials (legacy).""" users: LegacyUser session: LegacySession @dataclass(frozen=True, kw_only=True) class AuthenticateViaRefreshTokenResponse(DataClassDictMixin): """Define an API response for authentication via refresh token.""" auth: AuthTokens @dataclass(frozen=True, kw_only=True) class UserPreferences(DataClassDictMixin): """Define user preferences.""" user_id: int military_time_enabled: bool celsius_enabled: bool disconnect_alerts_enabled: bool home_away_alerts_enabled: bool battery_alerts_enabled: bool @dataclass(frozen=True, kw_only=True) class UserPreferencesResponse(DataClassDictMixin): """Define an API response containing all devices.""" user_preferences: UserPreferences bachya-aionotion-e90a0f0/aionotion/util/000077500000000000000000000000001457416102500203255ustar00rootroot00000000000000bachya-aionotion-e90a0f0/aionotion/util/__init__.py000066400000000000000000000000301457416102500224270ustar00rootroot00000000000000"""Define utilities.""" bachya-aionotion-e90a0f0/aionotion/util/auth.py000066400000000000000000000006031457416102500216370ustar00rootroot00000000000000"""Define auth utilities.""" from typing import Any import jwt def decode_jwt(encoded_jwt: str) -> dict[str, Any]: """Decode and return a JWT. Args: encoded_jwt: An encoded JWT. Returns: A decoded JWT. """ return jwt.decode( encoded_jwt, "secret", algorithms=["HS256"], options={"verify_signature": False}, ) bachya-aionotion-e90a0f0/aionotion/util/dt.py000066400000000000000000000012121457416102500213020ustar00rootroot00000000000000"""Define datetime utilities.""" from datetime import datetime try: from datetime import UTC except ImportError: # In place for support of Python 3.10 from datetime import timezone UTC = timezone.utc def utcnow() -> datetime: """Return the current UTC time. Returns: A ``datetime.datetime`` object. """ return datetime.now(tz=UTC) def utc_from_timestamp(timestamp: float) -> datetime: """Return a UTC time from a timestamp. Args: timestamp: The epoch to convert. Returns: A parsed ``datetime.datetime`` object. """ return datetime.fromtimestamp(timestamp, tz=UTC) bachya-aionotion-e90a0f0/examples/000077500000000000000000000000001457416102500171675ustar00rootroot00000000000000bachya-aionotion-e90a0f0/examples/__init__.py000066400000000000000000000000271457416102500212770ustar00rootroot00000000000000"""Define examples.""" bachya-aionotion-e90a0f0/examples/test_api.py000066400000000000000000000045451457416102500213610ustar00rootroot00000000000000"""Run an example script to quickly test.""" import asyncio import logging import os from aiohttp import ClientSession from aionotion import async_get_client_with_credentials from aionotion.errors import NotionError _LOGGER = logging.getLogger() EMAIL = os.environ.get("NOTION_EMAIL") PASSWORD = os.environ.get("NOTION_PASSWORD") async def main() -> None: """Create the aiohttp session and run the example.""" logging.basicConfig(level=logging.INFO) if not EMAIL or not PASSWORD: _LOGGER.error( "No email or password set (use NOTION_EMAIL and NOTION_PASSWORD " "environment variables)" ) return async with ClientSession() as session: try: client = await async_get_client_with_credentials( EMAIL, PASSWORD, session=session ) bridges = await client.bridge.async_all() _LOGGER.info("BRIDGES: %s", bridges) _LOGGER.info("============================================================") sensors = await client.sensor.async_all() _LOGGER.info("SENSORS: %s", sensors) _LOGGER.info("============================================================") listeners = await client.listener.async_all() _LOGGER.info("LISTENERS: %s", listeners) _LOGGER.info("============================================================") listener_definitions = await client.listener.async_definitions() _LOGGER.info("LISTENER DEFINITIONS: %s", listener_definitions) _LOGGER.info("============================================================") systems = await client.system.async_all() _LOGGER.info("SYSTEMS: %s", systems) _LOGGER.info("============================================================") user_info = await client.user.async_info() _LOGGER.info("USER_INFO: %s", user_info) _LOGGER.info("============================================================") user_preferences = await client.user.async_preferences() _LOGGER.info("USER_PREFERENCES: %s", user_preferences) _LOGGER.info("============================================================") except NotionError as err: _LOGGER.error("There was an error: %s", err) asyncio.run(main()) bachya-aionotion-e90a0f0/poetry.lock000066400000000000000000004103311457416102500175470ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, ] [package.dependencies] aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, ] [package.dependencies] frozenlist = ">=1.1.0" [[package]] name = "aresponses" version = "3.0.0" description = "Asyncio response mocking. Similar to the responses library used for 'requests'" optional = false python-versions = ">=3.7" files = [ {file = "aresponses-3.0.0-py3-none-any.whl", hash = "sha256:8093ab4758eb4aba91c765a50295b269ecfc0a9e7c7158954760bc0c23503970"}, {file = "aresponses-3.0.0.tar.gz", hash = "sha256:8731d0609fe4c954e21f17753dc868dca9e2e002b020a33dc9212004599b11e7"}, ] [package.dependencies] aiohttp = [ {version = ">=3.7.0", markers = "python_version >= \"3.10\" and python_version < \"3.12\""}, {version = ">=3.7.0,<3.8.dev0 || >=3.9.dev0", markers = "python_version >= \"3.12\""}, ] pytest-asyncio = {version = ">=0.17.0", markers = "python_version >= \"3.7\""} [[package]] name = "astroid" version = "3.1.0" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, ] [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} [[package]] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.7" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] [[package]] name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[docs,tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "black" version = "23.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "blacken-docs" version = "1.16.0" description = "Run Black on Python code blocks in documentation files." optional = false python-versions = ">=3.8" files = [ {file = "blacken_docs-1.16.0-py3-none-any.whl", hash = "sha256:b0dcb84b28ebfb352a2539202d396f50e15a54211e204a8005798f1d1edb7df8"}, {file = "blacken_docs-1.16.0.tar.gz", hash = "sha256:b4bdc3f3d73898dfbf0166f292c6ccfe343e65fc22ddef5319c95d1a8dcc6c1c"}, ] [package.dependencies] black = ">=22.1.0" [[package]] name = "certifi" version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, ] [[package]] name = "ciso8601" version = "2.3.1" description = "Fast ISO8601 date time parser for Python written in C" optional = false python-versions = "*" files = [ {file = "ciso8601-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:57db9a28e87f9e4fccba643fb70a9ba1515adc5e1325508eb2c10dd96620314c"}, {file = "ciso8601-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c59646197ddbf84909b6c31d55f744cfeef51811e3910b61d0f58f2885823fd"}, {file = "ciso8601-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a25da209193134842cd573464a5323f46fcc3ed781b633f15a34793ba7e1064"}, {file = "ciso8601-2.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ae83f4e60fc7e260a4188e4ec4ac1bdd40bdb382eeda92fc266c5aa2f0a1ee"}, {file = "ciso8601-2.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2c1ef17d1ea52a39b2dce6535583631ae4bfb65c76f0ee8c99413a6861a46c9e"}, {file = "ciso8601-2.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3771049ba29bd1077588c0a24be1d53f7493e7cc686b2caa92f7cae129636a0e"}, {file = "ciso8601-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:55381365366dacb57207cec610d26c9a6c0d237cb65a0cf67a2baaa5299f2366"}, {file = "ciso8601-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f25647803c9a5aaaed130c53bbec7ea06a4f95ba5c7016f59e444b4ef7ac39e"}, {file = "ciso8601-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:473288cd63efe6a2cf3f4b5f90394e53095358ccb13d6128f87a2da85d0f389b"}, {file = "ciso8601-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:121d27c55f4455eaa27ba3bd602beca915df9a352f235e935636a4660321070e"}, {file = "ciso8601-2.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef44cb4dc83f37019a356c7a72692cbe17072456f4879ca6bc0339f67eee5d00"}, {file = "ciso8601-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:364702e338212b6c1a8643d9399ada21560cf132f363853473560625cb4207f1"}, {file = "ciso8601-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8acb45545e6a654310c6ef788aacb2d73686646c414ceacdd9f5f78a83165af5"}, {file = "ciso8601-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:99addd8b113f85fac549167073f317a318cd2b5841552598ceb97b97c5708a38"}, {file = "ciso8601-2.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f39bb5936debf21c52e5d52b89f26857c303da80c43a72883946096a6ef5e561"}, {file = "ciso8601-2.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:21cf83ca945bb26ecd95364ae2c9ed0276378e5fe35ce1b64d4c6d5b33038ea3"}, {file = "ciso8601-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:013410263cba46748d2de29e9894341ae41223356cde7970478c32bd0984d10c"}, {file = "ciso8601-2.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b26935687ef1837b56997d8c61f1d789e698be58b261410e629eda9c89812141"}, {file = "ciso8601-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0d980a2a88030d4d8b2434623c250866a75b4979d289eba69bec445c51ace99f"}, {file = "ciso8601-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:87721de54e008fb1c4c3978553b05a9c417aa25b76ddf5702d6f7e8d9b109288"}, {file = "ciso8601-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f107a4c051e7c0416824279264d94f4ed3da0fbd82bd96ec3c3293426826de4"}, {file = "ciso8601-2.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:02ecbd7c8336c4e1c6bb725b898e29414ee92bdc0be6c72fb07036836b1ac867"}, {file = "ciso8601-2.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36525b1f380f4601533f4631c69911e44efb9cb50beab1da3248b0daa32bced4"}, {file = "ciso8601-2.3.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:874d20c6339e9096baaadfd1b9610bb8d5b373a0f2858cc06de8142b98d2129c"}, {file = "ciso8601-2.3.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:46a3663c2cf838f0149e1cdb8e4bdc95716e03cf2d5f803a6eb755d825896ebe"}, {file = "ciso8601-2.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e8e76825f80ce313d75bbbef1d3b8bd9e0ce31dbc157d1981e9593922c9983e7"}, {file = "ciso8601-2.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6850889813f3135e0aa18f0aaec64249dd81d36a1b9bce60bb45182930c86663"}, {file = "ciso8601-2.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c690ac24ec3407f68cdfd5e032c6cb18126ef33d6c4b3db0669b9cbb8c96bd4"}, {file = "ciso8601-2.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:024c52d5d0670f15ca3dc53eff7345b6eaee22fba929675f6a408f9d1e159d98"}, {file = "ciso8601-2.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7ae2c3442d042de5330672d0d28486ed92f9d7c6dc010943aa618fd361d4638"}, {file = "ciso8601-2.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:22128f0def36fa3c4cf0c482a216e8b8ad722def08bc11c07438eff82bdcd02a"}, {file = "ciso8601-2.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:025859ec286a994aa3f2120c0f27d053b719cabc975398338374f2cc1f961125"}, {file = "ciso8601-2.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2a64ff58904d4418d60fa9619014ae820ae21f7aef58da46df78a4c647f951ec"}, {file = "ciso8601-2.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d1f85c0b7fa742bbfd18177137ccbaa3f867dd06157f91595075bb959a733048"}, {file = "ciso8601-2.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ac59453664781dfddebee51f9a36e41819993823fdb09ddc0ce0e4bd3ff0c3"}, {file = "ciso8601-2.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:eaecca7e0c3ef9e8f5e963e212b083684e849f9a9bb25834d3042363223a73cd"}, {file = "ciso8601-2.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ad8f417c45eea973a694599b96f40d841215bfee352cb9963383e8d66b309981"}, {file = "ciso8601-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:b869396e9756a7c0696d8eb69ce1d8980bea5e25c86e5996b10d78c900a4362c"}, {file = "ciso8601-2.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7eb7b5ef8714d3d1fe9f3256b7a679ad783da899a0b7503a5ace78186735f840"}, {file = "ciso8601-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:02828107880848ff497971ebc98e6dc851ad7af8ec14a58089e0e11f3111cad6"}, {file = "ciso8601-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:566b4a8b2f9717e54ffcdd732a7c8051a91da30a60a4f1dafb62e303a1dbac69"}, {file = "ciso8601-2.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58a749d63f28c2eda71416c9d6014113b0748abf5fd14c502b01bd515502fedf"}, {file = "ciso8601-2.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:cb135de0e3b8feb7e74a4f7a234e8c8545957fe8d26316a1a549553f425c629d"}, {file = "ciso8601-2.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:695583810836a42945084b33621b22b0309701c6916689f6a3588fa44c5bc413"}, {file = "ciso8601-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:21204d98496cf5c0511dc21533be55c2a2d34b8c65603946a116812ffbae3b2d"}, {file = "ciso8601-2.3.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c29ea2b03dee2dc0a5d3e4a0b7d7768c597781e9fa451fe1025600f7cb55a89"}, {file = "ciso8601-2.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7533256af90724b8b7a707dcd1be4b67989447595c8e1e1c28399d4fd51dac50"}, {file = "ciso8601-2.3.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4bc9d577c0d1e57532513fc2899f5231727e28981a426767f7fa13dacb18c06"}, {file = "ciso8601-2.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4e30501eed43eea7ef64f032c81cd1d8b2020035cbdcefad40db72e2f3bc97ff"}, {file = "ciso8601-2.3.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070f568de3bc269268296cb9265704dc5fcb9d4c12b1f1c67536624174df5d09"}, {file = "ciso8601-2.3.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9065053c034c80c0afd74c71a4906675d07078a05cfd1cb5ff70661378cdbe60"}, {file = "ciso8601-2.3.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac00d293cdb3d1a5c78e09b3d75c7b0292ab45d5b26853b436ff5087eba2165"}, {file = "ciso8601-2.3.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:06941e2ee46701f083aeb21d13eb762d74d5ed6c46ff22119f27a42ed6edc8f9"}, {file = "ciso8601-2.3.1.tar.gz", hash = "sha256:3212c7ffe5d8080270548b5f2692ffd2039683b6628a8d2ad456122cc5793c4c"}, ] [[package]] name = "click" version = "8.1.6" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "codespell" version = "2.2.6" description = "Codespell" optional = false python-versions = ">=3.8" files = [ {file = "codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07"}, {file = "codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9"}, ] [package.extras] dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] hard-encoding-detection = ["chardet"] toml = ["tomli"] types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.4.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "darglint" version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." optional = false python-versions = ">=3.6,<4.0" files = [ {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, ] [[package]] name = "dill" version = "0.3.7" description = "serialize all of Python" optional = false python-versions = ">=3.7" files = [ {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, ] [package.extras] graph = ["objgraph (>=1.7.2)"] [[package]] name = "distlib" version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filelock" version = "3.12.2" description = "A platform independent file lock." optional = false python-versions = ">=3.7" files = [ {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, ] [package.extras] docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] [[package]] name = "gitdb" version = "4.0.10" description = "Git Object Database" optional = false python-versions = ">=3.7" files = [ {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] [package.dependencies] smmap = ">=3.0.1,<6" [[package]] name = "gitpython" version = "3.1.42" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ {file = "GitPython-3.1.42-py3-none-any.whl", hash = "sha256:1bf9cd7c9e7255f77778ea54359e54ac22a72a5b51288c457c881057b7bb9ecd"}, {file = "GitPython-3.1.42.tar.gz", hash = "sha256:2d99869e0fef71a73cbd242528105af1d6c1b108c60dfabd994bf292f76c3ceb"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar"] [[package]] name = "identify" version = "2.5.26" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ {file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"}, {file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "isort" version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] colors = ["colorama (>=0.4.6)"] [[package]] name = "mashumaro" version = "3.12" description = "Fast and well tested serialization library" optional = false python-versions = ">=3.8" files = [ {file = "mashumaro-3.12-py3-none-any.whl", hash = "sha256:bc4ab7ecaca106fcde706d77cef22816149285a10727b88141599855d4603e2f"}, {file = "mashumaro-3.12.tar.gz", hash = "sha256:bb4ff10aee689edff24f6ff369843e1a826193d396b449b86ef58489bfe40c83"}, ] [package.dependencies] typing-extensions = ">=4.1.0" [package.extras] msgpack = ["msgpack (>=0.5.6)"] orjson = ["orjson"] toml = ["tomli (>=1.1.0)", "tomli-w (>=1.0)"] yaml = ["pyyaml (>=3.13)"] [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "multidict" version = "6.0.4" description = "multidict implementation" optional = false python-versions = ">=3.7" files = [ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] [[package]] name = "mypy" version = "1.9.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, ] [package.dependencies] setuptools = "*" [[package]] name = "packaging" version = "23.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] [[package]] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] name = "platformdirs" version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "3.6.2" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"}, {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] name = "pre-commit-hooks" version = "4.5.0" description = "Some out-of-the-box hooks for pre-commit." optional = false python-versions = ">=3.8" files = [ {file = "pre_commit_hooks-4.5.0-py2.py3-none-any.whl", hash = "sha256:b779d5c44ede9b1fda48e2d96b08e9aa5b1d2fdb8903ca09f0dbaca22d529edb"}, {file = "pre_commit_hooks-4.5.0.tar.gz", hash = "sha256:ffbe2af1c85ac9a7695866955680b4dee98822638b748a6f3debefad79748c8a"}, ] [package.dependencies] "ruamel.yaml" = ">=0.15" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.7" files = [ {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" version = "3.1.0" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, ] [package.dependencies] astroid = ">=3.1.0,<=3.2.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] [[package]] name = "pytest" version = "8.1.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.4,<2.0" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-aiohttp" version = "1.0.5" description = "Pytest plugin for aiohttp support" optional = false python-versions = ">=3.7" files = [ {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, ] [package.dependencies] aiohttp = ">=3.8.1" pytest = ">=6.1.0" pytest-asyncio = ">=0.17.2" [package.extras] testing = ["coverage (==6.2)", "mypy (==0.931)"] [[package]] name = "pytest-asyncio" version = "0.23.5.post1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ {file = "pytest-asyncio-0.23.5.post1.tar.gz", hash = "sha256:b9a8806bea78c21276bc34321bbf234ba1b2ea5b30d9f0ce0f2dea45e4685813"}, {file = "pytest_asyncio-0.23.5.post1-py3-none-any.whl", hash = "sha256:30f54d27774e79ac409778889880242b0403d09cabd65b727ce90fe92dd5d80e"}, ] [package.dependencies] pytest = ">=7.0.0,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pyupgrade" version = "3.15.1" description = "A tool to automatically upgrade syntax for newer versions." optional = false python-versions = ">=3.8.1" files = [ {file = "pyupgrade-3.15.1-py2.py3-none-any.whl", hash = "sha256:c5e005de2805edcd333d1deb04553200ec69da85e4bc9db37b16345ed9e27ed9"}, {file = "pyupgrade-3.15.1.tar.gz", hash = "sha256:7690857cae0f6253f39241dcd2e57118c333c438b78609fc3c17a5aa61227b7d"}, ] [package.dependencies] tokenize-rt = ">=5.2.0" [[package]] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruamel-yaml" version = "0.17.32" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3" files = [ {file = "ruamel.yaml-0.17.32-py3-none-any.whl", hash = "sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447"}, {file = "ruamel.yaml-0.17.32.tar.gz", hash = "sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2"}, ] [package.dependencies] "ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.12\""} [package.extras] docs = ["ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel-yaml-clib" version = "0.2.7" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false python-versions = ">=3.5" files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_12_0_arm64.whl", hash = "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win32.whl", hash = "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win_amd64.whl", hash = "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win32.whl", hash = "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win32.whl", hash = "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win32.whl", hash = "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5"}, {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, ] [[package]] name = "ruff" version = "0.3.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77f2612752e25f730da7421ca5e3147b213dca4f9a0f7e0b534e9562c5441f01"}, {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9966b964b2dd1107797be9ca7195002b874424d1d5472097701ae8f43eadef5d"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b83d17ff166aa0659d1e1deaf9f2f14cbe387293a906de09bc4860717eb2e2da"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb875c6cc87b3703aeda85f01c9aebdce3d217aeaca3c2e52e38077383f7268a"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be75e468a6a86426430373d81c041b7605137a28f7014a72d2fc749e47f572aa"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:967978ac2d4506255e2f52afe70dda023fc602b283e97685c8447d036863a302"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1231eacd4510f73222940727ac927bc5d07667a86b0cbe822024dd00343e77e9"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6d613b19e9a8021be2ee1d0e27710208d1603b56f47203d0abbde906929a9b"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8439338a6303585d27b66b4626cbde89bb3e50fa3cae86ce52c1db7449330a7"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:de8b480d8379620cbb5ea466a9e53bb467d2fb07c7eca54a4aa8576483c35d36"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b74c3de9103bd35df2bb05d8b2899bf2dbe4efda6474ea9681280648ec4d237d"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f380be9fc15a99765c9cf316b40b9da1f6ad2ab9639e551703e581a5e6da6745"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0ac06a3759c3ab9ef86bbeca665d31ad3aa9a4b1c17684aadb7e61c10baa0df4"}, {file = "ruff-0.3.2-py3-none-win32.whl", hash = "sha256:9bd640a8f7dd07a0b6901fcebccedadeb1a705a50350fb86b4003b805c81385a"}, {file = "ruff-0.3.2-py3-none-win_amd64.whl", hash = "sha256:0c1bdd9920cab5707c26c8b3bf33a064a4ca7842d91a99ec0634fec68f9f4037"}, {file = "ruff-0.3.2-py3-none-win_arm64.whl", hash = "sha256:5f65103b1d76e0d600cabd577b04179ff592064eaa451a70a81085930e907d0b"}, {file = "ruff-0.3.2.tar.gz", hash = "sha256:fa78ec9418eb1ca3db392811df3376b46471ae93792a81af2d1cbb0e5dcb5142"}, ] [[package]] name = "setuptools" version = "68.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ {file = "setuptools-68.1.0-py3-none-any.whl", hash = "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715"}, {file = "setuptools-68.1.0.tar.gz", hash = "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.6" files = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] [[package]] name = "tokenize-rt" version = "5.2.0" description = "A wrapper around the stdlib `tokenize` which roundtrips." optional = false python-versions = ">=3.8" files = [ {file = "tokenize_rt-5.2.0-py2.py3-none-any.whl", hash = "sha256:b79d41a65cfec71285433511b50271b05da3584a1da144a0752e9c621a285289"}, {file = "tokenize_rt-5.2.0.tar.gz", hash = "sha256:9fe80f8a5c1edad2d3ede0f37481cc0cc1538a2f442c9c2f9e4feacd2792d054"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "tomlkit" version = "0.12.1" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, ] [[package]] name = "types-cryptography" version = "3.3.23.2" description = "Typing stubs for cryptography" optional = false python-versions = "*" files = [ {file = "types-cryptography-3.3.23.2.tar.gz", hash = "sha256:09cc53f273dd4d8c29fa7ad11fefd9b734126d467960162397bc5e3e604dea75"}, {file = "types_cryptography-3.3.23.2-py3-none-any.whl", hash = "sha256:b965d548f148f8e87f353ccf2b7bd92719fdf6c845ff7cedf2abb393a0643e4f"}, ] [[package]] name = "types-pyjwt" version = "1.7.1" description = "Typing stubs for PyJWT" optional = false python-versions = "*" files = [ {file = "types-PyJWT-1.7.1.tar.gz", hash = "sha256:99c1a0d94d370951f9c6e57b1c369be280b2cbfab72c0f9c0998707490f015c9"}, {file = "types_PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:810112a84b6c060bb5bc1959a1d229830465eccffa91d8a68eeaac28fb7713ac"}, ] [package.dependencies] types-cryptography = "*" [[package]] name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" version = "20.24.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ {file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, {file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<4" [package.extras] docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "vulture" version = "2.11" description = "Find dead code" optional = false python-versions = ">=3.8" files = [ {file = "vulture-2.11-py2.py3-none-any.whl", hash = "sha256:12d745f7710ffbf6aeb8279ba9068a24d4e52e8ed333b8b044035c9d6b823aba"}, {file = "vulture-2.11.tar.gz", hash = "sha256:f0fbb60bce6511aad87ee0736c502456737490a82d919a44e6d92262cb35f1c2"}, ] [package.dependencies] tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "yamllint" version = "1.35.1" description = "A linter for YAML files." optional = false python-versions = ">=3.8" files = [ {file = "yamllint-1.35.1-py3-none-any.whl", hash = "sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3"}, {file = "yamllint-1.35.1.tar.gz", hash = "sha256:7a003809f88324fd2c877734f2d575ee7881dd9043360657cc8049c809eba6cd"}, ] [package.dependencies] pathspec = ">=0.5.3" pyyaml = "*" [package.extras] dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] [[package]] name = "yarl" version = "1.9.4" description = "Yet another URL library" optional = false python-versions = ">=3.7" files = [ {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" content-hash = "44695cca3b2d470aa62eca312b640a91c753074aa01ca1e3af4daff52486ccf5" bachya-aionotion-e90a0f0/pyproject.toml000066400000000000000000000072131457416102500202700ustar00rootroot00000000000000[build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.black] target-version = ["py39"] [tool.coverage.report] exclude_lines = ["raise NotImplementedError", "TYPE_CHECKING", "ImportError"] fail_under = 100 show_missing = true [tool.coverage.run] source = ["aionotion"] [tool.isort] known_first_party = "aionotion,examples,tests" multi_line_output = 3 profile = "black" [tool.mypy] check_untyped_defs = true disallow_incomplete_defs = true disallow_subclassing_any = true disallow_untyped_calls = true disallow_untyped_decorators = true disallow_untyped_defs = true follow_imports = "silent" ignore_missing_imports = true no_implicit_optional = true platform = "linux" python_version = "3.12" show_error_codes = true strict_equality = true warn_incomplete_stub = true warn_redundant_casts = true warn_return_any = true warn_unreachable = true warn_unused_configs = true warn_unused_ignores = true [tool.poetry] name = "aionotion" version = "2024.03.1" description = "A simple Python 3 library for Notion Home Monitoring" readme = "README.md" authors = ["Aaron Bach "] license = "MIT" repository = "https://github.com/bachya/aionotion" classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] [tool.poetry.dependencies] PyJWT = ">=2.4.0" aiohttp = ">=3.9.0" certifi = ">=2023.07.22" # We can remove ciso8601 when we drop Python 3.10: ciso8601 = "^2.3.0" frozenlist = "^1.4.0" mashumaro = "^3.12" python = "^3.10" yarl = ">=1.9.2" [tool.poetry.group.dev.dependencies] GitPython = ">=3.1.35" Pygments = ">=2.15.0" aresponses = ">=2.1.6,<4.0.0" blacken-docs = "^1.12.1" codespell = "^2.2.2" coverage = {version = ">=6.5,<8.0", extras = ["toml"]} darglint = "^1.8.1" isort = "^5.10.1" mypy = "^1.2.0" pre-commit = ">=2.20,<4.0" pre-commit-hooks = "^4.3.0" pylint = ">=2.15.5,<4.0.0" pytest = ">=7.2,<9.0" pytest-aiohttp = "^1.0.0" pytest-asyncio = ">=0.20.1,<0.24.0" pytest-cov = "^4.0.0" pyupgrade = "^3.1.0" pyyaml = "^6.0.1" requests = ">=2.31.0" ruff = ">=0.0.261" types-pyjwt = "^1.7.1" vulture = "^2.6" yamllint = "^1.28.0" [tool.poetry.urls] "Bug Tracker" = "https://github.com/bachya/aionotion/issues" Changelog = "https://github.com/bachya/aionotion/releases" [tool.pylint.BASIC] expected-line-ending-format = "LF" [tool.pylint.DESIGN] max-attributes = 20 [tool.pylint.FORMAT] max-line-length = 88 [tool.pylint.MASTER] ignore = [ "tests", ] load-plugins = [ "pylint.extensions.bad_builtin", "pylint.extensions.code_style", "pylint.extensions.docparams", "pylint.extensions.docstyle", "pylint.extensions.empty_comment", "pylint.extensions.overlapping_exceptions", "pylint.extensions.typing", ] [tool.pylint."MESSAGES CONTROL"] # Reasons disabled: # invalid-enum-extension โ€“ We need to backport StrEnums until we drop Python 3.10 # unnecessary-pass - This can hurt readability disable = [ "invalid-enum-extension", "unnecessary-pass" ] [tool.pylint.REPORTS] score = false [tool.pylint.SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines = 12 # Ignore comments when computing similarities. ignore-comments = true # Ignore docstrings when computing similarities. ignore-docstrings = true # Ignore imports when computing similarities. ignore-imports = true [tool.vulture] min_confidence = 80 paths = ["aionotion", "tests"] verbose = false bachya-aionotion-e90a0f0/script/000077500000000000000000000000001457416102500166555ustar00rootroot00000000000000bachya-aionotion-e90a0f0/script/release000077500000000000000000000024631457416102500202300ustar00rootroot00000000000000#!/usr/bin/env bash set -e REPO_PATH="$( dirname "$( cd "$(dirname "$0")" ; pwd -P )" )" if [ "$(git rev-parse --abbrev-ref HEAD)" != "dev" ]; then echo "Refusing to publish a release from a branch other than dev" exit 1 fi if [ -z "$(command -v poetry)" ]; then echo "Poetry needs to be installed to run this script: pip3 install poetry" exit 1 fi function generate_version { latest_tag="$(git tag --sort=committerdate | tail -1)" month="$(date +'%Y.%m')" if [[ "$latest_tag" =~ "$month".* ]]; then patch="$(echo "$latest_tag" | cut -d . -f 3)" ((patch=patch+1)) echo "$month.$patch" else echo "$month.0" fi } # Temporarily uninstall pre-commit hooks so that we can push to dev and main: pre-commit uninstall # Pull the latest dev: git pull origin dev # Generate the next version (in the format YEAR.MONTH.RELEASE_NUMER): new_version=$(generate_version) # Update the PyPI package version: sed -i "" "s/^version = \".*\"/version = \"$new_version\"/g" "$REPO_PATH/pyproject.toml" git add pyproject.toml # Commit, tag, and push: git commit -m "Bump version to $new_version" git tag "$new_version" git push && git push --tags # Merge dev into main: git checkout main git merge dev git push git checkout dev # Re-initialize pre-commit: pre-commit install bachya-aionotion-e90a0f0/script/setup000077500000000000000000000002571457416102500177470ustar00rootroot00000000000000#!/bin/sh set -e if command -v "mise"; then mise install fi # Install all dependencies: pip3 install poetry poetry install # Install pre-commit hooks: pre-commit install bachya-aionotion-e90a0f0/tests/000077500000000000000000000000001457416102500165135ustar00rootroot00000000000000bachya-aionotion-e90a0f0/tests/__init__.py000066400000000000000000000000341457416102500206210ustar00rootroot00000000000000"""Define package tests.""" bachya-aionotion-e90a0f0/tests/common.py000066400000000000000000000020401457416102500203510ustar00rootroot00000000000000"""Define common test utilities.""" import os from uuid import uuid4 import jwt TEST_EMAIL = "user@email.com" TEST_PASSWORD = "password123" # noqa: S105 TEST_REFRESH_TOKEN = "abcde12345" TEST_USER_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" def generate_jwt(issued_at: float) -> bytes: """Generate a JWT. Args: issued_at: A timestamp at which the JWT is issued. Returns: The JWT string. """ return jwt.encode( { "sub": TEST_USER_UUID, "roles": ["delete_system", "manage_users"], "rtid": str(uuid4()), "exp": issued_at + (60 * 15), }, "secret", algorithm="HS256", ) def load_fixture(filename: str) -> str: """Load a fixture. Args: filename: The filename of the fixtures/ file to load. Returns: A string containing the contents of the file. """ path = os.path.join(os.path.dirname(__file__), "fixtures", filename) with open(path, encoding="utf-8") as fptr: return fptr.read() bachya-aionotion-e90a0f0/tests/conftest.py000066400000000000000000000203611457416102500207140ustar00rootroot00000000000000"""Define dynamic test fixtures.""" import json from collections.abc import Generator from time import time from typing import Any, cast import aiohttp import pytest from aresponses import ResponsesMockServer from tests.common import TEST_USER_UUID, generate_jwt, load_fixture def _generate_auth_response_success( access_token_issued_at: float | None, fixture_filename: str ) -> dict[str, Any]: """Generate a successful auth response payload. Args: access_token_issued_at: A timestamp at which an access token is issued. fixture_filename: The name of the fixture file. Returns: A successful auth response payload. """ if not access_token_issued_at: access_token_issued_at = time() response: dict[str, Any] = json.loads(load_fixture(fixture_filename)) response["auth"]["jwt"] = generate_jwt(access_token_issued_at) return response @pytest.fixture(name="auth_failure_response", scope="session") def auth_failure_response_fixture() -> dict[str, Any]: """Return a fixture for a failed auth response payload. Returns: A fixture for a failed auth response payload. """ return cast(dict[str, Any], json.loads(load_fixture("auth_failure_response.json"))) @pytest.fixture(name="auth_credentials_success_response") def auth_credentials_success_response_fixture( access_token_issued_at: float, ) -> dict[str, Any]: """Return a fixture for a successful auth response payload. Args: access_token_issued_at: A timestamp at which an access token is issued. Returns: A fixture for a successful auth response payload. """ return _generate_auth_response_success( access_token_issued_at, "auth_credentials_success_response.json" ) @pytest.fixture(name="auth_legacy_credentials_success_response") def auth_legacy_credentials_success_response_fixture() -> dict[str, Any]: """Return a fixture for a successful auth response payload (legacy) Returns: A fixture for a successful legacy auth response payload. """ return cast( dict[str, Any], json.loads(load_fixture("auth_legacy_credentials_success_response.json")), ) @pytest.fixture(name="auth_refresh_token_success_response") def auth_refresh_token_success_response_fixture( access_token_issued_at: float, ) -> dict[str, Any]: """Return a fixture for a successful auth response payload. Args: access_token_issued_at: A timestamp at which an access token is issued. Returns: A fixture for a successful auth response payload. """ return _generate_auth_response_success( access_token_issued_at, "auth_refresh_token_success_response.json" ) @pytest.fixture(name="access_token_issued_at") def access_token_issued_at_fixture() -> None: """Return a fixture for a timestamp at which an access token is issued. We don't use time() as a default because we have some tests where we need this value to be different *within* the function (and the fixture's default "function" scope will generate a single value for the entire function). By setting this to None, downstream fixtures can set a value that works for them. """ return None @pytest.fixture(name="authenticated_notion_api_server") def authenticated_notion_api_server_fixture( auth_credentials_success_response: dict[str, Any], auth_refresh_token_success_response: dict[str, Any], ) -> Generator[ResponsesMockServer, None, None]: """Return a fixture that mocks an authenticated Notion API server. Args: auth_credentials_success_response: An API response payload auth_refresh_token_success_response: An API response payload Yields: A fixture that mocks an authenticated Notion API server. """ server = ResponsesMockServer() server.add( "api.getnotion.com", "/api/auth/login", "post", response=aiohttp.web_response.json_response( auth_credentials_success_response, status=200 ), ) server.add( "api.getnotion.com", f"/api/auth/{TEST_USER_UUID}/refresh", "post", response=aiohttp.web_response.json_response( auth_refresh_token_success_response, status=200 ), ) yield server @pytest.fixture(name="bad_api_response", scope="session") def bad_api_response_fixture() -> dict[str, Any]: """Return a fixture for a bad API response. Returns: A fixture for a bad API response. """ return cast(dict[str, Any], json.loads(load_fixture("bad_api_response.json"))) @pytest.fixture(name="bridge_all_response", scope="session") def bridge_all_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/base_stations response. Returns: A fixture for a successful GET /api/base_stations response. """ return cast(dict[str, Any], json.loads(load_fixture("bridge_all_response.json"))) @pytest.fixture(name="bridge_get_response", scope="session") def bridge_get_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/base_stations/ response. Returns: A fixture for a successful GET /api/base_stations/ response. """ return cast(dict[str, Any], json.loads(load_fixture("bridge_get_response.json"))) @pytest.fixture(name="listener_definitions_response", scope="session") def listener_definitions_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/listener_definitions response. Returns: A fixture for a successful GET /api/listener_definitions response. """ return cast( dict[str, Any], json.loads(load_fixture("listener_definitions_response.json")) ) @pytest.fixture(name="sensor_all_response", scope="session") def sensor_all_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/sensors response. Returns: A fixture for a successful GET /api/sensors response. """ return cast(dict[str, Any], json.loads(load_fixture("sensor_all_response.json"))) @pytest.fixture(name="sensor_get_response", scope="session") def sensor_get_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/sensors/ response. Returns: A fixture for a successful GET /api/sensors/ response. """ return cast(dict[str, Any], json.loads(load_fixture("sensor_get_response.json"))) @pytest.fixture(name="sensor_listeners_response", scope="session") def sensor_listeners_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/sensors/listeners response. Returns: A fixture for a successful GET /api/sensors/listeners response. """ return cast( dict[str, Any], json.loads(load_fixture("sensor_listeners_response.json")) ) @pytest.fixture(name="system_all_response", scope="session") def system_all_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/systems response. Returns: A fixture for a successful GET /api/systems response. """ return cast(dict[str, Any], json.loads(load_fixture("system_all_response.json"))) @pytest.fixture(name="system_get_response", scope="session") def system_get_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/systems/ response. Returns: A fixture for a successful GET /api/systems/ response. """ return cast(dict[str, Any], json.loads(load_fixture("system_get_response.json"))) @pytest.fixture(name="user_info_response", scope="session") def user_info_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/users//user_info response. Returns: A fixture for a successful GET /api/users//user_info response. """ return cast(dict[str, Any], json.loads(load_fixture("user_info_response.json"))) @pytest.fixture(name="user_preferences_response", scope="session") def user_preferences_response_fixture() -> dict[str, Any]: """Return a fixture for a successful GET /api/users//user_preferences response. Returns: A fixture for a successful GET /api/users//user_preferences response. """ return cast( dict[str, Any], json.loads(load_fixture("user_preferences_response.json")) ) bachya-aionotion-e90a0f0/tests/fixtures/000077500000000000000000000000001457416102500203645ustar00rootroot00000000000000bachya-aionotion-e90a0f0/tests/fixtures/auth_credentials_success_response.json000066400000000000000000000006301457416102500302420ustar00rootroot00000000000000{ "user": { "id": 12345, "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "first_name": "The", "last_name": "Person", "email": "user@email.com", "phone_number": null, "role": "user", "organization": "Notion User", "created_at": "2019-04-30T01:35:03.781Z", "updated_at": "2024-01-20T23:54:14.350Z" }, "auth": { "jwt": "TBD", "refresh_token": "12345" } } bachya-aionotion-e90a0f0/tests/fixtures/auth_failure_response.json000066400000000000000000000001751457416102500256500ustar00rootroot00000000000000{ "errors": [ { "title": "Invalid email or password.", "message": "Invalid email or password." } ] } bachya-aionotion-e90a0f0/tests/fixtures/auth_legacy_credentials_success_response.json000066400000000000000000000007631457416102500315750ustar00rootroot00000000000000{ "users": { "id": 12345, "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "first_name": "The", "last_name": "Person", "email": "user@email.com", "phone_number": null, "role": "user", "organization": "Notion User", "authentication_token": "REDACTED", "created_at": "2019-04-30T01:35:03.781Z", "updated_at": "2024-01-21T04:58:50.277Z" }, "session": { "user_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "authentication_token": "REDACTED" } } bachya-aionotion-e90a0f0/tests/fixtures/auth_refresh_token_success_response.json000066400000000000000000000001031457416102500305760ustar00rootroot00000000000000{ "auth": { "jwt": "TBD", "refresh_token": "12345" } } bachya-aionotion-e90a0f0/tests/fixtures/bad_api_response.json000066400000000000000000000002411457416102500245510ustar00rootroot00000000000000{ "errors": [ { "title": "param is missing or the value is empty: auth", "message": "param is missing or the value is empty: auth" } ] } bachya-aionotion-e90a0f0/tests/fixtures/bridge_all_response.json000066400000000000000000000011461457416102500252630ustar00rootroot00000000000000{ "base_stations": [ { "id": 12345, "name": "Laundry Closet", "mode": "home", "hardware_id": "0x0000000000000000", "hardware_revision": 4, "firmware_version": { "wifi": "0.121.0", "wifi_app": "3.3.0", "silabs": "1.1.2" }, "missing_at": null, "created_at": "2019-04-30T01:43:50.497Z", "updated_at": "2023-12-12T22:33:01.073Z", "system_id": 12345, "firmware": { "wifi": "0.121.0", "wifi_app": "3.3.0", "silabs": "1.1.2" }, "links": { "system": 12345 } } ] } bachya-aionotion-e90a0f0/tests/fixtures/bridge_get_response.json000066400000000000000000000010561457416102500252720ustar00rootroot00000000000000{ "base_stations": { "id": 12345, "name": "Laundry Closet", "mode": "home", "hardware_id": "0x0000000000000000", "hardware_revision": 4, "firmware_version": { "wifi": "0.121.0", "wifi_app": "3.3.0", "silabs": "1.1.2" }, "missing_at": null, "created_at": "2019-04-30T01:43:50.497Z", "updated_at": "2023-12-12T22:33:01.073Z", "system_id": 12345, "firmware": { "wifi": "0.121.0", "wifi_app": "3.3.0", "silabs": "1.1.2" }, "links": { "system": 12345 } } } bachya-aionotion-e90a0f0/tests/fixtures/listener_definitions_response.json000066400000000000000000000136041457416102500274210ustar00rootroot00000000000000{ "listener_definitions": [ { "id": 0, "name": "battery", "conflict_type": "battery", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [3, 4, 5, 6, 7], "type": "sensor" }, { "id": 2, "name": "mold", "conflict_type": "mold", "priority": 4, "hidden": false, "conflicting_types": [], "resources": null, "compatible_hardware_revisions": [5, 6], "type": "sensor" }, { "id": 4, "name": "leak", "conflict_type": "probe", "priority": 1, "hidden": false, "conflicting_types": [], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" }, { "id": 13, "name": "garage_door", "conflict_type": "motion", "priority": 7, "hidden": false, "conflicting_types": ["sound"], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" }, { "id": 3, "name": "temperature", "conflict_type": "temperature", "priority": 3, "hidden": false, "conflicting_types": [], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" }, { "id": 35, "name": "system_status", "conflict_type": "system_status", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [], "type": "system" }, { "id": 25, "name": "firmware", "conflict_type": "firmware", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [0, 2, 3, 4, 5], "type": "bridge" }, { "id": 1, "name": "waterflow", "conflict_type": "waterflow", "priority": 1, "hidden": false, "conflicting_types": [], "resources": null, "compatible_hardware_revisions": [7], "type": "sensor" }, { "id": 23, "name": "occupancy", "conflict_type": "occupancy", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [], "type": "system" }, { "id": 10, "name": "connection", "conflict_type": "connection", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [3, 4, 5, 6, 7], "type": "sensor" }, { "id": 26, "name": "connection", "conflict_type": "connection", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [0, 2, 3, 4, 5], "type": "bridge" }, { "id": 7, "name": "alarm", "conflict_type": "sound", "priority": 2, "hidden": false, "conflicting_types": ["motion"], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" }, { "id": 24, "name": "firmware", "conflict_type": "firmware", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [3, 4, 5, 6, 7], "type": "sensor" }, { "id": 34, "name": "escalation", "conflict_type": "escalation", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [], "type": "integration" }, { "id": 16, "name": "window_hinged_horizontal", "conflict_type": "motion", "priority": 8, "hidden": false, "conflicting_types": ["sound"], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" }, { "id": 33, "name": "occupancy", "conflict_type": "occupancy", "priority": 50, "hidden": true, "conflicting_types": [], "resources": {}, "compatible_hardware_revisions": [], "type": "system_user" }, { "id": 5, "name": "safe", "conflict_type": "motion", "priority": 9, "hidden": false, "conflicting_types": ["sound"], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" }, { "id": 32, "name": "sliding", "conflict_type": "motion", "priority": 6, "hidden": false, "conflicting_types": ["sound"], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" }, { "id": 12, "name": "window_hinged_vertical", "conflict_type": "motion", "priority": 8, "hidden": false, "conflicting_types": ["sound"], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" }, { "id": 6, "name": "door", "conflict_type": "motion", "priority": 5, "hidden": false, "conflicting_types": ["sound"], "resources": { "install_image": "some_url", "icon": "some_other_url" }, "compatible_hardware_revisions": [4, 5, 6], "type": "sensor" } ] } bachya-aionotion-e90a0f0/tests/fixtures/sensor_all_response.json000066400000000000000000000017521457416102500253430ustar00rootroot00000000000000{ "sensors": [ { "id": 123456, "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "user": { "id": 12345, "email": "user@email.com" }, "bridge": { "id": 67890, "hardware_id": "0x0000000000000000" }, "last_bridge_hardware_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "name": "Sensor 1", "location_id": 123456, "system_id": 12345, "hardware_id": "0x0000000000000000", "hardware_revision": 5, "firmware_version": "1.1.2", "device_key": "0x0000000000000000", "encryption_key": true, "installed_at": "2019-06-17T03:30:27.766Z", "calibrated_at": "2024-01-19T00:38:15.372Z", "last_reported_at": "2024-01-21T00:00:46.705Z", "missing_at": null, "updated_at": "2024-01-19T00:38:16.856Z", "created_at": "2019-06-17T03:29:45.506Z", "signal_strength": 4, "firmware": { "status": "valid" }, "surface_type": null } ] } bachya-aionotion-e90a0f0/tests/fixtures/sensor_get_response.json000066400000000000000000000016421457416102500253500ustar00rootroot00000000000000{ "sensors": { "id": 123456, "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "user": { "id": 12345, "email": "user@email.com" }, "bridge": { "id": 67890, "hardware_id": "0x0000000000000000" }, "last_bridge_hardware_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "name": "Sensor 1", "location_id": 123456, "system_id": 12345, "hardware_id": "0x0000000000000000", "hardware_revision": 5, "firmware_version": "1.1.2", "device_key": "0x0000000000000000", "encryption_key": true, "installed_at": "2019-06-17T03:30:27.766Z", "calibrated_at": "2024-01-19T00:38:15.372Z", "last_reported_at": "2024-01-21T00:00:46.705Z", "missing_at": null, "updated_at": "2024-01-19T00:38:16.856Z", "created_at": "2019-06-17T03:29:45.506Z", "signal_strength": 4, "firmware": { "status": "valid" }, "surface_type": null } } bachya-aionotion-e90a0f0/tests/fixtures/sensor_listeners_response.json000066400000000000000000000042011457416102500265730ustar00rootroot00000000000000{ "listeners": [ { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "definition_id": 24, "created_at": "2019-06-17T03:29:45.722Z", "type": "sensor", "model_version": "1.0", "sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "status_localized": { "state": "Idle", "description": "Jun 18 at 12:17am" }, "insights": { "primary": { "origin": { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "type": "Sensor" }, "value": "idle", "data_received_at": "2023-06-18T06:17:00.697Z" } }, "configuration": {}, "pro_monitoring_status": "ineligible" }, { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "definition_id": 3, "created_at": "2023-06-02T15:56:37.826Z", "type": "sensor", "model_version": "3.1", "sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "status_localized": { "state": "71ยฐ", "description": "9:35pm" }, "insights": { "primary": { "origin": { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "type": "Sensor" }, "value": "inside", "data_received_at": "2024-02-05T01:34:20.240Z" } }, "configuration": { "lower": 15.56, "upper": 29.44, "offset": 0.0 }, "pro_monitoring_status": "eligible" }, { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "definition_id": 99999, "created_at": "2019-06-17T03:29:45.722Z", "type": "sensor", "model_version": "1.0", "sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "status_localized": { "state": "Idle", "description": "Jun 18 at 12:17am" }, "insights": { "primary": { "origin": { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "type": "Sensor" }, "value": "idle", "data_received_at": "2023-06-18T06:17:00.697Z" } }, "configuration": {}, "pro_monitoring_status": "ineligible" } ] } bachya-aionotion-e90a0f0/tests/fixtures/surface_types_response.json000066400000000000000000000003701457416102500260510ustar00rootroot00000000000000{ "surface_types": [ { "id": "03c8d427-54e7-4615-8ef7-4e10f28b0070", "name": "door", "slug": "door" }, { "id": "29bd2dfa-25a7-4a9a-84dd-e8deecbebe36", "name": "window", "slug": "window" } ] } bachya-aionotion-e90a0f0/tests/fixtures/system_all_response.json000066400000000000000000000013411457416102500253500ustar00rootroot00000000000000{ "systems": [ { "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "name": "Home", "mode": "home", "partners": [], "latitude": 89.0, "longitude": -170.0, "timezone_id": "Some/Timezone", "created_at": "2019-04-30T01:35:21.870Z", "updated_at": "2019-07-09T04:57:01.068Z", "night_time_start": "2019-05-01T04:00:00.000Z", "night_time_end": "2019-05-01T13:00:00.000Z", "id": 12345, "locality": "Moon", "postal_code": "11111", "administrative_area": "Moon", "fire_number": "(123) 456-7890", "police_number": "(123) 456-7890", "emergency_number": "(123) 456-7890", "address": null, "notion_pro_permit": null } ] } bachya-aionotion-e90a0f0/tests/fixtures/system_get_response.json000066400000000000000000000012511457416102500253570ustar00rootroot00000000000000{ "systems": { "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "name": "Home", "mode": "home", "partners": [], "latitude": 89, "longitude": -170, "timezone_id": "Some/Timezone", "created_at": "2019-04-30T01:35:21.870Z", "updated_at": "2019-07-09T04:57:01.068Z", "night_time_start": "2019-05-01T04:00:00.000Z", "night_time_end": "2019-05-01T13:00:00.000Z", "id": 12345, "locality": "Moon", "postal_code": "11111", "administrative_area": "Moon", "fire_number": "(123) 456-7890", "police_number": "(123) 456-7890", "emergency_number": "(123) 456-7890", "address": null, "notion_pro_permit": null } } bachya-aionotion-e90a0f0/tests/fixtures/system_locations_response.json000066400000000000000000000005501457416102500265740ustar00rootroot00000000000000{ "locations": [ { "id": 123456, "display_name": "Kitchen", "created_at": "2019-06-16T21:11:01.846Z", "updated_at": "2019-06-16T21:11:01.846Z", "sensor_ids": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"], "system_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "links": { "sensors": [123456] } } ] } bachya-aionotion-e90a0f0/tests/fixtures/system_users_response.json000066400000000000000000000025011457416102500257400ustar00rootroot00000000000000{ "system_users": [ { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "role": "owner", "mode": "home", "created_at": "2019-04-30T01:35:21.885Z", "updated_at": "2019-07-09T04:57:01.075Z", "user": { "id": 45868, "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "first_name": "The", "last_name": "Person", "email": "user@email.com", "phone_number": null, "role": "user", "organization": "Notion User", "created_at": "2019-04-30T01:35:03.781Z", "updated_at": "2023-12-21T04:13:53.048Z" }, "system": { "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "name": "Home", "mode": "home", "partners": [], "latitude": 51.5286416, "longitude": -0.1015987, "timezone_id": "Europe/London", "created_at": "2019-04-30T01:35:21.870Z", "updated_at": "2019-07-09T04:57:01.068Z", "night_time_start": "2019-05-01T04:00:00.000Z", "night_time_end": "2019-05-01T13:00:00.000Z", "id": 32453, "locality": "London", "postal_code": "12345", "administrative_area": "England", "fire_number": "(123) 456.7890", "police_number": "(123) 456.7890", "emergency_number": "(123) 456.7890" } } ] } bachya-aionotion-e90a0f0/tests/fixtures/user_info_response.json000066400000000000000000000005311457416102500251650ustar00rootroot00000000000000{ "users": { "id": 12345, "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "first_name": "The", "last_name": "Person", "email": "user@email.com", "phone_number": null, "role": "user", "organization": "Notion User", "created_at": "2019-04-30T01:35:03.781Z", "updated_at": "2023-12-21T04:13:53.048Z" } } bachya-aionotion-e90a0f0/tests/fixtures/user_preferences_response.json000066400000000000000000000003511457416102500265330ustar00rootroot00000000000000{ "user_preferences": { "user_id": 12345, "military_time_enabled": false, "celsius_enabled": false, "disconnect_alerts_enabled": true, "home_away_alerts_enabled": false, "battery_alerts_enabled": true } } bachya-aionotion-e90a0f0/tests/test_bridge.py000066400000000000000000000107171457416102500213660ustar00rootroot00000000000000"""Define tests for bridges.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any import aiohttp import pytest from aresponses import ResponsesMockServer from aionotion import async_get_client_with_credentials from tests.common import TEST_EMAIL, TEST_PASSWORD @pytest.mark.asyncio async def test_bridge_all( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, bridge_all_response: dict[str, Any], ) -> None: """Test getting all bridges. Args: aresponses: An aresponses server. authenticated_notion_api_server: A mock authenticated Notion API server bridge_all_response: An API response payload """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/base_stations", "get", response=aiohttp.web_response.json_response( bridge_all_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) bridges = await client.bridge.async_all() assert len(bridges) == 1 assert bridges[0].id == 12345 assert bridges[0].name == "Laundry Closet" assert bridges[0].mode == "home" assert bridges[0].hardware_id == "0x0000000000000000" assert bridges[0].hardware_revision == 4 assert bridges[0].firmware_version.silabs == "1.1.2" assert bridges[0].firmware_version.wifi == "0.121.0" assert bridges[0].firmware_version.wifi_app == "3.3.0" assert bridges[0].missing_at is None assert bridges[0].created_at == datetime( 2019, 4, 30, 1, 43, 50, 497000, tzinfo=timezone.utc ) assert bridges[0].updated_at == datetime( 2023, 12, 12, 22, 33, 1, 73000, tzinfo=timezone.utc ) assert bridges[0].system_id == 12345 assert bridges[0].firmware.silabs == "1.1.2" assert bridges[0].firmware.ti is None assert bridges[0].firmware.wifi == "0.121.0" assert bridges[0].firmware.wifi_app == "3.3.0" assert bridges[0].links["system"] == 12345 aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_bridge_get( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, bridge_get_response: dict[str, Any], ) -> None: """Test getting a bridge by ID. Args: aresponses: An aresponses server. authenticated_notion_api_server: A mock authenticated Notion API server bridge_get_response: An API response payload """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/base_stations/12345", "get", response=aiohttp.web_response.json_response( bridge_get_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) bridge = await client.bridge.async_get(12345) assert bridge.id == 12345 assert bridge.name == "Laundry Closet" assert bridge.mode == "home" assert bridge.hardware_id == "0x0000000000000000" assert bridge.hardware_revision == 4 assert bridge.firmware_version.silabs == "1.1.2" assert bridge.firmware_version.wifi == "0.121.0" assert bridge.firmware_version.wifi_app == "3.3.0" assert bridge.missing_at is None assert bridge.created_at == datetime( 2019, 4, 30, 1, 43, 50, 497000, tzinfo=timezone.utc ) assert bridge.updated_at == datetime( 2023, 12, 12, 22, 33, 1, 73000, tzinfo=timezone.utc ) assert bridge.system_id == 12345 assert bridge.firmware.silabs == "1.1.2" assert bridge.firmware.ti is None assert bridge.firmware.wifi == "0.121.0" assert bridge.firmware.wifi_app == "3.3.0" assert bridge.links["system"] == 12345 aresponses.assert_plan_strictly_followed() bachya-aionotion-e90a0f0/tests/test_client.py000066400000000000000000000323241457416102500214060ustar00rootroot00000000000000"""Define tests for the client.""" # pylint: disable=protected-access from __future__ import annotations import asyncio import logging from time import time from typing import Any from unittest.mock import Mock import aiohttp import pytest from aresponses import ResponsesMockServer from aionotion import ( async_get_client_with_credentials, async_get_client_with_refresh_token, ) from aionotion.client import Client from aionotion.errors import InvalidCredentialsError, RequestError from .common import TEST_EMAIL, TEST_PASSWORD, TEST_REFRESH_TOKEN, TEST_USER_UUID @pytest.mark.asyncio async def test_api_error( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, bad_api_response: dict[str, Any], ) -> None: """Test an invalid API call. Args: aresponses: An aresponses server authenticated_notion_api_server: A mock authenticated Notion API server bad_api_response: An API response payload """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/bad_endpoint", "get", response=aiohttp.web_response.json_response(bad_api_response, status=400), ) async with aiohttp.ClientSession() as session: with pytest.raises(RequestError): client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) await client.async_request("get", "/bad_endpoint") aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_auth_credentials_success( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, ) -> None: """Test authenticating against the API with credentials. Args: aresponses: An aresponses server authenticated_notion_api_server: A mock authenticated Notion API server """ async with authenticated_notion_api_server, aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) assert client._access_token is not None aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_auth_failure( aresponses: ResponsesMockServer, auth_failure_response: dict[str, Any] ) -> None: """Test invalid credentials. Args: aresponses: An aresponses server auth_failure_response: An API response payload """ aresponses.add( "api.getnotion.com", "/api/auth/login", "post", response=aiohttp.web_response.json_response(auth_failure_response, status=401), ) async with aiohttp.ClientSession() as session: with pytest.raises(InvalidCredentialsError): _ = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_auth_legacy_credentials_success( aresponses: ResponsesMockServer, auth_legacy_credentials_success_response: dict[str, Any], bridge_all_response: dict[str, Any], ) -> None: """Test authenticating against the API with credentials (legacy). Args: aresponses: An aresponses server auth_legacy_credentials_success_response: An API response payload bridge_all_response: An API response payload """ aresponses.add( "api.getnotion.com", "/api/users/sign_in", "post", response=aiohttp.web_response.json_response( auth_legacy_credentials_success_response, status=200 ), ) aresponses.add( "api.getnotion.com", "/api/base_stations", "get", response=aiohttp.web_response.json_response(bridge_all_response, status=200), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session, use_legacy_auth=True ) assert client._access_token is not None bridges = await client.bridge.async_all() assert len(bridges) == 1 aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_auth_refresh_token_success( aresponses: ResponsesMockServer, auth_refresh_token_success_response: dict[str, Any], ) -> None: """Test authenticating against the API with a refresh token. Args: aresponses: An aresponses server auth_refresh_token_success_response: An API response payload """ aresponses.add( "api.getnotion.com", f"/api/auth/{TEST_USER_UUID}/refresh", "post", response=aiohttp.web_response.json_response( auth_refresh_token_success_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_refresh_token( TEST_USER_UUID, TEST_REFRESH_TOKEN, session=session ) assert client._access_token is not None aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio @pytest.mark.parametrize("refresh_token", [None, "new_refresh_token"]) async def test_auth_refresh_token_success_existing_client( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, refresh_token: str | None, ) -> None: """Test authenticating against the API with a refresh token with an existing client. Args: aresponses: An aresponses server authenticated_notion_api_server: A mock authenticated Notion API server refresh_token: An optional refresh token """ async with authenticated_notion_api_server, aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) old_access_token = client._access_token assert old_access_token is not None assert client.refresh_token is not None await client.async_authenticate_from_refresh_token(refresh_token=refresh_token) new_access_token = client._access_token assert new_access_token is not None assert old_access_token != new_access_token aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio @pytest.mark.parametrize("access_token_issued_at", [time() - 30 * 60]) async def test_expired_access_token( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, bridge_all_response: dict[str, Any], caplog: Mock, ) -> None: """Test handling an expired access token. Args: aresponses: An aresponses server authenticated_notion_api_server: A mock authenticated Notion API server bridge_all_response: An API response payload caplog: A mocked logging utility. """ caplog.set_level(logging.DEBUG) async with authenticated_notion_api_server, aiohttp.ClientSession() as session: authenticated_notion_api_server.add( "api.getnotion.com", "/api/base_stations", "get", response=aiohttp.web_response.json_response( bridge_all_response, status=200 ), ) client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) _ = await client.bridge.async_all() assert any( m for m in caplog.messages if "Access token expired, refreshing..." in m ) aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio @pytest.mark.parametrize("access_token_issued_at", [time() - 30 * 60]) async def test_expired_access_token_concurrent_calls( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, bridge_all_response: dict[str, Any], caplog: Mock, sensor_all_response: dict[str, Any], ) -> None: """Test handling an expired access token with multiple concurrent calls. Args: aresponses: An aresponses server authenticated_notion_api_server: A mock authenticated Notion API server. bridge_all_response: An API response payload. caplog: A mocked logging utility. sensor_all_response: An API response payload. """ caplog.set_level(logging.DEBUG) async with authenticated_notion_api_server, aiohttp.ClientSession() as session: authenticated_notion_api_server.add( "api.getnotion.com", "/api/base_stations", "get", response=aiohttp.web_response.json_response( bridge_all_response, status=200 ), ) authenticated_notion_api_server.add( "api.getnotion.com", "/api/sensors", "get", response=aiohttp.web_response.json_response( sensor_all_response, status=200 ), ) client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) tasks = [client.bridge.async_all(), client.sensor.async_all()] results = await asyncio.gather(*tasks) # Assert the we got the results of both calls, even with a refreshed access # token in the middle: assert len(results) == 2 assert any( m for m in caplog.messages if "Access token expired, refreshing..." in m ) aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_premature_refresh_token( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, ) -> None: """Test attempting to refresh the access token before actually getting one. Args: aresponses: An aresponses server authenticated_notion_api_server: A mock authenticated Notion API server """ async with authenticated_notion_api_server, aiohttp.ClientSession() as session: client = Client(session=session) with pytest.raises(InvalidCredentialsError): await client.async_authenticate_from_refresh_token() aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_no_explicit_session( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, ) -> None: """Test authentication without an explicit ClientSession. Args: aresponses: An aresponses server authenticated_notion_api_server: A mock authenticated Notion API server """ async with authenticated_notion_api_server: client = await async_get_client_with_credentials(TEST_EMAIL, TEST_PASSWORD) assert client._access_token is not None aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_refresh_token_callback( aresponses: ResponsesMockServer, auth_refresh_token_success_response: dict[str, Any], authenticated_notion_api_server: ResponsesMockServer, ) -> None: """Test that a refresh token callback is called when the access token is refreshed. Args: aresponses: An aresponses server auth_refresh_token_success_response: An API response payload authenticated_notion_api_server: A mock authenticated Notion API server """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", f"/api/auth/{TEST_USER_UUID}/refresh", "post", response=aiohttp.web_response.json_response( auth_refresh_token_success_response, status=200 ), ) client = await async_get_client_with_credentials(TEST_EMAIL, TEST_PASSWORD) assert client._access_token is not None # Define and attach a refresh token callback, then refresh the access token: refresh_token_callback = Mock() remove_callback = client.add_refresh_token_callback(refresh_token_callback) await client.async_authenticate_from_refresh_token() # Cancel the callback and refresh the access token again: remove_callback() await client.async_authenticate_from_refresh_token() # Ensure that the callback was called only once: refresh_token_callback.assert_called_once_with(client._refresh_token) aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio @pytest.mark.parametrize("bridge_get_response", [{}]) async def test_validation_error( authenticated_notion_api_server: ResponsesMockServer, bridge_get_response: dict[str, Any], ) -> None: """Test a response validation error. Args: authenticated_notion_api_server: A mock authenticated Notion API server bridge_get_response: An API response payload """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/base_stations/98765", "get", response=aiohttp.web_response.json_response( bridge_get_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) with pytest.raises(RequestError): await client.bridge.async_get(98765) bachya-aionotion-e90a0f0/tests/test_listener.py000066400000000000000000000137241457416102500217600ustar00rootroot00000000000000"""Define tests for listeners.""" from __future__ import annotations import logging from datetime import datetime, timezone from typing import Any from unittest.mock import Mock import aiohttp import pytest from aresponses import ResponsesMockServer from aionotion import async_get_client_with_credentials from aionotion.listener.models import ListenerKind from tests.common import TEST_EMAIL, TEST_PASSWORD @pytest.mark.asyncio async def test_listener_all( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, caplog: Mock, sensor_listeners_response: dict[str, Any], ) -> None: """Test getting listeners for all sensors. Args: aresponses: An aresponses server. authenticated_notion_api_server: A mock authenticated Notion API server caplog: A mocked logging utility. sensor_listeners_response: An API response payload """ caplog.set_level(logging.INFO) async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/sensor/listeners", "get", response=aiohttp.web_response.json_response( sensor_listeners_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) listeners = await client.listener.async_all() assert len(listeners) == 3 assert listeners[0].id == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert listeners[0].definition_id == 24 assert listeners[0].created_at == datetime( 2019, 6, 17, 3, 29, 45, 722000, tzinfo=timezone.utc ) assert listeners[0].device_type == "sensor" assert listeners[0].model_version == "1.0" assert listeners[0].sensor_id == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert listeners[0].status_localized.state == "Idle" assert listeners[0].insights.primary.origin is not None assert ( listeners[0].insights.primary.origin.id == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ) assert listeners[0].insights.primary.origin.type == "Sensor" assert listeners[0].insights.primary.value == "idle" assert listeners[0].insights.primary.data_received_at == datetime( 2023, 6, 18, 6, 17, 0, 697000, tzinfo=timezone.utc ) assert listeners[0].configuration == {} assert listeners[0].pro_monitoring_status == "ineligible" assert listeners[0].kind == ListenerKind.SENSOR_FIRMWARE assert listeners[1].id == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert listeners[1].definition_id == 3 assert listeners[1].created_at == datetime( 2023, 6, 2, 15, 56, 37, 826000, tzinfo=timezone.utc ) assert listeners[1].device_type == "sensor" assert listeners[1].model_version == "3.1" assert listeners[1].sensor_id == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert listeners[1].status_localized.state == "71ยฐ" assert listeners[1].insights.primary.origin is not None assert ( listeners[0].insights.primary.origin.id == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ) assert listeners[1].insights.primary.origin.type == "Sensor" assert listeners[1].insights.primary.value == "inside" assert listeners[1].insights.primary.data_received_at == datetime( 2024, 2, 5, 1, 34, 20, 240000, tzinfo=timezone.utc ) assert listeners[1].configuration == { "lower": 15.56, "upper": 29.44, "offset": 0.0, } assert listeners[1].pro_monitoring_status == "eligible" assert listeners[1].kind == ListenerKind.TEMPERATURE assert any( m for m in caplog.messages if "Unknown listener kind: 99999" in m ) assert listeners[2].kind == ListenerKind.UNKNOWN aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_listener_definitions( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, listener_definitions_response: dict[str, Any], ) -> None: """Test getting listeners for all sensors. Args: aresponses: An aresponses server. authenticated_notion_api_server: A mock authenticated Notion API server listener_definitions_response: An API response payload """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/listener_definitions", "get", response=aiohttp.web_response.json_response( listener_definitions_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) definitions = await client.listener.async_definitions() assert len(definitions) == 20 assert definitions[0].id == 0 assert definitions[0].name == "battery" assert definitions[0].conflict_type == "battery" assert definitions[0].priority == 50 assert definitions[0].hidden is True assert definitions[0].conflicting_types == [] assert definitions[0].resources == {} assert definitions[0].compatible_hardware_revisions == [ 3, 4, 5, 6, 7, ] assert definitions[0].type == "sensor" aresponses.assert_plan_strictly_followed() bachya-aionotion-e90a0f0/tests/test_sensor.py000066400000000000000000000135171457416102500214440ustar00rootroot00000000000000"""Define tests for sensors.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any import aiohttp import pytest from aresponses import ResponsesMockServer from aionotion import async_get_client_with_credentials from tests.common import TEST_EMAIL, TEST_PASSWORD @pytest.mark.asyncio async def test_sensor_all( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, sensor_all_response: dict[str, Any], ) -> None: """Test getting all sensors. Args: aresponses: An aresponses server. authenticated_notion_api_server: A mock authenticated Notion API server sensor_all_response: An API response payload """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/sensors", "get", response=aiohttp.web_response.json_response( sensor_all_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) sensors = await client.sensor.async_all() assert len(sensors) == 1 assert sensors[0].id == 123456 assert sensors[0].uuid == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert sensors[0].user.id == 12345 assert sensors[0].user.email == "user@email.com" assert sensors[0].bridge.id == 67890 assert sensors[0].bridge.hardware_id == "0x0000000000000000" assert ( sensors[0].last_bridge_hardware_id == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ) assert sensors[0].name == "Sensor 1" assert sensors[0].location_id == 123456 assert sensors[0].system_id == 12345 assert sensors[0].hardware_id == "0x0000000000000000" assert sensors[0].hardware_revision == 5 assert sensors[0].firmware_version == "1.1.2" assert sensors[0].device_key == "0x0000000000000000" assert sensors[0].encryption_key is True assert sensors[0].installed_at == datetime( 2019, 6, 17, 3, 30, 27, 766000, tzinfo=timezone.utc ) assert sensors[0].calibrated_at == datetime( 2024, 1, 19, 0, 38, 15, 372000, tzinfo=timezone.utc ) assert sensors[0].last_reported_at == datetime( 2024, 1, 21, 0, 0, 46, 705000, tzinfo=timezone.utc ) assert sensors[0].missing_at is None assert sensors[0].updated_at == datetime( 2024, 1, 19, 0, 38, 16, 856000, tzinfo=timezone.utc ) assert sensors[0].created_at == datetime( 2019, 6, 17, 3, 29, 45, 506000, tzinfo=timezone.utc ) assert sensors[0].signal_strength == 4 assert sensors[0].firmware.status == "valid" assert sensors[0].surface_type is None aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_sensor_get( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, sensor_get_response: dict[str, Any], ) -> None: """Test getting a sensor by ID. Args: aresponses: An aresponses server. authenticated_notion_api_server: A mock authenticated Notion API server sensor_get_response: An API response payload """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/sensors/123456", "get", response=aiohttp.web_response.json_response( sensor_get_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) sensor = await client.sensor.async_get(123456) assert sensor.id == 123456 assert sensor.uuid == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert sensor.user.id == 12345 assert sensor.user.email == "user@email.com" assert sensor.bridge.id == 67890 assert sensor.bridge.hardware_id == "0x0000000000000000" assert ( sensor.last_bridge_hardware_id == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ) assert sensor.name == "Sensor 1" assert sensor.location_id == 123456 assert sensor.system_id == 12345 assert sensor.hardware_id == "0x0000000000000000" assert sensor.hardware_revision == 5 assert sensor.firmware_version == "1.1.2" assert sensor.device_key == "0x0000000000000000" assert sensor.encryption_key is True assert sensor.installed_at == datetime( 2019, 6, 17, 3, 30, 27, 766000, tzinfo=timezone.utc ) assert sensor.calibrated_at == datetime( 2024, 1, 19, 0, 38, 15, 372000, tzinfo=timezone.utc ) assert sensor.last_reported_at == datetime( 2024, 1, 21, 0, 0, 46, 705000, tzinfo=timezone.utc ) assert sensor.missing_at is None assert sensor.updated_at == datetime( 2024, 1, 19, 0, 38, 16, 856000, tzinfo=timezone.utc ) assert sensor.created_at == datetime( 2019, 6, 17, 3, 29, 45, 506000, tzinfo=timezone.utc ) assert sensor.signal_strength == 4 assert sensor.firmware.status == "valid" assert sensor.surface_type is None aresponses.assert_plan_strictly_followed() bachya-aionotion-e90a0f0/tests/test_system.py000066400000000000000000000117641457416102500214610ustar00rootroot00000000000000"""Define tests for systems.""" from __future__ import annotations from datetime import datetime, timezone import aiohttp import pytest from aresponses import ResponsesMockServer from aionotion import async_get_client_with_credentials from .common import TEST_EMAIL, TEST_PASSWORD @pytest.mark.asyncio async def test_system_all( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, system_all_response: dict[str, str], ) -> None: """Test getting all systems. Args: aresponses: An aresponses server. authenticated_notion_api_server: A mock authenticated Notion API server system_all_response: A fixture for a system all response. """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/systems", "get", response=aiohttp.web_response.json_response( system_all_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) systems = await client.system.async_all() assert len(systems) == 1 assert systems[0].uuid == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert systems[0].name == "Home" assert systems[0].mode == "home" assert systems[0].partners == [] assert systems[0].latitude == 89.0 assert systems[0].longitude == -170.0 assert systems[0].timezone_id == "Some/Timezone" assert systems[0].created_at == datetime( 2019, 4, 30, 1, 35, 21, 870000, tzinfo=timezone.utc ) assert systems[0].updated_at == datetime( 2019, 7, 9, 4, 57, 1, 68000, tzinfo=timezone.utc ) assert systems[0].night_time_start == datetime( 2019, 5, 1, 4, 0, tzinfo=timezone.utc ) assert systems[0].night_time_end == datetime( 2019, 5, 1, 13, 0, tzinfo=timezone.utc ) assert systems[0].id == 12345 assert systems[0].locality == "Moon" assert systems[0].postal_code == "11111" assert systems[0].administrative_area == "Moon" assert systems[0].fire_number == "(123) 456-7890" assert systems[0].police_number == "(123) 456-7890" assert systems[0].emergency_number == "(123) 456-7890" assert systems[0].address is None assert systems[0].notion_pro_permit is None aresponses.assert_plan_strictly_followed() @pytest.mark.asyncio async def test_system_get( aresponses: ResponsesMockServer, authenticated_notion_api_server: ResponsesMockServer, system_get_response: dict[str, str], ) -> None: """Test getting a system by ID. Args: aresponses: An aresponses server. authenticated_notion_api_server: A mock authenticated Notion API server system_get_response: A fixture for a system get response. """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", "/api/systems/12345", "get", response=aiohttp.web_response.json_response( system_get_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) system = await client.system.async_get(12345) assert system.uuid == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert system.name == "Home" assert system.mode == "home" assert system.partners == [] assert system.latitude == 89.0 assert system.longitude == -170.0 assert system.timezone_id == "Some/Timezone" assert system.created_at == datetime( 2019, 4, 30, 1, 35, 21, 870000, tzinfo=timezone.utc ) assert system.updated_at == datetime( 2019, 7, 9, 4, 57, 1, 68000, tzinfo=timezone.utc ) assert system.night_time_start == datetime( 2019, 5, 1, 4, 0, tzinfo=timezone.utc ) assert system.night_time_end == datetime( 2019, 5, 1, 13, 0, tzinfo=timezone.utc ) assert system.id == 12345 assert system.locality == "Moon" assert system.postal_code == "11111" assert system.administrative_area == "Moon" assert system.fire_number == "(123) 456-7890" assert system.police_number == "(123) 456-7890" assert system.emergency_number == "(123) 456-7890" assert system.address is None assert system.notion_pro_permit is None aresponses.assert_plan_strictly_followed() bachya-aionotion-e90a0f0/tests/test_user.py000066400000000000000000000063621457416102500211110ustar00rootroot00000000000000"""Define tests for users.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any import aiohttp import pytest from aresponses import ResponsesMockServer from aionotion import async_get_client_with_credentials from tests.common import TEST_EMAIL, TEST_PASSWORD, TEST_USER_UUID @pytest.mark.asyncio async def test_user_info( authenticated_notion_api_server: ResponsesMockServer, user_info_response: dict[str, Any], ) -> None: """Test getting user preferences. Args: authenticated_notion_api_server: A mock authenticated Notion API server user_info_response: A fixture for a user information response payload. """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", f"/api/users/{TEST_USER_UUID}", "get", response=aiohttp.web_response.json_response(user_info_response, status=200), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) user_info = await client.user.async_info() assert user_info.id == 12345 assert user_info.uuid == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" assert user_info.first_name == "The" assert user_info.last_name == "Person" assert user_info.email == "user@email.com" assert user_info.phone_number is None assert user_info.role == "user" assert user_info.organization == "Notion User" assert user_info.created_at == datetime( 2019, 4, 30, 1, 35, 3, 781000, tzinfo=timezone.utc ) assert user_info.updated_at == datetime( 2023, 12, 21, 4, 13, 53, 48000, tzinfo=timezone.utc ) @pytest.mark.asyncio async def test_user_preferences( authenticated_notion_api_server: ResponsesMockServer, user_preferences_response: dict[str, Any], ) -> None: """Test getting user preferences. Args: authenticated_notion_api_server: A mock authenticated Notion API server user_preferences_response: A fixture for a user preferences response payload. """ async with authenticated_notion_api_server: authenticated_notion_api_server.add( "api.getnotion.com", f"/api/users/{TEST_USER_UUID}/user_preferences", "get", response=aiohttp.web_response.json_response( user_preferences_response, status=200 ), ) async with aiohttp.ClientSession() as session: client = await async_get_client_with_credentials( TEST_EMAIL, TEST_PASSWORD, session=session ) user_preferences = await client.user.async_preferences() assert user_preferences.user_id == 12345 assert user_preferences.military_time_enabled is False assert user_preferences.celsius_enabled is False assert user_preferences.disconnect_alerts_enabled is True assert user_preferences.home_away_alerts_enabled is False assert user_preferences.battery_alerts_enabled is True bachya-aionotion-e90a0f0/vulture_whitelist.py000066400000000000000000000000701457416102500215220ustar00rootroot00000000000000cls # unused variable (aionotion/sensor/models.py:179)