pax_global_header 0000666 0000000 0000000 00000000064 15204752553 0014522 g ustar 00root root 0000000 0000000 52 comment=9f56a143a2e4dbe14bcc224b2b488906c76d9eb2
Bluetooth-Devices-led-ble-4c385cb/ 0000775 0000000 0000000 00000000000 15204752553 0016766 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/.all-contributorsrc 0000664 0000000 0000000 00000000446 15204752553 0022623 0 ustar 00root root 0000000 0000000 {
"projectName": "led-ble",
"projectOwner": "bluetooth-devices",
"repoType": "github",
"repoHost": "https://github.com",
"files": ["README.md"],
"imageSize": 80,
"commit": true,
"commitConvention": "angular",
"contributors": [],
"contributorsPerLine": 7,
"skipCi": true
}
Bluetooth-Devices-led-ble-4c385cb/.editorconfig 0000664 0000000 0000000 00000000444 15204752553 0021445 0 ustar 00root root 0000000 0000000 # http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
end_of_line = lf
[*.bat]
indent_style = tab
end_of_line = crlf
[LICENSE]
insert_final_newline = false
[Makefile]
indent_style = tab
Bluetooth-Devices-led-ble-4c385cb/.flake8 0000664 0000000 0000000 00000000056 15204752553 0020142 0 ustar 00root root 0000000 0000000 [flake8]
exclude = docs
max-line-length = 120
Bluetooth-Devices-led-ble-4c385cb/.github/ 0000775 0000000 0000000 00000000000 15204752553 0020326 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15204752553 0022511 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/.github/ISSUE_TEMPLATE/1-bug_report.md 0000664 0000000 0000000 00000000422 15204752553 0025337 0 ustar 00root root 0000000 0000000 ---
name: Bug report
about: Create a report to help us improve
labels: bug
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
**Additional context**
Add any other context about the problem here.
Bluetooth-Devices-led-ble-4c385cb/.github/ISSUE_TEMPLATE/2-feature-request.md 0000664 0000000 0000000 00000000672 15204752553 0026320 0 ustar 00root root 0000000 0000000 ---
name: Feature request
about: Suggest an idea for this project
labels: enhancement
---
**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.
Bluetooth-Devices-led-ble-4c385cb/.github/dependabot.yml 0000664 0000000 0000000 00000001351 15204752553 0023156 0 ustar 00root root 0000000 0000000 # To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
commit-message:
prefix: "chore(deps-ci): "
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
Bluetooth-Devices-led-ble-4c385cb/.github/labels.toml 0000664 0000000 0000000 00000003515 15204752553 0022471 0 ustar 00root root 0000000 0000000 [breaking]
color = "ffcc00"
name = "breaking"
description = "Breaking change."
[bug]
color = "d73a4a"
name = "bug"
description = "Something isn't working"
[dependencies]
color = "0366d6"
name = "dependencies"
description = "Pull requests that update a dependency file"
[github_actions]
color = "000000"
name = "github_actions"
description = "Update of github actions"
[documentation]
color = "1bc4a5"
name = "documentation"
description = "Improvements or additions to documentation"
[duplicate]
color = "cfd3d7"
name = "duplicate"
description = "This issue or pull request already exists"
[enhancement]
color = "a2eeef"
name = "enhancement"
description = "New feature or request"
["good first issue"]
color = "7057ff"
name = "good first issue"
description = "Good for newcomers"
["help wanted"]
color = "008672"
name = "help wanted"
description = "Extra attention is needed"
[invalid]
color = "e4e669"
name = "invalid"
description = "This doesn't seem right"
[nochangelog]
color = "555555"
name = "nochangelog"
description = "Exclude pull requests from changelog"
[question]
color = "d876e3"
name = "question"
description = "Further information is requested"
[removed]
color = "e99695"
name = "removed"
description = "Removed piece of functionalities."
[tests]
color = "bfd4f2"
name = "tests"
description = "CI, CD and testing related changes"
[wontfix]
color = "ffffff"
name = "wontfix"
description = "This will not be worked on"
[discussion]
color = "c2e0c6"
name = "discussion"
description = "Some discussion around the project"
[hacktoberfest]
color = "ffa663"
name = "hacktoberfest"
description = "Good issues for Hacktoberfest"
[answered]
color = "0ee2b6"
name = "answered"
description = "Automatically closes as answered after a delay"
[waiting]
color = "5f7972"
name = "waiting"
description = "Automatically closes if no answer after a delay"
Bluetooth-Devices-led-ble-4c385cb/.github/workflows/ 0000775 0000000 0000000 00000000000 15204752553 0022363 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/.github/workflows/ci.yml 0000664 0000000 0000000 00000004435 15204752553 0023507 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches:
- main
pull_request:
concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- uses: pre-commit/action@v3.0.1
# Make sure commit messages follow the conventional commits convention:
# https://www.conventionalcommits.org
commitlint:
name: Lint Commit Messages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v6.2.1
test:
strategy:
fail-fast: false
matrix:
python-version:
- "3.11"
- "3.12"
- "3.13"
os:
- ubuntu-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- uses: snok/install-poetry@v1
- name: Install Dependencies
run: poetry install
- name: Test with Pytest
run: poetry run pytest --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
release:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: write
if: github.ref == 'refs/heads/main'
needs:
- test
- lint
- commitlint
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.head_ref || github.ref_name }}
- name: Python Semantic Release
id: release
uses: python-semantic-release/python-semantic-release@v10.5.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
if: steps.release.outputs.released == 'true'
- name: Publish package to GitHub Release
uses: python-semantic-release/publish-action@v10.5.3
if: steps.release.outputs.released == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
Bluetooth-Devices-led-ble-4c385cb/.github/workflows/hacktoberfest.yml 0000664 0000000 0000000 00000000534 15204752553 0025734 0 ustar 00root root 0000000 0000000 name: Hacktoberfest
on:
schedule:
# Run every day in October
- cron: "0 0 * 10 *"
# Run on the 1st of November to revert
- cron: "0 13 1 11 *"
jobs:
hacktoberfest:
runs-on: ubuntu-latest
steps:
- uses: browniebroke/hacktoberfest-labeler-action@v2.6.0
with:
github_token: ${{ secrets.GH_PAT }}
Bluetooth-Devices-led-ble-4c385cb/.github/workflows/issue-manager.yml 0000664 0000000 0000000 00000001340 15204752553 0025644 0 ustar 00root root 0000000 0000000 name: Issue Manager
on:
schedule:
- cron: "0 0 * * *"
issue_comment:
types:
- created
issues:
types:
- labeled
pull_request_target:
types:
- labeled
workflow_dispatch:
jobs:
issue-manager:
runs-on: ubuntu-latest
steps:
- uses: tiangolo/issue-manager@0.6.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
config: >
{
"answered": {
"message": "Assuming the original issue was solved, it will be automatically closed now."
},
"waiting": {
"message": "Automatically closing. To re-open, please provide the additional information requested."
}
}
Bluetooth-Devices-led-ble-4c385cb/.github/workflows/labels.yml 0000664 0000000 0000000 00000000774 15204752553 0024360 0 ustar 00root root 0000000 0000000 name: Sync Github labels
on:
push:
branches:
- main
paths:
- ".github/**"
jobs:
labels:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.8
- name: Install labels
run: pip install labels
- name: Sync config with Github
run: labels -u ${{ github.repository_owner }} -t ${{ secrets.GITHUB_TOKEN }} sync -f .github/labels.toml
Bluetooth-Devices-led-ble-4c385cb/.gitignore 0000664 0000000 0000000 00000004066 15204752553 0020764 0 ustar 00root root 0000000 0000000 # Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
Bluetooth-Devices-led-ble-4c385cb/.gitpod.yml 0000664 0000000 0000000 00000000306 15204752553 0021054 0 ustar 00root root 0000000 0000000 tasks:
- command: |
pip install poetry
PIP_USER=false poetry install
- command: |
pip install pre-commit
pre-commit install
PIP_USER=false pre-commit install-hooks
Bluetooth-Devices-led-ble-4c385cb/.idea/ 0000775 0000000 0000000 00000000000 15204752553 0017746 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/.idea/led-ble.iml 0000664 0000000 0000000 00000000515 15204752553 0021756 0 ustar 00root root 0000000 0000000
Bluetooth-Devices-led-ble-4c385cb/.idea/watcherTasks.xml 0000664 0000000 0000000 00000005253 15204752553 0023140 0 ustar 00root root 0000000 0000000
Bluetooth-Devices-led-ble-4c385cb/.idea/workspace.xml 0000664 0000000 0000000 00000002723 15204752553 0022472 0 ustar 00root root 0000000 0000000
Bluetooth-Devices-led-ble-4c385cb/.pre-commit-config.yaml 0000664 0000000 0000000 00000003715 15204752553 0023255 0 ustar 00root root 0000000 0000000 # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
exclude: "CHANGELOG.md"
default_stages: [pre-commit]
ci:
autofix_commit_msg: "chore(pre-commit.ci): auto fixes"
autoupdate_commit_msg: "chore(pre-commit.ci): pre-commit autoupdate"
repos:
- repo: https://github.com/commitizen-tools/commitizen
rev: v4.16.2
hooks:
- id: commitizen
stages: [commit-msg]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: debug-statements
- id: check-builtin-literals
- id: check-case-conflict
- id: check-docstring-first
- id: check-json
- id: check-toml
- id: check-xml
- id: check-yaml
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- id: debug-statements
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
args: ["--tab-width", "2"]
- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
hooks:
- id: pyupgrade
args: [--py310-plus]
- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
hooks:
- id: codespell
- repo: https://github.com/PyCQA/flake8
rev: 7.3.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v2.1.0
hooks:
- id: mypy
additional_dependencies: []
- repo: https://github.com/PyCQA/bandit
rev: 1.9.4
hooks:
- id: bandit
args: [-x, tests]
- repo: https://github.com/python-poetry/poetry
rev: 2.4.1
hooks:
- id: poetry-check
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
args: ["--tab-width", "2"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.13
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
Bluetooth-Devices-led-ble-4c385cb/.readthedocs.yml 0000664 0000000 0000000 00000001005 15204752553 0022050 0 ustar 00root root 0000000 0000000 # Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.12"
# Optionally declare the Python requirements required to build your docs
python:
install:
- method: pip
path: .
extra_requirements:
- docs
Bluetooth-Devices-led-ble-4c385cb/CHANGELOG.md 0000664 0000000 0000000 00000047562 15204752553 0020615 0 ustar 00root root 0000000 0000000 # CHANGELOG
## v1.1.7 (2025-04-07)
### Chore
* chore(pre-commit.ci): pre-commit autoupdate (#94)
updates:
- [github.com/commitizen-tools/commitizen: v4.4.1 → v4.5.0](https://github.com/commitizen-tools/commitizen/compare/v4.4.1...v4.5.0)
- [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.4)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`646106f`](https://github.com/Bluetooth-Devices/led-ble/commit/646106ff0b00b0a9d1c85a6466d92f1c8c7b9a15))
* chore(pre-commit.ci): pre-commit autoupdate (#91)
updates:
- [github.com/PyCQA/flake8: 7.1.2 → 7.2.0](https://github.com/PyCQA/flake8/compare/7.1.2...7.2.0)
- [github.com/python-poetry/poetry: 2.1.1 → 2.1.2](https://github.com/python-poetry/poetry/compare/2.1.1...2.1.2)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`d2748c8`](https://github.com/Bluetooth-Devices/led-ble/commit/d2748c855d0494b742e44a7aca5674a7af7e546b))
* chore(pre-commit.ci): pre-commit autoupdate (#90)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`7ef403b`](https://github.com/Bluetooth-Devices/led-ble/commit/7ef403b11bd798a6a2f4b1f031384a44bd02aa2b))
* chore(pre-commit.ci): pre-commit autoupdate (#89) ([`5062d78`](https://github.com/Bluetooth-Devices/led-ble/commit/5062d788504b3af1b06188ddd1d49b5b9918dbfa))
* chore(pre-commit.ci): pre-commit autoupdate (#88) ([`584a323`](https://github.com/Bluetooth-Devices/led-ble/commit/584a323d4f898a2283c051b1ad1c49d73d6bd580))
* chore(pre-commit.ci): pre-commit autoupdate (#87)
updates:
- [github.com/commitizen-tools/commitizen: v4.2.2 → v4.4.1](https://github.com/commitizen-tools/commitizen/compare/v4.2.2...v4.4.1)
- [github.com/astral-sh/ruff-pre-commit: v0.9.7 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.7...v0.9.9)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`e884add`](https://github.com/Bluetooth-Devices/led-ble/commit/e884add38caf84a545338e401baee596729cbbd5))
* chore(deps-ci): bump python-semantic-release/python-semantic-release from 9.17.0 to 9.21.0 in the github-actions group (#86) ([`3a40a13`](https://github.com/Bluetooth-Devices/led-ble/commit/3a40a13c433a6b2cc8763514ea97729e308591f4))
* chore(pre-commit.ci): pre-commit autoupdate (#85)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`7f5c813`](https://github.com/Bluetooth-Devices/led-ble/commit/7f5c813a55f3a91e3c150c4af3eb980139d3b724))
* chore(pre-commit.ci): pre-commit autoupdate (#84)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`fd81c8a`](https://github.com/Bluetooth-Devices/led-ble/commit/fd81c8a0e49ddeb8ce9489ee0d15e495593c196b))
### Fix
* fix: force release ([`509ddf8`](https://github.com/Bluetooth-Devices/led-ble/commit/509ddf84a399843c8fee2a682ebd236ca8313f73))
* fix: Add local_name is not None check (#93)
fix: add local_name is not None check
AdvertisementData.local_name is an Optional[str] field, so make sure to check presence before computing on the value.
See #92. ([`b21fe26`](https://github.com/Bluetooth-Devices/led-ble/commit/b21fe26beaaff9ceadda8353171d9d1ac2cf3581))
## v1.1.6 (2025-02-04)
### Fix
* fix: detect dream models based on name as well (#82) ([`f31ffed`](https://github.com/Bluetooth-Devices/led-ble/commit/f31ffed54f8a7a1bd6b639f173fca14bbce3b9fc))
## v1.1.5 (2025-02-04)
### Chore
* chore(pre-commit.ci): pre-commit autoupdate (#80)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`965493d`](https://github.com/Bluetooth-Devices/led-ble/commit/965493def28f295212646dd802006e1fb4883320))
* chore(deps): bump flux-led from 1.1.0 to 1.1.3 (#79) ([`2b58c7f`](https://github.com/Bluetooth-Devices/led-ble/commit/2b58c7f073db98e8ffa6ae6fecf2a3d9d3c948b2))
* chore(deps-ci): bump python-semantic-release/python-semantic-release from 9.16.1 to 9.17.0 in the github-actions group (#78) ([`5925b55`](https://github.com/Bluetooth-Devices/led-ble/commit/5925b5538b0a853f1304a74b1f3bf1f0ca96b8d1))
* chore(deps): bump sphinx from 6.2.1 to 7.4.7 (#76)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a4af2f4`](https://github.com/Bluetooth-Devices/led-ble/commit/a4af2f48aade1c4ef17e5aee4d660b131ef70edf))
* chore(pre-commit.ci): pre-commit autoupdate (#77)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`fc7a15b`](https://github.com/Bluetooth-Devices/led-ble/commit/fc7a15b48a765dc0f4f8b8a1bae866bd14377e76))
### Fix
* fix: update poetry to v2 + add license to metadata (#81) ([`ce71af7`](https://github.com/Bluetooth-Devices/led-ble/commit/ce71af7ebbd327e288f94e74c6deec9cd2a860b7))
## v1.1.4 (2025-01-23)
### Chore
* chore: update minimum bleak version (#74) ([`4757690`](https://github.com/Bluetooth-Devices/led-ble/commit/47576906fdd0865f750957451db5351b2affeff1))
### Fix
* fix: release upload privs (#75) ([`85f5e4a`](https://github.com/Bluetooth-Devices/led-ble/commit/85f5e4af771576e4d0febe36de78ab0eca6892e6))
## v1.1.3 (2025-01-23)
### Fix
* fix: release workflow (#73) ([`2cfae55`](https://github.com/Bluetooth-Devices/led-ble/commit/2cfae55f6b6938e27b22cf45919dea354a7600ab))
## v1.1.2 (2025-01-23)
### Chore
* chore(deps): bump sphinx-rtd-theme from 2.0.0 to 3.0.2 (#72)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a3cadfe`](https://github.com/Bluetooth-Devices/led-ble/commit/a3cadfefa384d64cf49a9b2d58a87f6b4735ff8f))
* chore(deps): bump myst-parser from 1.0.0 to 3.0.1 (#71)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`037d4aa`](https://github.com/Bluetooth-Devices/led-ble/commit/037d4aa46d81714539e5a5a511eb7dab7ea0126f))
* chore(deps-ci): bump the github-actions group with 8 updates (#69)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org> ([`699b4c5`](https://github.com/Bluetooth-Devices/led-ble/commit/699b4c57d4b00e607402be120070ba187752061f))
* chore: update dependabot.yml to include GHA ([`dc6eb0a`](https://github.com/Bluetooth-Devices/led-ble/commit/dc6eb0ab1f98c4c7696ec730a5a7dbd3a1b9d8f7))
* chore(pre-commit.ci): pre-commit autoupdate (#68)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`9977a83`](https://github.com/Bluetooth-Devices/led-ble/commit/9977a831fe9bec422ea4ad6ce66a67e38c0f79d6))
* chore(deps): bump sphinx from 5.3.0 to 6.2.1 (#67) ([`1edffe5`](https://github.com/Bluetooth-Devices/led-ble/commit/1edffe5971473547b0c105ae6cef0bca9a00b5fa))
* chore(deps): bump sphinx-rtd-theme from 1.3.0 to 2.0.0 (#65)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`e7f6571`](https://github.com/Bluetooth-Devices/led-ble/commit/e7f65711eda17ab5097d9734ddadac2233c702bb))
* chore(deps): bump myst-parser from 0.18.1 to 1.0.0 (#64)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`e847cb3`](https://github.com/Bluetooth-Devices/led-ble/commit/e847cb3f81c644ece92e394108cc52934a0e24ff))
* chore(deps): bump jinja2 from 3.1.4 to 3.1.5 (#66)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`dc3d478`](https://github.com/Bluetooth-Devices/led-ble/commit/dc3d478e23819e75433f68a04b8ebd4679441f45))
* chore(deps-dev): bump pytest from 7.4.4 to 8.3.4 (#63)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`befc9a7`](https://github.com/Bluetooth-Devices/led-ble/commit/befc9a717b9078e2d10bb8e4f54aac297e21322b))
* chore(deps-dev): bump pytest-cov from 3.0.0 to 6.0.0 (#62) ([`bad4581`](https://github.com/Bluetooth-Devices/led-ble/commit/bad458123d6bf30db344e175db23bb1e293b007d))
* chore(deps): bump flux-led from 1.0.4 to 1.1.0 (#61) ([`f0fc89d`](https://github.com/Bluetooth-Devices/led-ble/commit/f0fc89d09c12d9623f12eff0ba109f65773c7f5f))
* chore: create dependabot.yml ([`f5dae1b`](https://github.com/Bluetooth-Devices/led-ble/commit/f5dae1b83bf51c153e8245840e91617d7b36492c))
* chore(pre-commit.ci): pre-commit autoupdate (#60) ([`9c5e2ea`](https://github.com/Bluetooth-Devices/led-ble/commit/9c5e2ea9e6aec05b68294f7d7a4751f942f16d3d))
* chore(pre-commit.ci): pre-commit autoupdate (#59)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`71a479f`](https://github.com/Bluetooth-Devices/led-ble/commit/71a479f001e4ab1d432ba2e20f41496c44e3f35e))
* chore(pre-commit.ci): pre-commit autoupdate (#57)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`e002846`](https://github.com/Bluetooth-Devices/led-ble/commit/e002846222525702cb87be6a025d6654afef8a3c))
### Fix
* fix: raise CharacteristicMissingError for unsupported devices (#70) ([`72e74cf`](https://github.com/Bluetooth-Devices/led-ble/commit/72e74cf7add2047ebb2b1d02ffd4c9905ff245b1))
## v1.1.1 (2024-12-11)
### Fix
* fix: refactor to use kwargs for construct_levels_change (#53) ([`efd92ac`](https://github.com/Bluetooth-Devices/led-ble/commit/efd92aca9bcb05ac9d5ab72a19196c7717631552))
## v1.1.0 (2024-12-11)
### Chore
* chore: switch to ruff (#54) ([`ea4d4c6`](https://github.com/Bluetooth-Devices/led-ble/commit/ea4d4c6d4bb9b9cd046670e65b2ff26da3ad2b42))
* chore(pre-commit.ci): pre-commit autoupdate (#51)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`667c585`](https://github.com/Bluetooth-Devices/led-ble/commit/667c585067f2e2d102963478c465e2beccae3b8c))
* chore(pre-commit.ci): pre-commit autoupdate (#50)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`97869f5`](https://github.com/Bluetooth-Devices/led-ble/commit/97869f5420afb739fd5a909465cf1f4bb7811401))
* chore(pre-commit.ci): pre-commit autoupdate (#48)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`7de170f`](https://github.com/Bluetooth-Devices/led-ble/commit/7de170f9ceefb6f525b39ef60bb74c0ac6908300))
* chore(pre-commit.ci): pre-commit autoupdate (#47)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`c44f551`](https://github.com/Bluetooth-Devices/led-ble/commit/c44f55192d36a713a6985dc8a26b4fbffcec9d17))
* chore(pre-commit.ci): pre-commit autoupdate (#46)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`a612011`](https://github.com/Bluetooth-Devices/led-ble/commit/a612011fed7414ac92eaed0d5b36008d05a38251))
* chore(pre-commit.ci): pre-commit autoupdate (#45)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`da1c2a9`](https://github.com/Bluetooth-Devices/led-ble/commit/da1c2a9a9cb50be23b37840943a9d834f911bb25))
* chore(pre-commit.ci): pre-commit autoupdate (#44)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`5ebef13`](https://github.com/Bluetooth-Devices/led-ble/commit/5ebef13d9493b332285728282c11cf7f7bbf5e12))
* chore(pre-commit.ci): pre-commit autoupdate (#43)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`6e0098e`](https://github.com/Bluetooth-Devices/led-ble/commit/6e0098ef4d15e00f05ff2db006e58333ee890303))
* chore(pre-commit.ci): pre-commit autoupdate (#42)
* chore(pre-commit.ci): pre-commit autoupdate
updates:
- [github.com/commitizen-tools/commitizen: v2.29.1 → v3.27.0](https://github.com/commitizen-tools/commitizen/compare/v2.29.1...v3.27.0)
- [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.6.0)
- [github.com/pre-commit/mirrors-prettier: v2.7.1 → v4.0.0-alpha.8](https://github.com/pre-commit/mirrors-prettier/compare/v2.7.1...v4.0.0-alpha.8)
- [github.com/asottile/pyupgrade: v2.37.3 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v2.37.3...v3.16.0)
- [github.com/PyCQA/isort: 5.12.0 → 5.13.2](https://github.com/PyCQA/isort/compare/5.12.0...5.13.2)
- [github.com/psf/black: 22.6.0 → 24.4.2](https://github.com/psf/black/compare/22.6.0...24.4.2)
- [github.com/codespell-project/codespell: v2.1.0 → v2.3.0](https://github.com/codespell-project/codespell/compare/v2.1.0...v2.3.0)
- [github.com/PyCQA/flake8: 4.0.1 → 7.1.0](https://github.com/PyCQA/flake8/compare/4.0.1...7.1.0)
- [github.com/pre-commit/mirrors-mypy: v0.931 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v0.931...v1.10.1)
- [github.com/PyCQA/bandit: 1.7.4 → 1.7.9](https://github.com/PyCQA/bandit/compare/1.7.4...1.7.9)
* chore(pre-commit.ci): auto fixes
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ([`72fd68c`](https://github.com/Bluetooth-Devices/led-ble/commit/72fd68c1dee7c7cb03296265b894d46dc36d567a))
### Feature
* feat: add Python 3.13 support (#55) ([`05f3818`](https://github.com/Bluetooth-Devices/led-ble/commit/05f38188572e898af89f53a0bf1cc4f146186876))
### Unknown
* Remove FUNDING.yml (#49) ([`3777465`](https://github.com/Bluetooth-Devices/led-ble/commit/377746593002cc7db124edfb1f8af0673ee72c60))
## v1.0.2 (2024-06-24)
### Fix
* fix: fix license classifier (#41) ([`b331b97`](https://github.com/Bluetooth-Devices/led-ble/commit/b331b9744caeb5fded22d8435bb8c5db7be8a362))
## v1.0.1 (2023-09-25)
### Chore
* chore: add py3.11 to CI (#39) ([`75722e0`](https://github.com/Bluetooth-Devices/led-ble/commit/75722e0c31ece40538ec0b96d521847f8b7ccddd))
### Fix
* fix: bump psr to fix CI (#38) ([`ab09fed`](https://github.com/Bluetooth-Devices/led-ble/commit/ab09fedd632f937cb4064254c8e61c349f8c8d6d))
* fix: drop async_timeout on py3.11+ (#37) ([`fba769f`](https://github.com/Bluetooth-Devices/led-ble/commit/fba769f33cf7ac1f89105e29615e3d15707ecdcf))
* fix: do not try to stop notify if read char is missing (#36) ([`03c84f9`](https://github.com/Bluetooth-Devices/led-ble/commit/03c84f99deba04c3d04236f608e24ad137540b8c))
## v1.0.0 (2022-10-16)
### Breaking
* feat: update for new bleak version (#33)
BREAKING CHANGE: The set_ble_device function has been renamed set_ble_device_and_advertisement_data and now requires the advertisement_data.
BREAKING CHANGE: The constructor no longer takes a retry count since this does not need to be configurable ([`2be176c`](https://github.com/Bluetooth-Devices/led-ble/commit/2be176cfc5492f35bc3fc019e385a3698ea572bb))
## v0.10.1 (2022-09-15)
### Fix
* fix: handle additional bleak exceptions (#31) ([`1ff94f7`](https://github.com/Bluetooth-Devices/led-ble/commit/1ff94f770e86d630892261178018d861d4e74a72))
## v0.10.0 (2022-09-13)
### Feature
* feat: update for bleak 0.17 support (#29) ([`530de76`](https://github.com/Bluetooth-Devices/led-ble/commit/530de767892a51bb93a81830f418b168b3f13fd8))
## v0.9.1 (2022-09-11)
### Fix
* fix: typo in bleak-retry-connector min version pin (#28) ([`8638ab8`](https://github.com/Bluetooth-Devices/led-ble/commit/8638ab86a73fae3a8407b4b6b3f9fe3c4193bfb0))
## v0.9.0 (2022-09-11)
### Feature
* feat: implement smart backoff via bleak-retry-connector (#27) ([`a7bb1b1`](https://github.com/Bluetooth-Devices/led-ble/commit/a7bb1b1707c103010398091d5291d8827b730d7e))
## v0.8.5 (2022-09-11)
### Fix
* fix: bump bleak-retry-connector (#26) ([`ac3823e`](https://github.com/Bluetooth-Devices/led-ble/commit/ac3823e546e7263e345b1deae8a7f0b94487a89e))
## v0.8.4 (2022-09-11)
### Fix
* fix: bump bleak-retry-connector (#25) ([`0ad8e7b`](https://github.com/Bluetooth-Devices/led-ble/commit/0ad8e7bc240bcd9abfffb7efccef93186072c25c))
## v0.8.3 (2022-09-10)
### Fix
* fix: address property (#24) ([`b85439f`](https://github.com/Bluetooth-Devices/led-ble/commit/b85439febb7fbcfb9fa7e41a7a6f6991bd25dff4))
## v0.8.2 (2022-09-10)
### Fix
* fix: bump bleak retry connector (#23) ([`1fd8778`](https://github.com/Bluetooth-Devices/led-ble/commit/1fd8778e738b09122a15ec486bc83c7313545692))
## v0.8.1 (2022-09-10)
### Fix
* fix: bump bleak-retry-connector min version (#22) ([`2112b18`](https://github.com/Bluetooth-Devices/led-ble/commit/2112b18c4a7afbb5ea04a6d8c5ddb2f8232816da))
## v0.8.0 (2022-09-10)
### Feature
* feat: export get_device from bleak-retry-connector (#21) ([`5f41511`](https://github.com/Bluetooth-Devices/led-ble/commit/5f41511cd1684eb9277fc63896da63d50127b168))
## v0.7.1 (2022-09-06)
### Fix
* fix: effects on dream models (#20) ([`b8126c1`](https://github.com/Bluetooth-Devices/led-ble/commit/b8126c1f5fe098efcc8c3d3a43a42ed5cc9136d8))
## v0.7.0 (2022-09-05)
### Feature
* feat: add newly discovered model 0x15 (#17) ([`3c5f15c`](https://github.com/Bluetooth-Devices/led-ble/commit/3c5f15c80520b76fe6fa9e0933f64c3419cd3b07))
## v0.6.0 (2022-09-04)
### Feature
* feat: add support for more protocols (#16) ([`c7bbb15`](https://github.com/Bluetooth-Devices/led-ble/commit/c7bbb15ec2dd291f5918850b3bdddec8cf1abae6))
## v0.5.4 (2022-08-29)
### Fix
* fix: w channel not being cleared on rgb set (#15) ([`048bdff`](https://github.com/Bluetooth-Devices/led-ble/commit/048bdffd52ea78ba66a1d33793db58a725bc894b))
## v0.5.3 (2022-08-29)
### Fix
* fix: brightness (#14) ([`01dcf7b`](https://github.com/Bluetooth-Devices/led-ble/commit/01dcf7bd5f92a0c487924211490ba0498708100d))
## v0.5.2 (2022-08-29)
### Fix
* fix: missing exports (#13) ([`911c2a0`](https://github.com/Bluetooth-Devices/led-ble/commit/911c2a0dcbdc4041247fe53a060ae4a50a85faa7))
## v0.5.1 (2022-08-29)
### Fix
* fix: cleanups (#12) ([`9d3ae2a`](https://github.com/Bluetooth-Devices/led-ble/commit/9d3ae2a80bfc9d17bc9603003852b010a56a2494))
## v0.5.0 (2022-08-29)
### Feature
* feat: add rgbw support (#11) ([`14ae97b`](https://github.com/Bluetooth-Devices/led-ble/commit/14ae97ba4b51fb7ebb81634028e1eac623e9a3f5))
## v0.4.2 (2022-08-29)
### Fix
* fix: add log (#10) ([`d95fc61`](https://github.com/Bluetooth-Devices/led-ble/commit/d95fc61d0745002709558bc05812e8b5589ada62))
## v0.4.1 (2022-08-29)
### Fix
* fix: add state to log (#9) ([`ee85bdd`](https://github.com/Bluetooth-Devices/led-ble/commit/ee85bddec3b5dac4de1aa38742662ccf97fc0fda))
## v0.4.0 (2022-08-29)
### Feature
* feat: add model data (#8) ([`6df04cf`](https://github.com/Bluetooth-Devices/led-ble/commit/6df04cf9d0dfeaf6836830634df8df1a2bcbeb95))
## v0.3.0 (2022-08-29)
### Feature
* feat: add white channel (#7) ([`3112249`](https://github.com/Bluetooth-Devices/led-ble/commit/31122499beb71f7af68ad5854fb58f112803c654))
## v0.2.2 (2022-08-29)
### Fix
* fix: remove scaling (#6) ([`89ac78e`](https://github.com/Bluetooth-Devices/led-ble/commit/89ac78e5e41e4c5123cb8ef39505ca6bb9c5e24e))
## v0.2.1 (2022-08-29)
### Fix
* fix: fix disconnect (#5) ([`44f79ee`](https://github.com/Bluetooth-Devices/led-ble/commit/44f79eea35fb027299cda5b6c3fa06da9572f258))
## v0.2.0 (2022-08-29)
### Feature
* feat: add example (#4) ([`9d25f2a`](https://github.com/Bluetooth-Devices/led-ble/commit/9d25f2a2fd1043cf4679215ce16c0888f9ed6fa8))
## v0.1.0 (2022-08-29)
### Chore
* chore: initial commit ([`f2aa430`](https://github.com/Bluetooth-Devices/led-ble/commit/f2aa4303394315e7edefdd19a39f9193bf7a658c))
### Feature
* feat: first release (#3) ([`0875dc4`](https://github.com/Bluetooth-Devices/led-ble/commit/0875dc4ca17960cb634b66c3a3c61f9ff2c5f490))
* feat: build out the class (#2) ([`f70c1a3`](https://github.com/Bluetooth-Devices/led-ble/commit/f70c1a3288dfcf749200cab167f1ee67b2ffcd3e))
### Fix
* fix: ci (#1) ([`dba3484`](https://github.com/Bluetooth-Devices/led-ble/commit/dba3484f8aabb76db51365179cfecfb1caeed528))
### Unknown
* 0.0.1 ([`722ca70`](https://github.com/Bluetooth-Devices/led-ble/commit/722ca7065c09bd5ca8919b784af7c456e8268048))
Bluetooth-Devices-led-ble-4c385cb/CONTRIBUTING.md 0000664 0000000 0000000 00000007412 15204752553 0021223 0 ustar 00root root 0000000 0000000 # Contributing
Contributions are welcome, and they are greatly appreciated! Every little helps, and credit will always be given.
You can contribute in many ways:
## Types of Contributions
### Report Bugs
Report bugs to [our issue page][gh-issues]. If you are reporting a bug, please include:
- Your operating system name and version.
- Any details about your local setup that might be helpful in troubleshooting.
- Detailed steps to reproduce the bug.
### Fix Bugs
Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it.
### Implement Features
Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it.
### Write Documentation
LED BLE could always use more documentation, whether as part of the official LED BLE docs, in docstrings, or even on the web in blog posts, articles, and such.
### Submit Feedback
The best way to send feedback [our issue page][gh-issues] on GitHub. If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Remember that this is a volunteer-driven project, and that contributions are welcome 😊
## Get Started!
Ready to contribute? Here's how to set yourself up for local development.
1. Fork the repo on GitHub.
2. Clone your fork locally:
```shell
$ git clone git@github.com:your_name_here/led-ble.git
```
3. Install the project dependencies with [Poetry](https://python-poetry.org):
```shell
$ poetry install
```
4. Create a branch for local development:
```shell
$ git checkout -b name-of-your-bugfix-or-feature
```
Now you can make your changes locally.
5. When you're done making changes, check that your changes pass our tests:
```shell
$ poetry run pytest
```
6. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off:
```shell
$ pre-commit run -a
```
Or better, install the hooks once and have them run automatically each time you commit:
```shell
$ pre-commit install
```
7. Commit your changes and push your branch to GitHub:
```shell
$ git add .
$ git commit -m "feat(something): your detailed description of your changes"
$ git push origin name-of-your-bugfix-or-feature
```
Note: the commit message should follow [the conventional commits](https://www.conventionalcommits.org). We run [`commitlint` on CI](https://github.com/marketplace/actions/commit-linter) to validate it, and if you've installed pre-commit hooks at the previous step, the message will be checked at commit time.
8. Submit a pull request through the GitHub website or using the GitHub CLI (if you have it installed):
```shell
$ gh pr create --fill
```
## Pull Request Guidelines
We like to have the pull request open as soon as possible, that's a great place to discuss any piece of work, even unfinished. You can use draft pull request if it's still a work in progress. Here are a few guidelines to follow:
1. Include tests for feature or bug fixes.
2. Update the documentation for significant features.
3. Ensure tests are passing on CI.
## Tips
To run a subset of tests:
```shell
$ pytest tests
```
## Making a new release
The deployment should be automated and can be triggered from the Semantic Release workflow in GitHub. The next version will be based on [the commit logs](https://python-semantic-release.readthedocs.io/en/latest/commit-log-parsing.html#commit-log-parsing). This is done by [python-semantic-release](https://python-semantic-release.readthedocs.io/en/latest/index.html) via a GitHub action.
[gh-issues]: https://github.com/bluetooth-devices/led-ble/issues
Bluetooth-Devices-led-ble-4c385cb/LICENSE 0000664 0000000 0000000 00000026121 15204752553 0017775 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 J. Nick Koston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Bluetooth-Devices-led-ble-4c385cb/README.md 0000664 0000000 0000000 00000006626 15204752553 0020257 0 ustar 00root root 0000000 0000000 # LED BLE
Control a wide range of LED BLE devices
## Installation
Install this via pip (or your favourite package manager):
`pip install led-ble`
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
## Credits
This package was created with
[Cookiecutter](https://github.com/audreyr/cookiecutter) and the
[browniebroke/cookiecutter-pypackage](https://github.com/browniebroke/cookiecutter-pypackage)
project template.
Bluetooth-Devices-led-ble-4c385cb/commitlint.config.mjs 0000664 0000000 0000000 00000000362 15204752553 0023125 0 ustar 00root root 0000000 0000000 export default {
extends: ["@commitlint/config-conventional"],
rules: {
"header-max-length": [0, "always", Infinity],
"body-max-line-length": [0, "always", Infinity],
"footer-max-line-length": [0, "always", Infinity],
},
};
Bluetooth-Devices-led-ble-4c385cb/docs/ 0000775 0000000 0000000 00000000000 15204752553 0017716 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/docs/Makefile 0000664 0000000 0000000 00000001175 15204752553 0021362 0 ustar 00root root 0000000 0000000 # Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
Bluetooth-Devices-led-ble-4c385cb/docs/make.bat 0000664 0000000 0000000 00000001374 15204752553 0021330 0 ustar 00root root 0000000 0000000 @ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
Bluetooth-Devices-led-ble-4c385cb/docs/source/ 0000775 0000000 0000000 00000000000 15204752553 0021216 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/docs/source/_static/ 0000775 0000000 0000000 00000000000 15204752553 0022644 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/docs/source/_static/.gitkeep 0000664 0000000 0000000 00000000000 15204752553 0024263 0 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/docs/source/changelog.md 0000664 0000000 0000000 00000000045 15204752553 0023466 0 ustar 00root root 0000000 0000000 ```{include} ../../CHANGELOG.md
```
Bluetooth-Devices-led-ble-4c385cb/docs/source/conf.py 0000664 0000000 0000000 00000003641 15204752553 0022521 0 ustar 00root root 0000000 0000000 # Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
from typing import Any
# -- Project information -----------------------------------------------------
project = "LED BLE"
copyright = "2020, J. Nick Koston"
author = "J. Nick Koston"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"myst_parser",
]
# The suffix of source filenames.
source_suffix = [".rst", ".md"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns: list[Any] = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
Bluetooth-Devices-led-ble-4c385cb/docs/source/contributing.md 0000664 0000000 0000000 00000000050 15204752553 0024242 0 ustar 00root root 0000000 0000000 ```{include} ../../CONTRIBUTING.md
```
Bluetooth-Devices-led-ble-4c385cb/docs/source/index.md 0000664 0000000 0000000 00000000347 15204752553 0022653 0 ustar 00root root 0000000 0000000 # Welcome to LED BLE documentation!
```{toctree}
:caption: Installation & Usage
:maxdepth: 2
installation
usage
```
```{toctree}
:caption: Project Info
:maxdepth: 2
changelog
contributing
```
```{include} ../../README.md
```
Bluetooth-Devices-led-ble-4c385cb/docs/source/installation.md 0000664 0000000 0000000 00000000262 15204752553 0024241 0 ustar 00root root 0000000 0000000 # Installation
The package is published on [PyPI](https://pypi.org/project/deezer-python/) and can be installed with `pip` (or any equivalent):
```bash
pip install led-ble
```
Bluetooth-Devices-led-ble-4c385cb/docs/source/usage.md 0000664 0000000 0000000 00000000135 15204752553 0022643 0 ustar 00root root 0000000 0000000 # Usage
To use this package, import it:
```python
import led_ble
```
TODO: Document usage
Bluetooth-Devices-led-ble-4c385cb/examples/ 0000775 0000000 0000000 00000000000 15204752553 0020604 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/examples/run.py 0000664 0000000 0000000 00000003077 15204752553 0021771 0 ustar 00root root 0000000 0000000 import asyncio
import logging
from bleak import BleakScanner
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
from led_ble import LEDBLE, LEDBLEState
_LOGGER = logging.getLogger(__name__)
ADDRESS = "D0291B39-3A1B-7FF2-787B-4E743FED5B25"
ADDRESS = "D0291B39-3A1B-7FF2-787B-4E743FED5B25"
async def run() -> None:
scanner = BleakScanner()
future: asyncio.Future[BLEDevice] = asyncio.Future()
def on_detected(device: BLEDevice, adv: AdvertisementData) -> None:
if future.done():
return
_LOGGER.info("Detected: %s", device)
if device.address.lower() == ADDRESS.lower():
_LOGGER.info("Found device: %s", device.address)
future.set_result(device)
scanner.register_detection_callback(on_detected)
await scanner.start()
def on_state_changed(state: LEDBLEState) -> None:
_LOGGER.info("State changed: %s", state)
device = await future
led = LEDBLE(device)
cancel_callback = led.register_callback(on_state_changed)
await led.update()
await led.turn_on()
await led.set_rgb((255, 0, 0), 255)
await asyncio.sleep(1)
await led.set_rgb((0, 255, 0), 128)
await asyncio.sleep(1)
await led.set_rgb((0, 0, 255), 255)
await asyncio.sleep(1)
await led.set_rgbw((255, 255, 255, 128), 255)
await asyncio.sleep(1)
await led.turn_off()
await led.update()
cancel_callback()
await scanner.stop()
logging.basicConfig(level=logging.INFO)
logging.getLogger("led_ble").setLevel(logging.DEBUG)
asyncio.run(run())
Bluetooth-Devices-led-ble-4c385cb/poetry.lock 0000664 0000000 0000000 00000367404 15204752553 0021200 0 ustar 00root root 0000000 0000000 # This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand.
[[package]]
name = "aiooui"
version = "0.1.9"
description = "Async OUI lookups"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
markers = "platform_system == \"Linux\""
files = [
{file = "aiooui-0.1.9-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:64d904b43f14dd1d8d9fcf1684d9e2f558bc5e0bd68dc10023c93355c9027907"},
{file = "aiooui-0.1.9-py3-none-any.whl", hash = "sha256:737a5e62d8726540218c2b70e5f966d9912121e4644f3d490daf8f3c18b182e5"},
{file = "aiooui-0.1.9.tar.gz", hash = "sha256:e8c8bc59ab352419e0747628b4cce7c4e04d492574c1971e223401126389c5d8"},
]
[[package]]
name = "alabaster"
version = "0.7.16"
description = "A light, configurable Sphinx theme"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"},
{file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"},
]
[[package]]
name = "async-timeout"
version = "4.0.3"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]]
name = "babel"
version = "2.16.0"
description = "Internationalization utilities"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
{file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
]
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
[[package]]
name = "bleak"
version = "1.1.1"
description = "Bluetooth Low Energy platform Agnostic Klient"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "bleak-1.1.1-py3-none-any.whl", hash = "sha256:e601371396e357d95ee3c256db65b7da624c94ef6f051d47dfce93ea8361c22e"},
{file = "bleak-1.1.1.tar.gz", hash = "sha256:eeef18053eb3bd569a25bff62cd4eb9ee56be4d84f5321023a7c4920943e6ccb"},
]
[package.dependencies]
async-timeout = {version = ">=3.0.0", markers = "python_version < \"3.11\""}
dbus-fast = {version = ">=1.83.0", markers = "platform_system == \"Linux\""}
pyobjc-core = {version = ">=10.3", markers = "platform_system == \"Darwin\""}
pyobjc-framework-CoreBluetooth = {version = ">=10.3", markers = "platform_system == \"Darwin\""}
pyobjc-framework-libdispatch = {version = ">=10.3", markers = "platform_system == \"Darwin\""}
typing-extensions = {version = ">=4.7.0", markers = "python_version < \"3.12\""}
winrt-runtime = {version = ">=3.1", markers = "platform_system == \"Windows\""}
"winrt-Windows.Devices.Bluetooth" = {version = ">=3.1", markers = "platform_system == \"Windows\""}
"winrt-Windows.Devices.Bluetooth.Advertisement" = {version = ">=3.1", markers = "platform_system == \"Windows\""}
"winrt-Windows.Devices.Bluetooth.GenericAttributeProfile" = {version = ">=3.1", markers = "platform_system == \"Windows\""}
"winrt-Windows.Devices.Enumeration" = {version = ">=3.1", markers = "platform_system == \"Windows\""}
"winrt-Windows.Foundation" = {version = ">=3.1", markers = "platform_system == \"Windows\""}
"winrt-Windows.Foundation.Collections" = {version = ">=3.1", markers = "platform_system == \"Windows\""}
"winrt-Windows.Storage.Streams" = {version = ">=3.1", markers = "platform_system == \"Windows\""}
[package.extras]
pythonista = ["bleak-pythonista (>=0.1.1)"]
[[package]]
name = "bleak-retry-connector"
version = "2.13.1"
description = "A connector for Bleak Clients that handles transient connection failures"
optional = false
python-versions = ">=3.9,<4.0"
groups = ["main"]
files = [
{file = "bleak_retry_connector-2.13.1-py3-none-any.whl", hash = "sha256:9fdab97d7f1cc1b1948412af2cc6f7721e843fb9d2f9b02b7cc26eb52c7ee486"},
{file = "bleak_retry_connector-2.13.1.tar.gz", hash = "sha256:af344bd81d0f7d33a0994e30fe9e28dfdc3cb970095cc1ba547a3b6ae2ee4543"},
]
[package.dependencies]
async-timeout = ">=4.0.1"
bleak = ">=0.19.0"
bluetooth-adapters = {version = ">=0.15.2", markers = "platform_system == \"Linux\""}
dbus-fast = {version = ">=1.14.0", markers = "platform_system == \"Linux\""}
[package.extras]
docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"]
[[package]]
name = "bluetooth-adapters"
version = "0.21.1"
description = "Tools to enumerate and find Bluetooth Adapters"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Linux\""
files = [
{file = "bluetooth_adapters-0.21.1-py3-none-any.whl", hash = "sha256:5637d25fbdff215abda884f93912089e58cd75cc44c4d78902b773fd19e86ea8"},
{file = "bluetooth_adapters-0.21.1.tar.gz", hash = "sha256:6840bb235ce51e7f03779126e59e73266ad36893d7ca7d562e6008fde7a17fba"},
]
[package.dependencies]
aiooui = ">=0.1.1"
async-timeout = {version = ">=3.0.0", markers = "python_version < \"3.11\""}
bleak = ">=0.21.1"
dbus-fast = {version = ">=1.21.0", markers = "platform_system == \"Linux\""}
uart-devices = ">=0.1.0"
usb-devices = ">=0.4.5"
[package.extras]
docs = ["Sphinx (>=5,<8)", "myst-parser (>=0.18,<3.1)", "sphinx-rtd-theme (>=1,<4)"]
[[package]]
name = "certifi"
version = "2024.12.14"
description = "Python package for providing Mozilla's CA Bundle."
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
{file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
]
[[package]]
name = "charset-normalizer"
version = "3.4.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
{file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
{file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
{file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
{file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
{file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
{file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
{file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
{file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
{file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
{file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
{file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
{file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
{file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
{file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
{file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
{file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
{file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
{file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
{file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {main = "extra == \"docs\" and sys_platform == \"win32\"", dev = "sys_platform == \"win32\""}
[[package]]
name = "coverage"
version = "7.10.7"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"},
{file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"},
{file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"},
{file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"},
{file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"},
{file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"},
{file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"},
{file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"},
{file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"},
{file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"},
{file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"},
{file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"},
{file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"},
{file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"},
{file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"},
{file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"},
{file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"},
{file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"},
{file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"},
{file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"},
{file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"},
{file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"},
{file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"},
{file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"},
{file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"},
{file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"},
{file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"},
{file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"},
{file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"},
{file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"},
{file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"},
{file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"},
{file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"},
{file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"},
{file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"},
{file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"},
{file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"},
{file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"},
{file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"},
{file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"},
{file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"},
{file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"},
{file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"},
{file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"},
{file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"},
{file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"},
{file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"},
{file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"},
{file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"},
{file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"},
{file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"},
{file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"},
{file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"},
{file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"},
{file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"},
{file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"},
{file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"},
{file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"},
{file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"},
{file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"},
{file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"},
{file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"},
{file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"},
{file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"},
{file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"},
{file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"},
{file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"},
{file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"},
{file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"},
{file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"},
{file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"},
{file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"},
{file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"},
{file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"},
{file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"},
{file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"},
{file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"},
{file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"},
{file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"},
{file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"},
{file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"},
{file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"},
{file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"},
{file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"},
{file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"},
{file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"},
{file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"},
{file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"},
{file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"},
{file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"},
{file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"},
{file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"},
{file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"},
{file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"},
{file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"},
{file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"},
{file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"},
{file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"},
{file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"},
{file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"},
{file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"},
{file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"},
{file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"},
{file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"},
]
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
[[package]]
name = "dbus-fast"
version = "2.30.2"
description = "A faster version of dbus-next"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
markers = "platform_system == \"Linux\""
files = [
{file = "dbus_fast-2.30.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:353dcb06498ec1dcb1acc3c7a48c30d1232699f629291b18bdbe6f236bfd3882"},
{file = "dbus_fast-2.30.2-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:7fdda7f540ec8f44cef3c95467be4850f311e36c9f1573f1d33e5cd9037800ed"},
{file = "dbus_fast-2.30.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84e77820fdee8ae6aab588e3ff7a3cd2b532caf4b0e37d9474247b5891b0c0d7"},
{file = "dbus_fast-2.30.2-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:c6d226db36d3fe48f7677b1494e857f5e047e3d74146dafa3917eb64e6c0a55b"},
{file = "dbus_fast-2.30.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f253e27e0a57d106dcfd68afbdebd3fcdcb16aaf96e7ec3e0a6a318acc5dea7f"},
{file = "dbus_fast-2.30.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b4ee5a99eba4e93cf769173c33fa4b7b4589e818d43b3ef1dd304620c395ff28"},
{file = "dbus_fast-2.30.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:410c6866324b44f5987338d568ed6b945bae58d7875f483a99db40e97c3d0728"},
{file = "dbus_fast-2.30.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a38903ace05643667c2f95054b169e92eb7aa09c0710a639ac523102042355d8"},
{file = "dbus_fast-2.30.2-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:77e1fd68b61ddcf622d4653d96a42d527ea3588b0f09418c83e69bdb3758e86a"},
{file = "dbus_fast-2.30.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1ca38304578d7040b8bdecb97405d7e9bb264a27e31cbc56e6784f447c8891"},
{file = "dbus_fast-2.30.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:92793bc00591f1ecb76fc31a1148f550f6cd742df96b51088bdec0dac888f37e"},
{file = "dbus_fast-2.30.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:55eabe25b6976924d299ddf4207a2409f75c5c6545847b5fc263b6c2bda12356"},
{file = "dbus_fast-2.30.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4db238b1e826cc521a0afdbb09131947a8dd09c69ece3829d2710c14237ee20c"},
{file = "dbus_fast-2.30.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bc3d7e3974d0cd99cba4338f901e949bc8121a2e6ca289e86da21516ccab21f"},
{file = "dbus_fast-2.30.2-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:0579c0e8546faf332e80d1fee6314a2f2574ee5baee6a03eea47972f3b3ac250"},
{file = "dbus_fast-2.30.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73de2f271d20eefc730d651fc6fc642d5fb338c720eb984c548115183a9d00e5"},
{file = "dbus_fast-2.30.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:20260b567464751e1b5af4d44c4e20c841cb888a74ba94f71e06dd9c6a3eca21"},
{file = "dbus_fast-2.30.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9a1baae67542085c648a5daf8d7c7a622618c1908bf031c928fc41da9abe743a"},
{file = "dbus_fast-2.30.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:976e4fd7d384b11849e5f8ecfd985fddfe0a6f78198cbda6ea25e81754ded3eb"},
{file = "dbus_fast-2.30.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08021c33059f56947c7574ede0a525c934182f9c5441c483ff111f853117bac9"},
{file = "dbus_fast-2.30.2-cp313-cp313-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b6d921a4cb4f2c567bd12c74db2ff11aa23a0078d5bd43d7fc8f16ddfa73553e"},
{file = "dbus_fast-2.30.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53efa0780905e2f812ec54c00f4b0f3b151697040f28d3296ca3b746b5b16507"},
{file = "dbus_fast-2.30.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:355bc76ddda73908ecc5fcdf77c1717fec9409cbea0f9cc91391613cf4ecf98b"},
{file = "dbus_fast-2.30.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0fe08c0dcebe85e7aa2275cb8c4a0be7dec320472e781ef7f96e392d22188d8d"},
{file = "dbus_fast-2.30.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:73da9659ba913ce60778406f09600f5df99ec53629327934480cf9dc733ef565"},
{file = "dbus_fast-2.30.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a40e806e157037ed0ce4ba13c360350e95eb18b35c5529752ccf8818c224802"},
{file = "dbus_fast-2.30.2-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:26896a4ef8956dc8062e731dc2b8871b67dad37fb126bda3ae11a68885b88ee6"},
{file = "dbus_fast-2.30.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f82857b96cdcf678d72318d51aa68150dfb5daa9b3ae410552a0381e4504624b"},
{file = "dbus_fast-2.30.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c557a21c1f85751aa3e8cf7b45287bd65f5da9d9800894820780e5d5943700f5"},
{file = "dbus_fast-2.30.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:75d66497bb0a0b46fcb6b8cdfe382de98ced6c111783b03644c941445ef01781"},
{file = "dbus_fast-2.30.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e4a1f585c53caf49baf60ea71bbfa06f737fb76ab927ed996f1450f988d461ee"},
{file = "dbus_fast-2.30.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b89ccbee16ef5ae82f8b3cf582764811fe07284d21e83b04b1d683da67c1b0b0"},
{file = "dbus_fast-2.30.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:d3bc987e8b9764a787b3bd3833fa9602eb5b43a4c51b7e33ed7c653770af3597"},
{file = "dbus_fast-2.30.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed282fc8dc82d57d8b57621965aadb2ee4efa32898772bfbed5164a6308d332d"},
{file = "dbus_fast-2.30.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2982219d780e8746259245f4da5868918fd4423cfe325274167e5c8f4ef34835"},
{file = "dbus_fast-2.30.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:241872ba85b806050b5eac2a98541b46cab592b6b8a9e1ae0e603b17a4e31db3"},
{file = "dbus_fast-2.30.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09f9dce490a7f55a3f76f5ee5604658c431b49322ce0e3b1967de49eed961ed1"},
{file = "dbus_fast-2.30.2.tar.gz", hash = "sha256:87296315f4a2f2416d495f48cca019ba901d046989e94df2adda84c3170da7b3"},
]
[[package]]
name = "docutils"
version = "0.21.2"
description = "Docutils -- Python Documentation Utilities"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"},
{file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"},
]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version == \"3.10\""
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "flux-led"
version = "1.2.0"
description = "A Python library to communicate with the flux_led smart bulbs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "flux_led-1.2.0-py3-none-any.whl", hash = "sha256:3a55d9f6dfdab922900157e45844483c97ad04c8ef03d220d225f7a473ac66f3"},
{file = "flux_led-1.2.0.tar.gz", hash = "sha256:3cfb68df243bdc9bdde6a91e9d05960949de70b8a76fdb799b70021c54380c3d"},
]
[package.dependencies]
async_timeout = ">=3.0.0"
webcolors = "*"
[package.extras]
all = ["Sphinx (>=3.4.3)", "async_timeout (>=3.0.0)", "bump2version (>=1.0.1)", "codecov (>=2.1.4)", "coverage (>=5.1)", "ipython (>=7.15.0)", "m2r2 (>=0.2.7)", "pytest (>=5.4.3)", "pytest-asyncio", "pytest-cov (>=2.9.0)", "pytest-raises (>=0.11)", "pytest-runner (>=5.2)", "pytest-runner (>=5.2)", "ruff (==0.11.2)", "sphinx_rtd_theme (>=0.5.1)", "tox (>=3.15.2)", "twine (>=3.1.1)", "typing_extensions ; python_version < \"3.8\"", "webcolors", "wheel (>=0.34.2)"]
dev = ["Sphinx (>=3.4.3)", "bump2version (>=1.0.1)", "codecov (>=2.1.4)", "coverage (>=5.1)", "ipython (>=7.15.0)", "m2r2 (>=0.2.7)", "pytest (>=5.4.3)", "pytest-asyncio", "pytest-cov (>=2.9.0)", "pytest-raises (>=0.11)", "pytest-runner (>=5.2)", "pytest-runner (>=5.2)", "ruff (==0.11.2)", "sphinx_rtd_theme (>=0.5.1)", "tox (>=3.15.2)", "twine (>=3.1.1)", "wheel (>=0.34.2)"]
setup = ["pytest-runner (>=5.2)"]
test = ["codecov (>=2.1.4)", "pytest (>=5.4.3)", "pytest-asyncio", "pytest-cov (>=2.9.0)", "pytest-raises (>=0.11)", "ruff (==0.11.2)"]
[[package]]
name = "idna"
version = "3.15"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"},
{file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"},
]
[package.extras]
all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "imagesize"
version = "1.4.1"
description = "Getting image size from png/jpeg/jpeg2000/gif file"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
{file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "jinja2"
version = "3.1.6"
description = "A very fast and expressive template engine."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
]
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
code-style = ["pre-commit (>=3.0,<4.0)"]
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
linkify = ["linkify-it-py (>=1,<3)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "markupsafe"
version = "3.0.2"
description = "Safely add untrusted strings to HTML/XML markup."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
{file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
{file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
{file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
{file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
{file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
{file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
{file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
{file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
{file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
{file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
]
[[package]]
name = "mdit-py-plugins"
version = "0.4.2"
description = "Collection of plugins for markdown-it-py"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"},
{file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"},
]
[package.dependencies]
markdown-it-py = ">=1.0.0,<4.0.0"
[package.extras]
code-style = ["pre-commit"]
rtd = ["myst-parser", "sphinx-book-theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "myst-parser"
version = "3.0.1"
description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser,"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"},
{file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"},
]
[package.dependencies]
docutils = ">=0.18,<0.22"
jinja2 = "*"
markdown-it-py = ">=3.0,<4.0"
mdit-py-plugins = ">=0.4,<1.0"
pyyaml = "*"
sphinx = ">=6,<8"
[package.extras]
code-style = ["pre-commit (>=3.0,<4.0)"]
linkify = ["linkify-it-py (>=2.0,<3.0)"]
rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"]
testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"]
testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"]
[[package]]
name = "packaging"
version = "24.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
markers = {main = "extra == \"docs\""}
[[package]]
name = "pluggy"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pygments"
version = "2.20.0"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
{file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
]
markers = {main = "extra == \"docs\""}
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pyobjc-core"
version = "10.3.2"
description = "Python<->ObjC Interoperability Module"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_system == \"Darwin\""
files = [
{file = "pyobjc_core-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb40672d682851a5c7fd84e5041c4d069b62076168d72591abb5fcc871bb039"},
{file = "pyobjc_core-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cea5e77659619ad93c782ca07644b6efe7d7ec6f59e46128843a0a87c1af511a"},
{file = "pyobjc_core-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:16644a92fb9661de841ba6115e5354db06a1d193a5e239046e840013c7b3874d"},
{file = "pyobjc_core-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:76b8b911d94501dac89821df349b1860bb770dce102a1a293f524b5b09dd9462"},
{file = "pyobjc_core-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8c6288fdb210b64115760a4504efbc4daffdc390d309e9318eb0e3e3b78d2828"},
{file = "pyobjc_core-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:87901e9f7032f33eb4fa884e407bf2744d5a0791b379bfca783982a02be3f7fb"},
{file = "pyobjc_core-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:636971ab48a4198ca129e149fe58ccf85a7b4a9b93d27f5ae920d88eb2655431"},
{file = "pyobjc_core-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:48e9ac3af42b2340dae709a8b894f5ef7e5132d8546adcd1797cffcc449dabdc"},
{file = "pyobjc_core-10.3.2.tar.gz", hash = "sha256:dbf1475d864ce594288ce03e94e3a98dc7f0e4639971eb1e312bdf6661c21e0e"},
]
[[package]]
name = "pyobjc-framework-cocoa"
version = "10.3.2"
description = "Wrappers for the Cocoa frameworks on macOS"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_system == \"Darwin\""
files = [
{file = "pyobjc_framework_Cocoa-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:61f44c2adab28fdf3aa3d593c9497a2d9ceb9583ed9814adb48828c385d83ff4"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7caaf8b260e81b27b7b787332846f644b9423bfc1536f6ec24edbde59ab77a87"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c49e99fc4b9e613fb308651b99d52a8a9ae9916c8ef27aa2f5d585b6678a59bf"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1161b5713f9b9934c12649d73a6749617172e240f9431eff9e22175262fdfda"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:08e48b9ee4eb393447b2b781d16663b954bd10a26927df74f92e924c05568d89"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7faa448d2038ae0e0287a326d390002e744bb6470e45995e2dbd16c892e4495a"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:fcd53fee2be9708576617994b107aedc2c40824b648cd51e780e8399c0a447b6"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:838fcf0d10674bde9ff64a3f20c0e188f2dc5e804476d80509b81c4ac1dabc59"},
{file = "pyobjc_framework_cocoa-10.3.2.tar.gz", hash = "sha256:673968e5435845bef969bfe374f31a1a6dc660c98608d2b84d5cae6eafa5c39d"},
]
[package.dependencies]
pyobjc-core = ">=10.3.2"
[[package]]
name = "pyobjc-framework-corebluetooth"
version = "10.3.2"
description = "Wrappers for the framework CoreBluetooth on macOS"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_system == \"Darwin\""
files = [
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:af3e2f935a6a7e5b009b4cf63c64899592a7b46c3ddcbc8f2e28848842ef65f4"},
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:973b78f47c7e2209a475e60bcc7d1b4a87be6645d39b4e8290ee82640e1cc364"},
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4bafdf1be15eae48a4878dbbf1bf19877ce28cbbba5baa0267a9564719ee736e"},
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4d7dc7494de66c850bda7b173579df7481dc97046fa229d480fe9bf90b2b9651"},
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:62e09e730f4d98384f1b6d44718812195602b3c82d5c78e09f60e8a934e7b266"},
{file = "pyobjc_framework_corebluetooth-10.3.2.tar.gz", hash = "sha256:c0a077bc3a2466271efa382c1e024630bc43cc6f9ab8f3f97431ad08b1ad52bb"},
]
[package.dependencies]
pyobjc-core = ">=10.3.2"
pyobjc-framework-Cocoa = ">=10.3.2"
[[package]]
name = "pyobjc-framework-libdispatch"
version = "10.3.2"
description = "Wrappers for libdispatch on macOS"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_system == \"Darwin\""
files = [
{file = "pyobjc_framework_libdispatch-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35233a8b1135567c7696087f924e398799467c7f129200b559e8e4fa777af860"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:061f6aa0f88d11d993e6546ec734303cb8979f40ae0f5cd23541236a6b426abd"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6bb528f34538f35e1b79d839dbfc398dd426990e190d9301fe2d811fddc3da62"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1357729d5fded08fbf746834ebeef27bee07d6acb991f3b8366e8f4319d882c4"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:210398f9e1815ceeff49b578bf51c2d6a4a30d4c33f573da322f3d7da1add121"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e7ae5988ac0b369ad40ce5497af71864fac45c289fa52671009b427f03d6871f"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:f9d51d52dff453a4b19c096171a6cd31dd5e665371c00c1d72d480e1c22cd3d4"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ef755bcabff2ea8db45603a8294818e0eeae85bf0b7b9d59e42f5947a26e33b9"},
{file = "pyobjc_framework_libdispatch-10.3.2.tar.gz", hash = "sha256:e9f4311fbf8df602852557a98d2a64f37a9d363acf4d75634120251bbc7b7304"},
]
[package.dependencies]
pyobjc-core = ">=10.3.2"
pyobjc-framework-Cocoa = ">=10.3.2"
[[package]]
name = "pytest"
version = "8.4.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"},
{file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"},
]
[package.dependencies]
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
iniconfig = ">=1"
packaging = ">=20"
pluggy = ">=1.5,<2"
pygments = ">=2.7.2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "7.0.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"},
{file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"},
]
[package.dependencies]
coverage = {version = ">=7.10.6", extras = ["toml"]}
pluggy = ">=1.2"
pytest = ">=7"
[package.extras]
testing = ["process-tests", "pytest-xdist", "virtualenv"]
[[package]]
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
]
[[package]]
name = "requests"
version = "2.32.4"
description = "Python HTTP for Humans."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
{file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset_normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "snowballstemmer"
version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
[[package]]
name = "sphinx"
version = "7.4.7"
description = "Python documentation generator"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"},
{file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"},
]
[package.dependencies]
alabaster = ">=0.7.14,<0.8.0"
babel = ">=2.13"
colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
docutils = ">=0.20,<0.22"
imagesize = ">=1.3"
Jinja2 = ">=3.1"
packaging = ">=23.0"
Pygments = ">=2.17"
requests = ">=2.30.0"
snowballstemmer = ">=2.2"
sphinxcontrib-applehelp = "*"
sphinxcontrib-devhelp = "*"
sphinxcontrib-htmlhelp = ">=2.0.0"
sphinxcontrib-jsmath = "*"
sphinxcontrib-qthelp = "*"
sphinxcontrib-serializinghtml = ">=1.1.9"
tomli = {version = ">=2", markers = "python_version < \"3.11\""}
[package.extras]
docs = ["sphinxcontrib-websupport"]
lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"]
test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
[[package]]
name = "sphinx-rtd-theme"
version = "3.1.0"
description = "Read the Docs theme for Sphinx"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89"},
{file = "sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c"},
]
[package.dependencies]
docutils = ">0.18,<0.23"
sphinx = ">=6,<10"
sphinxcontrib-jquery = ">=4,<5"
[package.extras]
dev = ["bump2version", "transifex-client", "twine", "wheel"]
[[package]]
name = "sphinxcontrib-applehelp"
version = "2.0.0"
description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
{file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "sphinxcontrib-devhelp"
version = "2.0.0"
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
{file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "sphinxcontrib-htmlhelp"
version = "2.1.0"
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
{file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["html5lib", "pytest"]
[[package]]
name = "sphinxcontrib-jquery"
version = "4.1"
description = "Extension to include jQuery on newer Sphinx releases"
optional = true
python-versions = ">=2.7"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"},
{file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"},
]
[package.dependencies]
Sphinx = ">=1.8"
[[package]]
name = "sphinxcontrib-jsmath"
version = "1.0.1"
description = "A sphinx extension which renders display math in HTML via JavaScript"
optional = true
python-versions = ">=3.5"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
]
[package.extras]
test = ["flake8", "mypy", "pytest"]
[[package]]
name = "sphinxcontrib-qthelp"
version = "2.0.0"
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
{file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["defusedxml (>=0.7.1)", "pytest"]
[[package]]
name = "sphinxcontrib-serializinghtml"
version = "2.0.0"
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
{file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "tomli"
version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
]
markers = {main = "extra == \"docs\" and python_version == \"3.10\"", dev = "python_full_version <= \"3.11.0a6\""}
[[package]]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version < \"3.12\" or platform_system == \"Windows\""
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "uart-devices"
version = "0.1.0"
description = "UART Devices for Linux"
optional = false
python-versions = "<4.0,>=3.8"
groups = ["main"]
markers = "platform_system == \"Linux\""
files = [
{file = "uart_devices-0.1.0-py3-none-any.whl", hash = "sha256:f019357945a4f2d619e43a7cef7cee4f52aeff06aa5c674f9da448dce3c9cd64"},
{file = "uart_devices-0.1.0.tar.gz", hash = "sha256:7f0342c0ba0bc2a4c13c9ead5462dc9feeaca507e5c7017ebd074a69567ad9b1"},
]
[[package]]
name = "urllib3"
version = "2.6.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"},
{file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"},
]
[package.extras]
brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
[[package]]
name = "usb-devices"
version = "0.4.5"
description = "Tools for mapping, describing, and resetting USB devices"
optional = false
python-versions = ">=3.9,<4.0"
groups = ["main"]
markers = "platform_system == \"Linux\""
files = [
{file = "usb_devices-0.4.5-py3-none-any.whl", hash = "sha256:8a415219ef1395e25aa0bddcad484c88edf9673acdeae8a07223ca7222a01dcf"},
{file = "usb_devices-0.4.5.tar.gz", hash = "sha256:9b5c7606df2bc791c6c45b7f76244a0cbed83cb6fa4c68791a143c03345e195d"},
]
[[package]]
name = "webcolors"
version = "24.11.1"
description = "A library for working with the color formats defined by HTML and CSS."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9"},
{file = "webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6"},
]
[[package]]
name = "winrt-runtime"
version = "3.2.1"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "winrt_runtime-3.2.1-cp310-cp310-win32.whl", hash = "sha256:25a2d1e2b45423742319f7e10fa8ca2e7063f01284b6e85e99d805c4b50bbfb3"},
{file = "winrt_runtime-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:dc81d5fb736bf1ddecf743928622253dce4d0aac9a57faad776d7a3834e13257"},
{file = "winrt_runtime-3.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:363f584b1e9fcb601e3e178636d8877e6f0537ac3c96ce4a96f06066f8ff0eae"},
{file = "winrt_runtime-3.2.1-cp311-cp311-win32.whl", hash = "sha256:9e9b64f1ba631cc4b9fe60b8ff16fef3f32c7ce2fcc84735a63129ff8b15c022"},
{file = "winrt_runtime-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0a9046ae416808420a358c51705af8ae100acd40bc578be57ddfdd51cbb0f9c"},
{file = "winrt_runtime-3.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:e94f3cb40ea2d723c44c82c16d715c03c6b3bd977d135b49535fdd5415fd9130"},
{file = "winrt_runtime-3.2.1-cp312-cp312-win32.whl", hash = "sha256:762b3d972a2f7037f7db3acbaf379dd6d8f6cda505f71f66c6b425d1a1eae2f1"},
{file = "winrt_runtime-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:06510db215d4f0dc45c00fbb1251c6544e91742a0ad928011db33b30677e1576"},
{file = "winrt_runtime-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:14562c29a087ccad38e379e585fef333e5c94166c807bdde67b508a6261aa195"},
{file = "winrt_runtime-3.2.1-cp313-cp313-win32.whl", hash = "sha256:44e2733bc709b76c554aee6c7fe079443b8306b2e661e82eecfebe8b9d71e4d1"},
{file = "winrt_runtime-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:3c1fdcaeedeb2920dc3b9039db64089a6093cad2be56a3e64acc938849245a6d"},
{file = "winrt_runtime-3.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:28f3dab083412625ff4d2b46e81246932e6bebddf67bea7f05e01712f54e6159"},
{file = "winrt_runtime-3.2.1-cp314-cp314-win32.whl", hash = "sha256:9b6298375468ac2f6815d0c008a059fc16508c8f587e824c7936ed9216480dad"},
{file = "winrt_runtime-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:e36e587ab5fd681ee472cd9a5995743f75107a1a84d749c64f7e490bc86bc814"},
{file = "winrt_runtime-3.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:35d6241a2ebd5598e4788e69768b8890ee1eee401a819865767a1fbdd3e9a650"},
{file = "winrt_runtime-3.2.1-cp39-cp39-win32.whl", hash = "sha256:07c0cb4a53a4448c2cb7597b62ae8c94343c289eeebd8f83f946eb2c817bde01"},
{file = "winrt_runtime-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:1856325ca3354b45e0789cf279be9a882134085d34214946db76110d98391efa"},
{file = "winrt_runtime-3.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:cf237858de1d62e4c9b132c66b52028a7a3e8534e8ab90b0e29a68f24f7be39d"},
{file = "winrt_runtime-3.2.1.tar.gz", hash = "sha256:c8dca19e12b234ae6c3dadf1a4d0761b51e708457492c13beb666556958801ea"},
]
[package.dependencies]
typing_extensions = ">=4.12.2"
[[package]]
name = "winrt-windows-devices-bluetooth"
version = "3.2.1"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "winrt_windows_devices_bluetooth-3.2.1-cp310-cp310-win32.whl", hash = "sha256:49489351037094a088a08fbdf0f99c94e3299b574edb211f717c4c727770af78"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:20f6a21029034c18ea6a6b6df399671813b071102a0d6d8355bb78cf4f547cdb"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:69c523814eab795bc1bf913292309cb1025ef0a67d5fc33863a98788995e551d"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp311-cp311-win32.whl", hash = "sha256:f4082a00b834c1e34b961e0612f3e581356bdb38c5798bd6842f88ec02e5152b"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:44277a3f2cc5ac32ce9b4b2d96c5c5f601d394ac5f02cc71bcd551f738660e2d"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:0803a417403a7d225316b9b0c4fe3f8446579d6a22f2f729a2c21f4befc74a80"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp312-cp312-win32.whl", hash = "sha256:18c833ec49e7076127463679e85efc59f61785ade0dc185c852586b21be1f31c"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:9b6702c462b216c91e32388023a74d0f87210cef6fd5d93b7191e9427ce2faca"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:419fd1078c7749119f6b4bbf6be4e586e03a0ed544c03b83178f1d85f1b3d148"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp313-cp313-win32.whl", hash = "sha256:12b0a16fb36ce0b42243ca81f22a6b53fbb344ed7ea07a6eeec294604f0505e4"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:6703dfbe444ee22426738830fb305c96a728ea9ccce905acfdf811d81045fdb3"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:2cf8a0bfc9103e32dc7237af15f84be06c791f37711984abdca761f6318bbdb2"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp314-cp314-win32.whl", hash = "sha256:de36ded53ca3ba12fc6dd4deb14b779acc391447726543815df4800348aad63a"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3295d932cc93259d5ccb23a41e3a3af4c78ce5d6a6223b2b7638985f604fa34c"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:1f61c178766a1bbce0669f44790c6161ff4669404c477b4aedaa576348f9e102"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp39-cp39-win32.whl", hash = "sha256:32fc355bfdc5d6b3b1875df16eaf12f9b9fc0445e01177833c27d9a4fc0d50b6"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b886ef1fc0ed49163ae6c2422dd5cb8dd4709da7972af26c8627e211872818d0"},
{file = "winrt_windows_devices_bluetooth-3.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:8643afa53f9fb8fe3b05967227f86f0c8e1d7b822289e60a848c6368acc977d2"},
{file = "winrt_windows_devices_bluetooth-3.2.1.tar.gz", hash = "sha256:db496d2d92742006d5a052468fc355bf7bb49e795341d695c374746113d74505"},
]
[package.dependencies]
winrt-runtime = ">=3.2.1.0,<3.3.0.0"
[package.extras]
all = ["winrt-Windows.Devices.Bluetooth.GenericAttributeProfile[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Devices.Bluetooth.Rfcomm[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Devices.Enumeration[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Devices.Radios[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation.Collections[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Networking[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Storage.Streams[all] (>=3.2.1.0,<3.3.0.0)"]
[[package]]
name = "winrt-windows-devices-bluetooth-advertisement"
version = "3.2.1"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp310-cp310-win32.whl", hash = "sha256:a758c5f81a98cc38347fdfb024ce62720969480e8c5b98e402b89d2b09b32866"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:f982ef72e729ddd60cdb975293866e84bb838798828933012a57ee4bf12b0ea1"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:e88a72e1e09c7ccc899a9e6d2ab3fc0f43b5dd4509bcc49ec4abf65b55ab015f"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp311-cp311-win32.whl", hash = "sha256:fe17c2cf63284646622e8b2742b064bf7970bbf53cfab02062136c67fa6b06c9"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:78e99dd48b4d89b71b7778c5085fdba64e754dd3ebc54fd09c200fe5222c6e09"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6d5d2295474deab444fc4311580c725a2ca8a814b0f3344d0779828891d75401"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp312-cp312-win32.whl", hash = "sha256:901933cc40de5eb7e5f4188897c899dd0b0f577cb2c13eab1a63c7dfe89b08c4"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:e6c66e7d4f4ca86d2c801d30efd2b9673247b59a2b4c365d9e11650303d68d89"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:447d19defd8982d39944642eb7ebe89e4e20259ec9734116cf88879fb2c514ff"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp313-cp313-win32.whl", hash = "sha256:4122348ea525a914e85615647a0b54ae8b2f42f92cdbf89c5a12eea53ef6ed90"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:b66410c04b8dae634a7e4b615c3b7f8adda9c7d4d6902bcad5b253da1a684943"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:07af19b1d252ddb9dd3eb2965118bc2b7cabff4dda6e499341b765e5038ca61d"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp314-cp314-win32.whl", hash = "sha256:2985565c265b3f9eab625361b0e40e88c94b03d89f5171f36146f2e88b3ee214"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:d102f3fac64fde32332e370969dfbc6f37b405d8cc055d9da30d14d07449a3c2"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:ffeb5e946cd42c32c6999a62e240d6730c653cdfb7b49c7839afba375e20a62a"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp39-cp39-win32.whl", hash = "sha256:6c4747d2e5b0e2ef24e9b84a848cf8fc50fb5b268a2086b5ee8680206d1e0197"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:18d4c5d8b80ee2d29cc13c2fc1353fdb3c0f620c8083701c9b9ecf5e6c503c8d"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:75dd856611d847299078d56aee60e319df52975b931c992cd1d32ad5143fe772"},
{file = "winrt_windows_devices_bluetooth_advertisement-3.2.1.tar.gz", hash = "sha256:0223852a7b7fa5c8dea3c6a93473bd783df4439b1ed938d9871f947933e574cc"},
]
[package.dependencies]
winrt-runtime = ">=3.2.1.0,<3.3.0.0"
[package.extras]
all = ["winrt-Windows.Devices.Bluetooth[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation.Collections[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Storage.Streams[all] (>=3.2.1.0,<3.3.0.0)"]
[[package]]
name = "winrt-windows-devices-bluetooth-genericattributeprofile"
version = "3.2.1"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp310-cp310-win32.whl", hash = "sha256:af4914d7b30b49232092cd3b934e3ed6f5d3b1715ba47238541408ee595b7f46"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:0e557dd52fc80392b8bd7c237e1153a50a164b3983838b4ac674551072efc9ed"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:64cff62baa6b7aadd6c206e61d149113fdcda17360feb6e9d05bc8bbda4b9fde"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp311-cp311-win32.whl", hash = "sha256:832cf65d035a11e6dbfef4fd66abdcc46be7e911ec96e2e72e98e12d8d5b9d3c"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:8179638a6c721b0bbf04ba251ef98d5e02d9a17f0cce377398e42c4fbb441415"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:70b7edfca3190b89ae38bf60972b11978311b6d933d3142ae45560c955dbf5c7"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp312-cp312-win32.whl", hash = "sha256:ef894d21e0a805f3e114940254636a8045335fa9de766c7022af5d127dfad557"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:db05de95cd1b24a51abb69cb936a8b17e9214e015757d0b37e3a5e207ddceb3d"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d4e131cf3d15fc5ad81c1bcde3509ac171298217381abed6bdf687f29871984"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp313-cp313-win32.whl", hash = "sha256:b1879c8dcf46bd2110b9ad4b0b185f4e2a5f95170d014539203a5fee2b2115f0"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d8d89f01e9b6931fb48217847caac3227a0aeb38a5b7782af71c2e7b262ec30"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:4e71207bb89798016b1795bb15daf78afe45529f2939b3b9e78894cfe650b383"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp314-cp314-win32.whl", hash = "sha256:d5f83739ca370f0baf52b0400aebd6240ab80150081fbfba60fd6e7b2e7b4c5f"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:13786a5853a933de140d456cd818696e1121c7c296ae7b7af262fc5d2cffb851"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:5140682da2860f6a55eb6faf9e980724dc457c2e4b4b35a10e1cebd8fc97d892"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp39-cp39-win32.whl", hash = "sha256:963339a0161f9970b577a6193924be783978d11693da48b41a025f61b3c5562a"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:d43615c5dfa939dd30fe80dc0649434a13cc7cf0294ad0d7283d5a9f48c6ce86"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:8e70fa970997e2e67a8a4172bc00b0b2a79b5ff5bb2668f79cf10b3fd63d3974"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1.tar.gz", hash = "sha256:cdf6ddc375e9150d040aca67f5a17c41ceaf13a63f3668f96608bc1d045dde71"},
]
[package.dependencies]
winrt-runtime = ">=3.2.1.0,<3.3.0.0"
[package.extras]
all = ["winrt-Windows.Devices.Bluetooth[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Devices.Enumeration[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation.Collections[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Storage.Streams[all] (>=3.2.1.0,<3.3.0.0)"]
[[package]]
name = "winrt-windows-devices-enumeration"
version = "3.2.1"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "winrt_windows_devices_enumeration-3.2.1-cp310-cp310-win32.whl", hash = "sha256:40dac777d8f45b41449f3ff1ae70f0d457f1ede53f53962a6e2521b651533db5"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:a101ec3e0ad0a0783032fdcd5dc48e7cd68ee034cbde4f903a8c7b391532c71a"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:3296a3863ac086928ff3f3dc872b2a2fb971dab728817424264f3ca547504e9e"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp311-cp311-win32.whl", hash = "sha256:9f29465a6c6b0456e4330d4ad09eccdd53a17e1e97695c2e57db0d4666cc0011"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2a725d04b4cb43aa0e2af035f73a60d16a6c0ff165fcb6b763383e4e33a975fd"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6365ef5978d4add26678827286034acf474b6b133aa4054e76567d12194e6817"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp312-cp312-win32.whl", hash = "sha256:1db22b0292b93b0688d11ad932ad1f3629d4f471310281a2fbfe187530c2c1f3"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:a73bc88d7f510af454f2b392985501c96f39b89fd987140708ccaec1588ceebc"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:2853d687803f0dd76ae1afe3648abc0453e09dff0e7eddbb84b792eddb0473ca"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp313-cp313-win32.whl", hash = "sha256:14a71cdcc84f624c209cbb846ed6bd9767a9a9437b2bf26b48ac9a91599da6e9"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:6ca40d334734829e178ad46375275c4f7b5d6d2d4fc2e8879690452cbfb36015"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:2d14d187f43e4409c7814b7d1693c03a270e77489b710d92fcbbaeca5de260d4"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp314-cp314-win32.whl", hash = "sha256:e087364273ed7c717cd0191fed4be9def6fdf229fe9b536a4b8d0228f7814106"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:0da1ddb8285d97a6775c36265d7157acf1bbcb88bcc9a7ce9a4549906c822472"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:09bf07e74e897e97a49a9275d0a647819254ddb74142806bbbcf4777ed240a22"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp39-cp39-win32.whl", hash = "sha256:986e8d651b769a0e60d2834834bdd3f6959f6a88caa0c9acb917797e6b43a588"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10da7d403ac4afd385fe13bd5808c9a5dd616a8ef31ca5c64cea3f87673661c1"},
{file = "winrt_windows_devices_enumeration-3.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:679e471d21ac22cb50de1bf4dfc4c0c3f5da9f3e3fbc7f08dcacfe9de9d6dd58"},
{file = "winrt_windows_devices_enumeration-3.2.1.tar.gz", hash = "sha256:df316899e39bfc0ffc1f3cb0f5ee54d04e1d167fbbcc1484d2d5121449a935cf"},
]
[package.dependencies]
winrt-runtime = ">=3.2.1.0,<3.3.0.0"
[package.extras]
all = ["winrt-Windows.ApplicationModel.Background[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation.Collections[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Security.Credentials[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Storage.Streams[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.UI.Popups[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.UI[all] (>=3.2.1.0,<3.3.0.0)"]
[[package]]
name = "winrt-windows-foundation"
version = "3.2.1"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "winrt_windows_foundation-3.2.1-cp310-cp310-win32.whl", hash = "sha256:677e98165dcbbf7a2367f905bc61090ef2c568b6e465f87cf7276df4734f3b0b"},
{file = "winrt_windows_foundation-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:a8f27b4f0fdb73ccc4a3e24bc8010a6607b2bdd722fa799eafce7daa87d19d39"},
{file = "winrt_windows_foundation-3.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:d900c6165fab4ea589811efa2feed27b532e1b6f505f63bf63e2052b8cb6bdc4"},
{file = "winrt_windows_foundation-3.2.1-cp311-cp311-win32.whl", hash = "sha256:d1b5970241ccd61428f7330d099be75f4f52f25e510d82c84dbbdaadd625e437"},
{file = "winrt_windows_foundation-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:f3762be2f6e0f2aedf83a0742fd727290b397ffe3463d963d29211e4ebb53a7e"},
{file = "winrt_windows_foundation-3.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:806c77818217b3476e6c617293b3d5b0ff8a9901549dc3417586f6799938d671"},
{file = "winrt_windows_foundation-3.2.1-cp312-cp312-win32.whl", hash = "sha256:867642ccf629611733db482c4288e17b7919f743a5873450efb6d69ae09fdc2b"},
{file = "winrt_windows_foundation-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:45550c5b6c2125cde495c409633e6b1ea5aa1677724e3b95eb8140bfccbe30c9"},
{file = "winrt_windows_foundation-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:94f4661d71cb35ebc52be7af112f2eeabdfa02cb05e0243bf9d6bd2cafaa6f37"},
{file = "winrt_windows_foundation-3.2.1-cp313-cp313-win32.whl", hash = "sha256:3998dc58ed50ecbdbabace1cdef3a12920b725e32a5806d648ad3f4829d5ba46"},
{file = "winrt_windows_foundation-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:6e98617c1e46665c7a56ce3f5d28e252798416d1ebfee3201267a644a4e3c479"},
{file = "winrt_windows_foundation-3.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:2a8c1204db5c352f6a563130a5a41d25b887aff7897bb677d4ff0b660315aad4"},
{file = "winrt_windows_foundation-3.2.1-cp314-cp314-win32.whl", hash = "sha256:35e973ab3c77c2a943e139302256c040e017fd6ff1a75911c102964603bba1da"},
{file = "winrt_windows_foundation-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22a7ebcec0d262e60119cff728f32962a02df60471ded8b2735a655eccc0ef5"},
{file = "winrt_windows_foundation-3.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:3be7fbae829b98a6a946db4fbaf356b11db1fbcbb5d4f37e7a73ac6b25de8b87"},
{file = "winrt_windows_foundation-3.2.1-cp39-cp39-win32.whl", hash = "sha256:14d5191725301498e4feb744d91f5b46ce317bf3d28370efda407d5c87f4423b"},
{file = "winrt_windows_foundation-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:de5e4f61d253a91ba05019dbf4338c43f962bdad935721ced5e7997933994af5"},
{file = "winrt_windows_foundation-3.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:ebbf6e8168398c9ed0c72c8bdde95a406b9fbb9a23e3705d4f0fe28e5a209705"},
{file = "winrt_windows_foundation-3.2.1.tar.gz", hash = "sha256:ad2f1fcaa6c34672df45527d7c533731fdf65b67c4638c2b4aca949f6eec0656"},
]
[package.dependencies]
winrt-runtime = ">=3.2.1.0,<3.3.0.0"
[package.extras]
all = ["winrt-Windows.Foundation.Collections[all] (>=3.2.1.0,<3.3.0.0)"]
[[package]]
name = "winrt-windows-foundation-collections"
version = "3.2.1"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "winrt_windows_foundation_collections-3.2.1-cp310-cp310-win32.whl", hash = "sha256:46948484addfc4db981dab35688d4457533ceb54d4954922af41503fddaa8389"},
{file = "winrt_windows_foundation_collections-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:899eaa3a93c35bfb1857d649e8dd60c38b978dda7cedd9725fcdbcebba156fd6"},
{file = "winrt_windows_foundation_collections-3.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:c36eb49ad1eba1b32134df768bb47af13cabb9b59f974a3cea37843e2d80e0e6"},
{file = "winrt_windows_foundation_collections-3.2.1-cp311-cp311-win32.whl", hash = "sha256:9b272d9936e7db4840881c5dcf921eb26789ae4ef23fb6ec15e13e19a16254e7"},
{file = "winrt_windows_foundation_collections-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:c646a5d442dd6540ade50890081ca118b41f073356e19032d0a5d7d0d38fbc89"},
{file = "winrt_windows_foundation_collections-3.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:2c4630027c93cdd518b0cf4cc726b8fbdbc3388e36d02aa1de190a0fc18ca523"},
{file = "winrt_windows_foundation_collections-3.2.1-cp312-cp312-win32.whl", hash = "sha256:15704eef3125788f846f269cf54a3d89656fa09a1dc8428b70871f717d595ad6"},
{file = "winrt_windows_foundation_collections-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:550dfb8c82fe74d9e0728a2a16a9175cc9e34ca2b8ef758d69b2a398894b698b"},
{file = "winrt_windows_foundation_collections-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:810ad4bd11ab4a74fdbcd3ed33b597ef7c0b03af73fc9d7986c22bcf3bd24f84"},
{file = "winrt_windows_foundation_collections-3.2.1-cp313-cp313-win32.whl", hash = "sha256:4267a711b63476d36d39227883aeb3fb19ac92b88a9fc9973e66fbce1fd4aed9"},
{file = "winrt_windows_foundation_collections-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:5e12a6e75036ee90484c33e204b85fb6785fcc9e7c8066ad65097301f48cdd10"},
{file = "winrt_windows_foundation_collections-3.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:34b556255562f1b36d07fba933c2bcd9f0db167fa96727a6cbb4717b152ad7a2"},
{file = "winrt_windows_foundation_collections-3.2.1-cp314-cp314-win32.whl", hash = "sha256:33188ed2d63e844c8adfbb82d1d3d461d64aaf78d225ce9c5930421b413c45ab"},
{file = "winrt_windows_foundation_collections-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:d4cfece7e9c0ead2941e55a1da82f20d2b9c8003bb7a8853bb7f999b539f80a4"},
{file = "winrt_windows_foundation_collections-3.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:3884146fea13727510458f6a14040b7632d5d90127028b9bfd503c6c655d0c01"},
{file = "winrt_windows_foundation_collections-3.2.1-cp39-cp39-win32.whl", hash = "sha256:20610f098b84c87765018cbc71471092197881f3b92e5d06158fad3bfcea2563"},
{file = "winrt_windows_foundation_collections-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9739775320ac4c0238e1775d94a54e886d621f9995977e65d4feb8b3778c111"},
{file = "winrt_windows_foundation_collections-3.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:e4c6bddb1359d5014ceb45fe2ecd838d4afeb1184f2ea202c2d21037af0d08a3"},
{file = "winrt_windows_foundation_collections-3.2.1.tar.gz", hash = "sha256:0eff1ad0d8d763ad17e9e7bbd0c26a62b27215016393c05b09b046d6503ae6d5"},
]
[package.dependencies]
winrt-runtime = ">=3.2.1.0,<3.3.0.0"
[package.extras]
all = ["winrt-Windows.Foundation[all] (>=3.2.1.0,<3.3.0.0)"]
[[package]]
name = "winrt-windows-storage-streams"
version = "3.2.1"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "winrt_windows_storage_streams-3.2.1-cp310-cp310-win32.whl", hash = "sha256:89bb2d667ebed6861af36ed2710757456e12921ee56347946540320dacf6c003"},
{file = "winrt_windows_storage_streams-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:48a78e5dc7d3488eb77e449c278bc6d6ac28abcdda7df298462c4112d7635d00"},
{file = "winrt_windows_storage_streams-3.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:da71231d4a554f9f15f1249b4990c6431176f6dfb0e3385c7caa7896f4ca24d6"},
{file = "winrt_windows_storage_streams-3.2.1-cp311-cp311-win32.whl", hash = "sha256:7dace2f9e364422255d0e2f335f741bfe7abb1f4d4f6003622b2450b87c91e69"},
{file = "winrt_windows_storage_streams-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:b02fa251a7eef6081eca1a5f64ecf349cfd1ac0ac0c5a5a30be52897d060bed5"},
{file = "winrt_windows_storage_streams-3.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:efdf250140340a75647e8e8ad002782d91308e9fdd1e19470a5b9cc969ae4780"},
{file = "winrt_windows_storage_streams-3.2.1-cp312-cp312-win32.whl", hash = "sha256:77c1f0e004b84347b5bd705e8f0fc63be8cd29a6093be13f1d0869d0d97b7d78"},
{file = "winrt_windows_storage_streams-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4508ee135af53e4fc142876abbf4bc7c2a95edfc7d19f52b291a8499cacd6dc"},
{file = "winrt_windows_storage_streams-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:040cb94e6fb26b0d00a00e8b88b06fadf29dfe18cf24ed6cb3e69709c3613307"},
{file = "winrt_windows_storage_streams-3.2.1-cp313-cp313-win32.whl", hash = "sha256:401bb44371720dc43bd1e78662615a2124372e7d5d9d65dfa8f77877bbcb8163"},
{file = "winrt_windows_storage_streams-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:202c5875606398b8bfaa2a290831458bb55f2196a39c1d4e5fa88a03d65ef915"},
{file = "winrt_windows_storage_streams-3.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:ca3c5ec0aab60895006bf61053a1aca6418bc7f9a27a34791ba3443b789d230d"},
{file = "winrt_windows_storage_streams-3.2.1-cp314-cp314-win32.whl", hash = "sha256:5cd0dbad86fcc860366f6515fce97177b7eaa7069da261057be4813819ba37ee"},
{file = "winrt_windows_storage_streams-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3c5bf41d725369b9986e6d64bad7079372b95c329897d684f955d7028c7f27a0"},
{file = "winrt_windows_storage_streams-3.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:293e09825559d0929bbe5de01e1e115f7a6283d8996ab55652e5af365f032987"},
{file = "winrt_windows_storage_streams-3.2.1-cp39-cp39-win32.whl", hash = "sha256:1c630cfdece58fcf82e4ed86c826326123529836d6d4d855ae8e9ceeff67b627"},
{file = "winrt_windows_storage_streams-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7ff22434a4829d616a04b068a191ac79e008f6c27541bb178c1f6f1fe7a1657"},
{file = "winrt_windows_storage_streams-3.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:fa90244191108f85f6f7afb43a11d365aca4e0722fe8adc62fb4d2c678d0993d"},
{file = "winrt_windows_storage_streams-3.2.1.tar.gz", hash = "sha256:476f522722751eb0b571bc7802d85a82a3cae8b1cce66061e6e758f525e7b80f"},
]
[package.dependencies]
winrt-runtime = ">=3.2.1.0,<3.3.0.0"
[package.extras]
all = ["winrt-Windows.Foundation.Collections[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Foundation[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.Storage[all] (>=3.2.1.0,<3.3.0.0)", "winrt-Windows.System[all] (>=3.2.1.0,<3.3.0.0)"]
[extras]
docs = ["Sphinx", "myst-parser", "sphinx-rtd-theme"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.10,<3.14"
content-hash = "08a7d02ceaa1a8af680ea26cb97fc6f71da694d8494dc5e7beef5daa4fae65d5"
Bluetooth-Devices-led-ble-4c385cb/pyproject.toml 0000664 0000000 0000000 00000005275 15204752553 0021713 0 ustar 00root root 0000000 0000000 [project]
name = "led-ble"
version = "1.1.11"
license = "Apache-2.0"
description = "Control a wide range of LED BLE devices"
authors = [{ name = "J. Nick Koston", email = "nick@koston.org" }]
readme = "README.md"
requires-python = ">=3.10"
dynamic = ["classifiers", "dependencies", "optional-dependencies"]
[project.urls]
"Documentation" = "https://led-ble.readthedocs.io"
"Repository" = "https://github.com/bluetooth-devices/led-ble"
"Bug Tracker" = "https://github.com/bluetooth-devices/led-ble/issues"
"Changelog" = "https://github.com/bluetooth-devices/led-ble/blob/main/CHANGELOG.md"
[tool.poetry]
classifiers = [
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries",
]
packages = [
{ include = "led_ble", from = "src" },
]
[tool.poetry.dependencies]
python = ">=3.10,<3.14"
# Documentation Dependencies
Sphinx = {version = ">=5,<8", optional = true}
sphinx-rtd-theme = {version = ">=1,<4", optional = true}
myst-parser = {version = ">=0.18,<3.1", optional = true}
bleak-retry-connector = ">=2.3.0"
bleak = ">=0.22.0"
async-timeout = {version = ">=3.0.0", python = "<3.11"}
flux-led = ">=0.28.32"
[tool.poetry.extras]
docs = [
"myst-parser",
"sphinx",
"sphinx-rtd-theme",
]
[tool.poetry.group.dev.dependencies]
pytest = "^8.4"
pytest-cov = "^7.0"
[tool.semantic_release]
branch = "main"
version_toml = ["pyproject.toml:project.version"]
version_variables = ["src/led_ble/__init__.py:__version__"]
build_command = "pip install poetry && poetry build"
[tool.pytest.ini_options]
addopts = "-v -Wdefault --cov=led_ble --cov-report=term-missing:skip-covered"
pythonpath = ["src"]
[tool.coverage.run]
branch = true
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"@overload",
"if TYPE_CHECKING",
"raise NotImplementedError",
]
[tool.isort]
profile = "black"
known_first_party = ["led_ble", "tests"]
[tool.ruff]
target-version = "py39"
[tool.mypy]
check_untyped_defs = true
disallow_any_generics = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
mypy_path = "src/"
no_implicit_optional = true
show_error_codes = true
warn_unreachable = true
warn_unused_ignores = true
exclude = [
'docs/.*',
'setup.py',
]
[[tool.mypy.overrides]]
module = "tests.*"
allow_untyped_defs = true
# Tests routinely monkeypatch instance methods with Mock/AsyncMock, which is a
# normal pattern in test code rather than a real typing error.
disable_error_code = ["method-assign"]
[[tool.mypy.overrides]]
module = "docs.*"
ignore_errors = true
[build-system]
requires = ["poetry-core>=2.0.0"]
build-backend = "poetry.core.masonry.api"
Bluetooth-Devices-led-ble-4c385cb/renovate.json 0000664 0000000 0000000 00000000101 15204752553 0021474 0 ustar 00root root 0000000 0000000 {
"extends": ["github>browniebroke/renovate-configs:python"]
}
Bluetooth-Devices-led-ble-4c385cb/setup.py 0000664 0000000 0000000 00000000355 15204752553 0020503 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
# This is a shim to allow GitHub to detect the package, build is done with poetry
# Taken from https://github.com/Textualize/rich
import setuptools
if __name__ == "__main__":
setuptools.setup(name="led-ble")
Bluetooth-Devices-led-ble-4c385cb/src/ 0000775 0000000 0000000 00000000000 15204752553 0017555 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/src/led_ble/ 0000775 0000000 0000000 00000000000 15204752553 0021143 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/src/led_ble/__init__.py 0000664 0000000 0000000 00000000525 15204752553 0023256 0 ustar 00root root 0000000 0000000 from __future__ import annotations
__version__ = "1.1.11"
from bleak_retry_connector import get_device
from .exceptions import CharacteristicMissingError
from .led_ble import BLEAK_EXCEPTIONS, LEDBLE, LEDBLEState
__all__ = [
"BLEAK_EXCEPTIONS",
"CharacteristicMissingError",
"LEDBLE",
"LEDBLEState",
"get_device",
]
Bluetooth-Devices-led-ble-4c385cb/src/led_ble/const.py 0000664 0000000 0000000 00000001210 15204752553 0022635 0 ustar 00root root 0000000 0000000 BASE_UUID_FORMAT = "0000{}-0000-1000-8000-00805f9b34fb"
# "ff01" - 0x97 socket - LEDnetWF010097DAB37A, LEDnetWF01001C49D272
# "ffd4" - Triones:B30200000459C - legacy
STATE_COMMAND = b"\xef\x01\x77"
class CharacteristicMissingError(Exception):
"""Raised when a characteristic is missing."""
# "ffe5" potentially invalid, try last
POSSIBLE_WRITE_CHARACTERISTIC_UUIDS = [
BASE_UUID_FORMAT.format(part) for part in ["ff01", "ffd5", "ffd9", "ffe9", "ffe5"]
]
POSSIBLE_READ_CHARACTERISTIC_UUIDS = [
BASE_UUID_FORMAT.format(part) for part in ["ff02", "ffd0", "ffd4", "ffe0", "ffe4"]
]
QUERY_STATE_BYTES = bytearray([0xEF, 0x01, 0x77])
Bluetooth-Devices-led-ble-4c385cb/src/led_ble/exceptions.py 0000664 0000000 0000000 00000000140 15204752553 0023671 0 ustar 00root root 0000000 0000000 class CharacteristicMissingError(Exception):
"""Raised when a characteristic is missing."""
Bluetooth-Devices-led-ble-4c385cb/src/led_ble/led_ble.py 0000664 0000000 0000000 00000056775 15204752553 0023127 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import asyncio
import colorsys
import logging
from collections.abc import Callable, Coroutine
from dataclasses import replace
from typing import Any, TypeVar
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
from bleak.backends.service import BleakGATTCharacteristic, BleakGATTServiceCollection
from bleak.exc import BleakDBusError
from bleak_retry_connector import BLEAK_RETRY_EXCEPTIONS as BLEAK_EXCEPTIONS
from bleak_retry_connector import (
BleakClientWithServiceCache,
BleakError,
BleakNotFoundError,
establish_connection,
retry_bluetooth_connection_error,
)
from flux_led.base_device import PROTOCOL_NAME_TO_CLS, PROTOCOL_TYPES
from flux_led.const import LevelWriteMode
from flux_led.pattern import EFFECT_ID_NAME, EFFECT_LIST, PresetPattern
from flux_led.utils import rgbw_brightness
from led_ble.model_db import LEDBLEModel
from .const import (
POSSIBLE_READ_CHARACTERISTIC_UUIDS,
POSSIBLE_WRITE_CHARACTERISTIC_UUIDS,
STATE_COMMAND,
)
from .exceptions import CharacteristicMissingError
from .model_db import get_model
from .models import LEDBLEState
from .util import asyncio_timeout
BLEAK_BACKOFF_TIME = 0.25
__version__ = "0.5.0"
WrapFuncType = TypeVar("WrapFuncType", bound=Callable[..., Any])
DISCONNECT_DELAY = 120
RETRY_BACKOFF_EXCEPTIONS = (BleakDBusError,)
_LOGGER = logging.getLogger(__name__)
DEFAULT_ATTEMPTS = 3
DREAM_EFFECTS = {f"Effect {i + 1}": i for i in range(0, 255)}
DREAM_EFFECT_LIST = list(DREAM_EFFECTS)
class LEDBLE:
def __init__(
self, ble_device: BLEDevice, advertisement_data: AdvertisementData | None = None
) -> None:
"""Init the LEDBLE."""
self._ble_device = ble_device
self._advertisement_data = advertisement_data
self._operation_lock = asyncio.Lock()
self._state = LEDBLEState()
self._connect_lock: asyncio.Lock = asyncio.Lock()
self._read_char: BleakGATTCharacteristic | None = None
self._write_char: BleakGATTCharacteristic | None = None
self._disconnect_timer: asyncio.TimerHandle | None = None
self._background_tasks: set[asyncio.Task[Any]] = set()
self._client: BleakClientWithServiceCache | None = None
self._expected_disconnect = False
self.loop = asyncio.get_running_loop()
self._callbacks: list[Callable[[LEDBLEState], None]] = []
self._model_data: LEDBLEModel | None = None
self._protocol: PROTOCOL_TYPES | None = None
self._resolve_protocol_event = asyncio.Event()
def set_ble_device_and_advertisement_data(
self, ble_device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
"""Set the ble device."""
self._ble_device = ble_device
self._advertisement_data = advertisement_data
@property
def address(self) -> str:
"""Return the address."""
return self._ble_device.address
@property
def _address(self) -> str:
"""Return the address."""
return self._ble_device.address
@property
def model_data(self) -> LEDBLEModel:
"""Return the model data."""
assert self._model_data is not None # nosec
return self._model_data
@property
def name(self) -> str:
"""Get the name of the device."""
return self._ble_device.name or self._ble_device.address
@property
def rssi(self) -> int | None:
"""Get the rssi of the device."""
if self._advertisement_data:
return self._advertisement_data.rssi
return None
@property
def state(self) -> LEDBLEState:
"""Return the state."""
return self._state
@property
def rgb(self) -> tuple[int, int, int]:
return self._state.rgb
@property
def w(self) -> int:
return self._state.w
@property
def rgb_unscaled(self) -> tuple[int, int, int]:
"""Return the unscaled RGB."""
r, g, b = self.rgb
hsv = colorsys.rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
r_p, g_p, b_p = colorsys.hsv_to_rgb(hsv[0], hsv[1], 1)
return round(r_p * 255), round(g_p * 255), round(b_p * 255)
@property
def on(self) -> bool:
return self._state.power
@property
def brightness(self) -> int:
"""Return current brightness 0-255."""
if self.w:
return self.w
r, g, b = self.rgb
_, _, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)
return int(v * 255)
async def update(self) -> None:
"""Update the LEDBLE."""
await self._ensure_connected()
await self._resolve_protocol()
_LOGGER.debug("%s: Updating", self.name)
assert self._protocol is not None # nosec
command = self._protocol.construct_state_query()
await self._send_command([command])
async def turn_on(self) -> None:
"""Turn on."""
_LOGGER.debug("%s: Turn on", self.name)
assert self._protocol is not None # nosec
await self._send_command(self._protocol.construct_state_change(True))
self._state = replace(self._state, power=True)
self._fire_callbacks()
async def turn_off(self) -> None:
"""Turn off."""
_LOGGER.debug("%s: Turn off", self.name)
assert self._protocol is not None # nosec
await self._send_command(self._protocol.construct_state_change(False))
self._state = replace(self._state, power=False)
self._fire_callbacks()
async def set_brightness(self, brightness: int) -> None:
"""Set the brightness."""
_LOGGER.debug("%s: Set brightness: %s", self.name, brightness)
effect = self.effect
if effect:
effect_brightness = round(brightness / 255 * 100)
await self.async_set_effect(effect, self.speed, effect_brightness)
return
if self.w:
await self.set_white(brightness)
return
await self.set_rgb(self.rgb_unscaled, brightness)
async def set_rgb(
self, rgb: tuple[int, int, int], brightness: int | None = None
) -> None:
"""Set rgb."""
_LOGGER.debug("%s: Set rgb: %s brightness: %s", self.name, rgb, brightness)
for value in rgb:
if not 0 <= value <= 255:
raise ValueError(f"Value {value} is outside the valid range of 0-255")
if brightness is not None:
rgb = self._calculate_brightness(rgb, brightness)
_LOGGER.debug("%s: Set rgb after brightness: %s", self.name, rgb)
assert self._protocol is not None # nosec
r, g, b = rgb
command = self._protocol.construct_levels_change(
persist=True,
red=r,
green=g,
blue=b,
warm_white=None,
cool_white=None,
write_mode=LevelWriteMode.COLORS,
)
await self._send_command(command)
self._state = replace(
self._state,
rgb=rgb,
w=0,
preset_pattern=1 if self.dream else self.preset_pattern_num,
)
self._fire_callbacks()
async def set_rgbw(
self, rgbw: tuple[int, int, int, int], brightness: int | None = None
) -> None:
"""Set rgbw."""
_LOGGER.debug("%s: Set rgbw: %s brightness: %s", self.name, rgbw, brightness)
for value in rgbw:
if not 0 <= value <= 255:
raise ValueError(f"Value {value} is outside the valid range of 0-255")
r, g, b, w = rgbw_brightness(rgbw, brightness)
_LOGGER.debug("%s: Set rgbw after brightness: %s", self.name, rgbw)
assert self._protocol is not None # nosec
command = self._protocol.construct_levels_change(
persist=True,
red=r,
green=g,
blue=b,
warm_white=w,
cool_white=None,
write_mode=LevelWriteMode.ALL,
)
await self._send_command(command)
self._state = replace(
self._state,
rgb=(rgbw[0], rgbw[1], rgbw[2]),
w=rgbw[3],
preset_pattern=1 if self.dream else self.preset_pattern_num,
)
self._fire_callbacks()
async def set_white(self, brightness: int) -> None:
"""Set rgb."""
_LOGGER.debug("%s: Set white: %s", self.name, brightness)
if not 0 <= brightness <= 255:
raise ValueError(f"Value {brightness} is outside the valid range of 0-255")
assert self._protocol is not None # nosec
command = self._protocol.construct_levels_change(
persist=True,
red=0,
green=0,
blue=0,
warm_white=brightness,
cool_white=None,
write_mode=LevelWriteMode.WHITES,
)
await self._send_command(command)
self._state = replace(
self._state,
rgb=(0, 0, 0),
w=brightness,
preset_pattern=1 if self.dream else self.preset_pattern_num,
)
self._fire_callbacks()
def _generate_preset_pattern(
self, pattern: int, speed: int, brightness: int
) -> bytes:
"""Generate the preset pattern protocol bytes."""
if self.dream:
# TODO: move this to the protocol
brightness = int(brightness * 255 / 100)
speed = int(speed * 255 / 100)
return bytes([0x9E, 0x00, pattern, speed, brightness, 0x00, 0xE9])
PresetPattern.valid_or_raise(pattern)
if not (1 <= brightness <= 100):
raise ValueError("Brightness must be between 1 and 100")
assert self._protocol is not None # nosec
return bytes(
self._protocol.construct_preset_pattern(pattern, speed, brightness)
)
async def async_set_preset_pattern(
self, effect: int, speed: int, brightness: int = 100
) -> None:
"""Set a preset pattern on the device."""
command = self._generate_preset_pattern(effect, speed, brightness)
await self._send_command(bytes(command))
if self.dream:
self._state = replace(self._state, preset_pattern=0, mode=effect)
else:
self._state = replace(self._state, preset_pattern=effect)
self._fire_callbacks()
async def async_set_effect(
self, effect: str, speed: int, brightness: int = 100
) -> None:
"""Set an effect."""
await self.async_set_preset_pattern(
self._effect_to_pattern(effect), speed, brightness
)
async def stop(self) -> None:
"""Stop the LEDBLE."""
_LOGGER.debug("%s: Stop", self.name)
await self._execute_disconnect()
def _calculate_brightness(
self, rgb: tuple[int, int, int], level: int
) -> tuple[int, int, int]:
hsv = colorsys.rgb_to_hsv(*rgb)
r, g, b = colorsys.hsv_to_rgb(hsv[0], hsv[1], level)
return int(r), int(g), int(b)
def _fire_callbacks(self) -> None:
"""Fire the callbacks."""
for callback in self._callbacks:
callback(self._state)
def register_callback(
self, callback: Callable[[LEDBLEState], None]
) -> Callable[[], None]:
"""Register a callback to be called when the state changes."""
def unregister_callback() -> None:
self._callbacks.remove(callback)
self._callbacks.append(callback)
return unregister_callback
async def _ensure_connected(self) -> None:
"""Ensure connection to device is established."""
if self._connect_lock.locked():
_LOGGER.debug(
"%s: Connection already in progress, waiting for it to complete; RSSI: %s",
self.name,
self.rssi,
)
if self._client and self._client.is_connected:
self._reset_disconnect_timer()
return
async with self._connect_lock:
# Check again while holding the lock
if self._client and self._client.is_connected:
self._reset_disconnect_timer()
return
_LOGGER.debug("%s: Connecting; RSSI: %s", self.name, self.rssi)
for attempt in range(2):
client = await establish_connection(
BleakClientWithServiceCache,
self._ble_device,
self.name,
self._disconnected,
use_services_cache=True,
ble_device_callback=lambda: self._ble_device,
)
_LOGGER.debug("%s: Connected; RSSI: %s", self.name, self.rssi)
if self._resolve_characteristics(client.services):
# Supported characteristics found
break
else:
if attempt == 0:
# Try to handle services failing to load
await client.clear_cache()
await client.disconnect()
continue
await client.disconnect()
raise CharacteristicMissingError(
"Failed to find supported characteristics, device may not be supported"
)
self._client = client
self._reset_disconnect_timer()
_LOGGER.debug(
"%s: Subscribe to notifications; RSSI: %s", self.name, self.rssi
)
await client.start_notify(self._read_char, self._notification_handler)
if not self._protocol:
await self._resolve_protocol()
@property
def model_num(self) -> int:
"""Return the model num."""
return self._state.model_num
@property
def version_num(self) -> int:
"""Return the version num."""
return self._state.version_num
@property
def preset_pattern_num(self) -> int:
"""Return the preset_pattern."""
return self._state.preset_pattern
@property
def mode(self) -> int:
"""Return the mode."""
return self._state.mode
@property
def speed(self) -> int:
"""Return the speed."""
return self._state.speed
def _effect_to_pattern(self, effect: str) -> int:
"""Convert an effect to a pattern code."""
if self.dream:
if effect not in DREAM_EFFECTS:
raise ValueError(f"Effect {effect} is not valid")
return DREAM_EFFECTS[effect]
return PresetPattern.str_to_val(effect)
@property
def effect_list(self) -> list[str]:
"""Return the list of available effects."""
if self.dream:
return DREAM_EFFECT_LIST
return EFFECT_LIST
@property
def dream(self) -> bool:
"""Return if the device is a dream."""
return self.model_num in (0x10,) or (
self._advertisement_data is not None
and self._advertisement_data.local_name is not None
and self._advertisement_data.local_name.startswith("Dream")
)
@property
def effect(self) -> str | None:
"""Return the current effect."""
if self.dream and self.preset_pattern_num == 0:
return f"Effect {self.mode + 1}"
return self._named_effect
@property
def _named_effect(self) -> str | None:
"""Returns the named effect."""
return EFFECT_ID_NAME.get(self.preset_pattern_num)
def _notification_handler(self, _sender: int, data: bytearray) -> None:
"""Handle notification responses."""
_LOGGER.debug("%s: Notification received: %s", self.name, data.hex())
if len(data) == 4 and data[0] == 0xCC:
on = data[1] == 0x23
self._state = replace(self._state, power=on)
return
if len(data) < 11:
return
model_num = data[1]
on = data[2] == 0x23
preset_pattern = data[3]
mode = data[4]
speed = data[5]
r = data[6]
g = data[7]
b = data[8]
w = data[9]
version = data[10]
self._state = LEDBLEState(
on, (r, g, b), w, model_num, preset_pattern, mode, speed, version
)
_LOGGER.debug(
"%s: Notification received; RSSI: %s: %s %s",
self.name,
self.rssi,
data.hex(),
self._state,
)
if not self._resolve_protocol_event.is_set():
self._resolve_protocol_event.set()
self._model_data = get_model(model_num)
self._set_protocol(self._model_data.protocol_for_version_num(version))
self._fire_callbacks()
def _reset_disconnect_timer(self) -> None:
"""Reset disconnect timer."""
if self._disconnect_timer:
self._disconnect_timer.cancel()
self._expected_disconnect = False
self._disconnect_timer = self.loop.call_later(
DISCONNECT_DELAY, self._disconnect
)
def _disconnected(self, client: BleakClientWithServiceCache) -> None:
"""Disconnected callback."""
if self._expected_disconnect:
_LOGGER.debug(
"%s: Disconnected from device; RSSI: %s", self.name, self.rssi
)
return
_LOGGER.warning(
"%s: Device unexpectedly disconnected; RSSI: %s",
self.name,
self.rssi,
)
def _disconnect(self) -> None:
"""Disconnect from device."""
self._disconnect_timer = None
self._create_background_task(self._execute_timed_disconnect())
def _create_background_task(self, coro: Coroutine[Any, Any, None]) -> None:
"""Schedule a coroutine and keep a strong reference until it finishes.
asyncio only holds a weak reference to running tasks, so the task is
tracked in ``self._background_tasks`` to prevent it from being garbage
collected mid-flight. The done-callback discards it once it completes;
``set.discard`` is idempotent and tracks N concurrent tasks correctly.
"""
task = asyncio.create_task(coro)
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.discard)
async def _execute_timed_disconnect(self) -> None:
"""Execute timed disconnection."""
_LOGGER.debug(
"%s: Disconnecting after timeout of %s",
self.name,
DISCONNECT_DELAY,
)
await self._execute_disconnect()
async def _execute_disconnect(self) -> None:
"""Execute disconnection."""
async with self._connect_lock:
read_char = self._read_char
client = self._client
self._expected_disconnect = True
self._client = None
self._read_char = None
self._write_char = None
if client and client.is_connected:
if read_char:
try:
await client.stop_notify(read_char)
except BleakError:
_LOGGER.debug(
"%s: Failed to stop notifications", self.name, exc_info=True
)
await client.disconnect()
@retry_bluetooth_connection_error(DEFAULT_ATTEMPTS)
async def _send_command_locked(self, commands: list[bytes]) -> None:
"""Send command to device and read response."""
try:
await self._execute_command_locked(commands)
except BleakDBusError as ex:
# Disconnect so we can reset state and try again
await asyncio.sleep(BLEAK_BACKOFF_TIME)
_LOGGER.debug(
"%s: RSSI: %s; Backing off %ss; Disconnecting due to error: %s",
self.name,
self.rssi,
BLEAK_BACKOFF_TIME,
ex,
)
await self._execute_disconnect()
raise
except BleakError as ex:
# Disconnect so we can reset state and try again
_LOGGER.debug(
"%s: RSSI: %s; Disconnecting due to error: %s", self.name, self.rssi, ex
)
await self._execute_disconnect()
raise
async def _send_command(
self, commands: list[bytes] | bytes, retry: int | None = None
) -> None:
"""Send command to device and read response."""
await self._ensure_connected()
await self._resolve_protocol()
if not isinstance(commands, list):
commands = [commands]
await self._send_command_while_connected(commands, retry)
async def _send_command_while_connected(
self, commands: list[bytes], retry: int | None = None
) -> None:
"""Send command to device and read response."""
_LOGGER.debug(
"%s: Sending commands %s",
self.name,
[command.hex() for command in commands],
)
if self._operation_lock.locked():
_LOGGER.debug(
"%s: Operation already in progress, waiting for it to complete; RSSI: %s",
self.name,
self.rssi,
)
async with self._operation_lock:
try:
await self._send_command_locked(commands)
return
except BleakNotFoundError:
_LOGGER.error(
"%s: device not found, no longer in range, or poor RSSI: %s",
self.name,
self.rssi,
exc_info=True,
)
raise
except CharacteristicMissingError as ex:
_LOGGER.debug(
"%s: characteristic missing: %s; RSSI: %s",
self.name,
ex,
self.rssi,
exc_info=True,
)
raise
except BLEAK_EXCEPTIONS:
_LOGGER.debug("%s: communication failed", self.name, exc_info=True)
raise
raise RuntimeError("Unreachable")
async def _execute_command_locked(self, commands: list[bytes]) -> None:
"""Execute command and read response."""
assert self._client is not None # nosec
if not self._read_char:
raise CharacteristicMissingError("Read characteristic missing")
if not self._write_char:
raise CharacteristicMissingError("Write characteristic missing")
for command in commands:
await self._client.write_gatt_char(self._write_char, command, False)
def _resolve_characteristics(self, services: BleakGATTServiceCollection) -> bool:
"""Resolve characteristics."""
# Reset first so a partial resolve from a prior (now-disconnected)
# attempt can't satisfy the read-and-write check with stale objects.
self._read_char = None
self._write_char = None
for characteristic in POSSIBLE_READ_CHARACTERISTIC_UUIDS:
if char := services.get_characteristic(characteristic):
self._read_char = char
break
for characteristic in POSSIBLE_WRITE_CHARACTERISTIC_UUIDS:
if char := services.get_characteristic(characteristic):
self._write_char = char
break
return bool(self._read_char and self._write_char)
async def _resolve_protocol(self) -> None:
"""Resolve protocol."""
if self._resolve_protocol_event.is_set():
return
await self._send_command_while_connected([STATE_COMMAND])
async with asyncio_timeout(10):
await self._resolve_protocol_event.wait()
def _set_protocol(self, protocol: str) -> None:
cls = PROTOCOL_NAME_TO_CLS.get(protocol)
if cls is None:
raise ValueError(f"Invalid protocol: {protocol}")
self._protocol = cls()
Bluetooth-Devices-led-ble-4c385cb/src/led_ble/model_db.py 0000664 0000000 0000000 00000006711 15204752553 0023267 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from dataclasses import dataclass
from flux_led.const import COLOR_MODE_RGBW, COLOR_MODES_RGB_W
from flux_led.models_db import MinVersionProtocol
from flux_led.protocol import PROTOCOL_LEDENET_ORIGINAL_RGBW
DEFAULT_MODEL = 0xE3
@dataclass(frozen=True)
class LEDBLEModel:
model_num: int # The model number aka byte 1
models: list[str] # The model names from discovery
description: str # Description of the model ({type} {color_mode})
protocols: list[
MinVersionProtocol
] # The device protocols, must be ordered highest version to lowest version
color_modes: set[
str
] # The color modes to use if there is no mode_to_color_mode_mapping
def protocol_for_version_num(self, version_num: int) -> str:
protocol = self.protocols[-1].protocol
for min_version_protocol in self.protocols:
if version_num >= min_version_protocol.min_version:
protocol = min_version_protocol.protocol
break
return protocol
MODELS = [
LEDBLEModel(
model_num=0x04,
models=["Triones:C10511000166"],
description="Controller RGB&W",
protocols=[
MinVersionProtocol(0, PROTOCOL_LEDENET_ORIGINAL_RGBW),
],
color_modes={COLOR_MODE_RGBW}, # Formerly rgbwcapable
),
LEDBLEModel(
model_num=0x10,
models=["Dream~MAC"],
description="Controller Dream",
protocols=[
MinVersionProtocol(0, PROTOCOL_LEDENET_ORIGINAL_RGBW),
],
color_modes=COLOR_MODES_RGB_W, # Formerly rgbwcapable
),
LEDBLEModel(
model_num=0x15,
models=["LEDBlue-C2AF4BD5"],
description="Bulb RGB/W",
protocols=[
MinVersionProtocol(0, PROTOCOL_LEDENET_ORIGINAL_RGBW),
],
color_modes=COLOR_MODES_RGB_W, # Formerly rgbwcapable
),
LEDBLEModel(
model_num=0x54,
models=["LEDBLE-DE1254F9"],
description="Controller RGB&W",
protocols=[
MinVersionProtocol(0, PROTOCOL_LEDENET_ORIGINAL_RGBW),
],
color_modes=COLOR_MODES_RGB_W, # Formerly rgbwcapable
),
LEDBLEModel(
model_num=0xE3,
models=["QHM-095F"],
description="Controller RGB/W",
protocols=[
MinVersionProtocol(0, PROTOCOL_LEDENET_ORIGINAL_RGBW),
],
color_modes=COLOR_MODES_RGB_W, # Formerly rgbwcapable
),
]
MODEL_MAP: dict[int, LEDBLEModel] = {model.model_num: model for model in MODELS}
def get_model(model_num: int, fallback_protocol: str | None = None) -> LEDBLEModel:
"""Return the LEDNETModel for the model_num."""
return MODEL_MAP.get(
model_num,
_unknown_ledble_model(
model_num, fallback_protocol or PROTOCOL_LEDENET_ORIGINAL_RGBW
),
)
def is_known_model(model_num: int) -> bool:
"""Return true of the model is known."""
return model_num in MODEL_MAP
UNKNOWN_MODEL = "Unknown Model"
def _unknown_ledble_model(model_num: int, fallback_protocol: str) -> LEDBLEModel:
"""Create a LEDNETModel for an unknown model_num."""
return LEDBLEModel(
model_num=model_num,
models=[],
description=UNKNOWN_MODEL,
protocols=[MinVersionProtocol(0, fallback_protocol)],
color_modes=COLOR_MODES_RGB_W,
)
def get_model_description(model_num: int) -> str:
"""Return the description for a model."""
return get_model(model_num).description
Bluetooth-Devices-led-ble-4c385cb/src/led_ble/models.py 0000664 0000000 0000000 00000000465 15204752553 0023005 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class LEDBLEState:
power: bool = False
rgb: tuple[int, int, int] = (0, 0, 0)
w: int = 0
model_num: int = 0
preset_pattern: int = 0
mode: int = 0
speed: int = 0
version_num: int = 0
Bluetooth-Devices-led-ble-4c385cb/src/led_ble/py.typed 0000664 0000000 0000000 00000000000 15204752553 0022630 0 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/src/led_ble/util.py 0000664 0000000 0000000 00000000357 15204752553 0022477 0 ustar 00root root 0000000 0000000 """Utils."""
from __future__ import annotations
import sys
if sys.version_info[:2] < (3, 11):
from async_timeout import timeout as asyncio_timeout # noqa: F401
else:
from asyncio import timeout as asyncio_timeout # noqa: F401
Bluetooth-Devices-led-ble-4c385cb/tests/ 0000775 0000000 0000000 00000000000 15204752553 0020130 5 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/tests/__init__.py 0000664 0000000 0000000 00000000000 15204752553 0022227 0 ustar 00root root 0000000 0000000 Bluetooth-Devices-led-ble-4c385cb/tests/conftest.py 0000664 0000000 0000000 00000005107 15204752553 0022332 0 ustar 00root root 0000000 0000000 """Shared test fixtures for led-ble.
The library is async and constructs ``asyncio`` primitives in ``LEDBLE.__init__``
(which calls ``asyncio.get_running_loop()``). Rather than pull in a plugin we
drive a single explicit event loop per test: the ``loop`` fixture exposes it,
``make_led`` constructs an ``LEDBLE`` *inside* a running loop, and async methods
are exercised with ``loop.run_until_complete(...)``.
"""
from __future__ import annotations
import asyncio
from collections.abc import Iterator
from typing import cast
from collections.abc import Callable
import pytest
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
from led_ble.led_ble import LEDBLE
class FakeBLEDevice:
"""Minimal stand-in for ``bleak.backends.device.BLEDevice``."""
def __init__(
self, address: str = "AA:BB:CC:DD:EE:FF", name: str | None = "LEDnet"
) -> None:
self.address = address
self.name = name
class FakeAdvertisement:
"""Minimal stand-in for ``AdvertisementData`` (only fields we read)."""
def __init__(self, rssi: int = -60, local_name: str | None = None) -> None:
self.rssi = rssi
self.local_name = local_name
class FakeServices:
"""Stand-in for ``BleakGATTServiceCollection`` characteristic lookup."""
def __init__(self, chars: dict[str, object] | None = None) -> None:
self._chars = chars or {}
def get_characteristic(self, uuid: str) -> object | None:
return self._chars.get(uuid)
@pytest.fixture
def loop() -> Iterator[asyncio.AbstractEventLoop]:
"""A dedicated event loop, set as current for the duration of the test."""
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
try:
yield new_loop
finally:
new_loop.close()
asyncio.set_event_loop(None)
@pytest.fixture
def make_led(loop: asyncio.AbstractEventLoop) -> Callable[..., LEDBLE]:
"""Factory that builds an ``LEDBLE`` bound to the test's event loop."""
def _make(
name: str | None = "LEDnet",
address: str = "AA:BB:CC:DD:EE:FF",
advertisement: FakeAdvertisement | None = None,
) -> LEDBLE:
device = cast(BLEDevice, FakeBLEDevice(address, name))
adv = cast("AdvertisementData | None", advertisement)
async def _construct() -> LEDBLE:
return LEDBLE(device, adv)
return loop.run_until_complete(_construct())
return _make
@pytest.fixture
def led(make_led: Callable[..., LEDBLE]) -> LEDBLE:
"""A default ``LEDBLE`` instance with no advertisement data."""
return make_led()
Bluetooth-Devices-led-ble-4c385cb/tests/test_init.py 0000664 0000000 0000000 00000000064 15204752553 0022504 0 ustar 00root root 0000000 0000000 import led_ble
def test_add():
assert led_ble
Bluetooth-Devices-led-ble-4c385cb/tests/test_led_ble.py 0000664 0000000 0000000 00000041614 15204752553 0023135 0 ustar 00root root 0000000 0000000 """Tests for the LEDBLE device class.
These cover the pure / mockable logic: properties, the notification parser,
effect handling, command construction dispatch, validation, and the small
amount of connection bookkeeping that does not require a real bleak backend.
"""
from __future__ import annotations
from dataclasses import replace
from unittest.mock import AsyncMock, Mock
import pytest
from flux_led.const import LevelWriteMode
from flux_led.pattern import EFFECT_ID_NAME, EFFECT_LIST, PresetPattern
from led_ble import LEDBLEState
from led_ble.const import (
POSSIBLE_READ_CHARACTERISTIC_UUIDS,
POSSIBLE_WRITE_CHARACTERISTIC_UUIDS,
)
from led_ble.exceptions import CharacteristicMissingError
from led_ble.led_ble import DREAM_EFFECT_LIST, DREAM_EFFECTS
from .conftest import FakeAdvertisement, FakeBLEDevice, FakeServices
# A model_num / version that resolves to a real flux_led protocol class.
KNOWN_MODEL = 0xE3
def _protocol_mock() -> Mock:
"""A protocol whose construct_* methods return sentinel command lists."""
protocol = Mock()
protocol.construct_state_change.return_value = [b"\x01"]
protocol.construct_levels_change.return_value = [b"\x02"]
protocol.construct_state_query.return_value = b"\x03"
protocol.construct_preset_pattern.return_value = bytearray(b"\x04")
return protocol
# ---------------------------------------------------------------------------
# Identity / addressing properties
# ---------------------------------------------------------------------------
def test_address_property(led):
assert led.address == "AA:BB:CC:DD:EE:FF"
def test_name_uses_device_name(led):
assert led.name == "LEDnet"
def test_name_falls_back_to_address(make_led):
led = make_led(name=None)
assert led.name == "AA:BB:CC:DD:EE:FF"
def test_rssi_none_without_advertisement(led):
assert led.rssi is None
def test_rssi_from_advertisement(make_led):
led = make_led(advertisement=FakeAdvertisement(rssi=-42))
assert led.rssi == -42
def test_set_ble_device_and_advertisement_data(led):
new_device = FakeBLEDevice(address="11:22:33:44:55:66", name="New")
adv = FakeAdvertisement(rssi=-10)
led.set_ble_device_and_advertisement_data(new_device, adv)
assert led.address == "11:22:33:44:55:66"
assert led.name == "New"
assert led.rssi == -10
# ---------------------------------------------------------------------------
# State-derived properties
# ---------------------------------------------------------------------------
def test_state_property_defaults(led):
assert led.state == LEDBLEState()
assert led.on is False
assert led.rgb == (0, 0, 0)
assert led.w == 0
def test_color_and_meta_properties_reflect_state(led):
led._state = LEDBLEState(
power=True,
rgb=(10, 20, 30),
w=40,
model_num=0x54,
preset_pattern=37,
mode=2,
speed=3,
version_num=8,
)
assert led.on is True
assert led.rgb == (10, 20, 30)
assert led.w == 40
assert led.model_num == 0x54
assert led.preset_pattern_num == 37
assert led.mode == 2
assert led.speed == 3
assert led.version_num == 8
def test_brightness_uses_white_when_present(led):
led._state = replace(led._state, w=123, rgb=(255, 0, 0))
assert led.brightness == 123
def test_brightness_uses_rgb_value_when_no_white(led):
led._state = replace(led._state, w=0, rgb=(255, 0, 0))
# value channel of pure red at full saturation is 255.
assert led.brightness == 255
def test_brightness_zero_when_off_and_dark(led):
assert led.brightness == 0
def test_rgb_unscaled_normalizes_to_full_value(led):
led._state = replace(led._state, rgb=(128, 0, 0))
# Scaling a dim red back up to full brightness yields pure red.
assert led.rgb_unscaled == (255, 0, 0)
# ---------------------------------------------------------------------------
# Dream detection / effects
# ---------------------------------------------------------------------------
def test_dream_true_for_dream_model(led):
led._state = replace(led._state, model_num=0x10)
assert led.dream is True
def test_dream_true_from_advertisement_local_name(make_led):
led = make_led(advertisement=FakeAdvertisement(local_name="Dream-1234"))
assert led.dream is True
def test_dream_false_otherwise(led):
assert led.dream is False
def test_effect_list_normal(led):
assert led.effect_list == EFFECT_LIST
def test_effect_list_dream(led):
led._state = replace(led._state, model_num=0x10)
assert led.effect_list == DREAM_EFFECT_LIST
assert len(DREAM_EFFECT_LIST) == 255
def test_effect_named_for_known_preset(led):
preset_id, name = next(iter(EFFECT_ID_NAME.items()))
led._state = replace(led._state, preset_pattern=preset_id)
assert led.effect == name
def test_effect_none_for_unknown_preset(led):
led._state = replace(led._state, preset_pattern=0)
assert led.effect is None
def test_effect_dream_reports_mode_based_name(led):
led._state = replace(led._state, model_num=0x10, preset_pattern=0, mode=4)
assert led.effect == "Effect 5"
def test_effect_to_pattern_normal(led):
name = EFFECT_LIST[0]
assert led._effect_to_pattern(name) == PresetPattern.str_to_val(name)
def test_effect_to_pattern_dream_valid(led):
led._state = replace(led._state, model_num=0x10)
assert led._effect_to_pattern("Effect 1") == DREAM_EFFECTS["Effect 1"]
def test_effect_to_pattern_dream_invalid_raises(led):
led._state = replace(led._state, model_num=0x10)
with pytest.raises(ValueError, match="not valid"):
led._effect_to_pattern("Nope")
# ---------------------------------------------------------------------------
# Notification handler (pure packet parsing)
# ---------------------------------------------------------------------------
def test_notification_power_on_short_packet(led):
led._notification_handler(0, bytearray([0xCC, 0x23, 0x00, 0x00]))
assert led.on is True
def test_notification_power_off_short_packet(led):
led._state = replace(led._state, power=True)
led._notification_handler(0, bytearray([0xCC, 0x24, 0x00, 0x00]))
assert led.on is False
def test_notification_ignores_too_short_packet(led):
before = led.state
led._notification_handler(0, bytearray([0x81, 0x00, 0x23, 0x01, 0x02]))
assert led.state == before
def test_notification_full_packet_parses_all_fields(led):
packet = bytearray([0x81, KNOWN_MODEL, 0x23, 0x01, 0x02, 0x03, 10, 20, 30, 40, 5])
led._notification_handler(0, packet)
state = led.state
assert state.power is True
assert state.model_num == KNOWN_MODEL
assert state.preset_pattern == 0x01
assert state.mode == 0x02
assert state.speed == 0x03
assert state.rgb == (10, 20, 30)
assert state.w == 40
assert state.version_num == 5
def test_notification_resolves_protocol_once(led):
packet = bytearray([0x81, KNOWN_MODEL, 0x23, 0x01, 0x02, 0x03, 10, 20, 30, 40, 5])
led._notification_handler(0, packet)
assert led._resolve_protocol_event.is_set()
assert led._protocol is not None
assert led.model_data.model_num == KNOWN_MODEL
def test_notification_fires_callbacks(led):
received: list[LEDBLEState] = []
led.register_callback(received.append)
packet = bytearray([0x81, KNOWN_MODEL, 0x23, 0x01, 0x02, 0x03, 10, 20, 30, 40, 5])
led._notification_handler(0, packet)
assert len(received) == 1
assert received[0].rgb == (10, 20, 30)
# ---------------------------------------------------------------------------
# Callbacks
# ---------------------------------------------------------------------------
def test_register_and_unregister_callback(led):
calls: list[LEDBLEState] = []
unregister = led.register_callback(calls.append)
led._fire_callbacks()
assert len(calls) == 1
unregister()
led._fire_callbacks()
assert len(calls) == 1
# ---------------------------------------------------------------------------
# Protocol resolution helpers
# ---------------------------------------------------------------------------
def test_set_protocol_valid(led):
led._set_protocol("LEDENET_ORIGINAL_RGBW")
assert led._protocol is not None
def test_set_protocol_invalid_raises(led):
with pytest.raises(ValueError, match="Invalid protocol"):
led._set_protocol("NOT_A_PROTOCOL")
# ---------------------------------------------------------------------------
# Characteristic resolution
# ---------------------------------------------------------------------------
def test_resolve_characteristics_found(led):
read_uuid = POSSIBLE_READ_CHARACTERISTIC_UUIDS[0]
write_uuid = POSSIBLE_WRITE_CHARACTERISTIC_UUIDS[0]
read_char, write_char = object(), object()
services = FakeServices({read_uuid: read_char, write_uuid: write_char})
assert led._resolve_characteristics(services) is True
assert led._read_char is read_char
assert led._write_char is write_char
def test_resolve_characteristics_missing(led):
assert led._resolve_characteristics(FakeServices({})) is False
assert led._read_char is None
assert led._write_char is None
def test_resolve_characteristics_resets_stale_chars(led):
"""A partial resolve must not leak a stale char into the next attempt.
Simulates the reconnect path: attempt 0 resolves only the read char,
attempt 1 sees services that only resolve the write char. Without a
reset, the stale read char from the dead client would satisfy the
read-and-write check and return True against mismatched clients.
"""
read_uuid = POSSIBLE_READ_CHARACTERISTIC_UUIDS[0]
write_uuid = POSSIBLE_WRITE_CHARACTERISTIC_UUIDS[0]
stale_read = object()
# Attempt 0: only the read char resolves -> partial, returns False.
assert led._resolve_characteristics(FakeServices({read_uuid: stale_read})) is False
assert led._read_char is stale_read
# Attempt 1: fresh services expose only the write char.
fresh_write = object()
assert (
led._resolve_characteristics(FakeServices({write_uuid: fresh_write})) is False
)
# The stale read char must have been cleared, not retained.
assert led._read_char is None
assert led._write_char is fresh_write
# ---------------------------------------------------------------------------
# Brightness math / preset generation
# ---------------------------------------------------------------------------
def test_calculate_brightness_returns_ints(led):
result = led._calculate_brightness((255, 0, 0), 128)
assert len(result) == 3
assert all(isinstance(v, int) for v in result)
def test_generate_preset_pattern_rejects_out_of_range_brightness(led):
led._set_protocol("LEDENET_ORIGINAL_RGBW")
valid_pattern = PresetPattern.str_to_val(EFFECT_LIST[0])
with pytest.raises(ValueError, match="between 1 and 100"):
led._generate_preset_pattern(valid_pattern, 50, 0)
with pytest.raises(ValueError, match="between 1 and 100"):
led._generate_preset_pattern(valid_pattern, 50, 101)
def test_generate_preset_pattern_normal(led):
led._set_protocol("LEDENET_ORIGINAL_RGBW")
valid_pattern = PresetPattern.str_to_val(EFFECT_LIST[0])
result = led._generate_preset_pattern(valid_pattern, 50, 100)
assert isinstance(result, (bytes, bytearray))
def test_generate_preset_pattern_dream(led):
led._state = replace(led._state, model_num=0x10)
result = led._generate_preset_pattern(5, 50, 100)
assert result[0] == 0x9E
assert result[2] == 5
assert result[-1] == 0xE9
# ---------------------------------------------------------------------------
# Async command methods (mock the transport)
# ---------------------------------------------------------------------------
def test_turn_on(loop, led):
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
loop.run_until_complete(led.turn_on())
assert led.on is True
led._send_command.assert_awaited_once_with([b"\x01"])
def test_turn_off(loop, led):
led._state = replace(led._state, power=True)
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
loop.run_until_complete(led.turn_off())
assert led.on is False
def test_set_rgb_updates_state(loop, led):
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
loop.run_until_complete(led.set_rgb((10, 20, 30)))
assert led.rgb == (10, 20, 30)
assert led.w == 0
led._protocol.construct_levels_change.assert_called_once()
_, kwargs = led._protocol.construct_levels_change.call_args
assert kwargs["write_mode"] == LevelWriteMode.COLORS
def test_set_rgb_rejects_out_of_range(loop, led):
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
with pytest.raises(ValueError, match="300 is outside"):
loop.run_until_complete(led.set_rgb((10, 20, 300)))
led._send_command.assert_not_awaited()
def test_set_rgbw_updates_state(loop, led):
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
loop.run_until_complete(led.set_rgbw((10, 20, 30, 40)))
assert led.rgb == (10, 20, 30)
assert led.w == 40
def test_set_rgbw_rejects_out_of_range(loop, led):
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
with pytest.raises(ValueError, match="999 is outside"):
loop.run_until_complete(led.set_rgbw((10, 20, 30, 999)))
led._send_command.assert_not_awaited()
def test_set_white_updates_state(loop, led):
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
loop.run_until_complete(led.set_white(200))
assert led.w == 200
assert led.rgb == (0, 0, 0)
def test_set_white_rejects_out_of_range(loop, led):
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
with pytest.raises(ValueError, match="256 is outside"):
loop.run_until_complete(led.set_white(256))
led._send_command.assert_not_awaited()
def test_set_brightness_uses_white_path(loop, led):
led._state = replace(led._state, w=100)
led.set_white = AsyncMock()
loop.run_until_complete(led.set_brightness(150))
led.set_white.assert_awaited_once_with(150)
def test_set_brightness_uses_rgb_path(loop, led):
led._state = replace(led._state, w=0, rgb=(255, 0, 0))
led.set_rgb = AsyncMock()
loop.run_until_complete(led.set_brightness(150))
led.set_rgb.assert_awaited_once()
def test_set_brightness_uses_effect_path(loop, led):
led._state = replace(led._state, w=0, preset_pattern=next(iter(EFFECT_ID_NAME)))
led.async_set_effect = AsyncMock()
loop.run_until_complete(led.set_brightness(255))
led.async_set_effect.assert_awaited_once()
def test_async_set_preset_pattern_normal_state(loop, led):
led._send_command = AsyncMock()
led._generate_preset_pattern = Mock(return_value=bytearray(b"\x04"))
loop.run_until_complete(led.async_set_preset_pattern(37, 50, 100))
assert led.preset_pattern_num == 37
def test_async_set_preset_pattern_dream_state(loop, led):
led._state = replace(led._state, model_num=0x10)
led._send_command = AsyncMock()
led._generate_preset_pattern = Mock(return_value=bytearray(b"\x04"))
loop.run_until_complete(led.async_set_preset_pattern(7, 50, 100))
assert led.preset_pattern_num == 0
assert led.mode == 7
def test_async_set_effect_dispatches_pattern(loop, led):
led.async_set_preset_pattern = AsyncMock()
name = EFFECT_LIST[0]
loop.run_until_complete(led.async_set_effect(name, 50, 100))
led.async_set_preset_pattern.assert_awaited_once_with(
PresetPattern.str_to_val(name), 50, 100
)
def test_update_sends_state_query(loop, led):
led._ensure_connected = AsyncMock()
led._resolve_protocol = AsyncMock()
led._protocol = _protocol_mock()
led._send_command = AsyncMock()
loop.run_until_complete(led.update())
led._send_command.assert_awaited_once_with([b"\x03"])
def test_stop_executes_disconnect(loop, led):
led._execute_disconnect = AsyncMock()
loop.run_until_complete(led.stop())
led._execute_disconnect.assert_awaited_once()
# ---------------------------------------------------------------------------
# Low-level command execution
# ---------------------------------------------------------------------------
def test_execute_command_locked_requires_read_char(loop, led):
led._client = Mock()
led._read_char = None
led._write_char = Mock()
with pytest.raises(CharacteristicMissingError, match="Read"):
loop.run_until_complete(led._execute_command_locked([b"\x01"]))
def test_execute_command_locked_requires_write_char(loop, led):
led._client = Mock()
led._read_char = Mock()
led._write_char = None
with pytest.raises(CharacteristicMissingError, match="Write"):
loop.run_until_complete(led._execute_command_locked([b"\x01"]))
def test_execute_command_locked_writes_each_command(loop, led):
client = Mock()
client.write_gatt_char = AsyncMock()
led._client = client
led._read_char = Mock()
led._write_char = Mock()
loop.run_until_complete(led._execute_command_locked([b"\x01", b"\x02"]))
assert client.write_gatt_char.await_count == 2
Bluetooth-Devices-led-ble-4c385cb/tests/test_led_ble_connection.py 0000664 0000000 0000000 00000036013 15204752553 0025351 0 ustar 00root root 0000000 0000000 """Tests for the connection / command-dispatch lifecycle.
These exercise the disconnect bookkeeping and the send pipeline by mocking the
bleak client and the lower-level locked sender, without a real BLE backend.
"""
from __future__ import annotations
import asyncio
from unittest.mock import AsyncMock, Mock
import pytest
from bleak.exc import BleakDBusError, BleakError
from bleak_retry_connector import BleakNotFoundError
from led_ble.const import STATE_COMMAND
from led_ble.exceptions import CharacteristicMissingError
from led_ble.led_ble import LEDBLE
# A model_num / version that resolves to a real flux_led protocol class.
KNOWN_MODEL = 0xE3
def test_internal_address_property(led):
assert led._address == led.address == "AA:BB:CC:DD:EE:FF"
def test_set_rgb_applies_brightness(loop, led):
led._protocol = Mock()
led._protocol.construct_levels_change.return_value = [b"\x02"]
led._send_command = AsyncMock()
loop.run_until_complete(led.set_rgb((255, 0, 0), brightness=64))
# Brightness scaling dims the stored color away from full red.
assert led.rgb != (255, 0, 0)
assert led.rgb[0] <= 64
# ---------------------------------------------------------------------------
# Disconnect timer / lifecycle
# ---------------------------------------------------------------------------
def test_reset_disconnect_timer_schedules_and_clears_expected_flag(loop, led):
led._expected_disconnect = True
led._reset_disconnect_timer()
try:
assert led._disconnect_timer is not None
assert led._expected_disconnect is False
finally:
led._disconnect_timer.cancel()
def test_reset_disconnect_timer_cancels_previous(loop, led):
led._reset_disconnect_timer()
first = led._disconnect_timer
led._reset_disconnect_timer()
try:
assert first.cancelled()
assert led._disconnect_timer is not first
finally:
led._disconnect_timer.cancel()
def test_disconnected_callback_expected(led):
led._expected_disconnect = True
# Should not raise; expected disconnects are logged at debug only.
led._disconnected(Mock())
def test_disconnected_callback_unexpected(led):
led._expected_disconnect = False
led._disconnected(Mock())
def test_disconnect_schedules_execution(loop, led):
led._execute_timed_disconnect = AsyncMock()
led._disconnect_timer = Mock()
async def run():
led._disconnect()
await asyncio.sleep(0)
loop.run_until_complete(run())
assert led._disconnect_timer is None
led._execute_timed_disconnect.assert_awaited_once()
def test_disconnect_holds_task_reference_until_done(loop, led):
"""The disconnect task must be referenced so it is not GC'd mid-flight."""
started = asyncio.Event()
release = asyncio.Event()
async def slow_disconnect() -> None:
started.set()
await release.wait()
led._execute_timed_disconnect = slow_disconnect
async def run() -> None:
led._disconnect()
await started.wait()
# While the coroutine is suspended, a strong reference is held.
assert len(led._background_tasks) == 1
(task,) = led._background_tasks
assert not task.done()
release.set()
await task
loop.run_until_complete(run())
# The done-callback discards the reference once the task completes.
assert led._background_tasks == set()
def test_execute_timed_disconnect_delegates(loop, led):
led._execute_disconnect = AsyncMock()
loop.run_until_complete(led._execute_timed_disconnect())
led._execute_disconnect.assert_awaited_once()
def test_execute_disconnect_with_connected_client(loop, led):
client = Mock()
client.is_connected = True
client.stop_notify = AsyncMock()
client.disconnect = AsyncMock()
read_char = Mock()
led._client = client
led._read_char = read_char
led._write_char = Mock()
loop.run_until_complete(led._execute_disconnect())
assert led._expected_disconnect is True
assert led._client is None
assert led._read_char is None
assert led._write_char is None
client.stop_notify.assert_awaited_once_with(read_char)
client.disconnect.assert_awaited_once()
def test_execute_disconnect_with_no_client(loop, led):
led._client = None
# Should be a no-op beyond resetting flags.
loop.run_until_complete(led._execute_disconnect())
assert led._expected_disconnect is True
assert led._client is None
# ---------------------------------------------------------------------------
# Send pipeline
# ---------------------------------------------------------------------------
def test_send_command_wraps_single_command_in_list(loop, led):
led._ensure_connected = AsyncMock()
led._resolve_protocol = AsyncMock()
led._send_command_while_connected = AsyncMock()
loop.run_until_complete(led._send_command(b"\x01"))
args, _ = led._send_command_while_connected.await_args
assert args[0] == [b"\x01"]
def test_send_command_passes_through_list(loop, led):
led._ensure_connected = AsyncMock()
led._resolve_protocol = AsyncMock()
led._send_command_while_connected = AsyncMock()
loop.run_until_complete(led._send_command([b"\x01", b"\x02"]))
args, _ = led._send_command_while_connected.await_args
assert args[0] == [b"\x01", b"\x02"]
def test_send_command_while_connected_success(loop, led):
led._send_command_locked = AsyncMock()
loop.run_until_complete(led._send_command_while_connected([b"\x01"]))
led._send_command_locked.assert_awaited_once_with([b"\x01"])
def test_send_command_while_connected_reraises_not_found(loop, led):
led._send_command_locked = AsyncMock(side_effect=BleakNotFoundError)
with pytest.raises(BleakNotFoundError):
loop.run_until_complete(led._send_command_while_connected([b"\x01"]))
def test_send_command_while_connected_reraises_characteristic_missing(loop, led):
led._send_command_locked = AsyncMock(
side_effect=CharacteristicMissingError("missing")
)
with pytest.raises(CharacteristicMissingError):
loop.run_until_complete(led._send_command_while_connected([b"\x01"]))
# ---------------------------------------------------------------------------
# Protocol resolution
# ---------------------------------------------------------------------------
def test_resolve_protocol_returns_early_when_resolved(loop, led):
led._resolve_protocol_event.set()
led._send_command_while_connected = AsyncMock()
loop.run_until_complete(led._resolve_protocol())
led._send_command_while_connected.assert_not_awaited()
def test_resolve_protocol_queries_then_waits(loop, led):
async def _send(_commands):
led._resolve_protocol_event.set()
led._send_command_while_connected = AsyncMock(side_effect=_send)
loop.run_until_complete(led._resolve_protocol())
led._send_command_while_connected.assert_awaited_once_with([STATE_COMMAND])
assert led._resolve_protocol_event.is_set()
# ---------------------------------------------------------------------------
# _ensure_connected
# ---------------------------------------------------------------------------
def test_ensure_connected_returns_early_when_already_connected(loop, led):
client = Mock()
client.is_connected = True
led._client = client
led._reset_disconnect_timer = Mock()
loop.run_until_complete(led._ensure_connected())
led._reset_disconnect_timer.assert_called_once()
# The existing client is kept; no reconnection attempt was made.
assert led._client is client
def test_ensure_connected_happy_path(loop, led, monkeypatch):
client = Mock()
client.is_connected = True
client.start_notify = AsyncMock()
client.services = Mock()
monkeypatch.setattr(
"led_ble.led_ble.establish_connection", AsyncMock(return_value=client)
)
led._resolve_characteristics = Mock(return_value=True)
led._resolve_protocol = AsyncMock()
led._protocol = None
loop.run_until_complete(led._ensure_connected())
try:
assert led._client is client
client.start_notify.assert_awaited_once()
led._resolve_protocol.assert_awaited_once()
finally:
if led._disconnect_timer:
led._disconnect_timer.cancel()
def test_ensure_connected_retries_then_raises_when_chars_missing(
loop, led, monkeypatch
):
client = Mock()
client.is_connected = True
client.clear_cache = AsyncMock()
client.disconnect = AsyncMock()
monkeypatch.setattr(
"led_ble.led_ble.establish_connection", AsyncMock(return_value=client)
)
led._resolve_characteristics = Mock(return_value=False)
with pytest.raises(CharacteristicMissingError):
loop.run_until_complete(led._ensure_connected())
# First attempt clears the service cache and reconnects, second gives up.
client.clear_cache.assert_awaited_once()
assert client.disconnect.await_count == 2
# ---------------------------------------------------------------------------
# Extra branch coverage
# ---------------------------------------------------------------------------
def test_notification_resolves_protocol_only_once(led):
packet = bytearray([0x81, KNOWN_MODEL, 0x23, 0x01, 0x02, 0x03, 10, 20, 30, 40, 5])
led._notification_handler(0, packet)
first_protocol = led._protocol
# A second packet must not re-resolve the protocol.
led._notification_handler(0, packet)
assert led._protocol is first_protocol
def test_execute_disconnect_swallows_stop_notify_error(loop, led):
client = Mock()
client.is_connected = True
client.stop_notify = AsyncMock(side_effect=BleakError("boom"))
client.disconnect = AsyncMock()
led._client = client
led._read_char = Mock()
# stop_notify failing must not prevent disconnect.
loop.run_until_complete(led._execute_disconnect())
client.disconnect.assert_awaited_once()
assert led._client is None
def test_send_command_while_connected_reraises_bleak_exceptions(loop, led):
led._send_command_locked = AsyncMock(side_effect=BleakError("comm failed"))
with pytest.raises(BleakError):
loop.run_until_complete(led._send_command_while_connected([b"\x01"]))
# ---------------------------------------------------------------------------
# _send_command_locked error recovery
#
# The locked sender must disconnect (to reset state for a later retry) and
# re-raise on a BLE error. The retry decorator wraps this method, so we
# neutralise asyncio.sleep to keep the backoff/retry delays out of the test.
# ---------------------------------------------------------------------------
def test_send_command_locked_disconnects_on_dbus_error(
loop: asyncio.AbstractEventLoop, led: LEDBLE, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(asyncio, "sleep", AsyncMock())
led._execute_command_locked = AsyncMock(
side_effect=BleakDBusError("org.bluez.Error.Failed", [])
)
led._execute_disconnect = AsyncMock()
with pytest.raises(BleakDBusError):
loop.run_until_complete(led._send_command_locked([b"\x01"]))
# A DBus error backs off then disconnects before propagating.
led._execute_disconnect.assert_awaited()
def test_send_command_locked_disconnects_on_bleak_error(
loop: asyncio.AbstractEventLoop, led: LEDBLE, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(asyncio, "sleep", AsyncMock())
led._execute_command_locked = AsyncMock(side_effect=BleakError("boom"))
led._execute_disconnect = AsyncMock()
with pytest.raises(BleakError):
loop.run_until_complete(led._send_command_locked([b"\x01"]))
led._execute_disconnect.assert_awaited()
# ---------------------------------------------------------------------------
# Lock guards (concurrency edge paths)
# ---------------------------------------------------------------------------
def test_ensure_connected_double_checked_lock(
loop: asyncio.AbstractEventLoop, led: LEDBLE, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A peer that connects while we wait for the lock is honoured on recheck.
Exercises the "connection already in progress" log and the second
``is_connected`` check performed *after* the lock is acquired.
"""
led._reset_disconnect_timer = Mock()
# If the post-lock recheck fails to short-circuit, we'd fall through to a
# real reconnect — make that loud rather than hanging.
monkeypatch.setattr(
"led_ble.led_ble.establish_connection",
AsyncMock(side_effect=AssertionError("should not reconnect")),
)
client = Mock()
client.is_connected = True
async def run() -> None:
await led._connect_lock.acquire()
task = asyncio.ensure_future(led._ensure_connected())
# Let the task log the in-progress branch and block acquiring the lock.
await asyncio.sleep(0)
# A concurrent connection lands before we release the lock.
led._client = client
led._connect_lock.release()
await task
loop.run_until_complete(run())
assert led._client is client
led._reset_disconnect_timer.assert_called()
def test_send_command_while_connected_waits_for_operation_lock(
loop: asyncio.AbstractEventLoop, led: LEDBLE
) -> None:
"""The operation-in-progress branch logs, then proceeds once the lock frees."""
led._send_command_locked = AsyncMock()
async def run() -> None:
await led._operation_lock.acquire()
task = asyncio.ensure_future(led._send_command_while_connected([b"\x01"]))
# Let the task log the in-progress branch and block on the lock.
await asyncio.sleep(0)
led._operation_lock.release()
await task
loop.run_until_complete(run())
led._send_command_locked.assert_awaited_once_with([b"\x01"])
# ---------------------------------------------------------------------------
# _ensure_connected / _execute_disconnect extra branches
# ---------------------------------------------------------------------------
def test_ensure_connected_skips_protocol_resolve_when_already_set(
loop: asyncio.AbstractEventLoop, led: LEDBLE, monkeypatch: pytest.MonkeyPatch
) -> None:
client = Mock()
client.is_connected = True
client.start_notify = AsyncMock()
client.services = Mock()
monkeypatch.setattr(
"led_ble.led_ble.establish_connection", AsyncMock(return_value=client)
)
led._resolve_characteristics = Mock(return_value=True)
led._resolve_protocol = AsyncMock()
led._protocol = Mock() # already resolved from a prior connection
loop.run_until_complete(led._ensure_connected())
try:
led._resolve_protocol.assert_not_awaited()
finally:
if led._disconnect_timer:
led._disconnect_timer.cancel()
def test_execute_disconnect_without_read_char_skips_stop_notify(
loop: asyncio.AbstractEventLoop, led: LEDBLE
) -> None:
client = Mock()
client.is_connected = True
client.stop_notify = AsyncMock()
client.disconnect = AsyncMock()
led._client = client
led._read_char = None # no characteristic to unsubscribe from
loop.run_until_complete(led._execute_disconnect())
client.stop_notify.assert_not_awaited()
client.disconnect.assert_awaited_once()
assert led._client is None
Bluetooth-Devices-led-ble-4c385cb/tests/test_model_db.py 0000664 0000000 0000000 00000006113 15204752553 0023307 0 ustar 00root root 0000000 0000000 """Tests for the model database (pure, hardware-free logic)."""
from __future__ import annotations
from flux_led.models_db import MinVersionProtocol
from flux_led.protocol import PROTOCOL_LEDENET_ORIGINAL_RGBW
from led_ble.model_db import (
DEFAULT_MODEL,
MODEL_MAP,
MODELS,
UNKNOWN_MODEL,
LEDBLEModel,
get_model,
get_model_description,
is_known_model,
)
def test_known_models_are_indexed_by_number():
for model in MODELS:
assert MODEL_MAP[model.model_num] is model
def test_get_model_returns_known_model():
model = get_model(0x04)
assert model.model_num == 0x04
assert model.models == ["Triones:C10511000166"]
assert model.description == "Controller RGB&W"
def test_get_model_unknown_returns_fallback_with_default_protocol():
model = get_model(0xAB)
assert model.model_num == 0xAB
assert model.models == []
assert model.description == UNKNOWN_MODEL
assert model.protocols == [MinVersionProtocol(0, PROTOCOL_LEDENET_ORIGINAL_RGBW)]
def test_get_model_unknown_honors_custom_fallback_protocol():
model = get_model(0xAB, fallback_protocol="LEDENET_ORIGINAL")
assert model.protocols == [MinVersionProtocol(0, "LEDENET_ORIGINAL")]
def test_is_known_model():
assert is_known_model(0x04) is True
assert is_known_model(DEFAULT_MODEL) is True
assert is_known_model(0xAB) is False
def test_get_model_description_known_and_unknown():
assert get_model_description(0x15) == "Bulb RGB/W"
assert get_model_description(0xAB) == UNKNOWN_MODEL
def test_protocol_for_version_num_single_protocol():
model = get_model(0xE3)
# Only one protocol registered (min_version 0) -> always selected.
assert model.protocol_for_version_num(0) == PROTOCOL_LEDENET_ORIGINAL_RGBW
assert model.protocol_for_version_num(99) == PROTOCOL_LEDENET_ORIGINAL_RGBW
def test_protocol_for_version_num_selects_by_min_version():
model = LEDBLEModel(
model_num=0x99,
models=["test"],
description="test",
protocols=[
MinVersionProtocol(5, "LEDENET_ORIGINAL_RGBW"),
MinVersionProtocol(0, "LEDENET_ORIGINAL"),
],
color_modes=set(),
)
# Version >= 5 -> first (highest) protocol.
assert model.protocol_for_version_num(7) == "LEDENET_ORIGINAL_RGBW"
assert model.protocol_for_version_num(5) == "LEDENET_ORIGINAL_RGBW"
# Version < 5 -> falls through to the lower protocol.
assert model.protocol_for_version_num(4) == "LEDENET_ORIGINAL"
assert model.protocol_for_version_num(0) == "LEDENET_ORIGINAL"
def test_protocol_for_version_num_below_all_minimums_uses_default() -> None:
# When no protocol's min_version is satisfied, the loop completes without
# a match and the last (lowest) protocol set before the loop is returned.
model = LEDBLEModel(
model_num=0x99,
models=["test"],
description="test",
protocols=[
MinVersionProtocol(5, "PROTO_HIGH"),
MinVersionProtocol(2, "PROTO_LOW"),
],
color_modes=set(),
)
assert model.protocol_for_version_num(1) == "PROTO_LOW"
Bluetooth-Devices-led-ble-4c385cb/tests/test_models.py 0000664 0000000 0000000 00000002252 15204752553 0023025 0 ustar 00root root 0000000 0000000 """Tests for the LEDBLEState dataclass."""
from __future__ import annotations
from dataclasses import FrozenInstanceError, replace
import pytest
from led_ble import LEDBLEState
def test_defaults():
state = LEDBLEState()
assert state.power is False
assert state.rgb == (0, 0, 0)
assert state.w == 0
assert state.model_num == 0
assert state.preset_pattern == 0
assert state.mode == 0
assert state.speed == 0
assert state.version_num == 0
def test_positional_construction_matches_notification_order():
# Mirrors how _notification_handler builds the state from a packet.
state = LEDBLEState(True, (10, 20, 30), 40, 0xE3, 1, 2, 3, 5)
assert state.power is True
assert state.rgb == (10, 20, 30)
assert state.w == 40
assert state.model_num == 0xE3
assert state.version_num == 5
def test_is_frozen():
state = LEDBLEState()
with pytest.raises(FrozenInstanceError):
state.power = True # type: ignore[misc]
def test_replace_returns_new_instance():
state = LEDBLEState()
updated = replace(state, power=True)
assert updated.power is True
assert state.power is False
assert updated is not state