pax_global_header00006660000000000000000000000064147504766010014524gustar00rootroot0000000000000052 comment=5ab1283e968b9b74091394b589b0671b8e7af892 aionotion-2025.02.0/000077500000000000000000000000001475047660100140335ustar00rootroot00000000000000aionotion-2025.02.0/.codeclimate.yml000066400000000000000000000003401475047660100171020ustar00rootroot00000000000000--- engines: duplication: enabled: true config: languages: - python fixme: enabled: true radon: enabled: true ratings: paths: - "**.py" exclude_paths: - dist/ - docs/ - tests/ aionotion-2025.02.0/.github/000077500000000000000000000000001475047660100153735ustar00rootroot00000000000000aionotion-2025.02.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001475047660100175565ustar00rootroot00000000000000aionotion-2025.02.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000007641475047660100222570ustar00rootroot00000000000000--- 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. aionotion-2025.02.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000006461475047660100233110ustar00rootroot00000000000000--- 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. aionotion-2025.02.0/.github/actions/000077500000000000000000000000001475047660100170335ustar00rootroot00000000000000aionotion-2025.02.0/.github/actions/install-uv/000077500000000000000000000000001475047660100211315ustar00rootroot00000000000000aionotion-2025.02.0/.github/actions/install-uv/action.yml000066400000000000000000000010201475047660100231220ustar00rootroot00000000000000name: "Install uv" description: "Installs uv (pinned to the version used by this repo)" runs: using: "composite" steps: - name: Get uv version from pyproject.toml shell: bash id: uv-version run: | echo "version=$(grep "uv==" pyproject.toml | awk -F'==' '{print $2'} | tr -d '",')" >> $GITHUB_OUTPUT - name: Install uv uses: astral-sh/setup-uv@v5 with: cache-dependency-glob: "uv.lock" enable-cache: true version: ${{ steps.uv-version.outputs.version }} aionotion-2025.02.0/.github/config/000077500000000000000000000000001475047660100166405ustar00rootroot00000000000000aionotion-2025.02.0/.github/config/labeler.yml000066400000000000000000000000431475047660100207660ustar00rootroot00000000000000"release": - base-branch: "main" aionotion-2025.02.0/.github/config/labels.yml000066400000000000000000000030371475047660100206300ustar00rootroot00000000000000--- - name: "breaking-change" color: ee0701 description: "A breaking change for existing users" - name: "help-wanted" color: 0e8a16 description: "Needs a helping hang or expertise in order to resolve" - name: "no-stale" color: fef2c0 description: "This issue or PR is exempted from the stale bot" - name: "release" color: d93f0b description: "A release of the library" - name: "stale" color: fef2c0 description: "There has not been activity on this issue or PR for some time" - color: "ff2191" description: "A bugfix" name: "type: bugfix" - color: "ff2191" description: "A change to the local or production build system" name: "type: build" - color: "ff2191" description: "A change to a CI/CD configuration" name: "type: ci" - color: "ff2191" description: "A change that doesn't modify source or test files" name: "type: chore" - color: "ff2191" description: "A documentation change" name: "type: docs" - color: "ff2191" description: "A new feature or enhancement" name: "type: feature" - color: "ff2191" description: "A code change that improves performance" name: "type: performance" - color: "ff2191" description: "A code change that neither fixes a bug nor adds a feature" name: "type: refactor" - color: "ff2191" description: "Reverts a previous commit" name: "type: reversion" - color: "ff2191" description: "A change that does not affect the meaning of the code" name: "type: style" - color: "ff2191" description: "Adds missing tests or corrects existing tests" name: "type: test" aionotion-2025.02.0/.github/pull_request_template.md000066400000000000000000000014451475047660100223400ustar00rootroot00000000000000## Description: - N/A, [SSIA][ssia] ## Issues Fixed: - N/A ## How To Test: - N/A ## Checklist: - [ ] I confirm that one or more new tests are written for the new functionality. - [ ] I have ensured that all tests pass (with 100% test coverage). - [ ] I have updated `README.md` with any new documentation. - [ ] I will "squash merge" this PR. ## TODOs: - N/A [ssia]: https://en.wiktionary.org/wiki/SSIA aionotion-2025.02.0/.github/release-drafter.yml000066400000000000000000000013171475047660100211650ustar00rootroot00000000000000--- categories: - title: "๐Ÿšจ Breaking Changes" labels: - "breaking-change" - title: "๐Ÿš€ Features" labels: - "type: feature" - title: "๐Ÿ› Bug Fixes" labels: - "type: bugfix" - title: "๐ŸŽ๏ธ Tuning" labels: - "type: performance" - "type: style" - title: "๐Ÿ› ๏ธ Tooling" labels: - "type: build" - "type: ci" - title: "๐Ÿงฐ Maintenance" labels: - "type: chore" - "type: docs" - "type: refactor" - "type: reversion" - "type: test" exclude-labels: - "release" change-template: "- $TITLE (#$NUMBER)" name-template: "$NEXT_PATCH_VERSION" tag-template: "$NEXT_PATCH_VERSION" template: | $CHANGES aionotion-2025.02.0/.github/workflows/000077500000000000000000000000001475047660100174305ustar00rootroot00000000000000aionotion-2025.02.0/.github/workflows/codeql.yml000066400000000000000000000010021475047660100214130ustar00rootroot00000000000000--- 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 aionotion-2025.02.0/.github/workflows/lock.yml000066400000000000000000000006251475047660100211060ustar00rootroot00000000000000--- 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: "" aionotion-2025.02.0/.github/workflows/publish.yml000066400000000000000000000010501475047660100216150ustar00rootroot00000000000000--- 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.13 id: python uses: actions/setup-python@v5 with: python-version: 3.13 - name: ๐Ÿšœ Install uv uses: ./.github/actions/install-uv - name: ๐Ÿš€ Publish to PyPi run: | uv build uv publish --token ${{ secrets.PYPI_API_KEY }} aionotion-2025.02.0/.github/workflows/release-drafter.yml000066400000000000000000000005331475047660100232210ustar00rootroot00000000000000--- 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 }} aionotion-2025.02.0/.github/workflows/scan-pull-request.yml000066400000000000000000000111301475047660100235330ustar00rootroot00000000000000name: Scan Pull Request on: pull_request: types: - edited - opened - reopened - synchronize workflow_dispatch: permissions: contents: read pull-requests: write repository-projects: read jobs: lint-pr-title: name: ๐Ÿท๏ธ Lint PR Title runs-on: ubuntu-latest steps: - name: Lint title uses: amannn/action-semantic-pull-request@v5 id: lint-title env: GITHUB_TOKEN: ${{ github.token }} - name: Create error comment uses: marocchino/sticky-pull-request-comment@v2 if: ${{ always() && steps.lint-title.outputs.error_message != null }} with: header: lint-title-error-comment message: | We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like this pull request's title needs to be adjusted. Details: ``` ${{ steps.lint-title.outputs.error_message }} ``` - name: Delete error comment uses: marocchino/sticky-pull-request-comment@v2 if: ${{ steps.lint-title.outputs.error_message == null }} with: header: lint-title-error-comment delete: true set-labels: name: ๐Ÿท๏ธ Set Labels runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set labels uses: actions/labeler@v5 with: configuration-path: ./.github/config/labeler.yml sync-labels: true - name: Assign Conventional Commit label shell: bash env: PR_CURRENT_LABELS_JSON: ${{ toJson(github.event.pull_request.labels) }} PR_TITLE: ${{ github.event.pull_request.title }} GITHUB_TOKEN: ${{ github.token }} run: | # Create a mapping between Conventional Commit prefixes and our labels: label_map='{ "build": "type: build", "chore": "type: chore", "ci": "type: ci", "docs": "type: docs", "feat": "type: feature", "fix": "type: bugfix", "perf": "type: performance", "refactor": "type: refactor", "revert": "type: reversion", "style": "type: style", "test": "type: test" }' # Strip any surrounding whitespace from the sanitized PR title: pr_title="$(echo "$PR_TITLE" | tr -d '\n' | xargs)" # Parse the existing labels: pr_current_labels=$(echo "$PR_CURRENT_LABELS_JSON" | jq '.[].name') # Determine the Conventional Commit type based upon the PR title: commit_type="$(echo "$pr_title" | cut -d: -f1 | sed 's/(.*)//g; s/!//g')" echo "Detected Conventional Commit type: '$commit_type'" if [[ -z "$commit_type" ]]; then echo "Commit type could not be extracted from PR title: '$pr_title'" exit 1 fi # Pull the appropriate label based on the detected Conventional Commit type: label_to_apply="$(echo "$label_map" | jq -r --arg type "$commit_type" '.[$type] // empty')" if [[ -z "$label_to_apply" ]]; then echo "Unrecognized Conventional Commit type: '$commit_type'" exit 1 fi echo "Mapping Conventional Commit type '$commit_type' to label: '$label_to_apply'" # Determine whether any outdated Conventional Commit labels need to be # removed: labels_to_remove_csv=$(echo "$PR_CURRENT_LABELS_JSON" | jq -r --argjson label_map "$label_map" --arg current_label "$label_to_apply" '.[].name | select(. != $current_label and (. as $existing | $label_map | any(.[]; . == $existing)))' | paste -sd, -) echo "Removing incorrect Conventional Commit labels: '$labels_to_remove_csv'" # If the label to add is already applied, skip it: labels_to_add_csv="" if echo "$pr_current_labels" | grep -qw "$label_to_apply"; then echo "Label already exists on the PR: '$label_to_apply'" else echo "Label should be added to the PR: '$label_to_apply'" labels_to_add_csv+="$label_to_apply" fi # Apply the label changes: if [[ -n "$labels_to_remove_csv" || -n "$labels_to_add_csv" ]]; then gh pr edit \ "${{ github.event.pull_request.number }}" \ ${labels_to_add_csv:+--add-label "$labels_to_add_csv"} \ ${labels_to_remove_csv:+--remove-label "$labels_to_remove_csv"} else echo "No label changes needed" fi aionotion-2025.02.0/.github/workflows/stale.yml000066400000000000000000000023461475047660100212700ustar00rootroot00000000000000--- 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. aionotion-2025.02.0/.github/workflows/static-analysis.yml000066400000000000000000000016331475047660100232660ustar00rootroot00000000000000--- name: Linting and Static Analysis "on": pull_request: branches: - dev - main workflow_dispatch: 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.13 id: setup-python uses: actions/setup-python@v5 with: python-version: "3.13" - name: ๐Ÿšœ Install uv uses: ./.github/actions/install-uv - name: ๐Ÿ— Install workflow dependencies run: | uv sync --extra lint - name: Get all changed files id: changed-files uses: tj-actions/changed-files@v45.0.6 with: fetch_depth: 0 - name: Run pre-commit hooks run: | uv run pre-commit run \ --files ${{ steps.changed-files.outputs.all_changed_files }} aionotion-2025.02.0/.github/workflows/sync-labels.yml000066400000000000000000000010371475047660100223700ustar00rootroot00000000000000--- name: Sync Labels "on": push: branches: - dev paths: - .github/config/labels.yml - .github/config/labeler.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 with: manifest: .github/config/labels.yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} aionotion-2025.02.0/.github/workflows/test.yml000066400000000000000000000035511475047660100211360ustar00rootroot00000000000000--- name: Tests and Coverage "on": pull_request: branches: - dev - main workflow_dispatch: jobs: test: name: Tests runs-on: ubuntu-latest strategy: matrix: python-version: - "3.11" - "3.12" - "3.13" 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: ๐Ÿšœ Install uv uses: ./.github/actions/install-uv - name: ๐Ÿ— Install package dependencies run: | uv sync --extra test - name: ๐Ÿš€ Run pytest run: uv run pytest --cov aionotion tests - name: โฌ†๏ธ Upload coverage artifact uses: actions/upload-artifact@v4 with: name: coverage-${{ matrix.python-version }} path: .coverage include-hidden-files: true 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@v4 - name: ๐Ÿ— Set up Python 3.13 id: setup-python uses: actions/setup-python@v5 with: python-version: "3.13" - name: ๐Ÿšœ Install uv uses: ./.github/actions/install-uv - name: ๐Ÿ— Install package dependencies run: | uv sync --extra test - name: ๐Ÿš€ Process coverage results run: | uv run coverage combine coverage*/.coverage* uv run coverage xml -i - name: ๐Ÿ“Š Upload coverage report to codecov.io uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} aionotion-2025.02.0/.gitignore000066400000000000000000000001451475047660100160230ustar00rootroot00000000000000*.egg-info .DS_Store .coverage .mypy_cache .nox .tox .venv __pycache__ coverage.xml docs/_build tags aionotion-2025.02.0/.mise.toml000066400000000000000000000001721475047660100157430ustar00rootroot00000000000000[tools] act = { version="0.2.71" } python = { version="3.12.8" } [env] _.python.venv = { path = ".venv", create = true } aionotion-2025.02.0/.pre-commit-config.yaml000066400000000000000000000102171475047660100203150ustar00rootroot00000000000000--- repos: - repo: local hooks: - id: blacken-docs name: "โ˜•๏ธ Format documentation using black" language: system files: '\.(rst|md|markdown|py|tex)$' entry: uv run blacken-docs require_serial: true - id: check-ast name: "๐Ÿ Checking Python AST" language: system types: [python] entry: uv run check-ast - id: check-case-conflict name: "๐Ÿ”  Checking for case conflicts" language: system entry: uv run check-case-conflict - id: check-docstring-first name: "โ„น๏ธ Checking docstrings are first" language: system types: [python] entry: uv run check-docstring-first - id: check-executables-have-shebangs name: "๐Ÿง Checking that executables have shebangs" language: system types: [text, executable] entry: uv run check-executables-have-shebangs stages: [pre-commit, pre-push, manual] - id: check-json name: "๏ฝ› Checking JSON files" language: system types: [json] entry: uv run check-json - id: check-merge-conflict name: "๐Ÿ’ฅ Checking for merge conflicts" language: system types: [text] entry: uv run check-merge-conflict - id: check-symlinks name: "๐Ÿ”— Checking for broken symlinks" language: system types: [symlink] entry: uv run check-symlinks - id: check-toml name: "โœ… Checking TOML files" language: system types: [toml] entry: uv run check-toml - id: codespell name: "โœ… Checking code for misspellings" language: system types: [text] exclude: | (?x)^($^ |.*uv\.lock )$ entry: uv run codespell - id: debug-statements name: "๐Ÿชต Checking for debug statements and imports (Python)" language: system types: [python] entry: uv run debug-statement-hook - id: detect-private-key name: "๐Ÿ•ต๏ธ Detecting private keys" language: system types: [text] entry: uv run detect-private-key - id: end-of-file-fixer name: "๐Ÿ”š Checking end of files" language: system types: [text] entry: uv run end-of-file-fixer stages: [pre-commit, pre-push, manual] - id: fix-byte-order-marker name: "๐Ÿš Checking UTF-8 byte order marker" language: system types: [text] entry: uv run fix-byte-order-marker - id: format name: "โ˜•๏ธ Formatting code using ruff" language: system types: [python] entry: uv run ruff format exclude: | (?x)^($^ |docs/.* )$ - id: mypy name: "๐Ÿ†Ž Performing static type checking using mypy" language: system types: [python] entry: uv run mypy - id: no-commit-to-branch name: "๐Ÿ›‘ Checking for commit to protected branch" language: system entry: uv run no-commit-to-branch pass_filenames: false always_run: true args: - --branch=development - --branch=main - id: pylint name: "๐ŸŒŸ Starring code with pylint" language: system types: [python] entry: uv run pylint - id: ruff name: "๐Ÿ‘” Enforcing style guide with ruff" language: system types: [python] entry: uv run ruff check --fix exclude: | (?x)^($^ |docs/.* )$ require_serial: true - id: trailing-whitespace name: "โœ„ Trimming trailing whitespace" language: system types: [text] entry: uv run trailing-whitespace-fixer stages: [pre-commit, pre-push, manual] - id: uv-lock name: "๐Ÿ”’ Ensure the uv.lock file is up to date" language: system entry: uv lock --locked files: pyproject.toml$ pass_filenames: false - repo: https://github.com/pre-commit/mirrors-prettier rev: "v3.0.0-alpha.4" hooks: - id: prettier name: "๐Ÿ’„ Ensuring files are prettier" aionotion-2025.02.0/LICENSE000066400000000000000000000020601475047660100150360ustar00rootroot00000000000000MIT License Copyright (c) 2019-2025 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. aionotion-2025.02.0/README.md000066400000000000000000000162471475047660100153240ustar00rootroot00000000000000# ๐Ÿ“Ÿ 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.11 - Python 3.12 - Python 3.13 # 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 aionotion-2025.02.0/aionotion/000077500000000000000000000000001475047660100160325ustar00rootroot00000000000000aionotion-2025.02.0/aionotion/__init__.py000066400000000000000000000003151475047660100201420ustar00rootroot00000000000000"""Define the aionotion package.""" from .client import ( async_get_client, # noqa: F401 async_get_client_with_credentials, # noqa: F401 async_get_client_with_refresh_token, # noqa: F401 ) aionotion-2025.02.0/aionotion/bridge/000077500000000000000000000000001475047660100172665ustar00rootroot00000000000000aionotion-2025.02.0/aionotion/bridge/__init__.py000066400000000000000000000025531475047660100214040ustar00rootroot00000000000000"""Define endpoints for interacting with bridges.""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.bridge.models import ( Bridge as BridgeModel, 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 aionotion-2025.02.0/aionotion/bridge/models.py000066400000000000000000000025151475047660100211260ustar00rootroot00000000000000"""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 aionotion-2025.02.0/aionotion/client.py000066400000000000000000000327111475047660100176660ustar00rootroot00000000000000"""Define a base client for interacting with Notion.""" from __future__ import annotations import asyncio from collections.abc import Callable from datetime import datetime from http import HTTPStatus from typing import Any, TypeVar, cast from uuid import uuid4 from aiohttp import ClientSession, ClientTimeout from aiohttp.client_exceptions import ClientResponseError 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_response: 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 not refresh_token: refresh_token = self._refresh_token if not refresh_token: msg = "No valid refresh token provided" raise InvalidCredentialsError(msg) async with self._refresh_lock: self._refreshing = True 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, headers: dict[str, str] | None = None, json: dict[str, Any] | None = None, ) -> 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. headers: Additional headers to include in the request. json: A JSON payload 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}" headers = {} if self._access_token: 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, headers=headers, json=json) as resp: data = await resp.json() try: resp.raise_for_status() except ClientResponseError as err: if resp.status == HTTPStatus.UNAUTHORIZED: msg = "Invalid credentials" raise InvalidCredentialsError(msg) 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, headers: dict[str, str] | None = None, json: dict[str, Any] | None = None, ) -> 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. headers: Additional headers to include in the request. json: A JSON payload 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, headers=headers, json=json, ) try: return cast(NotionBaseModelT, model.from_dict(raw_data)) except ( MissingField, SuitableVariantNotFoundError, UnserializableDataError, ) as err: msg = f"Error while parsing response from {endpoint}: {err}" raise RequestError(msg) 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 aionotion-2025.02.0/aionotion/const.py000066400000000000000000000001311475047660100175250ustar00rootroot00000000000000"""Define package constants.""" import logging LOGGER = logging.getLogger(__package__) aionotion-2025.02.0/aionotion/errors.py000066400000000000000000000004341475047660100177210ustar00rootroot00000000000000"""Define package errors.""" class NotionError(Exception): """Define a base error.""" class RequestError(NotionError): """Define an error related to invalid requests.""" class InvalidCredentialsError(NotionError): """Define an error for unauthenticated accounts.""" aionotion-2025.02.0/aionotion/listener/000077500000000000000000000000001475047660100176575ustar00rootroot00000000000000aionotion-2025.02.0/aionotion/listener/__init__.py000066400000000000000000000026201475047660100217700ustar00rootroot00000000000000"""Define endpoints for interacting with listeners.""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.listener.models import ( Listener as ListenerModel, 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 aionotion-2025.02.0/aionotion/listener/models.py000066400000000000000000000063371475047660100215250ustar00rootroot00000000000000"""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] aionotion-2025.02.0/aionotion/py.typed000066400000000000000000000000001475047660100175170ustar00rootroot00000000000000aionotion-2025.02.0/aionotion/sensor/000077500000000000000000000000001475047660100173435ustar00rootroot00000000000000aionotion-2025.02.0/aionotion/sensor/__init__.py000066400000000000000000000025231475047660100214560ustar00rootroot00000000000000"""Define endpoints for interacting with sensors.""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.sensor.models import ( Sensor as SensorModel, 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 aionotion-2025.02.0/aionotion/sensor/models.py000066400000000000000000000043251475047660100212040ustar00rootroot00000000000000"""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 aionotion-2025.02.0/aionotion/system/000077500000000000000000000000001475047660100173565ustar00rootroot00000000000000aionotion-2025.02.0/aionotion/system/__init__.py000066400000000000000000000025141475047660100214710ustar00rootroot00000000000000"""Define endpoints for interacting with systems (accounts).""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.system.models import ( System as SystemModel, 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 aionotion-2025.02.0/aionotion/system/models.py000066400000000000000000000024601475047660100212150ustar00rootroot00000000000000"""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 aionotion-2025.02.0/aionotion/user/000077500000000000000000000000001475047660100170105ustar00rootroot00000000000000aionotion-2025.02.0/aionotion/user/__init__.py000066400000000000000000000027561475047660100211330ustar00rootroot00000000000000"""Define endpoints for interacting with users.""" from __future__ import annotations from typing import TYPE_CHECKING from aionotion.user.models import ( User as UserModel, UserInformationResponse, UserPreferences, UserPreferencesResponse, ) if TYPE_CHECKING: from aionotion.client import Client class User: """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 aionotion-2025.02.0/aionotion/user/models.py000066400000000000000000000051721475047660100206520ustar00rootroot00000000000000"""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 aionotion-2025.02.0/aionotion/util/000077500000000000000000000000001475047660100170075ustar00rootroot00000000000000aionotion-2025.02.0/aionotion/util/__init__.py000066400000000000000000000000301475047660100211110ustar00rootroot00000000000000"""Define utilities.""" aionotion-2025.02.0/aionotion/util/auth.py000066400000000000000000000006751475047660100203320ustar00rootroot00000000000000"""Define auth utilities.""" from __future__ import annotations 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}, ) aionotion-2025.02.0/aionotion/util/dt.py000066400000000000000000000010261475047660100177670ustar00rootroot00000000000000"""Define datetime utilities.""" from datetime import UTC, datetime 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) aionotion-2025.02.0/examples/000077500000000000000000000000001475047660100156515ustar00rootroot00000000000000aionotion-2025.02.0/examples/__init__.py000066400000000000000000000000271475047660100177610ustar00rootroot00000000000000"""Define examples.""" aionotion-2025.02.0/examples/test_api.py000066400000000000000000000045311475047660100200360ustar00rootroot00000000000000"""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: _LOGGER.exception("There was an error") asyncio.run(main()) aionotion-2025.02.0/pyproject.toml000066400000000000000000000300021475047660100167420ustar00rootroot00000000000000[build-system] requires = [ "poetry-core==2.0.1", ] build-backend = "poetry.core.masonry.api" [project] authors = [ {name = "Aaron Bach", email = "bachya1208@gmail.com"}, ] classifiers = [ "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] 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.5.0", "mashumaro==3.12", "yarl>=1.9.2", ] description = "A simple Python 3 library for Notion Home Monitoring" license = "MIT" name = "aionotion" readme = "README.md" repository = "https://github.com/bachya/aionotion" requires-python = ">=3.11,<3.14" version = "2025.02.0" [project.optional-dependencies] build = [ "uv==0.5.26", ] lint = [ "blacken-docs==1.19.1", "codespell==2.4.0", "darglint==1.8.1", "mypy==1.14.1", "pre-commit-hooks==5.0.0", "pre-commit==4.1.0", "pylint==3.3.3", "pytest-asyncio==0.25.2", "pytest==8.3.4", "ruff==0.9.3", "yamllint==1.28.0", ] test = [ "aresponses>=2.1.6", "pytest-aiohttp==1.0.0", "pytest-asyncio==0.25.2", "pytest-cov==6.0.0", "pytest==8.3.4", ] [tool.coverage.report] exclude_lines = ["raise NotImplementedError", "TYPE_CHECKING", "@overload"] fail_under = 100 show_missing = true [tool.coverage.run] source = ["aionotion"] [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.11" 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.pylint.BASIC] class-const-naming-style = "any" expected-line-ending-format = "LF" [tool.pylint.DESIGN] max-attributes = 20 [tool.pylint.FORMAT] max-line-length = 88 [tool.pylint.MAIN] py-version = "3.11" ignore = [ "tests", ] # Use a conservative default here; 2 should speed up most setups and not hurt # any too bad. Override on command line as appropriate. jobs = 2 init-hook = """\ from pathlib import Path; \ import sys; \ from pylint.config import find_default_config_files; \ sys.path.append( \ str(Path(next(find_default_config_files())).parent.joinpath('pylint/plugins')) ) \ """ load-plugins = [ "pylint.extensions.code_style", "pylint.extensions.typing", ] persistent = false fail-on = [ "I", ] [tool.pylint."MESSAGES CONTROL"] disable = [ # These are subjective and should be left up to the developer: "abstract-method", "duplicate-code", "too-many-arguments", "too-many-lines", "too-many-locals", "too-many-positional-arguments", # Handled by ruff # Ref: "anomalous-backslash-in-string", # W605 "assert-on-string-literal", # PLW0129 "assert-on-tuple", # F631 "await-outside-async", # PLE1142 "bad-classmethod-argument", # N804 "bad-format-string", # W1302, F "bad-format-string-key", # W1300, F "bad-str-strip-call", # PLE1310 "bad-string-format-type", # PLE1307 "bare-except", # E722 "bidirectional-unicode", # PLE2502 "binary-op-exception", # PLW0711 "broad-except", # BLE001 "broad-exception-raised", # TRY002 "cell-var-from-loop", # B023 "comparison-of-constants", # PLR0133 "comparison-with-itself", # PLR0124 "consider-alternative-union-syntax", # UP007 "consider-iterating-dictionary", # SIM118 "consider-merging-isinstance", # PLR1701 "consider-using-alias", # UP006 "consider-using-dict-comprehension", # C402 "consider-using-f-string", # PLC0209 "consider-using-generator", # C417 "consider-using-get", # SIM401 "consider-using-set-comprehension", # C401 "consider-using-sys-exit", # PLR1722 "consider-using-ternary", # SIM108 "continue-in-finally", # PLE0116 "duplicate-bases", # PLE0241 "duplicate-except", # B014 "duplicate-key", # F601 "duplicate-string-formatting-argument", # F "duplicate-value", # F "empty-docstring", # D419 "eval-used", # S307 "exec-used", # S102 "expression-not-assigned", # B018 "f-string-without-interpolation", # F541 "forgotten-debug-statement", # T100 "format-needs-mapping", # F502 "format-string-without-interpolation", # F "function-redefined", # F811 "global-variable-not-assigned", # PLW0602 "implicit-str-concat", # ISC001 "import-self", # PLW0406 "inconsistent-quotes", # Q000 "invalid-all-object", # PLE0604 "invalid-character-backspace", # PLE2510 "invalid-character-esc", # PLE2513 "invalid-character-nul", # PLE2514 "invalid-character-sub", # PLE2512 "invalid-character-zero-width-space", # PLE2515 "invalid-envvar-default", # PLW1508 "invalid-name", # N815 "keyword-arg-before-vararg", # B026 "line-too-long", # E501, disabled globally "literal-comparison", # F632 "logging-format-interpolation", # G "logging-fstring-interpolation", # G "logging-not-lazy", # G "logging-too-few-args", # PLE1206 "logging-too-many-args", # PLE1205 "misplaced-bare-raise", # PLE0704 "misplaced-future", # F404 "missing-class-docstring", # D101 "missing-final-newline", # W292 "missing-format-string-key", # F524 "missing-function-docstring", # D103 "missing-module-docstring", # D100 "mixed-format-string", # F506 "multiple-imports", #E401 "named-expr-without-context", # PLW0131 "nested-min-max", # PLW3301 "no-else-break", # RET508 "no-else-continue", # RET507 "no-else-raise", # RET506 "no-else-return", # RET505 "no-method-argument", # N805 "no-self-argument", # N805 "nonexistent-operator", # B002 "nonlocal-without-binding", # PLE0117 "not-in-loop", # F701, F702 "notimplemented-raised", # F901 "pointless-statement", # B018 "property-with-parameters", # PLR0206 "protected-access", # SLF001 "raise-missing-from", # B904 "redefined-builtin", # A001 "redefined-slots-in-subclass", # W0244 "return-in-init", # PLE0101 "return-outside-function", # F706 "singleton-comparison", # E711, E712 "subprocess-run-check", # PLW1510 "super-with-arguments", # UP008 "superfluous-parens", # UP034 "syntax-error", # E999 "too-few-format-args", # F524 "too-many-branches", # PLR0912 "too-many-format-args", # F522 "too-many-return-statements", # PLR0911 "too-many-star-expressions", # F622 "too-many-statements", # PLR0915 "trailing-comma-tuple", # COM818 "truncated-format-string", # F501 "try-except-raise", # TRY302 "undefined-all-variable", # F822 "undefined-variable", # F821 "ungrouped-imports", # I001 "unidiomatic-typecheck", # E721 "unnecessary-comprehension", # C416 "unnecessary-direct-lambda-call", # PLC3002 "unnecessary-lambda-assignment", # PLC3001 "unnecessary-pass", # PIE790 "unneeded-not", # SIM208 "unused-argument", # ARG001, we don't use it "unused-format-string-argument", #F507 "unused-format-string-key", # F504 "unused-import", # F401 "unused-variable", # F841 "use-a-generator", # C417 "use-dict-literal", # C406 "use-list-literal", # C405 "used-prior-global-declaration", # PLE0118 "useless-else-on-loop", # PLW0120 "useless-import-alias", # PLC0414 "useless-object-inheritance", # UP004 "useless-return", # PLR1711 "wildcard-import", # F403 "wrong-import-order", # I001 "wrong-import-position", # E402 "yield-inside-async-function", # PLE1700 "yield-outside-function", # F704 # Handled by mypy # Ref: "abstract-class-instantiated", "arguments-differ", "assigning-non-slot", "assignment-from-no-return", "assignment-from-none", "bad-exception-cause", "bad-format-character", "bad-reversed-sequence", "bad-super-call", "bad-thread-instantiation", "catching-non-exception", "comparison-with-callable", "deprecated-class", "dict-iter-missing-items", "format-combined-specification", "global-variable-undefined", "import-error", "inconsistent-mro", "inherit-non-class", "init-is-generator", "invalid-class-object", "invalid-enum-extension", "invalid-envvar-value", "invalid-format-returned", "invalid-hash-returned", "invalid-metaclass", "invalid-overridden-method", "invalid-repr-returned", "invalid-sequence-index", "invalid-slice-index", "invalid-slots", "invalid-slots-object", "invalid-star-assignment-target", "invalid-str-returned", "invalid-unary-operand-type", "invalid-unicode-codec", "isinstance-second-argument-not-valid-type", "method-hidden", "misplaced-format-function", "missing-format-argument-key", "missing-format-attribute", "missing-kwoa", "no-member", "no-value-for-parameter", "non-iterator-returned", "non-str-assignment-to-dunder-name", "nonlocal-and-global", "not-a-mapping", "not-an-iterable", "not-async-context-manager", "not-callable", "not-context-manager", "overridden-final-method", "raising-bad-type", "raising-non-exception", "redundant-keyword-arg", "relative-beyond-top-level", "self-cls-assignment", "signature-differs", "star-needs-assignment-target", "subclassed-final-class", "super-without-brackets", "too-many-function-args", "typevar-double-variance", "typevar-name-mismatch", "unbalanced-dict-unpacking", "unbalanced-tuple-unpacking", "unexpected-keyword-arg", "unhashable-member", "unpacking-non-sequence", "unsubscriptable-object", "unsupported-assignment-operation", "unsupported-binary-operation", "unsupported-delete-operation", "unsupported-membership-test", "used-before-assignment", "using-final-decorator-in-unsupported-version", "wrong-exception-operation", ] enable = [ "useless-suppression", "use-symbolic-message-instead", ] [tool.pylint.TYPING] runtime-typing = false [tool.pylint.CODE_STYLE] max-line-length-suggestions = 72 [tool.ruff] target-version = "py311" [tool.ruff.lint] select = [ "ALL" ] ignore = [ "A005", # Shadowing a Python standard-library module "D202", # No blank lines allowed after function docstring "D203", # 1 blank line required before class docstring "D213", # Multi-line docstring summary should start at the second line "PLR0913", # This is subjective "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target "PT012", # `pytest.raises()` block should contain a single simple statement "TCH", # flake8-type-checking # May conflict with the formatter: # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules "COM812", "COM819", "D206", "D300", "E111", "E114", "E117", "ISC001", "ISC002", "Q000", "Q001", "Q002", "Q003", "W191", ] [tool.ruff.lint.isort] force-sort-within-sections = true known-first-party = [ "aionotion", "examples", "tests", ] combine-as-imports = true split-on-trailing-comma = false [tool.ruff.lint.per-file-ignores] "tests/*" = [ "ARG001", # Tests ofen have unused arguments "FBT001", # Test fixtures may be boolean values "PLR2004", # Checking for magic values in tests isn't helpful "S101", # Assertions are fine in tests "SLF001", # We'll access a lot of private third-party members in tests ] aionotion-2025.02.0/renovate.json000066400000000000000000000006731475047660100165570ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended", ":semanticCommitTypeAll(chore)"], "dependencyDashboard": true, "internalChecksFilter": "strict", "labels": ["dependencies"], "minimumReleaseAge": "3 days", "prConcurrentLimit": 3, "prCreation": "immediate", "prHourlyLimit": 3, "rangeStrategy": "pin", "rebaseWhen": "behind-base-branch", "semanticCommits": "enabled" } aionotion-2025.02.0/script/000077500000000000000000000000001475047660100153375ustar00rootroot00000000000000aionotion-2025.02.0/script/release000077500000000000000000000023401475047660100167040ustar00rootroot00000000000000#!/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 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 # Update the uv lockfile: uv lock git add uv.lock # 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 aionotion-2025.02.0/script/setup000077500000000000000000000045441475047660100164340ustar00rootroot00000000000000#!/usr/bin/env bash set -Eeuo pipefail trap cleanup SIGINT SIGTERM ERR EXIT SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) REPO_DIR=$(dirname "$SCRIPT_DIR") usage() { cat <&2 -e "${1-}" } fail() { local msg=$1 local code=${2-1} msg "${RED}$msg${NOFORMAT}" printf "\n" usage exit "$code" } parse_params() { args=() while [[ $# -gt 0 ]]; do case "$1" in -h | --help) usage && exit 0 ;; -v | --verbose) set -x ;; -?*) fail "Unknown option: $1" ;; *) args+=("$1") ;; esac shift done return 0 } setup_colors() { if [[ -t 2 ]] && [[ -z "${NO_COLOR-}" ]] && [[ "${TERM-}" != "dumb" ]]; then NOFORMAT="\033[0m" RED="\033[0;31m" BLUE="\033[0;34m" GREEN='\033[0;32m' else NOFORMAT="" RED="" BLUE="" GREEN="" fi } validate_dependencies_exist() { local dependencies=( "pre-commit" "python" ) for dependency in "${dependencies[@]}"; do if ! command -v "$dependency" &>/dev/null; then fail "Missing dependency: $dependency" fi done } main() { setup_colors parse_params "$@" if command -v "mise"; then msg "${BLUE}๐Ÿ” mise detected; configuring runtimes...${NOFORMAT}" mise install -y fi # Check if we're running in Python a virtual environment (creating one if not): if [[ -z "${VIRTUAL_ENV-}" ]]; then msg "${BLUE}๐Ÿšœ Creating Python virtual environment...${NOFORMAT}" python -m venv "$REPO_DIR/.venv" # shellcheck disable=SC1091 source "$REPO_DIR/.venv/bin/activate" fi msg "${BLUE}๐Ÿšœ Installing dependencies ...${NOFORMAT}" if ! command -v "uv" &>/dev/null; then if ! command -v "pip" &>/dev/null; then python -m ensurepip fi uv_version="$(grep "uv==" "$REPO_DIR/pyproject.toml" | awk -F'==' '{print $2}' | tr -d '",')" python -m pip install uv=="$uv_version" fi uv sync --all-extras msg "${BLUE}๐Ÿšœ Installing pre-commit hooks...${NOFORMAT}" pre-commit install # At this stage, we should have all of our dependencies installed; confirm that: validate_dependencies_exist msg "${GREEN}โœ… Setup complete!${NOFORMAT}" } main "$@" aionotion-2025.02.0/tests/000077500000000000000000000000001475047660100151755ustar00rootroot00000000000000aionotion-2025.02.0/tests/__init__.py000066400000000000000000000000341475047660100173030ustar00rootroot00000000000000"""Define package tests.""" aionotion-2025.02.0/tests/common.py000066400000000000000000000021451475047660100170410ustar00rootroot00000000000000"""Define common test utilities.""" from pathlib import Path from uuid import uuid4 import jwt TEST_EMAIL = "user@email.com" TEST_PASSWORD = "password123" # noqa: S105 TEST_REFRESH_TOKEN = "abcde12345" # noqa: S105 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 = Path(f"{Path(__file__).parent}/fixtures/{filename}") with Path.open(path, encoding="utf-8") as fptr: return fptr.read() aionotion-2025.02.0/tests/conftest.py000066400000000000000000000207111475047660100173750ustar00rootroot00000000000000"""Define dynamic test fixtures.""" from __future__ import annotations import json from time import time from typing import Any, cast import aiohttp from aresponses import ResponsesMockServer import pytest 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="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 @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="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], ) -> ResponsesMockServer: """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 ), ) return 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")) ) aionotion-2025.02.0/tests/fixtures/000077500000000000000000000000001475047660100170465ustar00rootroot00000000000000aionotion-2025.02.0/tests/fixtures/auth_credentials_success_response.json000066400000000000000000000006301475047660100267240ustar00rootroot00000000000000{ "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" } } aionotion-2025.02.0/tests/fixtures/auth_failure_response.json000066400000000000000000000001751475047660100243320ustar00rootroot00000000000000{ "errors": [ { "title": "Invalid email or password.", "message": "Invalid email or password." } ] } aionotion-2025.02.0/tests/fixtures/auth_legacy_credentials_success_response.json000066400000000000000000000007631475047660100302570ustar00rootroot00000000000000{ "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" } } aionotion-2025.02.0/tests/fixtures/auth_refresh_token_success_response.json000066400000000000000000000001031475047660100272600ustar00rootroot00000000000000{ "auth": { "jwt": "TBD", "refresh_token": "12345" } } aionotion-2025.02.0/tests/fixtures/bad_api_response.json000066400000000000000000000002411475047660100232330ustar00rootroot00000000000000{ "errors": [ { "title": "param is missing or the value is empty: auth", "message": "param is missing or the value is empty: auth" } ] } aionotion-2025.02.0/tests/fixtures/bridge_all_response.json000066400000000000000000000011461475047660100237450ustar00rootroot00000000000000{ "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 } } ] } aionotion-2025.02.0/tests/fixtures/bridge_get_response.json000066400000000000000000000010561475047660100237540ustar00rootroot00000000000000{ "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 } } } aionotion-2025.02.0/tests/fixtures/listener_definitions_response.json000066400000000000000000000136041475047660100261030ustar00rootroot00000000000000{ "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" } ] } aionotion-2025.02.0/tests/fixtures/sensor_all_response.json000066400000000000000000000017521475047660100240250ustar00rootroot00000000000000{ "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 } ] } aionotion-2025.02.0/tests/fixtures/sensor_get_response.json000066400000000000000000000016421475047660100240320ustar00rootroot00000000000000{ "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 } } aionotion-2025.02.0/tests/fixtures/sensor_listeners_response.json000066400000000000000000000042011475047660100252550ustar00rootroot00000000000000{ "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" } ] } aionotion-2025.02.0/tests/fixtures/surface_types_response.json000066400000000000000000000003701475047660100245330ustar00rootroot00000000000000{ "surface_types": [ { "id": "03c8d427-54e7-4615-8ef7-4e10f28b0070", "name": "door", "slug": "door" }, { "id": "29bd2dfa-25a7-4a9a-84dd-e8deecbebe36", "name": "window", "slug": "window" } ] } aionotion-2025.02.0/tests/fixtures/system_all_response.json000066400000000000000000000013411475047660100240320ustar00rootroot00000000000000{ "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 } ] } aionotion-2025.02.0/tests/fixtures/system_get_response.json000066400000000000000000000012511475047660100240410ustar00rootroot00000000000000{ "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 } } aionotion-2025.02.0/tests/fixtures/system_locations_response.json000066400000000000000000000005501475047660100252560ustar00rootroot00000000000000{ "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] } } ] } aionotion-2025.02.0/tests/fixtures/system_users_response.json000066400000000000000000000025011475047660100244220ustar00rootroot00000000000000{ "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" } } ] } aionotion-2025.02.0/tests/fixtures/user_info_response.json000066400000000000000000000005311475047660100236470ustar00rootroot00000000000000{ "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" } } aionotion-2025.02.0/tests/fixtures/user_preferences_response.json000066400000000000000000000003511475047660100252150ustar00rootroot00000000000000{ "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 } } aionotion-2025.02.0/tests/test_bridge.py000066400000000000000000000107431475047660100200470ustar00rootroot00000000000000"""Define tests for bridges.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any import aiohttp from aresponses import ResponsesMockServer import pytest 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() aionotion-2025.02.0/tests/test_client.py000066400000000000000000000325141475047660100200710ustar00rootroot00000000000000"""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 from aresponses import ResponsesMockServer import pytest 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) aionotion-2025.02.0/tests/test_listener.py000066400000000000000000000137501475047660100204410ustar00rootroot00000000000000"""Define tests for listeners.""" from __future__ import annotations from datetime import datetime, timezone import logging from typing import Any from unittest.mock import Mock import aiohttp from aresponses import ResponsesMockServer import pytest 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() aionotion-2025.02.0/tests/test_sensor.py000066400000000000000000000135431475047660100201250ustar00rootroot00000000000000"""Define tests for sensors.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any import aiohttp from aresponses import ResponsesMockServer import pytest 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() aionotion-2025.02.0/tests/test_system.py000066400000000000000000000120101475047660100201240ustar00rootroot00000000000000"""Define tests for systems.""" from __future__ import annotations from datetime import datetime, timezone import aiohttp from aresponses import ResponsesMockServer import pytest 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() aionotion-2025.02.0/tests/test_user.py000066400000000000000000000064061475047660100175720ustar00rootroot00000000000000"""Define tests for users.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any import aiohttp from aresponses import ResponsesMockServer import pytest 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 aionotion-2025.02.0/uv.lock000066400000000000000000004613161475047660100153520ustar00rootroot00000000000000version = 1 requires-python = ">=3.11, <3.14" resolution-markers = [ "python_full_version < '3.12'", "python_full_version >= '3.12'", ] [[package]] name = "aiohappyeyeballs" version = "2.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7f/55/e4373e888fdacb15563ef6fa9fa8c8252476ea071e96fb46defac9f18bf2/aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745", size = 21977 } wheels = [ { url = "https://files.pythonhosted.org/packages/b9/74/fbb6559de3607b3300b9be3cc64e97548d55678e44623db17820dbd20002/aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8", size = 14756 }, ] [[package]] name = "aiohttp" version = "3.11.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, { name = "aiosignal" }, { name = "attrs" }, { name = "frozenlist" }, { name = "multidict" }, { name = "propcache" }, { name = "yarl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fe/ed/f26db39d29cd3cb2f5a3374304c713fe5ab5a0e4c8ee25a0c45cc6adf844/aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e", size = 7669618 } wheels = [ { url = "https://files.pythonhosted.org/packages/34/ae/e8806a9f054e15f1d18b04db75c23ec38ec954a10c0a68d3bd275d7e8be3/aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76", size = 708624 }, { url = "https://files.pythonhosted.org/packages/c7/e0/313ef1a333fb4d58d0c55a6acb3cd772f5d7756604b455181049e222c020/aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538", size = 468507 }, { url = "https://files.pythonhosted.org/packages/a9/60/03455476bf1f467e5b4a32a465c450548b2ce724eec39d69f737191f936a/aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204", size = 455571 }, { url = "https://files.pythonhosted.org/packages/be/f9/469588603bd75bf02c8ffb8c8a0d4b217eed446b49d4a767684685aa33fd/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9", size = 1685694 }, { url = "https://files.pythonhosted.org/packages/88/b9/1b7fa43faf6c8616fa94c568dc1309ffee2b6b68b04ac268e5d64b738688/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03", size = 1743660 }, { url = "https://files.pythonhosted.org/packages/2a/8b/0248d19dbb16b67222e75f6aecedd014656225733157e5afaf6a6a07e2e8/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287", size = 1785421 }, { url = "https://files.pythonhosted.org/packages/c4/11/f478e071815a46ca0a5ae974651ff0c7a35898c55063305a896e58aa1247/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e", size = 1675145 }, { url = "https://files.pythonhosted.org/packages/26/5d/284d182fecbb5075ae10153ff7374f57314c93a8681666600e3a9e09c505/aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665", size = 1619804 }, { url = "https://files.pythonhosted.org/packages/1b/78/980064c2ad685c64ce0e8aeeb7ef1e53f43c5b005edcd7d32e60809c4992/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b", size = 1654007 }, { url = "https://files.pythonhosted.org/packages/21/8d/9e658d63b1438ad42b96f94da227f2e2c1d5c6001c9e8ffcc0bfb22e9105/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34", size = 1650022 }, { url = "https://files.pythonhosted.org/packages/85/fd/a032bf7f2755c2df4f87f9effa34ccc1ef5cea465377dbaeef93bb56bbd6/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d", size = 1732899 }, { url = "https://files.pythonhosted.org/packages/c5/0c/c2b85fde167dd440c7ba50af2aac20b5a5666392b174df54c00f888c5a75/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2", size = 1755142 }, { url = "https://files.pythonhosted.org/packages/bc/78/91ae1a3b3b3bed8b893c5d69c07023e151b1c95d79544ad04cf68f596c2f/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773", size = 1692736 }, { url = "https://files.pythonhosted.org/packages/77/89/a7ef9c4b4cdb546fcc650ca7f7395aaffbd267f0e1f648a436bec33c9b95/aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62", size = 416418 }, { url = "https://files.pythonhosted.org/packages/fc/db/2192489a8a51b52e06627506f8ac8df69ee221de88ab9bdea77aa793aa6a/aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac", size = 442509 }, { url = "https://files.pythonhosted.org/packages/69/cf/4bda538c502f9738d6b95ada11603c05ec260807246e15e869fc3ec5de97/aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886", size = 704666 }, { url = "https://files.pythonhosted.org/packages/46/7b/87fcef2cad2fad420ca77bef981e815df6904047d0a1bd6aeded1b0d1d66/aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2", size = 464057 }, { url = "https://files.pythonhosted.org/packages/5a/a6/789e1f17a1b6f4a38939fbc39d29e1d960d5f89f73d0629a939410171bc0/aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c", size = 455996 }, { url = "https://files.pythonhosted.org/packages/b7/dd/485061fbfef33165ce7320db36e530cd7116ee1098e9c3774d15a732b3fd/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a", size = 1682367 }, { url = "https://files.pythonhosted.org/packages/e9/d7/9ec5b3ea9ae215c311d88b2093e8da17e67b8856673e4166c994e117ee3e/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231", size = 1736989 }, { url = "https://files.pythonhosted.org/packages/d6/fb/ea94927f7bfe1d86178c9d3e0a8c54f651a0a655214cce930b3c679b8f64/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e", size = 1793265 }, { url = "https://files.pythonhosted.org/packages/40/7f/6de218084f9b653026bd7063cd8045123a7ba90c25176465f266976d8c82/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8", size = 1691841 }, { url = "https://files.pythonhosted.org/packages/77/e2/992f43d87831cbddb6b09c57ab55499332f60ad6fdbf438ff4419c2925fc/aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8", size = 1619317 }, { url = "https://files.pythonhosted.org/packages/96/74/879b23cdd816db4133325a201287c95bef4ce669acde37f8f1b8669e1755/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c", size = 1641416 }, { url = "https://files.pythonhosted.org/packages/30/98/b123f6b15d87c54e58fd7ae3558ff594f898d7f30a90899718f3215ad328/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab", size = 1646514 }, { url = "https://files.pythonhosted.org/packages/d7/38/257fda3dc99d6978ab943141d5165ec74fd4b4164baa15e9c66fa21da86b/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da", size = 1702095 }, { url = "https://files.pythonhosted.org/packages/0c/f4/ddab089053f9fb96654df5505c0a69bde093214b3c3454f6bfdb1845f558/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853", size = 1734611 }, { url = "https://files.pythonhosted.org/packages/c3/d6/f30b2bc520c38c8aa4657ed953186e535ae84abe55c08d0f70acd72ff577/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e", size = 1694576 }, { url = "https://files.pythonhosted.org/packages/bc/97/b0a88c3f4c6d0020b34045ee6d954058abc870814f6e310c4c9b74254116/aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600", size = 411363 }, { url = "https://files.pythonhosted.org/packages/7f/23/cc36d9c398980acaeeb443100f0216f50a7cfe20c67a9fd0a2f1a5a846de/aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d", size = 437666 }, { url = "https://files.pythonhosted.org/packages/49/d1/d8af164f400bad432b63e1ac857d74a09311a8334b0481f2f64b158b50eb/aiohttp-3.11.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:541d823548ab69d13d23730a06f97460f4238ad2e5ed966aaf850d7c369782d9", size = 697982 }, { url = "https://files.pythonhosted.org/packages/92/d1/faad3bf9fa4bfd26b95c69fc2e98937d52b1ff44f7e28131855a98d23a17/aiohttp-3.11.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:929f3ed33743a49ab127c58c3e0a827de0664bfcda566108989a14068f820194", size = 460662 }, { url = "https://files.pythonhosted.org/packages/db/61/0d71cc66d63909dabc4590f74eba71f91873a77ea52424401c2498d47536/aiohttp-3.11.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0882c2820fd0132240edbb4a51eb8ceb6eef8181db9ad5291ab3332e0d71df5f", size = 452950 }, { url = "https://files.pythonhosted.org/packages/07/db/6d04bc7fd92784900704e16b745484ef45b77bd04e25f58f6febaadf7983/aiohttp-3.11.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63de12e44935d5aca7ed7ed98a255a11e5cb47f83a9fded7a5e41c40277d104", size = 1665178 }, { url = "https://files.pythonhosted.org/packages/54/5c/e95ade9ae29f375411884d9fd98e50535bf9fe316c9feb0f30cd2ac8f508/aiohttp-3.11.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa54f8ef31d23c506910c21163f22b124facb573bff73930735cf9fe38bf7dff", size = 1717939 }, { url = "https://files.pythonhosted.org/packages/6f/1c/1e7d5c5daea9e409ed70f7986001b8c9e3a49a50b28404498d30860edab6/aiohttp-3.11.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a344d5dc18074e3872777b62f5f7d584ae4344cd6006c17ba12103759d407af3", size = 1775125 }, { url = "https://files.pythonhosted.org/packages/5d/66/890987e44f7d2f33a130e37e01a164168e6aff06fce15217b6eaf14df4f6/aiohttp-3.11.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7fb429ab1aafa1f48578eb315ca45bd46e9c37de11fe45c7f5f4138091e2f1", size = 1677176 }, { url = "https://files.pythonhosted.org/packages/8f/dc/e2ba57d7a52df6cdf1072fd5fa9c6301a68e1cd67415f189805d3eeb031d/aiohttp-3.11.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c341c7d868750e31961d6d8e60ff040fb9d3d3a46d77fd85e1ab8e76c3e9a5c4", size = 1603192 }, { url = "https://files.pythonhosted.org/packages/6c/9e/8d08a57de79ca3a358da449405555e668f2c8871a7777ecd2f0e3912c272/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed9ee95614a71e87f1a70bc81603f6c6760128b140bc4030abe6abaa988f1c3d", size = 1618296 }, { url = "https://files.pythonhosted.org/packages/56/51/89822e3ec72db352c32e7fc1c690370e24e231837d9abd056490f3a49886/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de8d38f1c2810fa2a4f1d995a2e9c70bb8737b18da04ac2afbf3971f65781d87", size = 1616524 }, { url = "https://files.pythonhosted.org/packages/2c/fa/e2e6d9398f462ffaa095e84717c1732916a57f1814502929ed67dd7568ef/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a9b7371665d4f00deb8f32208c7c5e652059b0fda41cf6dbcac6114a041f1cc2", size = 1685471 }, { url = "https://files.pythonhosted.org/packages/ae/5f/6bb976e619ca28a052e2c0ca7b0251ccd893f93d7c24a96abea38e332bf6/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:620598717fce1b3bd14dd09947ea53e1ad510317c85dda2c9c65b622edc96b12", size = 1715312 }, { url = "https://files.pythonhosted.org/packages/79/c1/756a7e65aa087c7fac724d6c4c038f2faaa2a42fe56dbc1dd62a33ca7213/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf8d9bfee991d8acc72d060d53860f356e07a50f0e0d09a8dfedea1c554dd0d5", size = 1672783 }, { url = "https://files.pythonhosted.org/packages/73/ba/a6190ebb02176c7f75e6308da31f5d49f6477b651a3dcfaaaca865a298e2/aiohttp-3.11.11-cp313-cp313-win32.whl", hash = "sha256:9d73ee3725b7a737ad86c2eac5c57a4a97793d9f442599bea5ec67ac9f4bdc3d", size = 410229 }, { url = "https://files.pythonhosted.org/packages/b8/62/c9fa5bafe03186a0e4699150a7fed9b1e73240996d0d2f0e5f70f3fdf471/aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99", size = 436081 }, ] [[package]] name = "aionotion" version = "2025.2.0" source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "certifi" }, { name = "ciso8601" }, { name = "frozenlist" }, { name = "mashumaro" }, { name = "pyjwt" }, { name = "yarl" }, ] [package.optional-dependencies] build = [ { name = "uv" }, ] lint = [ { name = "blacken-docs" }, { name = "codespell" }, { name = "darglint" }, { name = "mypy" }, { name = "pre-commit" }, { name = "pre-commit-hooks" }, { name = "pylint" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "ruff" }, { name = "yamllint" }, ] test = [ { name = "aresponses" }, { name = "pytest" }, { name = "pytest-aiohttp" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, ] [package.metadata] requires-dist = [ { name = "aiohttp", specifier = ">=3.9.0" }, { name = "aresponses", marker = "extra == 'test'", specifier = ">=2.1.6" }, { name = "blacken-docs", marker = "extra == 'lint'", specifier = "==1.19.1" }, { name = "certifi", specifier = ">=2023.7.22" }, { name = "ciso8601", specifier = "==2.3.0" }, { name = "codespell", marker = "extra == 'lint'", specifier = "==2.4.0" }, { name = "darglint", marker = "extra == 'lint'", specifier = "==1.8.1" }, { name = "frozenlist", specifier = "==1.5.0" }, { name = "mashumaro", specifier = "==3.12" }, { name = "mypy", marker = "extra == 'lint'", specifier = "==1.14.1" }, { name = "pre-commit", marker = "extra == 'lint'", specifier = "==4.1.0" }, { name = "pre-commit-hooks", marker = "extra == 'lint'", specifier = "==5.0.0" }, { name = "pyjwt", specifier = ">=2.4.0" }, { name = "pylint", marker = "extra == 'lint'", specifier = "==3.3.3" }, { name = "pytest", marker = "extra == 'lint'", specifier = "==8.3.4" }, { name = "pytest", marker = "extra == 'test'", specifier = "==8.3.4" }, { name = "pytest-aiohttp", marker = "extra == 'test'", specifier = "==1.0.0" }, { name = "pytest-asyncio", marker = "extra == 'lint'", specifier = "==0.25.2" }, { name = "pytest-asyncio", marker = "extra == 'test'", specifier = "==0.25.2" }, { name = "pytest-cov", marker = "extra == 'test'", specifier = "==6.0.0" }, { name = "ruff", marker = "extra == 'lint'", specifier = "==0.9.3" }, { name = "uv", marker = "extra == 'build'", specifier = "==0.5.26" }, { name = "yamllint", marker = "extra == 'lint'", specifier = "==1.28.0" }, { name = "yarl", specifier = ">=1.9.2" }, ] [[package]] name = "aiosignal" version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "frozenlist" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, ] [[package]] name = "aresponses" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "pytest-asyncio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/96/99b3f8c2ad6a5547bcb4726e572513564c23d4a3a3e21a03038dd3a84fe4/aresponses-3.0.0.tar.gz", hash = "sha256:8731d0609fe4c954e21f17753dc868dca9e2e002b020a33dc9212004599b11e7", size = 14796 } wheels = [ { url = "https://files.pythonhosted.org/packages/00/e6/554d29eb029da3b0ce3642483fce758043f3132b94ab54ecd1053eee29ee/aresponses-3.0.0-py3-none-any.whl", hash = "sha256:8093ab4758eb4aba91c765a50295b269ecfc0a9e7c7158954760bc0c23503970", size = 9879 }, ] [[package]] name = "astroid" version = "3.3.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/80/c5/5c83c48bbf547f3dd8b587529db7cf5a265a3368b33e85e76af8ff6061d3/astroid-3.3.8.tar.gz", hash = "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b", size = 398196 } wheels = [ { url = "https://files.pythonhosted.org/packages/07/28/0bc8a17d6cd4cc3c79ae41b7105a2b9a327c110e5ddd37a8a27b29a5c8a2/astroid-3.3.8-py3-none-any.whl", hash = "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c", size = 275153 }, ] [[package]] name = "attrs" version = "25.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } wheels = [ { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, ] [[package]] name = "black" version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "mypy-extensions" }, { name = "packaging" }, { name = "pathspec" }, { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, ] [[package]] name = "blacken-docs" version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "black" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fd/a2/772f95006335a59837b415b8b3b38a7c5f2606060d6336ee111521af563e/blacken_docs-1.19.1.tar.gz", hash = "sha256:3cf7a10f5b87886683e3ab07a0dc17de41425f3d21e2948e59f1c6079c45b328", size = 14918 } wheels = [ { url = "https://files.pythonhosted.org/packages/d9/1b/166bd46537f9d7832d856ddf3b5cc2ef9761a2cb4b31c83c245dd8987c11/blacken_docs-1.19.1-py3-none-any.whl", hash = "sha256:73c3dee042a28f2d4f7df6e2c340869d6ced9704f6174d035d9b6199567f890d", size = 8280 }, ] [[package]] name = "certifi" version = "2025.1.31" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } wheels = [ { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, ] [[package]] name = "cfgv" version = "3.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, ] [[package]] name = "ciso8601" version = "2.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/29/39180b182b53acf7b68abd74f79df995fcb1eee077047cb265c16e227fbc/ciso8601-2.3.0.tar.gz", hash = "sha256:19e3fbd786d8bec3358eac94d8774d365b694b604fd1789244b87083f66c8900", size = 26839 } wheels = [ { url = "https://files.pythonhosted.org/packages/15/2c/17ea5bc503b9f775afd9cf959850771f8db77a6dfb88e8bf07051bd4cf20/ciso8601-2.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87a6f58bdda833cb8d78c6482a179fff663903a8f562755e119bf815b1014f2e", size = 24827 }, { url = "https://files.pythonhosted.org/packages/e1/27/6dd3886e11a254d9089e6d8f0b7e7ddcc87f310fcd572c529774fc0173d5/ciso8601-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7667faf021314315a3c498e4c7c8cf57a7014af0960ddd5b671bcf03b2d0132b", size = 15884 }, { url = "https://files.pythonhosted.org/packages/1d/b4/c8a36c96831e3c925e096d1d8cc2c247cce5dfce25bdd06d63b2bd58d948/ciso8601-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa90488666ee44796932850fc419cd55863b320f77b1474991e60f321b5ac7d2", size = 15972 }, { url = "https://files.pythonhosted.org/packages/44/53/b50cdd5fef796e757898887a8aabc185eaf82e45ff53b73cfe4b60026758/ciso8601-2.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1aba1f59b6d27ec694128f9ba85e22c1f17e67ffc5b1b0a991628bb402e25e81", size = 39077 }, { url = "https://files.pythonhosted.org/packages/d8/5c/6cbdbd9992fa83452825db50167d9a82599e5e897614d170f9fe60ed7ce6/ciso8601-2.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:896dd46c7f2129140fc36dbe9ccf78cec02143b941b5a608e652cd40e39f6064", size = 48744 }, { url = "https://files.pythonhosted.org/packages/e7/22/1716f960426e482b2200ff902654b719ecc838b4741d9c07678fc57c7e40/ciso8601-2.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cf6dfa22f21f838b730f977bc7ad057c37646f683bf42a727b4e763f44d47dc", size = 49896 }, { url = "https://files.pythonhosted.org/packages/5c/61/d437a8f6abe43ef4c0a834fd563c70a2e90de56b1b3b9e6458e44a25a9a6/ciso8601-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:a8c4aa6880fd698075d5478615d4668e70af6424d90b1686c560c1ec3459926a", size = 17170 }, ] [[package]] name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, ] [[package]] name = "codespell" version = "2.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b0/2f/706691245790ae6c63252d48b7ff5e3635951d55b3ce3c0ac13d898bf70b/codespell-2.4.0.tar.gz", hash = "sha256:587d45b14707fb8ce51339ba4cce50ae0e98ce228ef61f3c5e160e34f681be58", size = 344743 } wheels = [ { url = "https://files.pythonhosted.org/packages/19/ce/39bfb82aa428a3ac4d94cc8c9faa3eadeadb2606eee3b584f68d9b575b43/codespell-2.4.0-py3-none-any.whl", hash = "sha256:b4c5b779f747dd481587aeecb5773301183f52b94b96ed51a28126d0482eec1d", size = 344508 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] name = "coverage" version = "7.6.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 } wheels = [ { url = "https://files.pythonhosted.org/packages/85/d2/5e175fcf6766cf7501a8541d81778fd2f52f4870100e791f5327fd23270b/coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3", size = 208088 }, { url = "https://files.pythonhosted.org/packages/4b/6f/06db4dc8fca33c13b673986e20e466fd936235a6ec1f0045c3853ac1b593/coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43", size = 208536 }, { url = "https://files.pythonhosted.org/packages/0d/62/c6a0cf80318c1c1af376d52df444da3608eafc913b82c84a4600d8349472/coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132", size = 240474 }, { url = "https://files.pythonhosted.org/packages/a3/59/750adafc2e57786d2e8739a46b680d4fb0fbc2d57fbcb161290a9f1ecf23/coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f", size = 237880 }, { url = "https://files.pythonhosted.org/packages/2c/f8/ef009b3b98e9f7033c19deb40d629354aab1d8b2d7f9cfec284dbedf5096/coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994", size = 239750 }, { url = "https://files.pythonhosted.org/packages/a6/e2/6622f3b70f5f5b59f705e680dae6db64421af05a5d1e389afd24dae62e5b/coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99", size = 238642 }, { url = "https://files.pythonhosted.org/packages/2d/10/57ac3f191a3c95c67844099514ff44e6e19b2915cd1c22269fb27f9b17b6/coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd", size = 237266 }, { url = "https://files.pythonhosted.org/packages/ee/2d/7016f4ad9d553cabcb7333ed78ff9d27248ec4eba8dd21fa488254dff894/coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377", size = 238045 }, { url = "https://files.pythonhosted.org/packages/a7/fe/45af5c82389a71e0cae4546413266d2195c3744849669b0bab4b5f2c75da/coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8", size = 210647 }, { url = "https://files.pythonhosted.org/packages/db/11/3f8e803a43b79bc534c6a506674da9d614e990e37118b4506faf70d46ed6/coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609", size = 211508 }, { url = "https://files.pythonhosted.org/packages/86/77/19d09ea06f92fdf0487499283b1b7af06bc422ea94534c8fe3a4cd023641/coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853", size = 208281 }, { url = "https://files.pythonhosted.org/packages/b6/67/5479b9f2f99fcfb49c0d5cf61912a5255ef80b6e80a3cddba39c38146cf4/coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078", size = 208514 }, { url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537 }, { url = "https://files.pythonhosted.org/packages/4b/7e/5ac4c90192130e7cf8b63153fe620c8bfd9068f89a6d9b5f26f1550f7a26/coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50", size = 238572 }, { url = "https://files.pythonhosted.org/packages/dc/03/0334a79b26ecf59958f2fe9dd1f5ab3e2f88db876f5071933de39af09647/coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022", size = 240639 }, { url = "https://files.pythonhosted.org/packages/d7/45/8a707f23c202208d7b286d78ad6233f50dcf929319b664b6cc18a03c1aae/coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b", size = 240072 }, { url = "https://files.pythonhosted.org/packages/66/02/603ce0ac2d02bc7b393279ef618940b4a0535b0868ee791140bda9ecfa40/coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0", size = 238386 }, { url = "https://files.pythonhosted.org/packages/04/62/4e6887e9be060f5d18f1dd58c2838b2d9646faf353232dec4e2d4b1c8644/coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852", size = 240054 }, { url = "https://files.pythonhosted.org/packages/5c/74/83ae4151c170d8bd071924f212add22a0e62a7fe2b149edf016aeecad17c/coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359", size = 210904 }, { url = "https://files.pythonhosted.org/packages/c3/54/de0893186a221478f5880283119fc40483bc460b27c4c71d1b8bba3474b9/coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247", size = 211692 }, { url = "https://files.pythonhosted.org/packages/25/6d/31883d78865529257bf847df5789e2ae80e99de8a460c3453dbfbe0db069/coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9", size = 208308 }, { url = "https://files.pythonhosted.org/packages/70/22/3f2b129cc08de00c83b0ad6252e034320946abfc3e4235c009e57cfeee05/coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b", size = 208565 }, { url = "https://files.pythonhosted.org/packages/97/0a/d89bc2d1cc61d3a8dfe9e9d75217b2be85f6c73ebf1b9e3c2f4e797f4531/coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690", size = 241083 }, { url = "https://files.pythonhosted.org/packages/4c/81/6d64b88a00c7a7aaed3a657b8eaa0931f37a6395fcef61e53ff742b49c97/coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18", size = 238235 }, { url = "https://files.pythonhosted.org/packages/9a/0b/7797d4193f5adb4b837207ed87fecf5fc38f7cc612b369a8e8e12d9fa114/coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c", size = 240220 }, { url = "https://files.pythonhosted.org/packages/65/4d/6f83ca1bddcf8e51bf8ff71572f39a1c73c34cf50e752a952c34f24d0a60/coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd", size = 239847 }, { url = "https://files.pythonhosted.org/packages/30/9d/2470df6aa146aff4c65fee0f87f58d2164a67533c771c9cc12ffcdb865d5/coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e", size = 237922 }, { url = "https://files.pythonhosted.org/packages/08/dd/723fef5d901e6a89f2507094db66c091449c8ba03272861eaefa773ad95c/coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694", size = 239783 }, { url = "https://files.pythonhosted.org/packages/3d/f7/64d3298b2baf261cb35466000628706ce20a82d42faf9b771af447cd2b76/coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6", size = 210965 }, { url = "https://files.pythonhosted.org/packages/d5/58/ec43499a7fc681212fe7742fe90b2bc361cdb72e3181ace1604247a5b24d/coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e", size = 211719 }, { url = "https://files.pythonhosted.org/packages/ab/c9/f2857a135bcff4330c1e90e7d03446b036b2363d4ad37eb5e3a47bbac8a6/coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe", size = 209050 }, { url = "https://files.pythonhosted.org/packages/aa/b3/f840e5bd777d8433caa9e4a1eb20503495709f697341ac1a8ee6a3c906ad/coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273", size = 209321 }, { url = "https://files.pythonhosted.org/packages/85/7d/125a5362180fcc1c03d91850fc020f3831d5cda09319522bcfa6b2b70be7/coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8", size = 252039 }, { url = "https://files.pythonhosted.org/packages/a9/9c/4358bf3c74baf1f9bddd2baf3756b54c07f2cfd2535f0a47f1e7757e54b3/coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098", size = 247758 }, { url = "https://files.pythonhosted.org/packages/cf/c7/de3eb6fc5263b26fab5cda3de7a0f80e317597a4bad4781859f72885f300/coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb", size = 250119 }, { url = "https://files.pythonhosted.org/packages/3e/e6/43de91f8ba2ec9140c6a4af1102141712949903dc732cf739167cfa7a3bc/coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0", size = 249597 }, { url = "https://files.pythonhosted.org/packages/08/40/61158b5499aa2adf9e37bc6d0117e8f6788625b283d51e7e0c53cf340530/coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf", size = 247473 }, { url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737 }, { url = "https://files.pythonhosted.org/packages/3c/6e/fe899fb937657db6df31cc3e61c6968cb56d36d7326361847440a430152e/coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312", size = 211611 }, { url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 }, ] [package.optional-dependencies] toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] name = "darglint" version = "1.8.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d4/2c/86e8549e349388c18ca8a4ff8661bb5347da550f598656d32a98eaaf91cc/darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da", size = 74435 } wheels = [ { url = "https://files.pythonhosted.org/packages/69/28/85d1e0396d64422c5218d68e5cdcc53153aa8a2c83c7dbc3ee1502adf3a1/darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d", size = 120767 }, ] [[package]] name = "dill" version = "0.3.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/70/43/86fe3f9e130c4137b0f1b50784dd70a5087b911fe07fa81e53e0c4c47fea/dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c", size = 187000 } wheels = [ { url = "https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a", size = 119418 }, ] [[package]] name = "distlib" version = "0.3.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, ] [[package]] name = "filelock" version = "3.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } wheels = [ { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, ] [[package]] name = "frozenlist" version = "1.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } wheels = [ { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] [[package]] name = "identify" version = "2.6.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/82/bf/c68c46601bacd4c6fb4dd751a42b6e7087240eaabc6487f2ef7a48e0e8fc/identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251", size = 99217 } wheels = [ { url = "https://files.pythonhosted.org/packages/74/a1/68a395c17eeefb04917034bd0a1bfa765e7654fa150cca473d669aa3afb5/identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881", size = 99083 }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] [[package]] name = "iniconfig" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] [[package]] name = "isort" version = "5.13.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, ] [[package]] name = "mashumaro" version = "3.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/18/5355583a63868cb58318d266daae6344e66b5bee324e0203c510a829dd38/mashumaro-3.12.tar.gz", hash = "sha256:bb4ff10aee689edff24f6ff369843e1a826193d396b449b86ef58489bfe40c83", size = 125581 } wheels = [ { url = "https://files.pythonhosted.org/packages/d6/30/eb3c3738ca98ecfc46624044fa3fa5cae95b80a64d67c79135a2ad172589/mashumaro-3.12-py3-none-any.whl", hash = "sha256:bc4ab7ecaca106fcde706d77cef22816149285a10727b88141599855d4603e2f", size = 89929 }, ] [[package]] name = "mccabe" version = "0.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } wheels = [ { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, ] [[package]] name = "multidict" version = "6.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } wheels = [ { url = "https://files.pythonhosted.org/packages/93/13/df3505a46d0cd08428e4c8169a196131d1b0c4b515c3649829258843dde6/multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", size = 48570 }, { url = "https://files.pythonhosted.org/packages/f0/e1/a215908bfae1343cdb72f805366592bdd60487b4232d039c437fe8f5013d/multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", size = 29316 }, { url = "https://files.pythonhosted.org/packages/70/0f/6dc70ddf5d442702ed74f298d69977f904960b82368532c88e854b79f72b/multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", size = 29640 }, { url = "https://files.pythonhosted.org/packages/d8/6d/9c87b73a13d1cdea30b321ef4b3824449866bd7f7127eceed066ccb9b9ff/multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", size = 131067 }, { url = "https://files.pythonhosted.org/packages/cc/1e/1b34154fef373371fd6c65125b3d42ff5f56c7ccc6bfff91b9b3c60ae9e0/multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", size = 138507 }, { url = "https://files.pythonhosted.org/packages/fb/e0/0bc6b2bac6e461822b5f575eae85da6aae76d0e2a79b6665d6206b8e2e48/multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", size = 133905 }, { url = "https://files.pythonhosted.org/packages/ba/af/73d13b918071ff9b2205fcf773d316e0f8fefb4ec65354bbcf0b10908cc6/multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", size = 129004 }, { url = "https://files.pythonhosted.org/packages/74/21/23960627b00ed39643302d81bcda44c9444ebcdc04ee5bedd0757513f259/multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", size = 121308 }, { url = "https://files.pythonhosted.org/packages/8b/5c/cf282263ffce4a596ed0bb2aa1a1dddfe1996d6a62d08842a8d4b33dca13/multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", size = 132608 }, { url = "https://files.pythonhosted.org/packages/d7/3e/97e778c041c72063f42b290888daff008d3ab1427f5b09b714f5a8eff294/multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", size = 127029 }, { url = "https://files.pythonhosted.org/packages/47/ac/3efb7bfe2f3aefcf8d103e9a7162572f01936155ab2f7ebcc7c255a23212/multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", size = 137594 }, { url = "https://files.pythonhosted.org/packages/42/9b/6c6e9e8dc4f915fc90a9b7798c44a30773dea2995fdcb619870e705afe2b/multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", size = 134556 }, { url = "https://files.pythonhosted.org/packages/1d/10/8e881743b26aaf718379a14ac58572a240e8293a1c9d68e1418fb11c0f90/multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", size = 130993 }, { url = "https://files.pythonhosted.org/packages/45/84/3eb91b4b557442802d058a7579e864b329968c8d0ea57d907e7023c677f2/multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", size = 26405 }, { url = "https://files.pythonhosted.org/packages/9f/0b/ad879847ecbf6d27e90a6eabb7eff6b62c129eefe617ea45eae7c1f0aead/multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", size = 28795 }, { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, { url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 }, { url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 }, { url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 }, { url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 }, { url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 }, { url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 }, { url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 }, { url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 }, { url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 }, { url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 }, { url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 }, { url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 }, { url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 }, { url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 }, { url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 }, { url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 }, { url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 }, { url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 }, { url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 }, { url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 }, { url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 }, { url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 }, { url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 }, { url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 }, { url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 }, { url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 }, { url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 }, { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, ] [[package]] name = "mypy" version = "1.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051 } wheels = [ { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432 }, { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515 }, { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791 }, { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203 }, { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900 }, { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869 }, { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668 }, { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060 }, { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167 }, { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341 }, { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991 }, { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016 }, { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097 }, { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728 }, { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965 }, { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660 }, { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198 }, { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276 }, { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905 }, ] [[package]] name = "mypy-extensions" version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, ] [[package]] name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] [[package]] name = "platformdirs" version = "4.3.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] [[package]] name = "pre-commit" version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, { name = "identify" }, { name = "nodeenv" }, { name = "pyyaml" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 } wheels = [ { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, ] [[package]] name = "pre-commit-hooks" version = "5.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/7d/3299241a753c738d114600c360d754550b28c285281dc6a5132c4ccfae65/pre_commit_hooks-5.0.0.tar.gz", hash = "sha256:10626959a9eaf602fbfc22bc61b6e75801436f82326bfcee82bb1f2fc4bc646e", size = 29747 } wheels = [ { url = "https://files.pythonhosted.org/packages/1e/29/db1d855a661c02dbde5cab3057969133fcc62e7a0c6393e48fe9d0e81679/pre_commit_hooks-5.0.0-py2.py3-none-any.whl", hash = "sha256:8d71cfb582c5c314a5498d94e0104b6567a8b93fb35903ea845c491f4e290a7a", size = 41245 }, ] [[package]] name = "propcache" version = "0.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } wheels = [ { url = "https://files.pythonhosted.org/packages/bc/0f/2913b6791ebefb2b25b4efd4bb2299c985e09786b9f5b19184a88e5778dd/propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16", size = 79297 }, { url = "https://files.pythonhosted.org/packages/cf/73/af2053aeccd40b05d6e19058419ac77674daecdd32478088b79375b9ab54/propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717", size = 45611 }, { url = "https://files.pythonhosted.org/packages/3c/09/8386115ba7775ea3b9537730e8cf718d83bbf95bffe30757ccf37ec4e5da/propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3", size = 45146 }, { url = "https://files.pythonhosted.org/packages/03/7a/793aa12f0537b2e520bf09f4c6833706b63170a211ad042ca71cbf79d9cb/propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9", size = 232136 }, { url = "https://files.pythonhosted.org/packages/f1/38/b921b3168d72111769f648314100558c2ea1d52eb3d1ba7ea5c4aa6f9848/propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787", size = 239706 }, { url = "https://files.pythonhosted.org/packages/14/29/4636f500c69b5edea7786db3c34eb6166f3384b905665ce312a6e42c720c/propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465", size = 238531 }, { url = "https://files.pythonhosted.org/packages/85/14/01fe53580a8e1734ebb704a3482b7829a0ef4ea68d356141cf0994d9659b/propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af", size = 231063 }, { url = "https://files.pythonhosted.org/packages/33/5c/1d961299f3c3b8438301ccfbff0143b69afcc30c05fa28673cface692305/propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7", size = 220134 }, { url = "https://files.pythonhosted.org/packages/00/d0/ed735e76db279ba67a7d3b45ba4c654e7b02bc2f8050671ec365d8665e21/propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f", size = 220009 }, { url = "https://files.pythonhosted.org/packages/75/90/ee8fab7304ad6533872fee982cfff5a53b63d095d78140827d93de22e2d4/propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54", size = 212199 }, { url = "https://files.pythonhosted.org/packages/eb/ec/977ffaf1664f82e90737275873461695d4c9407d52abc2f3c3e24716da13/propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505", size = 214827 }, { url = "https://files.pythonhosted.org/packages/57/48/031fb87ab6081764054821a71b71942161619549396224cbb242922525e8/propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82", size = 228009 }, { url = "https://files.pythonhosted.org/packages/1a/06/ef1390f2524850838f2390421b23a8b298f6ce3396a7cc6d39dedd4047b0/propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca", size = 231638 }, { url = "https://files.pythonhosted.org/packages/38/2a/101e6386d5a93358395da1d41642b79c1ee0f3b12e31727932b069282b1d/propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e", size = 222788 }, { url = "https://files.pythonhosted.org/packages/db/81/786f687951d0979007e05ad9346cd357e50e3d0b0f1a1d6074df334b1bbb/propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034", size = 40170 }, { url = "https://files.pythonhosted.org/packages/cf/59/7cc7037b295d5772eceb426358bb1b86e6cab4616d971bd74275395d100d/propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3", size = 44404 }, { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, { url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 }, { url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 }, { url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 }, { url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 }, { url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 }, { url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 }, { url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 }, { url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 }, { url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 }, { url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 }, { url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 }, { url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 }, { url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 }, { url = "https://files.pythonhosted.org/packages/0f/2a/329e0547cf2def8857157f9477669043e75524cc3e6251cef332b3ff256f/propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc", size = 77002 }, { url = "https://files.pythonhosted.org/packages/12/2d/c4df5415e2382f840dc2ecbca0eeb2293024bc28e57a80392f2012b4708c/propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9", size = 44639 }, { url = "https://files.pythonhosted.org/packages/d0/5a/21aaa4ea2f326edaa4e240959ac8b8386ea31dedfdaa636a3544d9e7a408/propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439", size = 44049 }, { url = "https://files.pythonhosted.org/packages/4e/3e/021b6cd86c0acc90d74784ccbb66808b0bd36067a1bf3e2deb0f3845f618/propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536", size = 224819 }, { url = "https://files.pythonhosted.org/packages/3c/57/c2fdeed1b3b8918b1770a133ba5c43ad3d78e18285b0c06364861ef5cc38/propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629", size = 229625 }, { url = "https://files.pythonhosted.org/packages/9d/81/70d4ff57bf2877b5780b466471bebf5892f851a7e2ca0ae7ffd728220281/propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b", size = 232934 }, { url = "https://files.pythonhosted.org/packages/3c/b9/bb51ea95d73b3fb4100cb95adbd4e1acaf2cbb1fd1083f5468eeb4a099a8/propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052", size = 227361 }, { url = "https://files.pythonhosted.org/packages/f1/20/3c6d696cd6fd70b29445960cc803b1851a1131e7a2e4ee261ee48e002bcd/propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce", size = 213904 }, { url = "https://files.pythonhosted.org/packages/a1/cb/1593bfc5ac6d40c010fa823f128056d6bc25b667f5393781e37d62f12005/propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d", size = 212632 }, { url = "https://files.pythonhosted.org/packages/6d/5c/e95617e222be14a34c709442a0ec179f3207f8a2b900273720501a70ec5e/propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce", size = 207897 }, { url = "https://files.pythonhosted.org/packages/8e/3b/56c5ab3dc00f6375fbcdeefdede5adf9bee94f1fab04adc8db118f0f9e25/propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95", size = 208118 }, { url = "https://files.pythonhosted.org/packages/86/25/d7ef738323fbc6ebcbce33eb2a19c5e07a89a3df2fded206065bd5e868a9/propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf", size = 217851 }, { url = "https://files.pythonhosted.org/packages/b3/77/763e6cef1852cf1ba740590364ec50309b89d1c818e3256d3929eb92fabf/propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f", size = 222630 }, { url = "https://files.pythonhosted.org/packages/4f/e9/0f86be33602089c701696fbed8d8c4c07b6ee9605c5b7536fd27ed540c5b/propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30", size = 216269 }, { url = "https://files.pythonhosted.org/packages/cc/02/5ac83217d522394b6a2e81a2e888167e7ca629ef6569a3f09852d6dcb01a/propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6", size = 39472 }, { url = "https://files.pythonhosted.org/packages/f4/33/d6f5420252a36034bc8a3a01171bc55b4bff5df50d1c63d9caa50693662f/propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1", size = 43363 }, { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, ] [[package]] name = "pyjwt" version = "2.10.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } wheels = [ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, ] [[package]] name = "pylint" version = "3.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astroid" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "dill" }, { name = "isort" }, { name = "mccabe" }, { name = "platformdirs" }, { name = "tomlkit" }, ] sdist = { url = "https://files.pythonhosted.org/packages/17/fd/e9a739afac274a39596bbe562e9d966db6f3917fdb2bd7322ffc56da0ba2/pylint-3.3.3.tar.gz", hash = "sha256:07c607523b17e6d16e2ae0d7ef59602e332caa762af64203c24b41c27139f36a", size = 1516550 } wheels = [ { url = "https://files.pythonhosted.org/packages/91/e1/26d55acea92b1ea4d33672e48f09ceeb274e84d7d542a4fb9a32a556db46/pylint-3.3.3-py3-none-any.whl", hash = "sha256:26e271a2bc8bce0fc23833805a9076dd9b4d5194e2a02164942cb3cdc37b4183", size = 521918 }, ] [[package]] name = "pytest" version = "8.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } wheels = [ { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, ] [[package]] name = "pytest-aiohttp" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "pytest" }, { name = "pytest-asyncio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/32/7578fa58132d5f597fd3ac25f1aec910fb5ba45524c6864d77450cc2e51f/pytest-aiohttp-1.0.0.tar.gz", hash = "sha256:8744295776292cb6683490be2c3fc59d9d9d5ec2df7298a29ed59bef6adcdd7a", size = 11021 } wheels = [ { url = "https://files.pythonhosted.org/packages/45/83/b310370af163577ba97dcefe43990dce3d67278c11f1dea15b50177922c0/pytest_aiohttp-1.0.0-py3-none-any.whl", hash = "sha256:1f5279a8ac316625b0a681cad6a6c3aa6daa9cabe98de93a2cfbd470e692861f", size = 8539 }, ] [[package]] name = "pytest-asyncio" version = "0.25.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/df/adcc0d60f1053d74717d21d58c0048479e9cab51464ce0d2965b086bd0e2/pytest_asyncio-0.25.2.tar.gz", hash = "sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f", size = 53950 } wheels = [ { url = "https://files.pythonhosted.org/packages/61/d8/defa05ae50dcd6019a95527200d3b3980043df5aa445d40cb0ef9f7f98ab/pytest_asyncio-0.25.2-py3-none-any.whl", hash = "sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075", size = 19400 }, ] [[package]] name = "pytest-cov" version = "6.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } wheels = [ { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } wheels = [ { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] [[package]] name = "ruamel-yaml" version = "0.18.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ea/46/f44d8be06b85bc7c4d8c95d658be2b68f27711f279bf9dd0612a5e4794f5/ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58", size = 143447 } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/36/dfc1ebc0081e6d39924a2cc53654497f967a084a436bb64402dfce4254d9/ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1", size = 117729 }, ] [[package]] name = "ruamel-yaml-clib" version = "0.2.12" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315 } wheels = [ { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224 }, { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480 }, { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068 }, { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012 }, { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352 }, { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344 }, { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498 }, { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205 }, { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185 }, { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433 }, { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362 }, { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118 }, { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497 }, { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042 }, { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831 }, { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692 }, { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777 }, { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523 }, { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011 }, { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488 }, { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066 }, { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785 }, { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017 }, { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270 }, { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059 }, { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583 }, { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190 }, ] [[package]] name = "ruff" version = "0.9.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1e/7f/60fda2eec81f23f8aa7cbbfdf6ec2ca11eb11c273827933fb2541c2ce9d8/ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a", size = 3586740 } wheels = [ { url = "https://files.pythonhosted.org/packages/f9/77/4fb790596d5d52c87fd55b7160c557c400e90f6116a56d82d76e95d9374a/ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624", size = 11656815 }, { url = "https://files.pythonhosted.org/packages/a2/a8/3338ecb97573eafe74505f28431df3842c1933c5f8eae615427c1de32858/ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c", size = 11594821 }, { url = "https://files.pythonhosted.org/packages/8e/89/320223c3421962762531a6b2dd58579b858ca9916fb2674874df5e97d628/ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4", size = 11040475 }, { url = "https://files.pythonhosted.org/packages/b2/bd/1d775eac5e51409535804a3a888a9623e87a8f4b53e2491580858a083692/ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439", size = 11856207 }, { url = "https://files.pythonhosted.org/packages/7f/c6/3e14e09be29587393d188454064a4aa85174910d16644051a80444e4fd88/ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5", size = 11420460 }, { url = "https://files.pythonhosted.org/packages/ef/42/b7ca38ffd568ae9b128a2fa76353e9a9a3c80ef19746408d4ce99217ecc1/ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4", size = 12605472 }, { url = "https://files.pythonhosted.org/packages/a6/a1/3167023f23e3530fde899497ccfe239e4523854cb874458ac082992d206c/ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1", size = 13243123 }, { url = "https://files.pythonhosted.org/packages/d0/b4/3c600758e320f5bf7de16858502e849f4216cb0151f819fa0d1154874802/ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5", size = 12744650 }, { url = "https://files.pythonhosted.org/packages/be/38/266fbcbb3d0088862c9bafa8b1b99486691d2945a90b9a7316336a0d9a1b/ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4", size = 14458585 }, { url = "https://files.pythonhosted.org/packages/63/a6/47fd0e96990ee9b7a4abda62de26d291bd3f7647218d05b7d6d38af47c30/ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6", size = 12419624 }, { url = "https://files.pythonhosted.org/packages/84/5d/de0b7652e09f7dda49e1a3825a164a65f4998175b6486603c7601279baad/ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730", size = 11843238 }, { url = "https://files.pythonhosted.org/packages/9e/be/3f341ceb1c62b565ec1fb6fd2139cc40b60ae6eff4b6fb8f94b1bb37c7a9/ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2", size = 11484012 }, { url = "https://files.pythonhosted.org/packages/a3/c8/ff8acbd33addc7e797e702cf00bfde352ab469723720c5607b964491d5cf/ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519", size = 12038494 }, { url = "https://files.pythonhosted.org/packages/73/b1/8d9a2c0efbbabe848b55f877bc10c5001a37ab10aca13c711431673414e5/ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b", size = 12473639 }, { url = "https://files.pythonhosted.org/packages/cb/44/a673647105b1ba6da9824a928634fe23186ab19f9d526d7bdf278cd27bc3/ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c", size = 9834353 }, { url = "https://files.pythonhosted.org/packages/c3/01/65cadb59bf8d4fbe33d1a750103e6883d9ef302f60c28b73b773092fbde5/ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4", size = 10821444 }, { url = "https://files.pythonhosted.org/packages/69/cb/b3fe58a136a27d981911cba2f18e4b29f15010623b79f0f2510fd0d31fd3/ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b", size = 10038168 }, ] [[package]] name = "setuptools" version = "75.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/92/ec/089608b791d210aec4e7f97488e67ab0d33add3efccb83a056cbafe3a2a6/setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6", size = 1343222 } wheels = [ { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3", size = 1228782 }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } wheels = [ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, ] [[package]] name = "tomlkit" version = "0.13.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } wheels = [ { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, ] [[package]] name = "typing-extensions" version = "4.12.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] [[package]] name = "uv" version = "0.5.26" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/14/3d/c9c131e3ceb8dd55abfc5cd2c5f641dbd4b659d750a09e35ce35fb691a17/uv-0.5.26.tar.gz", hash = "sha256:9e566b8d46ec1cf54ed8b36565041650d7005251330222b8f838c8f0a7cacd8a", size = 2708782 } wheels = [ { url = "https://files.pythonhosted.org/packages/c8/d7/60b7c55974ec258760b915c169331acb33c3c4f14c25092a2d4766fca9cb/uv-0.5.26-py3-none-linux_armv6l.whl", hash = "sha256:f20ece2d762d67ce3e62cea07fb3c420431a868b4bcb704169b59ccdd10cc2da", size = 15347173 }, { url = "https://files.pythonhosted.org/packages/ac/85/d356c091f1457683c3a288d3bf5699232390317325e1b441f4eeaffa7e39/uv-0.5.26-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:19cbf2eb624a7e05a5fd7644daee1860d17084706adc1d24950457b07ded19fa", size = 15522430 }, { url = "https://files.pythonhosted.org/packages/1f/b5/e291c390a64025d742f35f8f2a137e6ccf8296dbe26574ce60d8964aa9e6/uv-0.5.26-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99e6846842baa3950a35e799c66990d0d4edd0d9693f6917c43a51fd9e6a8f79", size = 14381974 }, { url = "https://files.pythonhosted.org/packages/cc/67/604aad203011108de4e34387cf40e1b284a2842210359d92635fdb6a0bad/uv-0.5.26-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:660da92587c6b13e9e27a533a8a9859bcada7e455f07197c9e6acefeed6fbadb", size = 14844477 }, { url = "https://files.pythonhosted.org/packages/6d/7b/1249ba95af3bce678e16d42e33b5d679f2676f326dfaeca95256f8a38567/uv-0.5.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e2cb8b2c2707e48ff21ebecec0c4fb66e7db54b91213c8677d8da42a2e61dee", size = 15091483 }, { url = "https://files.pythonhosted.org/packages/eb/b0/b75902abdbc326281ebfc2ff0228884389898dc2aaeaca532dc02c4926aa/uv-0.5.26-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9738fe3f0c3f1c9702bb4f56b0e90a3ac5b2f7ae423d96b2ae6b4e4ce8f59d80", size = 15857856 }, { url = "https://files.pythonhosted.org/packages/cc/2b/17845700a097c3efa7c6173004c1f3914b10cadeb074dd05f8a38ce2dbc3/uv-0.5.26-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:edd8685d997b1ab54eb7735f2421ab92453654f419b3635b9cc182cf050ed89f", size = 16813172 }, { url = "https://files.pythonhosted.org/packages/16/47/7ad75d7df7024d540d7b3d9cc5c1e63888844ef0c2af817c5f7b66584c9d/uv-0.5.26-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14d76399afb5b7fd3edbd8af9d80b9641146b97b571a2ec3401a0e6831c89d48", size = 16517913 }, { url = "https://files.pythonhosted.org/packages/0a/ea/3b8c64fae76cb6d9424b2e0c24c750cd0f2717d22336ffc57654db52855a/uv-0.5.26-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37f8e13ce8af2715bb6f8b37141913dc05bcd7a1dd91d6bb20cedac1bd217094", size = 20852887 }, { url = "https://files.pythonhosted.org/packages/86/ba/2f251fbb878b5f74de2778f765236d7dec2c6c31a9d6178d0c137176c7fc/uv-0.5.26-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:400803281738db532a0f732d20240e147e2516da768fc46a213eae62a789117c", size = 16178436 }, { url = "https://files.pythonhosted.org/packages/4b/04/c097db469e5f845db6ce273daa45f047b19ce4766b4db1076f2a7cdf2e04/uv-0.5.26-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:ce75b2ad23dc0702b54eb6cba9a709873b415d7a0afe0272f139a0a0835e7d9d", size = 15162683 }, { url = "https://files.pythonhosted.org/packages/00/55/f92d48cd88017b9dd788a9c1122aa447f74f8441266e0984a190f8260833/uv-0.5.26-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:cf814de2a294cb43bd8f79e444b37df17db0a72fd832fe53fc3addcd54fe3e6b", size = 15059817 }, { url = "https://files.pythonhosted.org/packages/85/4e/869a0037800c451130825eba632cc16c4a51cd5ecfaf43ea06f1335e4549/uv-0.5.26-py3-none-musllinux_1_1_i686.whl", hash = "sha256:7ae96b282894eae0a62a1bd2a0b19dfc5914530caab40b941c19e2bfeac8c756", size = 15480785 }, { url = "https://files.pythonhosted.org/packages/92/87/744426b947408fde640b988a3dcdf3c7ee17c1141a9add4dd736239fbbee/uv-0.5.26-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:009ce3b2c7bf6a37800db5fd44d24266b2f7bcd5af9dcd96c4045f9b62802881", size = 16289307 }, { url = "https://files.pythonhosted.org/packages/43/b8/195ffa65165531323e1542c531eba15611328cfcf0e095d9ccc33dd62d9e/uv-0.5.26-py3-none-win32.whl", hash = "sha256:b92113893468beb581e2145cdde31c511c7e43501b1f5ce216e89c706b523983", size = 15497083 }, { url = "https://files.pythonhosted.org/packages/14/d8/8965b7fbbc0f7fed23a98f95e52990aa3487a1defcff67e61ce5c3be4ced/uv-0.5.26-py3-none-win_amd64.whl", hash = "sha256:d3f0c84e092cbc152c430904c452726652360b901490841fb2b227424630a3b6", size = 16861407 }, { url = "https://files.pythonhosted.org/packages/e7/38/9b2d843b41e8f9206382336ba87bbd6cebba4e4cce4dacc8f1d4b6f3966f/uv-0.5.26-py3-none-win_arm64.whl", hash = "sha256:75318d97b8f6f7394bf1c8ea3979d5fc2dde83e87cd753ead753e451f4781cd7", size = 15688046 }, ] [[package]] name = "virtualenv" version = "20.29.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/ca/f23dcb02e161a9bba141b1c08aa50e8da6ea25e6d780528f1d385a3efe25/virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35", size = 7658028 } wheels = [ { url = "https://files.pythonhosted.org/packages/89/9b/599bcfc7064fbe5740919e78c5df18e5dceb0887e676256a1061bb5ae232/virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779", size = 4282379 }, ] [[package]] name = "yamllint" version = "1.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pathspec" }, { name = "pyyaml" }, { name = "setuptools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c8/82/4cd3ec8f98d821e7cc7ef504add450623d5c86b656faf65e9b0cc46f4be6/yamllint-1.28.0.tar.gz", hash = "sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b", size = 121934 } wheels = [ { url = "https://files.pythonhosted.org/packages/40/f9/882281af7c40a99bfa5b14585071c5aa13f48961582ebe067ae38221d0d9/yamllint-1.28.0-py2.py3-none-any.whl", hash = "sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2", size = 62369 }, ] [[package]] name = "yarl" version = "1.18.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } wheels = [ { url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 }, { url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 }, { url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 }, { url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 }, { url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 }, { url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 }, { url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 }, { url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 }, { url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 }, { url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 }, { url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 }, { url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 }, { url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 }, { url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 }, { url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 }, { url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 }, { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 }, { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 }, { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 }, { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587 }, { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386 }, { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421 }, { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384 }, { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689 }, { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453 }, { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872 }, { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497 }, { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981 }, { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229 }, { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383 }, { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152 }, { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723 }, { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, ]