pax_global_header 0000666 0000000 0000000 00000000064 14750476601 0014524 g ustar 00root root 0000000 0000000 52 comment=5ab1283e968b9b74091394b589b0671b8e7af892
aionotion-2025.02.0/ 0000775 0000000 0000000 00000000000 14750476601 0014033 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/.codeclimate.yml 0000664 0000000 0000000 00000000340 14750476601 0017102 0 ustar 00root root 0000000 0000000 ---
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/ 0000775 0000000 0000000 00000000000 14750476601 0015373 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14750476601 0017556 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000000764 14750476601 0022257 0 ustar 00root root 0000000 0000000 ---
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.md 0000664 0000000 0000000 00000000646 14750476601 0023311 0 ustar 00root root 0000000 0000000 ---
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/ 0000775 0000000 0000000 00000000000 14750476601 0017033 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/.github/actions/install-uv/ 0000775 0000000 0000000 00000000000 14750476601 0021131 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/.github/actions/install-uv/action.yml 0000664 0000000 0000000 00000001020 14750476601 0023122 0 ustar 00root root 0000000 0000000 name: "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/ 0000775 0000000 0000000 00000000000 14750476601 0016640 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/.github/config/labeler.yml 0000664 0000000 0000000 00000000043 14750476601 0020766 0 ustar 00root root 0000000 0000000 "release":
- base-branch: "main"
aionotion-2025.02.0/.github/config/labels.yml 0000664 0000000 0000000 00000003037 14750476601 0020630 0 ustar 00root root 0000000 0000000 ---
- 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.md 0000664 0000000 0000000 00000001445 14750476601 0022340 0 ustar 00root root 0000000 0000000 ## 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.yml 0000664 0000000 0000000 00000001317 14750476601 0021165 0 ustar 00root root 0000000 0000000 ---
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/ 0000775 0000000 0000000 00000000000 14750476601 0017430 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/.github/workflows/codeql.yml 0000664 0000000 0000000 00000001002 14750476601 0021413 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000000625 14750476601 0021106 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000001050 14750476601 0021615 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000000533 14750476601 0023221 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000011130 14750476601 0023533 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000002346 14750476601 0021270 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000001633 14750476601 0023266 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000001037 14750476601 0022370 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000003551 14750476601 0021136 0 ustar 00root root 0000000 0000000 ---
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/.gitignore 0000664 0000000 0000000 00000000145 14750476601 0016023 0 ustar 00root root 0000000 0000000 *.egg-info
.DS_Store
.coverage
.mypy_cache
.nox
.tox
.venv
__pycache__
coverage.xml
docs/_build
tags
aionotion-2025.02.0/.mise.toml 0000664 0000000 0000000 00000000172 14750476601 0015743 0 ustar 00root root 0000000 0000000 [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.yaml 0000664 0000000 0000000 00000010217 14750476601 0020315 0 ustar 00root root 0000000 0000000 ---
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/LICENSE 0000664 0000000 0000000 00000002060 14750476601 0015036 0 ustar 00root root 0000000 0000000 MIT 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.md 0000664 0000000 0000000 00000016247 14750476601 0015324 0 ustar 00root root 0000000 0000000 # ๐ 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]
`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/ 0000775 0000000 0000000 00000000000 14750476601 0016032 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/aionotion/__init__.py 0000664 0000000 0000000 00000000315 14750476601 0020142 0 ustar 00root root 0000000 0000000 """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/ 0000775 0000000 0000000 00000000000 14750476601 0017266 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/aionotion/bridge/__init__.py 0000664 0000000 0000000 00000002553 14750476601 0021404 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000002515 14750476601 0021126 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000032711 14750476601 0017666 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000000131 14750476601 0017525 0 ustar 00root root 0000000 0000000 """Define package constants."""
import logging
LOGGER = logging.getLogger(__package__)
aionotion-2025.02.0/aionotion/errors.py 0000664 0000000 0000000 00000000434 14750476601 0017721 0 ustar 00root root 0000000 0000000 """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/ 0000775 0000000 0000000 00000000000 14750476601 0017657 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/aionotion/listener/__init__.py 0000664 0000000 0000000 00000002620 14750476601 0021770 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000006337 14750476601 0021525 0 ustar 00root root 0000000 0000000 """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.typed 0000664 0000000 0000000 00000000000 14750476601 0017517 0 ustar 00root root 0000000 0000000 aionotion-2025.02.0/aionotion/sensor/ 0000775 0000000 0000000 00000000000 14750476601 0017343 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/aionotion/sensor/__init__.py 0000664 0000000 0000000 00000002523 14750476601 0021456 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000004325 14750476601 0021204 0 ustar 00root root 0000000 0000000 """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/ 0000775 0000000 0000000 00000000000 14750476601 0017356 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/aionotion/system/__init__.py 0000664 0000000 0000000 00000002514 14750476601 0021471 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000002460 14750476601 0021215 0 ustar 00root root 0000000 0000000 """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/ 0000775 0000000 0000000 00000000000 14750476601 0017010 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/aionotion/user/__init__.py 0000664 0000000 0000000 00000002756 14750476601 0021133 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000005172 14750476601 0020652 0 ustar 00root root 0000000 0000000 """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/ 0000775 0000000 0000000 00000000000 14750476601 0017007 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/aionotion/util/__init__.py 0000664 0000000 0000000 00000000030 14750476601 0021111 0 ustar 00root root 0000000 0000000 """Define utilities."""
aionotion-2025.02.0/aionotion/util/auth.py 0000664 0000000 0000000 00000000675 14750476601 0020332 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000001026 14750476601 0017767 0 ustar 00root root 0000000 0000000 """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/ 0000775 0000000 0000000 00000000000 14750476601 0015651 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/examples/__init__.py 0000664 0000000 0000000 00000000027 14750476601 0017761 0 ustar 00root root 0000000 0000000 """Define examples."""
aionotion-2025.02.0/examples/test_api.py 0000664 0000000 0000000 00000004531 14750476601 0020036 0 ustar 00root root 0000000 0000000 """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.toml 0000664 0000000 0000000 00000030002 14750476601 0016742 0 ustar 00root root 0000000 0000000 [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.json 0000664 0000000 0000000 00000000673 14750476601 0016557 0 ustar 00root root 0000000 0000000 {
"$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/ 0000775 0000000 0000000 00000000000 14750476601 0015337 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/script/release 0000775 0000000 0000000 00000002340 14750476601 0016704 0 ustar 00root root 0000000 0000000 #!/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/setup 0000775 0000000 0000000 00000004544 14750476601 0016434 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 14750476601 0015175 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/tests/__init__.py 0000664 0000000 0000000 00000000034 14750476601 0017303 0 ustar 00root root 0000000 0000000 """Define package tests."""
aionotion-2025.02.0/tests/common.py 0000664 0000000 0000000 00000002145 14750476601 0017041 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000020711 14750476601 0017375 0 ustar 00root root 0000000 0000000 """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/ 0000775 0000000 0000000 00000000000 14750476601 0017046 5 ustar 00root root 0000000 0000000 aionotion-2025.02.0/tests/fixtures/auth_credentials_success_response.json 0000664 0000000 0000000 00000000630 14750476601 0026724 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000000175 14750476601 0024332 0 ustar 00root root 0000000 0000000 {
"errors": [
{
"title": "Invalid email or password.",
"message": "Invalid email or password."
}
]
}
aionotion-2025.02.0/tests/fixtures/auth_legacy_credentials_success_response.json 0000664 0000000 0000000 00000000763 14750476601 0030257 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000000103 14750476601 0027260 0 ustar 00root root 0000000 0000000 {
"auth": {
"jwt": "TBD",
"refresh_token": "12345"
}
}
aionotion-2025.02.0/tests/fixtures/bad_api_response.json 0000664 0000000 0000000 00000000241 14750476601 0023233 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000001146 14750476601 0023745 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000001056 14750476601 0023754 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000013604 14750476601 0026103 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000001752 14750476601 0024025 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000001642 14750476601 0024032 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000004201 14750476601 0025255 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000000370 14750476601 0024533 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000001341 14750476601 0024032 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000001251 14750476601 0024041 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000000550 14750476601 0025256 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000002501 14750476601 0024422 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000000531 14750476601 0023647 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000000351 14750476601 0025215 0 ustar 00root root 0000000 0000000 {
"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.py 0000664 0000000 0000000 00000010743 14750476601 0020047 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000032514 14750476601 0020071 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000013750 14750476601 0020441 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000013543 14750476601 0020125 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000012010 14750476601 0020124 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000006406 14750476601 0017572 0 ustar 00root root 0000000 0000000 """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.lock 0000664 0000000 0000000 00000461316 14750476601 0015352 0 ustar 00root root 0000000 0000000 version = 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 },
]