From dd926f9139de5347decb5ef1ed5e68eb0cda1961 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Wed, 13 Nov 2024 14:02:50 -0500 Subject: [PATCH 01/19] Switching to ruff and mkdocs. --- .coverage 2 | Bin 53248 -> 0 bytes .devcontainer/devcontainer.json | 27 + .devcontainer/postCreateCommand.sh | 7 + .editorconfig | 25 +- .github/ISSUE_TEMPLATE/bug_report.md | 6 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/ISSUE_TEMPLATE/question.md | 2 +- .github/actions/setup-poetry-env/action.yml | 42 + .github/release-drafter.yml | 28 - .github/workflows/build.yml | 46 - .github/workflows/greetings.yml | 16 - .github/workflows/main.yml | 66 + .github/workflows/on-release-main.yml | 41 + .github/workflows/release-drafter.yml | 16 - .github/workflows/validate-codecov-config.yml | 15 + .gitignore | 592 +--- .pre-commit-config.yaml | 40 +- .readthedocs.yaml | 30 - CODE_OF_CONDUCT.md | 26 +- CONTRIBUTING.md | 148 +- Dockerfile | 21 + Makefile | 164 +- README.md | 23 +- codecov.yaml | 9 + cookiecutter-config-file.yml | 13 - docker/Dockerfile | 25 - docker/README.md | 47 - docs/Makefile | 20 - docs/_static/css/custom.css | 16 - docs/api/control.rst | 13 - docs/api/hardware.rst | 44 - docs/api/osl.rst | 7 - docs/api/tools.rst | 22 - docs/conf.py | 67 - docs/contributing/code_of_conduct.rst | 53 - docs/contributing/contributing_code.rst | 30 - docs/contributing/reporting_bugs.rst | 13 - docs/examples/basic_motion.rst | 106 - docs/examples/finite_state_machine.rst | 256 -- docs/examples/images/FSM_Diagram.svg | 190 -- docs/index.md | 68 + docs/index.rst | 111 - docs/make.bat | 35 - docs/modules.md | 1 + docs/requirements.txt | 964 ------ docs/tutorials/adding_joints.rst | 60 - docs/tutorials/adding_loadcell.rst | 69 - docs/tutorials/compiled_control.rst | 127 - docs/tutorials/current_mode.rst | 54 - docs/tutorials/getting_started.rst | 16 - docs/tutorials/impedance_mode.rst | 61 - docs/tutorials/logger_tutorial.rst | 97 - docs/tutorials/osl_clock.rst | 42 - docs/tutorials/position_mode.rst | 54 - docs/tutorials/reading_from_sensors.rst | 64 - docs/tutorials/voltage_mode.rst | 52 - examples/basic_motion.py | 7 +- examples/fsm_walking_compiled_controller.py | 88 +- mkdocs.yml | 54 + opensourceleg/actuators/README.md | 23 +- opensourceleg/actuators/__init__.py | 5 + opensourceleg/actuators/base.py | 37 +- opensourceleg/actuators/dephy.py | 87 +- .../images/Class Diagram Base Lib.svg | 2 +- opensourceleg/actuators/moteus.py | 36 +- opensourceleg/actuators/tmotor.py | 107 +- opensourceleg/benchmarks/decorators.py | 4 +- opensourceleg/benchmarks/threads.py | 1 - opensourceleg/collections/validators.py | 1 - opensourceleg/control/compiled_controller.py | 21 +- opensourceleg/control/state_machine.py | 30 +- opensourceleg/logging/exceptions.py | 4 +- opensourceleg/logging/logger.py | 3 +- opensourceleg/math/__init__.py | 4 +- opensourceleg/math/math.py | 25 +- opensourceleg/robots/base.py | 3 +- opensourceleg/robots/osl.py | 39 +- opensourceleg/safety/safety.py | 26 +- opensourceleg/sensors/adc.py | 31 +- opensourceleg/sensors/base.py | 14 +- opensourceleg/sensors/imu.py | 26 +- opensourceleg/sensors/loadcell.py | 35 +- opensourceleg/time/time.py | 26 +- opensourceleg/units/units.py | 25 +- poetry.lock | 2916 +++++++++++++++++ poetry.toml | 2 + pyproject.toml | 209 +- requirements.txt | 60 - setup.cfg | 7 - tests/test_actuators/test_actuators_base.py | 34 +- .../test_control/test_compiled_controller.py | 4 +- tests/test_control/test_state_machine.py | 8 +- tests/test_logging/test_logging_decorators.py | 4 +- tests/test_logging/test_logging_exceptions.py | 5 +- tests/test_logging/test_logging_logger.py | 228 +- tests/test_math/test_math.py | 23 +- tests/test_robots/test_robots_base.py | 26 +- tests/test_safety/test_safety.py | 22 +- tests/test_sensors/test_imu.py | 66 +- tests/test_sensors/test_loadcell.py | 16 +- tests/test_time/test_time.py | 1 - tests/test_units/test_units.py | 14 - tox.ini | 18 + tutorials/actuators/README.md | 6 +- tutorials/actuators/dephy/current_control.py | 28 +- .../actuators/dephy/impedance_control.py | 2 +- tutorials/actuators/dephy/position_control.py | 27 +- tutorials/actuators/dephy/sensor_reading.py | 1 - tutorials/actuators/dephy/voltage_control.py | 28 +- .../actuators/moteus/position_control.py | 39 +- tutorials/actuators/moteus/sensor_readings.py | 35 +- tutorials/actuators/moteus/torque_control.py | 44 +- .../actuators/moteus/velocity_control.py | 39 +- .../compiled_control/compiled_control.py | 4 +- tutorials/compiled_control/dot_product_3d.cpp | 2 +- 115 files changed, 4129 insertions(+), 4742 deletions(-) delete mode 100644 .coverage 2 create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/postCreateCommand.sh create mode 100644 .github/actions/setup-poetry-env/action.yml delete mode 100644 .github/release-drafter.yml delete mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/greetings.yml create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/on-release-main.yml delete mode 100644 .github/workflows/release-drafter.yml create mode 100644 .github/workflows/validate-codecov-config.yml delete mode 100644 .readthedocs.yaml create mode 100644 Dockerfile create mode 100644 codecov.yaml delete mode 100644 cookiecutter-config-file.yml delete mode 100644 docker/Dockerfile delete mode 100644 docker/README.md delete mode 100644 docs/Makefile delete mode 100644 docs/_static/css/custom.css delete mode 100644 docs/api/control.rst delete mode 100644 docs/api/hardware.rst delete mode 100644 docs/api/osl.rst delete mode 100644 docs/api/tools.rst delete mode 100644 docs/conf.py delete mode 100644 docs/contributing/code_of_conduct.rst delete mode 100644 docs/contributing/contributing_code.rst delete mode 100644 docs/contributing/reporting_bugs.rst delete mode 100644 docs/examples/basic_motion.rst delete mode 100644 docs/examples/finite_state_machine.rst delete mode 100644 docs/examples/images/FSM_Diagram.svg create mode 100644 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/make.bat create mode 100644 docs/modules.md delete mode 100644 docs/requirements.txt delete mode 100644 docs/tutorials/adding_joints.rst delete mode 100644 docs/tutorials/adding_loadcell.rst delete mode 100644 docs/tutorials/compiled_control.rst delete mode 100644 docs/tutorials/current_mode.rst delete mode 100644 docs/tutorials/getting_started.rst delete mode 100644 docs/tutorials/impedance_mode.rst delete mode 100644 docs/tutorials/logger_tutorial.rst delete mode 100644 docs/tutorials/osl_clock.rst delete mode 100644 docs/tutorials/position_mode.rst delete mode 100644 docs/tutorials/reading_from_sensors.rst delete mode 100644 docs/tutorials/voltage_mode.rst create mode 100644 mkdocs.yml create mode 100644 poetry.lock create mode 100644 poetry.toml delete mode 100644 requirements.txt delete mode 100644 setup.cfg create mode 100644 tox.ini diff --git a/.coverage 2 b/.coverage 2 deleted file mode 100644 index c17903d095ad3494fd050a486f48d9c426fb5861..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI4d2AHd9mi*OW@qnxKCl5dzUGCt@IG*R`%QDAtF8K4opSd_- zMKm`cbgArcB}Q+@XhiVv3#$N0e07`WALATCj4FymGl(;Zy|vdx~-cJMvjs+y_DtHPwsW_oTv!$bh0X%i*ycMpW=~SkLA*rN)E0D#xM; zBbx~=sz=p?tPCrWw664CGK0+s&0GLpVQxTcGiGRWHf8Qy1$6R>FOf1AJ(su-oJ*)C z)_g)dH6%8CPNd^KfP70jp2Wg|6xkE&>O?6gN~^26%zoptxkOp!^IKG$CUPv%Y^ro3 zx;3qg6@IgvPuXl_BcV3SmGdxnDvy$haRw~2V))?G)g~DlFHMT=g_@Op1-n+`3 zjzE1qfA?0Sq9C47PgK{&R$N7uW^DHaRR@nxTyL_wTtJ^3au?n3a*-A{(~0r_IzaW^>LXqN(Wt0rEoYJ zz&c$+z4$^gO)i?(-h4e#g59< zZ%qOURE>vwv#lJ>Y5g-CYfCvzQ zE02J{+js|#|3&8H*h1FMs$qo|B0vO)01+SpM1Tko0U|&IhyW2F0#`5ruPD?p^Oq2_ zrwT$<2mTVEeRlhITH0o}wBI1Nx6f(6eojXxvk5hU++X!2KY>`^ z7+j^nFBgK^KuQ?_$>6dm{CqDp5?mO@pYR2fBS{s0(l`1b_h5B5Y8kW5!Jy|GztY>A)+nUuOkiRi7m8jl(KTi{0n zEh#0bru2COdcn?lMe-OIwX|U4MmLnH$lm7I+w2Q=iXFd#i>Y5g-CY;0hw(73z5NL5AoQs(5^kA$WuuKKIbV<`L>#=F_?5V_3U@sAO2QX}cOl7Po{t8R%#27;;ZuM__`?|-D#n8L3Qq$4B90(Jq=$(K+OyHM!?^$fR&ojcp5ajt3k8m=4|>2WvKQl zSlha)tnd|P%tFs&SkPQG2{ePHmRDA@pr~%E1lbv-%Ho98AR2IVWd$gl|Au0LpKINqT5>lVQ~Uej6DdJuIHDwtyB%A_DMq|{hY(_wCT2RBuS2~(#wu&;UVF3-#HLz3+N=_;2+# zdj2Ht6}!aqo*kY>_Yucox9m(ir@7OvH(kT7Y0kr?4SXmy5g-B_w?WLznv?_8?^o1_grj{XZIS z5&>Y5xAlW2)w}g@clo}zUJUREku9_5CI}U1c(3;AOb{y z2oM1xKm>@u6-Yo3d@g+dU*$W&u`}!w_B{I~8)ZXmE%U;+0RHZO-+$2mf`8OM% z{3!=W>?-3XAL3^BZQgtc7KuSTKjS<65Dxg){hV=wM@L_(*flzXyRd{i{~)*P+${dk z=xY_+*Jqb-J3lzn-@NO?bk#d&XPy=%A13y>8@v!M@xF;3?mGYVxj`3qIEWh@H~GW| zQ#g5zjXNqz9*pgAm%AZsa7(p9-=Q|{i^w7G4L%p_s&y?Hc7oOVztx z_>$lCmo2YsZVSx*KyR#dfc6~6wF1Z8HM)1t(19noeejPvk>KaQE=m$QCGpqVA?&sT z^Q8WTUFan!R zgeL&T`+u(N@=nuHB0vO)01+SpM1Tko0U|&IhyW2F0z}~dn*hH5r}6*)cNvoRM1Tko z0U|&IhyW2F0z`la5CI}U1pX5Nod4gzmf`RJ|H(dO|6nKC`|KFZ0r&$u!rox7vtP6Q zFb`lKd>LRbdxAa69)`I9KVm;*yV*`Q!gQvxElgpX*?QK^R)7#KM1Tko0U|&IhyW2F z0z`la5CI}U1g>xbLIZqIUzm>OYBbZ($Y|=(T!p3%O)Z)lG*i(`K{FXm08KTTDm0VO sRHCUsQ;wz#4MXEc<3r;`<3Zy_<3i&^<3J;!v7-^u2xx3*@caM&22SMRSO5S3 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..25e74e42 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,27 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "opensourceleg", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", + "features": { + "ghcr.io/devcontainers-contrib/features/poetry:2": {} + }, + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "./.devcontainer/postCreateCommand.sh", + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": ["ms-python.python", "editorconfig.editorconfig"], + "settings": { + "python.testing.pytestArgs": ["tests"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.defaultInterpreterPath": "/workspaces/opensourceleg/.venv/bin/python", + "python.testing.pytestPath": "/workspaces/opensourceleg/.venv/bin/pytest" + } + } + } +} diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh new file mode 100755 index 00000000..38dca893 --- /dev/null +++ b/.devcontainer/postCreateCommand.sh @@ -0,0 +1,7 @@ +#! /usr/bin/env bash + +# Install Dependencies +poetry install --with dev + +# Install pre-commit hooks +poetry run pre-commit install --install-hooks diff --git a/.editorconfig b/.editorconfig index c02f5b71..9395b543 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,22 +1,5 @@ -# Check http://editorconfig.org for more information -# This is the main config file for this project: -root = true +max_line_length = 120 -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -indent_style = tab -trim_trailing_whitespace = true - -[*.{py, pyi}] -indent_style = tab - -[Makefile] -indent_style = tab - -[*.md] -trim_trailing_whitespace = false - -[*.{diff,patch}] -trim_trailing_whitespace = false +[*.json] +indent_style = space +indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index bdd76779..d4ee3806 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: πŸ› Bug report about: If something isn't working πŸ”§ -title: '' +title: "" labels: bug assignees: --- @@ -22,8 +22,8 @@ Steps to reproduce the behavior: ### Environment -* OS: [e.g. Linux / Windows / macOS] -* Python version, get it with: +- OS: [e.g. Linux / Windows / macOS] +- Python version, get it with: ```bash python --version diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c387120f..7ddaee21 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,7 @@ --- name: πŸš€ Feature request about: Suggest an idea for this project πŸ– -title: '' +title: "" labels: enhancement assignees: --- diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 9bdb0d73..2a7ea6b7 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,7 +1,7 @@ --- name: ❓ Question about: Ask a question about this project πŸŽ“ -title: '' +title: "" labels: question assignees: --- diff --git a/.github/actions/setup-poetry-env/action.yml b/.github/actions/setup-poetry-env/action.yml new file mode 100644 index 00000000..18738aea --- /dev/null +++ b/.github/actions/setup-poetry-env/action.yml @@ -0,0 +1,42 @@ +name: "setup-poetry-env" +description: "Composite action to setup the Python and poetry environment." + +inputs: + python-version: + required: false + description: "The python version to use" + default: "3.11" + +runs: + using: "composite" + steps: + - name: Set up python + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + + - name: Install Poetry + env: + POETRY_VERSION: "1.7.1" + run: curl -sSL https://install.python-poetry.org | python - -y + shell: bash + + - name: Add Poetry to Path + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + shell: bash + + - name: Configure Poetry virtual environment in project + run: poetry config virtualenvs.in-project true + shell: bash + + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache@v4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ inputs.python-version }}-${{ hashFiles('poetry.lock') }} + + - name: Install dependencies + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: poetry install --no-interaction + shell: bash diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index 0ce0984f..00000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Release drafter configuration https://github.com/release-drafter/release-drafter#configuration -# Emojis were chosen to match the https://gitmoji.carloscuesta.me/ - -name-template: "v$NEXT_PATCH_VERSION" -tag-template: "v$NEXT_PATCH_VERSION" - -categories: - - title: ":rocket: Features" - labels: [enhancement, feature] - - title: ":wrench: Fixes & Refactoring" - labels: [bug, refactoring, bugfix, fix] - - title: ":package: Build System & CI/CD" - labels: [build, ci, testing] - - title: ":boom: Breaking Changes" - labels: [breaking] - - title: ":pencil: Documentation" - labels: [documentation] - - title: ":arrow_up: Dependencies updates" - labels: [dependencies] - -template: | - ## What’s Changed - - $CHANGES - - ## :busts_in_silhouette: List of contributors - - $CONTRIBUTORS diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 9d41ac2b..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: build - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.9"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5.3.0 - with: - python-version: ${{ matrix.python-version }} - - - name: Install poetry - run: make poetry-download - - - name: Set up cache - uses: actions/cache@v4.1.2 - with: - path: .venv - key: venv-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}-${{ hashFiles('poetry.lock') }} - - name: Install dependencies - run: | - poetry config virtualenvs.in-project true - poetry install - - - name: Run style checks - run: | - make check-codestyle - - - name: Run lint checks - run: | - make lint - - - name: Run tests - run: | - make test - - - name: Run safety checks - run: | - make check-safety diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml deleted file mode 100644 index a1f6e89d..00000000 --- a/.github/workflows/greetings.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Greetings - -on: [pull_request, issues] - -jobs: - greeting: - runs-on: ubuntu-latest - steps: - - uses: actions/first-interaction@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - pr-message: 'Hello @${{ github.actor }}, thank you for submitting a PR! We will respond as soon as possible.' - issue-message: | - Hello @${{ github.actor }}, thank you for your interest in our work! - - If this is a bug report, please provide screenshots and **minimum viable code to reproduce your issue**, otherwise we can not help you. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..4a3c723a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,66 @@ +name: Main + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + + - uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + + - name: Set up the environment + uses: ./.github/actions/setup-poetry-env + + - name: Run checks + run: make check + + tests-and-type-check: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + fail-fast: false + defaults: + run: + shell: bash + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up the environment + uses: ./.github/actions/setup-poetry-env + with: + python-version: ${{ matrix.python-version }} + + - name: Run tests + run: poetry run pytest tests --cov --cov-config=pyproject.toml --cov-report=xml + + - name: Check typing + run: poetry run mypy + + - name: Upload coverage reports to Codecov with GitHub Action on Python 3.11 + uses: codecov/codecov-action@v4 + if: ${{ matrix.python-version == '3.11' }} + + check-docs: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up the environment + uses: ./.github/actions/setup-poetry-env + + - name: Check if documentation can be built + run: poetry run mkdocs build -s diff --git a/.github/workflows/on-release-main.yml b/.github/workflows/on-release-main.yml new file mode 100644 index 00000000..080026d4 --- /dev/null +++ b/.github/workflows/on-release-main.yml @@ -0,0 +1,41 @@ +name: release-main + +on: + release: + types: [published] + branches: [main] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up the environment + uses: ./.github/actions/setup-poetry-env + + - name: Export tag + id: vars + run: echo tag=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT + + - name: Build and publish + run: | + source .venv/bin/activate + poetry version $RELEASE_VERSION + make build-and-publish + env: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + RELEASE_VERSION: ${{ steps.vars.outputs.tag }} + deploy-docs: + needs: publish + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up the environment + uses: ./.github/actions/setup-poetry-env + + - name: Deploy documentation + run: poetry run mkdocs gh-deploy --force diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 7fb0ef13..00000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Release Drafter - -on: - push: - # branches to consider in the event; optional, defaults to all - branches: - - master - -jobs: - update_release_draft: - runs-on: ubuntu-latest - steps: - # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v6.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/validate-codecov-config.yml b/.github/workflows/validate-codecov-config.yml new file mode 100644 index 00000000..2a8fd11a --- /dev/null +++ b/.github/workflows/validate-codecov-config.yml @@ -0,0 +1,15 @@ +name: validate-codecov-config + +on: + pull_request: + paths: [codecov.yaml] + push: + branches: [main] + +jobs: + validate-codecov-config: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Validate codecov configuration + run: curl -sSL --fail-with-body --data-binary @codecov.yaml https://codecov.io/validate diff --git a/.gitignore b/.gitignore index 6bd7836e..f64e26aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,124 +1,7 @@ +docs/source + +# From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore -# Created by https://www.gitignore.io/api/osx,python,pycharm,windows,visualstudio,visualstudiocode -# Edit at https://www.gitignore.io/?templates=osx,python,pycharm,windows,visualstudio,visualstudiocode - -### OSX ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### PyCharm ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### PyCharm Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -.idea/**/sonarlint/ - -# SonarQube Plugin -.idea/**/sonarIssues.xml - -# Markdown Navigator plugin -.idea/**/markdown-navigator.xml -.idea/**/markdown-navigator/ - -### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -126,7 +9,6 @@ __pycache__/ # C extensions *.so -*.o # Distribution / packaging .Python @@ -142,7 +24,6 @@ parts/ sdist/ var/ wheels/ -pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg @@ -169,13 +50,25 @@ htmlcov/ 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 @@ -183,13 +76,20 @@ coverage.xml docs/_build/ # PyBuilder +.pybuilder/ target/ -# pyenv -.python-version +# Jupyter Notebook +.ipynb_checkpoints -# poetry -.venv +# 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. @@ -198,12 +98,40 @@ target/ # install all needed dependencies. #Pipfile.lock -# celery beat schedule file +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__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 @@ -211,11 +139,6 @@ celerybeat-schedule # Rope project settings .ropeproject -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - # mkdocs documentation /site @@ -227,399 +150,18 @@ dmypy.json # Pyre type checker .pyre/ -# Plugins -.secrets.baseline - -### VisualStudioCode ### -.vscode/* -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -### VisualStudio ### -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.npy -*.csv -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser +# pytype static type analyzer +.pytype/ -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# End of https://www.gitignore.io/api/osx,python,pycharm,windows,visualstudio,visualstudiocode -opensourceleg/knee_encoder_map.npy -opensourceleg/ankle_encoder_map.npy +# Cython debug symbols +cython_debug/ +# Vscode config files .vscode/ -poetry.lock + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aea4fbe0..1f33da27 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,36 +1,22 @@ -default_language_version: - python: python3 - -default_stages: [commit, push] - repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.5.0 + rev: "v4.4.0" hooks: + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml - id: check-yaml - id: end-of-file-fixer - exclude: LICENSE - - - repo: local - hooks: - - id: pyupgrade - name: pyupgrade - entry: poetry run pyupgrade --py39-plus - types: [python] - language: system + - id: trailing-whitespace - - repo: local + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.5.2" hooks: - - id: isort - name: isort - entry: poetry run isort --settings-path pyproject.toml - types: [python] - language: system + - id: ruff + args: [--exit-non-zero-on-fix] + - id: ruff-format - - repo: local + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.0.3" hooks: - - id: black - name: black - entry: poetry run black --config pyproject.toml - types: [python] - language: system + - id: prettier diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index 9a93c166..00000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the version of Python and other tools you might need -build: - os: ubuntu-20.04 - tools: - python: "3.9" - # You can also specify other tool versions: - # nodejs: "16" - # rust: "1.55" - # golang: "1.17" - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py - -# If using Sphinx, optionally build your docs in additional formats such as PDF -formats: - - pdf - -# Optionally declare the Python requirements required to build your docs -python: - install: - - requirements: requirements.txt - - requirements: docs/requirements.txt diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 01915f14..2fc8e2e7 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6729ed26..d64688ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,41 +1,133 @@ -# How to contribute +# Contributing to `opensourceleg` -## Installing dev dependencies +Contributions are welcome, and they are greatly appreciated! +Every little bit helps, and credit will always be given. -To get started you would need to install [`python3.9`](https://www.python.org/downloads/) and [`poetry`](https://python-poetry.org/docs/#installing-with-the-official-installer). Please follow the instructions on their official websites. +You can contribute in many ways: -* We use *poetry* to manage our python [dependencies](https://github.com/python-poetry/poetry). Please make sure that *poetry* is added to your **PATH** variable after installation and you can run `poetry` command in your terminal or command prompt. +# Types of Contributions -* After cloning the *opensourceleg* repository, please activate your *virtualenv* by running `poetry shell` command. This will create an isolated virtual environment for development. +## Report Bugs -* To install dependencies and prepare [`pre-commit`](https://pre-commit.com/) hooks you would need to run these commands from the root of the repository: +Report bugs at https://github.com/neurobionics/opensourceleg/issues - ```bash - make install - make pre-commit-install - ``` +If you are reporting a bug, please include: -## Submitting your code +- Your operating system name and version. +- Any details about your local setup that might be helpful in troubleshooting. +- Detailed steps to reproduce the bug. -Many checks are configured for this project. -* Command `make check-codestyle` will check black, isort and darglint. -* Command `make lint` will check types, docstrings and security using [Mypy](https://pypi.org/project/mypy/), [Darglint](https://pypi.org/project/darglint/), [Pydocstyle](https://pypi.org/project/pydocstyle/) -* Command `make check-safety` will look at the security of your code and dependencies using [Safety](https://pypi.org/project/safety/) and [Bandit](https://pypi.org/project/bandit/). +## Fix Bugs -Before submitting your code please do the following steps: +Look through the GitHub issues for bugs. +Anything tagged with "bug" and "help wanted" is open to whoever wants to implement a fix for it. -1. Add any changes you want -1. Add tests for the new changes -1. Edit documentation if you have changed something significant -1. Run `make codestyle` to format your changes. -1. Run `make lint` to ensure that types, security and docstrings are okay. -1. Run `make check-safety` to ensure that your code is secure. +## Implement Features -Your code will be checked by our CI/CD pipeline once you submit a pull request. Happy coding! +Look through the GitHub issues for features. +Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. -## Other ways you can help +## Write Documentation -You can contribute by spreading a word about this library. -It would also be a huge contribution to write -a short article on how you are using this project. -You can also share your best practices with us. +Cookiecutter PyPackage could always use more documentation, whether as part of the official docs, in docstrings, or even on the web in blog posts, articles, and such. + +## Submit Feedback + +The best way to send feedback is to file an issue at https://github.com/neurobionics/opensourceleg/issues. + +If you are proposing a new 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 up `opensourceleg` for local development. +Please note this documentation assumes you already have `poetry` and `Git` installed and ready to go. + +1. Fork the `opensourceleg` repo on GitHub. + +2. Clone your fork locally: + +```bash +cd +git clone git@github.com:YOUR_NAME/opensourceleg.git +``` + +3. Now we need to install the environment. Navigate into the directory + +```bash +cd opensourceleg +``` + +If you are using `pyenv`, select a version to use locally. (See installed versions with `pyenv versions`) + +```bash +pyenv local +``` + +Then, install and activate the environment with: + +```bash +poetry install +poetry shell +``` + +4. Install pre-commit to run linters/formatters at commit time: + +```bash +poetry run pre-commit install +``` + +5. Create a branch for local development: + +```bash +git checkout -b name-of-your-bugfix-or-feature +``` + +Now you can make your changes locally. + +6. Don't forget to add test cases for your added functionality to the `tests` directory. + +7. When you're done making changes, check that your changes pass the formatting tests. + +```bash +make check +``` + +Now, validate that all unit tests are passing: + +```bash +make test +``` + +9. Before raising a pull request you should also run tox. + This will run the tests across different versions of Python: + +```bash +tox +``` + +This requires you to have multiple versions of python installed. +This step is also triggered in the CI/CD pipeline, so you could also choose to skip this step locally. + +10. Commit your changes and push your branch to GitHub: + +```bash +git add . +git commit -m "Your detailed description of your changes." +git push origin name-of-your-bugfix-or-feature +``` + +11. Submit a pull request through the GitHub website. + +# Pull Request Guidelines + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. + +2. If the pull request adds functionality, the docs should be updated. + Put your new functionality into a function with a docstring, and add the feature to the list in `README.md`. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..66c024d0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +FROM python:3.9-slim-buster + +ENV POETRY_VERSION=1.4 \ + POETRY_VIRTUALENVS_CREATE=false + +# Install poetry +RUN pip install "poetry==$POETRY_VERSION" + +# Copy only requirements to cache them in docker layer +WORKDIR /code +COPY poetry.lock pyproject.toml /code/ + +# Project initialization: +RUN poetry install --no-interaction --no-ansi --no-root --no-dev + +# Copy Python code to the Docker image +COPY opensourceleg /code/opensourceleg/ + +CMD [ "python", "opensourceleg/foo.py"] diff --git a/Makefile b/Makefile index 4a9d624b..2157cf71 100644 --- a/Makefile +++ b/Makefile @@ -1,114 +1,56 @@ -#* Variables -SHELL := /usr/bin/env bash -PYTHON := python -PYTHONPATH := `pwd` - -#* Docker variables -IMAGE := opensourceleg -VERSION := latest - -#* Poetry -.PHONY: poetry-download -poetry-download: - curl -sSL https://install.python-poetry.org | $(PYTHON) - - -.PHONY: poetry-remove -poetry-remove: - curl -sSL https://install.python-poetry.org | $(PYTHON) - --uninstall - -#* Installation .PHONY: install -install: - poetry lock -n && poetry export --without-hashes > requirements.txt - poetry install -n - -poetry run mypy --install-types --non-interactive ./ --explicit-package-bases - -.PHONY: pre-commit-install -pre-commit-install: - poetry run pre-commit install - -#* Formatters -.PHONY: codestyle -codestyle: - poetry run pyupgrade --exit-zero-even-if-changed --py39-plus **/*.py - poetry run isort --settings-path pyproject.toml ./ - poetry run black --config pyproject.toml ./ +install: ## Install the poetry environment and install the pre-commit hooks + @echo "πŸš€ Creating virtual environment using pyenv and poetry" + @poetry install + @poetry run pre-commit install + @poetry shell + +.PHONY: check +check: ## Run code quality tools. + @echo "πŸš€ Checking Poetry lock file consistency with 'pyproject.toml': Running poetry check --lock" + @poetry check --lock + @echo "πŸš€ Linting code: Running pre-commit" + @poetry run pre-commit run -a + @echo "πŸš€ Static type checking: Running mypy" + @poetry run mypy + @echo "πŸš€ Checking for obsolete dependencies: Running deptry" + @poetry run deptry . -.PHONY: formatting -formatting: codestyle - -#* Linting .PHONY: test -test: - PYTHONPATH=$(PYTHONPATH) poetry run pytest -c pyproject.toml --cov-report=html --cov=opensourceleg tests/ - poetry run coverage-badge -o assets/images/coverage.svg -f - -.PHONY: check-codestyle -check-codestyle: - poetry run isort --diff --check-only --settings-path pyproject.toml ./ - poetry run black --diff --check --config pyproject.toml ./ - poetry run darglint --verbosity 2 opensourceleg tests - -.PHONY: mypy -mypy: - poetry run mypy --config-file pyproject.toml ./ --explicit-package-bases -#* --check-untyped-defs - -.PHONY: check-safety -check-safety: - poetry check - poetry run safety check --full-report --ignore 70612 - poetry run bandit --recursive -c pyproject.toml opensourceleg tests - -.PHONY: lint -lint: mypy - -.PHONY: update-dev-deps -update-dev-deps: - poetry add -D bandit@latest darglint@latest "isort[colors]@latest" mypy@latest pre-commit@latest pydocstyle@latest pylint@latest pytest@latest pyupgrade@latest safety@latest coverage@latest coverage-badge@latest pytest-html@latest pytest-cov@latest - poetry add -D --allow-prereleases black@latest - -#* Docker -# Example: make docker-build VERSION=latest -# Example: make docker-build IMAGE=some_name VERSION=0.1.0 -.PHONY: docker-build -docker-build: - @echo Building docker $(IMAGE):$(VERSION) ... - docker build \ - -t $(IMAGE):$(VERSION) . \ - -f ./docker/Dockerfile --no-cache - -# Example: make docker-remove VERSION=latest -# Example: make docker-remove IMAGE=some_name VERSION=0.1.0 -.PHONY: docker-remove -docker-remove: - @echo Removing docker $(IMAGE):$(VERSION) ... - docker rmi -f $(IMAGE):$(VERSION) - -#* Cleaning -.PHONY: pycache-remove -pycache-remove: - find . | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf - -.PHONY: dsstore-remove -dsstore-remove: - find . | grep -E ".DS_Store" | xargs rm -rf - -.PHONY: mypycache-remove -mypycache-remove: - find . | grep -E ".mypy_cache" | xargs rm -rf - -.PHONY: ipynbcheckpoints-remove -ipynbcheckpoints-remove: - find . | grep -E ".ipynb_checkpoints" | xargs rm -rf - -.PHONY: pytestcache-remove -pytestcache-remove: - find . | grep -E ".pytest_cache" | xargs rm -rf - -.PHONY: build-remove -build-remove: - rm -rf build/ - -.PHONY: cleanup -cleanup: pycache-remove dsstore-remove mypycache-remove ipynbcheckpoints-remove pytestcache-remove +test: ## Test the code with pytest + @echo "πŸš€ Testing code: Running pytest" + @poetry run pytest --cov --cov-config=pyproject.toml --cov-report=xml + +.PHONY: build +build: clean-build ## Build wheel file using poetry + @echo "πŸš€ Creating wheel file" + @poetry build + +.PHONY: clean-build +clean-build: ## clean build artifacts + @rm -rf dist + +.PHONY: publish +publish: ## publish a release to pypi. + @echo "πŸš€ Publishing: Dry run." + @poetry config pypi-token.pypi $(PYPI_TOKEN) + @poetry publish --dry-run + @echo "πŸš€ Publishing." + @poetry publish + +.PHONY: build-and-publish +build-and-publish: build publish ## Build and publish. + +.PHONY: docs-test +docs-test: ## Test if documentation can be built without warnings or errors + @poetry run mkdocs build -s + +.PHONY: docs +docs: ## Build and serve the documentation + @poetry run mkdocs serve + +.PHONY: help +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.DEFAULT_GOAL := help diff --git a/README.md b/README.md index b0ac7efe..3e26d547 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,26 @@ poetry install poetry shell ``` -## Documentation +Finally, install the environment and the pre-commit hooks with -You can find tutorials and API documentation at [opensourceleg.readthedocs.io](https://opensourceleg.readthedocs.io/en/latest/). +```bash +make install +``` + +You are now ready to start development on your project! +The CI/CD pipeline will be triggered when you open a pull request, merge to main, or when you create a new release. + +To finalize the set-up for publishing to PyPI or Artifactory, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/publishing/#set-up-for-pypi). +For activating the automatic documentation with MkDocs, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/mkdocs/#enabling-the-documentation-on-github). +To enable the code coverage reports, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/codecov/). + +## Releasing a new version + +- Create an API Token on [PyPI](https://pypi.org/). +- Add the API Token to your projects secrets with the name `PYPI_TOKEN` by visiting [this page](https://github.com/neurobionics/opensourceleg/settings/secrets/actions/new). +- Create a [new release](https://github.com/neurobionics/opensourceleg/releases/new) on Github. +- Create a new tag in the form `*.*.*`. +- For more details, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/cicd/#how-to-trigger-a-release). ## License @@ -60,4 +77,4 @@ The GPL license ensures that all these freedoms are protected, now and in the fu ## Contributing -Contributions are welcome, and they are greatly appreciated! For more details, read our [contribution guidelines](https://github.com/neurobionics/opensourceleg/blob/11765f7f7dd94e5d8699675149d5ff3596ea01b8/CONTRIBUTING.md). +Contributions are welcome, and they are greatly appreciated! For more details, read our [contribution guidelines](CONTRIBUTING.md). diff --git a/codecov.yaml b/codecov.yaml new file mode 100644 index 00000000..058cfb76 --- /dev/null +++ b/codecov.yaml @@ -0,0 +1,9 @@ +coverage: + range: 70..100 + round: down + precision: 1 + status: + project: + default: + target: 90% + threshold: 0.5% diff --git a/cookiecutter-config-file.yml b/cookiecutter-config-file.yml deleted file mode 100644 index 1d8727f0..00000000 --- a/cookiecutter-config-file.yml +++ /dev/null @@ -1,13 +0,0 @@ -# This file contains values from Cookiecutter - -default_context: - project_name: "opensourceleg" - project_description: "An open-source software library for numerical computation, data acquisition, and control of lower-limb robotic prostheses." - organization: "Neurobionics Laboratory" - license: "GNU LGPL v2.1" - minimal_python_version: 3.9 - github_name: "neurobionics" - email: "opensourceleg@gmail.com" - version: "0.1.0" - line_length: "88" - create_example_template: "none" diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 08feb0dd..00000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM python:3.11.4-slim-buster - -ENV LANG=C.UTF-8 \ - LC_ALL=C.UTF-8 \ - PATH="${PATH}:/root/.poetry/bin" - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - curl \ - && rm -rf /var/lib/apt/lists/* - -COPY pyproject.toml ./ - -# Install Poetry -RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | POETRY_HOME=/opt/poetry python && \ - cd /usr/local/bin && \ - ln -s /opt/poetry/bin/poetry && \ - poetry config virtualenvs.create false - -# Allow installing dev dependencies to run tests -ARG INSTALL_DEV=false -RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; else poetry install --no-root --no-dev ; fi" - -CMD mkdir -p /workspace -WORKDIR /workspace diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index 01b40b45..00000000 --- a/docker/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Docker for opensourceleg - -## Installation - -To create Docker you need to run: - -```bash -make docker-build -``` - -which is equivalent to: - -```bash -make docker-build VERSION=latest -``` - -You may provide name and version for the image. -Default name is `IMAGE := opensourceleg`. -Default version is `VERSION := latest`. - -```bash -make docker-build IMAGE=some_name VERSION=0.1.0 -``` - -## Usage - -```bash -docker run -it --rm \ - -v $(pwd):/workspace \ - opensourceleg bash -``` - -## How to clean up - -To uninstall docker image run `make docker-remove` with `VERSION`: - -```bash -make docker-remove VERSION=0.1.0 -``` - -you may also choose the image name - -```bash -make docker-remove IMAGE=some_name VERSION=latest -``` - -If you want to clean all, including `build` and `pycache` run `make cleanup` diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cbb..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# 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 = . -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) diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css deleted file mode 100644 index f030cb8f..00000000 --- a/docs/_static/css/custom.css +++ /dev/null @@ -1,16 +0,0 @@ -/* Newlines (\a) and spaces (\20) before each parameter */ -.sig-param::before { - content: "\a\20\20\20\20\20\20\20\20\20\20\20\20\20\20\20\20"; - white-space: pre; -} - -/* Newline after the last parameter (so the closing bracket is on a new line) */ -dt em.sig-param:last-of-type::after { - content: "\a"; - white-space: pre; -} - -/* To have blue background of width of the block (instead of width of content) */ -dl.class > dt:first-of-type { - display: block !important; -} diff --git a/docs/api/control.rst b/docs/api/control.rst deleted file mode 100644 index c16e25b6..00000000 --- a/docs/api/control.rst +++ /dev/null @@ -1,13 +0,0 @@ -======= -Control -======= - -Compiled Controller -------------------- -.. automodule:: opensourceleg.control.compiled_controller - :members: - -State Machine -------------- -.. automodule:: opensourceleg.control.state_machine - :members: diff --git a/docs/api/hardware.rst b/docs/api/hardware.rst deleted file mode 100644 index 11ca8f95..00000000 --- a/docs/api/hardware.rst +++ /dev/null @@ -1,44 +0,0 @@ -======== -Hardware -======== - -Actuators ---------- - -.. automodule:: opensourceleg.hardware.actuators - :members: - - * ``PI`` **float** = 3.14159 - * ``MOTOR_COUNT_PER_REV`` **float** = 16384 - * ``NM_PER_AMP`` **float** = 0.1133 - * ``NM_PER_MILLIAMP`` **float** = ``NM_PER_AMP`` / 1000 - * ``RAD_PER_COUNT`` **float** = 2 * ``PI`` / ``MOTOR_COUNT_PER_REV`` - * ``RAD_PER_DEG`` **float** = ``PI`` / 180 - * ``RAD_PER_SEC_GYROLSB`` **float** = ``PI`` / 180 / 32.8 - * ``M_PER_SEC_SQUARED_ACCLSB`` **float** = 9.80665 / 8192 - * ``IMPEDANCE_A`` **float** = 0.00028444 - * ``IMPEDANCE_C`` **float** = 0.0007812 - * ``NM_PER_RAD_TO_K`` **float** = ``RAD_PER_COUNT`` / ``IMPEDANCE_C`` * 1e3 / ``NM_PER_AMP`` - * ``NM_S_PER_RAD_TO_B`` **float** = ``RAD_PER_DEG`` / ``IMPEDANCE_A`` * 1e3 / ``NM_PER_AMP`` - * ``MAX_CASE_TEMPERATURE`` **float** = 80.0 - - -Joints ------- - -.. automodule:: opensourceleg.hardware.joints - :members: - :show-inheritance: - -Sensors -------- - -.. automodule:: opensourceleg.hardware.sensors - :members: - - -Thermal -------- - -.. automodule:: opensourceleg.hardware.thermal - :members: diff --git a/docs/api/osl.rst b/docs/api/osl.rst deleted file mode 100644 index f536eb55..00000000 --- a/docs/api/osl.rst +++ /dev/null @@ -1,7 +0,0 @@ -=============== -Open-Source Leg -=============== - -.. automodule:: opensourceleg.osl - :members: - :show-inheritance: diff --git a/docs/api/tools.rst b/docs/api/tools.rst deleted file mode 100644 index 01cacad6..00000000 --- a/docs/api/tools.rst +++ /dev/null @@ -1,22 +0,0 @@ -===== -Tools -===== - -Units ------ - -.. automodule:: opensourceleg.tools.units - :members: - -Utilities ---------- - -.. automodule:: opensourceleg.tools.utilities - :members: - -Logger ------- - -.. automodule:: opensourceleg.tools.logger - :members: - :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 8e8d05de..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,67 +0,0 @@ -# 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("..")) -sys.path.insert(0, os.path.abspath("../opensourceleg")) - - -# -- Project information ----------------------------------------------------- - -project = "Open-Source Leg" -copyright = ( - "2022, Open-Source Leg Project (https://opensourceleg.com), Michigan Robotics" -) -author = "Senthur Ayyappan, Kevin Best, Jace Derosia, and Prof. Elliott Rouse" - -# The full version, including alpha/beta/rc tags -release = "2.0.0" - - -# -- General configuration --------------------------------------------------- - -master_doc = "index" - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.intersphinx"] - -# 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 = ["_build", "Thumbs.db", ".DS_Store"] - - -# -- 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_book_theme" -html_title = "Open-Source Leg" - -# 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"] - -# These paths are either relative to html_static_path -# or fully qualified paths (eg. https://...) -html_css_files = [ - "css/custom.css", -] diff --git a/docs/contributing/code_of_conduct.rst b/docs/contributing/code_of_conduct.rst deleted file mode 100644 index bd473207..00000000 --- a/docs/contributing/code_of_conduct.rst +++ /dev/null @@ -1,53 +0,0 @@ -================ -Code of Conduct -================ - -Our Pledge ------------ - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. - -Our Standards --------------- - -Examples of behavior that contributes to creating a positive environment include: - -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery and unwelcome sexual attention or advances -- Trolling, insulting/derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or electronic address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a professional setting - -Our Responsibilities ---------------------- - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -Scope ------ - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -Enforcement ------------- - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensourceleg@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -Attribution ------------ - -This Code of Conduct is adapted from the `Contributor Covenant `_, version 1.4, available `here `_. - -For answers to common questions about this code of conduct, read `more `_. diff --git a/docs/contributing/contributing_code.rst b/docs/contributing/contributing_code.rst deleted file mode 100644 index dc08bb89..00000000 --- a/docs/contributing/contributing_code.rst +++ /dev/null @@ -1,30 +0,0 @@ -================= -Contributing Code -================= - -If you want to contribute code to **opensourceleg**, please follow these steps: - -1. Fork the **opensourceleg** repository on GitHub. -2. Clone your forked repository to your local machine. -3. Create a new branch for your changes. -4. Make your changes and commit them with clear and descriptive commit messages. -5. Push your changes to your forked repository. -6. Open a pull request on the **opensourceleg** repository. - -When submitting a pull request, please include: - -- A clear and descriptive title -- A detailed description of the changes you made -- Any relevant issue numbers (if applicable) - -We will review your pull request as soon as possible and provide feedback if necessary. - -Code Style -========== - -Please follow the ``PEP-8`` style guide when contributing code to **opensourceleg**. We also recommend using an automated code formatter like ``black`` to ensure consistent formatting. - -Testing -======= - -Please ensure that your code changes include tests to cover the new functionality or bug fix. We use ``pytest`` for testing, and you can run the tests locally by running ``pytest`` in the root directory of the repository. diff --git a/docs/contributing/reporting_bugs.rst b/docs/contributing/reporting_bugs.rst deleted file mode 100644 index 179b83de..00000000 --- a/docs/contributing/reporting_bugs.rst +++ /dev/null @@ -1,13 +0,0 @@ -============== -Reporting Bugs -============== - -We welcome contributions to the **opensourceleg** library! Whether you want to report a bug, request a feature, or submit a pull request, we appreciate your help in making this library better. - -If you encounter a bug or have a feature request, please open an issue on the **opensourceleg** GitHub repository. When reporting an issue, please include as much detail as possible, including: - -- A clear and descriptive title -- A detailed description of the issue or feature request -- Steps to reproduce the issue (if applicable) -- Any error messages or stack traces (if applicable) -- Your operating system and Python version diff --git a/docs/examples/basic_motion.rst b/docs/examples/basic_motion.rst deleted file mode 100644 index 90d0d7ff..00000000 --- a/docs/examples/basic_motion.rst +++ /dev/null @@ -1,106 +0,0 @@ -Basic Motion Test Script -================================ -In this example, we'll write a basic script that commands the OSL to move the joints in simple, periodic motions. -This script can be a helpful first step when comissioning a new OSL and making sure that the actuation systems -are configured appropriately. - -We'll assume that this is the first example someone looks at, and therefore we'll go through each set of commands in detail. - -Imports ----------- -For this example, we'll import `numpy`, the `OpenSourceLeg` class, and the `units` class from the `tools` module. - -.. literalinclude:: ../../examples/basic_motion.py - :language: python - :linenos: - :lines: 12-15 - -Making an OSL object ------------------------- - -We'll then create an instance of the `OpenSourceLeg` class and name it `osl`. -We pass the desired control loop frequency (200 Hz in our case) as an argument. -This sets the rate of data streaming for the actuators, as well as the frequency of the loop in ``osl.clock``. -We'll see more on this below. - -.. literalinclude:: ../../examples/basic_motion.py - :language: python - :linenos: - :lines: 17 - -Adding Joints --------------- - -Next, we add the knee and the ankle joints to the OSL via the ``add_joint`` method. -We supply the name of the joint and the gear ratio. -Our gear ratio is ``9*83/18=41.5``, which accounts for the internal ActPack ratio as well as the belt transmission. - -.. literalinclude:: ../../examples/basic_motion.py - :language: python - :linenos: - :lines: 18-19 - -.. note:: - If you're only using one joint, you can modify this script by simply commenting out method calls for the joint you're not using. - For example, if you're using ankle-only, you can comment out ``osl.add_joint("knee"...)`` and the subsequent ``knee`` related methods. - - -Generating Reference Trajectories --------------------------------------- - -Next, we define a funciton that returns a function handle for a simple harmonic trajectory. -Given a time input ``t``, instances of these functions return a reference position in degrees. -We'll make a function for both the knee and ankle joints with a period of 30 seconds. -We'll command the ankle to oscillate between -20 and 20 deg and the knee between 10 and 90 deg. - -.. literalinclude:: ../../examples/basic_motion.py - :language: python - :linenos: - :lines: 22-29 - -Configuring the OSL ------------------------ - -Now that the trajectories are configured, we're ready to start sending commands to the OSL. -We start with the syntax ``with osl:``. This syntax allows the library to call certain functions on the entrance and exit of the block. -This way, the library can ensure that the OSL turns off (returns to zero voltage mode) in the event of an error. - -We then home the joints using ``osl.home()``. -The homing routine drives the joints towards their hardstops in order to initialize the encoders. - -.. literalinclude:: ../../examples/basic_motion.py - :language: python - :linenos: - :lines: 31-32 - -After homing, we wait for user input to begin moving the joints. Then we put both joints into position control mode and -set the proportional gains to a low value for this basic example. - -.. literalinclude:: ../../examples/basic_motion.py - :language: python - :linenos: - :lines: 33-37 - -Main Loop ------------ - -Next, we can enter the main loop. The `OpenSourceLeg` class includes a built-in instance of a `SoftRealTimeLoop`. -This loop, which is called `clock`, will execute the contents of a ``for`` loop at the frequency specified when creating the `OpenSourceLeg` object using the syntax ``for t in osl.clock:``. -The loop will run until either ``osl.clock.stop()`` is called, an exception is raised, or the user presses ``ctrl+c`` on the keyboard. - -.. literalinclude:: ../../examples/basic_motion.py - :language: python - :linenos: - :lines: 39-53 - -Within the loop, we first call ``osl.update()`` to query the actuators and other sensors for their latest values. -We then update the knee and ankle position setpoints by calling our trajectory functions. -Note that we convert the setpoint to default OSL units (radians in this case), e.g., ``units.convert_to_default(knee_traj(t), units.position.deg)``. -Finally, we use the ``set_output_position()`` method to command the OSL joints to move to the new reference value. -We also we print values to the screen for debugging. - -Full Code for This Example --------------------------------------- -.. literalinclude:: ../../examples/basic_motion.py - :language: python - :linenos: \ No newline at end of file diff --git a/docs/examples/finite_state_machine.rst b/docs/examples/finite_state_machine.rst deleted file mode 100644 index 86029661..00000000 --- a/docs/examples/finite_state_machine.rst +++ /dev/null @@ -1,256 +0,0 @@ -Finite State Machine Controller -================================ - -Overview ---------------- - -The library ships with three example implementations of the same finite state machine walking controller. - -.. image:: ./images/FSM_Diagram.svg - :width: 400 - :align: center - :alt: A diagram of the finite state machine - -The first implementation is all in Python, and it uses the ``state_machine`` modules from the controls subpackage of this library. -If you plan to write your controllers exclusively in Python, this example would be a good place to start. - -The library also provides support for using compiled `C` and `C++` library functions via the ``compiled_controller`` modules. -You can see a very basic example usage of this module on the tutorials page, which may be helpful to walk through before starting with this example (:ref:`compiled_controller_tutorial_doc`). -The source code for the compiled controllers (C++ and MATLAB implementations) is available in `this repository -`_. -Please refer to the documentation in that repository for information on how to compile both the C++ and MATLAB source code. - -Python Implementation ------------------------ - -Setup and Configuraiton -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -First, we'll perform some standard imports. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 11-17 - -Next, we'll define all of the tunable FSM parameters. -These include the impedance parameters for each state as well as the transitions between states. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 19-60 - -.. note:: - These parameters were roughly tuned for a moderately paced walking gait. - You may want to tune them to better suit your intended use case. - -Next, we enter our main function for this script, `run_FSM_controller()`. -We first instantiate an OSL object, add the joints, and add a loadcell. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 63-87 - -.. note:: - If instantiating the OSL object is unfamiliar, check out the :ref:`adding_joints_tutorial` and :ref:`adding_loadcell_tutorial` tutorial pages. - -Then, we create a `StateMachine` instance. -We've written a helper function to do this just to keep the code tidy, -and we'll get to that function later on. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 89 - -Next, we configure the OSL log: - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 91-113 - -Main Loop -^^^^^^^^^^^ -Now that everything is set up, we will home the OSL and enter the main loop. -During each iteration of the main loop, we call the update method for both the OSL and the FSM. -We then write the current impedance parameters for each joint to the hardware. -A print statement is also included for debugging. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 115-169 - -.. note:: - The OSL library provides sensor values in default units. If your library expects other units, - you need to convert the values prior to assigning them. You can use the ``units`` module in the ``tools`` subpackage to do this. - For example you can convert from radians (the default) to degrees using ``ankle_angle_in_deg = units.convert_from_default(osl.ankle.output_position, units.position.deg)``. - -Building the State Machine -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The function ``build_4_state_FSM()`` uses the ``StateMachine`` functionality of the ``opensourceleg.control`` module to make a state machine with 4 states. -First, we create the states using the ``State`` class. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 175-188 - -Then, we assign impedance values for each state. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 190-224 - -After the states have been defined, we define transition functions. These functions take the ``osl`` instance as arguments and return a boolean when transition criteria are met. -For example, we first define the transition from early stance to late stance based on the loadcell `z` force and the ankle angle as: - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 226-235 - -The remaining transition functions are defined similarly. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 226-267 - -Next, we define events corresponding to the state transtions using the ``Event`` class. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 269-273 - -Finally, we make an instance of the ``StateMachine`` class and add the states, events, and transitions that we've created. -The ``add_transition()`` method takes arguments of a source state, a destination state, an event, and the callback function defining when that transition occurs. -After that, the FSM is fully built and can be returned. - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 275-311 - -Finally, we call our main function: - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :lines: 314-315 - -Full Code for This Example -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. literalinclude:: ../../examples/fsm_walking_controller.py - :language: python - :linenos: - -C++ and MATLAB Implementation ------------------------------------ -To get started, make sure you have compiled either the `C++` or the `MATLAB` source code and have a `FSMController.so` library. -If not, please see the source repository for compilation instructions. -To run this example as is, copy the library you generated to the `examples` directory. -Alternatively, you can modify the search path for the library when loading the controller (see below). - -Load Compiled Library -^^^^^^^^^^^^^^^^^^^^^ - -First, we'll perform standard imports, handle some paths, and setup our OSL object. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 11-36 - -.. note:: - If instantiating the OSL object is unfamiliar, check out the :ref:`adding_joints_tutorial` and :ref:`adding_loadcell_tutorial` tutorial pages. - -Next, we'll instantiate a ``CompiledController`` wrapper object. This takes arguments of -the name of the library (without extension), the path at which it is located (which in this case is the current directory), -and the names of the main, initializaiton, and cleanup functions. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 38-45 - -Define Custom Datatypes -^^^^^^^^^^^^^^^^^^^^^^^^ - -Next, we define the data structures used in the controller. These must exactly match (size and order) what was used to create the library. -First, we define a type for a single set of impedance parameters called ``impedance_param_type``, where `stiffness`, `damping`, and `eq_angle` are each doubles. -The ``CompiledController`` object provides building block types within its ``types`` attribute. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 47-54 - -We then define the type ``joint_impedance_set`` which contains a set of impedance parameters for each of the four states. -Because we have already defined the ``impedance_param_type`` in the previous lines, we can now use it to define additional types. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 55-63 - -We then similarly define the ``transition_parameters`` type as well as the overall ``UserParameters`` type. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 64-85 - -We also define a sensors type. We're using the default sensors, which the ``CompiledController`` class provides in list form already setup correctly. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 86 - -The final type definitions are for the input and output types. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 88-102 - -Configure Impedance and Transition Parameters -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The next section of code configures the impedance and transition paramters based on a pre-defined tuning. -Feel free to play with these values to get the behavior you want. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 105-140 - -Main Loop -^^^^^^^^^^^ - -Now that the controller is configured, we can home the osl, calibrate the loadcell, set both joints to impedance mode, and begin running. -Note that within the main loop after calling ``osl.update()``, we assign the relevant sensor values from the ``osl`` object to the controller inputs. -We also write to any other inputs that change every loop, such as the current time ``t``. -Once all inputs are assigned, we call the ``run()`` method, which calls our compiled library with the configured inputs. -The run method returns the outputs object, which is populated by the compiled library function. - - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 142-176 - -.. note:: - The OSL library provides sensor values in default units. If your library expects other units, - you need to convert the values prior to assigning them. You can use the ``units`` module in the ``tools`` subpackage to do this. - For example you can convert from radians (the default) to degrees using ``ankle_angle_in_deg = units.convert_from_default(osl.ankle.output_position, units.position.deg)``. - -Finally, we write from the outputs structure to the hardware. -Be careful with units at this step as well, making sure your values are either in default -units, or that you call appropriate conversion functions. As our library was in degrees, -we convert to radians. - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: - :lines: 179-204 - -Full Code for This Example -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. literalinclude:: ../../examples/fsm_walking_compiled_controller.py - :language: python - :linenos: \ No newline at end of file diff --git a/docs/examples/images/FSM_Diagram.svg b/docs/examples/images/FSM_Diagram.svg deleted file mode 100644 index 2b9f23f4..00000000 --- a/docs/examples/images/FSM_Diagram.svg +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - - - - - EarlyStance - - LateStance - - EarlyStance - - LateSwing - - - - - - diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..fea66eaa --- /dev/null +++ b/docs/index.md @@ -0,0 +1,68 @@ +# opensourceleg + +An open-source software library for numerical computation, data acquisition,
and control of lower-limb robotic prostheses. + +[![Release](https://img.shields.io/github/v/release/neurobionics/opensourceleg)](https://img.shields.io/github/v/release/neurobionics/opensourceleg) +[![Build status](https://img.shields.io/github/actions/workflow/status/neurobionics/opensourceleg/main.yml?branch=main)](https://github.com/neurobionics/opensourceleg/actions/workflows/main.yml?query=branch%3Amain) +[![Commit activity](https://img.shields.io/github/commit-activity/m/neurobionics/opensourceleg)](https://img.shields.io/github/commit-activity/m/neurobionics/opensourceleg) +[![License](https://img.shields.io/github/license/neurobionics/opensourceleg)](https://img.shields.io/github/license/neurobionics/opensourceleg) + + + +## Installation + +The easiest and quickest way to install the _opensourceleg_ library is via [pip](https://pip.pypa.io/en/stable/): + +```bash +pip install opensourceleg +``` + +> If you plan on installing the _opensourceleg_ library on a Raspberry Pi, we recommend using [opensourcelegpi](https://github.com/neurobionics/opensourcelegpi) tool, which is a cloud-based CI tool used to build an up-to-date OS for a [Raspberry Pi](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) that can be used headless/GUI-less to control autonomous/remote robotic systems. This tool bundles the _opensourceleg_ library and its dependencies into a single OS image, which can be flashed onto a microSD card and used to boot a Raspberry Pi. For more information, click [here](https://github.com/neurobionics/opensourcelegpi/blob/main/README.md). + +### Developing + +To modify, develop, or contribute to the [opensourceleg](https://pypi.org/project/opensourceleg/) library, we encourage you to install [Poetry](https://python-poetry.org), which is a python packaging and dependency management tool. Once you have Poetry installed on your local machine, you can clone the repository and install the _opensourceleg_ library by running the following commands: + +```bash +git clone https://github.com/neurobionics/opensourceleg.git +cd opensourceleg + +poetry install +poetry shell +``` + +Finally, install the environment and the pre-commit hooks with + +```bash +make install +``` + +You are now ready to start development on your project! +The CI/CD pipeline will be triggered when you open a pull request, merge to main, or when you create a new release. + +To finalize the set-up for publishing to PyPI or Artifactory, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/publishing/#set-up-for-pypi). +For activating the automatic documentation with MkDocs, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/mkdocs/#enabling-the-documentation-on-github). +To enable the code coverage reports, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/codecov/). + +## Releasing a new version + +- Create an API Token on [PyPI](https://pypi.org/). +- Add the API Token to your projects secrets with the name `PYPI_TOKEN` by visiting [this page](https://github.com/neurobionics/opensourceleg/settings/secrets/actions/new). +- Create a [new release](https://github.com/neurobionics/opensourceleg/releases/new) on Github. +- Create a new tag in the form `*.*.*`. +- For more details, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/cicd/#how-to-trigger-a-release). + +## License + +The _opensourceleg_ library is licensed under the terms of the [LGPL-v2.1 license](https://github.com/neurobionics/opensourceleg/raw/main/LICENSE). This license grants users a number of freedoms: + +- You are free to use the _opensourceleg_ library for any purpose. +- You are free to modify the _opensourceleg_ library to suit your needs. +- You can study how the _opensourceleg_ library works and change it. +- You can distribute modified versions of the _opensourceleg_ library. + +The GPL license ensures that all these freedoms are protected, now and in the future, requiring everyone to share their modifications when they also share the library in public. + +## Contributing + +Contributions are welcome, and they are greatly appreciated! For more details, read our [contribution guidelines](CONTRIBUTING.md). diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index d549607f..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,111 +0,0 @@ -=========================================================================================================================== -An open-source software library for numerical computation, data acquisition, and control of lower-limb robotic prostheses. -=========================================================================================================================== - -|build| |docs| |python| |license| - -.. |build| image:: https://github.com/imsenthur/opensourceleg/workflows/build/badge.svg?branch=master&event=push - :target: https://github.com/imsenthur/opensourceleg/actions?query=workflow%3Abuild - :alt: Build Status -.. |docs| image:: https://readthedocs.org/projects/opensourceleg/badge/?version=latest - :target: https://opensourceleg.readthedocs.io/en/latest/?badge=latest - :alt: Docs Status -.. |python| image:: https://img.shields.io/pypi/pyversions/opensourceleg.svg - :target: https://pypi.org/project/opensourceleg/ - :alt: Python Versions -.. |license| image:: https://img.shields.io/github/license/imsenthur/opensourceleg - :target: https://github.com/imsenthur/opensourceleg/blob/main/LICENSE - :alt: License - -| - -.. image:: ../assets/images/banner.gif - :width: 80% - -Prerequisites -============= - -Before installing **opensourceleg** library, you should ensure that you have the following prerequisites installed: - -* ``Python 3.9`` or later -* ``pip`` package manager - -Installation -============ - -The easiest and quickest way to install the *opensourceleg* library is via `pip `_: - -.. code-block:: bash - - pip install opensourceleg - -If you plan on installing the *opensourceleg* library on a Raspberry Pi, we recommend using `opensourcelegpi `_ tool, which is a cloud-based CI tool used to build an up-to-date OS for a `Raspberry Pi `_ that can be used headless/GUI-less to control autonomous/remote robotic systems. This tool bundles the *opensourceleg* library and its dependencies into a single OS image, which can be flashed onto a microSD card and used to boot a Raspberry Pi. For more information, click `here `_. - -Getting Started -================= - -For new users, we recommend visiting the :ref:`getting_started` page for an overview of the library and its documentation. - -Developing -========== - -To modify, develop, or contribute to the `opensourceleg `_ library, we encourage you to install `Poetry `_, which is a python packaging and dependency management tool. Once you have Poetry installed on your local machine, you can clone the repository and install the *opensourceleg* library by running the following commands: - -.. code-block:: bash - - git clone https://github.com/neurobionics/opensourceleg.git - cd opensourceleg - - poetry install - poetry shell - -License -======= - -The *opensourceleg* library is licensed under the terms of the `LGPL-v2.1 license `_. This license grants users a number of freedoms: - -* You are free to use the *opensourceleg* library for any purpose. -* You are free to modify the *opensourceleg* library to suit your needs. -* You can study how the *opensourceleg* library works and change it. -* You can distribute modified versions of the *opensourceleg* library. - -The GPL license ensures that all these freedoms are protected, now and in the future, requiring everyone to share their modifications when they also share the library in public. - -.. toctree:: - :hidden: - :caption: Tutorials - - /tutorials/getting_started - /tutorials/adding_joints - /tutorials/adding_loadcell - /tutorials/voltage_mode - /tutorials/current_mode - /tutorials/position_mode - /tutorials/impedance_mode - /tutorials/compiled_control - /tutorials/osl_clock - /tutorials/reading_from_sensors - -.. toctree:: - :hidden: - :caption: Examples - - /examples/basic_motion - /examples/finite_state_machine - -.. toctree:: - :hidden: - :caption: API Reference - - /api/osl - /api/hardware - /api/control - /api/tools - -.. toctree:: - :hidden: - :caption: Contributing - - /contributing/reporting_bugs - /contributing/contributing_code - /contributing/code_of_conduct diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 32bb2452..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -%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.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/modules.md b/docs/modules.md new file mode 100644 index 00000000..673c84e0 --- /dev/null +++ b/docs/modules.md @@ -0,0 +1 @@ +::: opensourceleg.actuators diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 234ec0f2..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,964 +0,0 @@ -accessible-pygments==0.0.4 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:416c6d8c1ea1c5ad8701903a20fcedf953c6e720d64f33dc47bfb2d3f2fa4e8d \ - --hash=sha256:e7b57a9b15958e9601c7e9eb07a440c813283545a20973f2574a5f453d0e953e -alabaster==0.7.13 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \ - --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2 -astroid==3.0.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:7d5895c9825e18079c5aeac0572bc2e4c83205c95d416e0b4fee8bc361d2d9ca \ - --hash=sha256:86b0bb7d7da0be1a7c4aedb7974e391b32d4ed89e33de6ed6902b4b15c97577e -atomicwrites==1.4.1 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "win32" \ - --hash=sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11 -attrs==23.1.0 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ - --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 -babel==2.13.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210 \ - --hash=sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec -bandit==1.7.5 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549 \ - --hash=sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e -beautifulsoup4==4.12.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da \ - --hash=sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a -black==22.12.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320 \ - --hash=sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351 \ - --hash=sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350 \ - --hash=sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f \ - --hash=sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf \ - --hash=sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148 \ - --hash=sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4 \ - --hash=sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d \ - --hash=sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc \ - --hash=sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d \ - --hash=sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2 \ - --hash=sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f -certifi==2023.7.22 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ - --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 -cfgv==3.4.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \ - --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560 -charset-normalizer==3.3.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5 \ - --hash=sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93 \ - --hash=sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a \ - --hash=sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d \ - --hash=sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c \ - --hash=sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1 \ - --hash=sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58 \ - --hash=sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2 \ - --hash=sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557 \ - --hash=sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147 \ - --hash=sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041 \ - --hash=sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2 \ - --hash=sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2 \ - --hash=sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7 \ - --hash=sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296 \ - --hash=sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690 \ - --hash=sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67 \ - --hash=sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57 \ - --hash=sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597 \ - --hash=sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846 \ - --hash=sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b \ - --hash=sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97 \ - --hash=sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c \ - --hash=sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62 \ - --hash=sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa \ - --hash=sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f \ - --hash=sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e \ - --hash=sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821 \ - --hash=sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3 \ - --hash=sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4 \ - --hash=sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb \ - --hash=sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727 \ - --hash=sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514 \ - --hash=sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d \ - --hash=sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761 \ - --hash=sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55 \ - --hash=sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f \ - --hash=sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c \ - --hash=sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034 \ - --hash=sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6 \ - --hash=sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae \ - --hash=sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1 \ - --hash=sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14 \ - --hash=sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1 \ - --hash=sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228 \ - --hash=sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708 \ - --hash=sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48 \ - --hash=sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f \ - --hash=sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5 \ - --hash=sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f \ - --hash=sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4 \ - --hash=sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8 \ - --hash=sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff \ - --hash=sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61 \ - --hash=sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b \ - --hash=sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97 \ - --hash=sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b \ - --hash=sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605 \ - --hash=sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728 \ - --hash=sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d \ - --hash=sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c \ - --hash=sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf \ - --hash=sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673 \ - --hash=sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1 \ - --hash=sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b \ - --hash=sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41 \ - --hash=sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8 \ - --hash=sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f \ - --hash=sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4 \ - --hash=sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008 \ - --hash=sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9 \ - --hash=sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5 \ - --hash=sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f \ - --hash=sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e \ - --hash=sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273 \ - --hash=sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45 \ - --hash=sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e \ - --hash=sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656 \ - --hash=sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e \ - --hash=sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c \ - --hash=sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2 \ - --hash=sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72 \ - --hash=sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056 \ - --hash=sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397 \ - --hash=sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42 \ - --hash=sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd \ - --hash=sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3 \ - --hash=sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213 \ - --hash=sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf \ - --hash=sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67 -click==8.1.3 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \ - --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 -colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 -contourpy==1.1.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6 \ - --hash=sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33 \ - --hash=sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8 \ - --hash=sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d \ - --hash=sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d \ - --hash=sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c \ - --hash=sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf \ - --hash=sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e \ - --hash=sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e \ - --hash=sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163 \ - --hash=sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532 \ - --hash=sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2 \ - --hash=sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8 \ - --hash=sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1 \ - --hash=sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b \ - --hash=sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9 \ - --hash=sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916 \ - --hash=sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23 \ - --hash=sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb \ - --hash=sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a \ - --hash=sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e \ - --hash=sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442 \ - --hash=sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684 \ - --hash=sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34 \ - --hash=sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d \ - --hash=sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d \ - --hash=sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9 \ - --hash=sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45 \ - --hash=sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718 \ - --hash=sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab \ - --hash=sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3 \ - --hash=sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae \ - --hash=sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb \ - --hash=sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5 \ - --hash=sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba \ - --hash=sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0 \ - --hash=sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217 \ - --hash=sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887 \ - --hash=sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887 \ - --hash=sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62 \ - --hash=sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431 \ - --hash=sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b \ - --hash=sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce \ - --hash=sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b \ - --hash=sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f \ - --hash=sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85 \ - --hash=sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e \ - --hash=sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7 \ - --hash=sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251 \ - --hash=sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970 \ - --hash=sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0 \ - --hash=sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7 -coverage-badge==1.1.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:c824a106503e981c02821e7d32f008fb3984b2338aa8c3800ec9357e33345b78 \ - --hash=sha256:e365d56e5202e923d1b237f82defd628a02d1d645a147f867ac85c58c81d7997 -coverage==6.5.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79 \ - --hash=sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a \ - --hash=sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f \ - --hash=sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a \ - --hash=sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa \ - --hash=sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398 \ - --hash=sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba \ - --hash=sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d \ - --hash=sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf \ - --hash=sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b \ - --hash=sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518 \ - --hash=sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d \ - --hash=sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795 \ - --hash=sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2 \ - --hash=sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e \ - --hash=sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32 \ - --hash=sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745 \ - --hash=sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b \ - --hash=sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e \ - --hash=sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d \ - --hash=sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f \ - --hash=sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660 \ - --hash=sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62 \ - --hash=sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6 \ - --hash=sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04 \ - --hash=sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c \ - --hash=sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5 \ - --hash=sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef \ - --hash=sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc \ - --hash=sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae \ - --hash=sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578 \ - --hash=sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466 \ - --hash=sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4 \ - --hash=sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91 \ - --hash=sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0 \ - --hash=sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4 \ - --hash=sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b \ - --hash=sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe \ - --hash=sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b \ - --hash=sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75 \ - --hash=sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b \ - --hash=sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c \ - --hash=sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72 \ - --hash=sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b \ - --hash=sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f \ - --hash=sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e \ - --hash=sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53 \ - --hash=sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3 \ - --hash=sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84 \ - --hash=sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987 -coverage[toml]==6.5.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79 \ - --hash=sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a \ - --hash=sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f \ - --hash=sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a \ - --hash=sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa \ - --hash=sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398 \ - --hash=sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba \ - --hash=sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d \ - --hash=sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf \ - --hash=sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b \ - --hash=sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518 \ - --hash=sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d \ - --hash=sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795 \ - --hash=sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2 \ - --hash=sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e \ - --hash=sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32 \ - --hash=sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745 \ - --hash=sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b \ - --hash=sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e \ - --hash=sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d \ - --hash=sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f \ - --hash=sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660 \ - --hash=sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62 \ - --hash=sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6 \ - --hash=sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04 \ - --hash=sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c \ - --hash=sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5 \ - --hash=sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef \ - --hash=sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc \ - --hash=sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae \ - --hash=sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578 \ - --hash=sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466 \ - --hash=sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4 \ - --hash=sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91 \ - --hash=sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0 \ - --hash=sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4 \ - --hash=sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b \ - --hash=sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe \ - --hash=sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b \ - --hash=sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75 \ - --hash=sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b \ - --hash=sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c \ - --hash=sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72 \ - --hash=sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b \ - --hash=sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f \ - --hash=sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e \ - --hash=sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53 \ - --hash=sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3 \ - --hash=sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84 \ - --hash=sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987 -cycler==0.12.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \ - --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c -darglint==1.8.1 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da \ - --hash=sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d -dill==0.3.7 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e \ - --hash=sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03 -distlib==0.3.7 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 \ - --hash=sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8 -docutils==0.17.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125 \ - --hash=sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61 -dparse==0.6.3 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0d8fe18714056ca632d98b24fbfc4e9791d4e47065285ab486182288813a5318 \ - --hash=sha256:27bb8b4bcaefec3997697ba3f6e06b2447200ba273c0b085c3d012a04571b528 -filelock==3.12.4 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4 \ - --hash=sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd -flexsea==8.0.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:6b728a7cd8c74b1184cea4dad13138f8e843a2f58d4f2291ff24f14ca0d0e7aa -fonttools==4.43.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:10003ebd81fec0192c889e63a9c8c63f88c7d72ae0460b7ba0cd2a1db246e5ad \ - --hash=sha256:10b3922875ffcba636674f406f9ab9a559564fdbaa253d66222019d569db869c \ - --hash=sha256:13a9a185259ed144def3682f74fdcf6596f2294e56fe62dfd2be736674500dba \ - --hash=sha256:17dbc2eeafb38d5d0e865dcce16e313c58265a6d2d20081c435f84dc5a9d8212 \ - --hash=sha256:18a2477c62a728f4d6e88c45ee9ee0229405e7267d7d79ce1f5ce0f3e9f8ab86 \ - --hash=sha256:18eefac1b247049a3a44bcd6e8c8fd8b97f3cad6f728173b5d81dced12d6c477 \ - --hash=sha256:1952c89a45caceedf2ab2506d9a95756e12b235c7182a7a0fff4f5e52227204f \ - --hash=sha256:1cf9e974f63b1080b1d2686180fc1fbfd3bfcfa3e1128695b5de337eb9075cef \ - --hash=sha256:1e09da7e8519e336239fbd375156488a4c4945f11c4c5792ee086dd84f784d02 \ - --hash=sha256:2062542a7565091cea4cc14dd99feff473268b5b8afdee564f7067dd9fff5860 \ - --hash=sha256:25d3da8a01442cbc1106490eddb6d31d7dffb38c1edbfabbcc8db371b3386d72 \ - --hash=sha256:34f713dad41aa21c637b4e04fe507c36b986a40f7179dcc86402237e2d39dcd3 \ - --hash=sha256:360201d46165fc0753229afe785900bc9596ee6974833124f4e5e9f98d0f592b \ - --hash=sha256:3b7ad05b2beeebafb86aa01982e9768d61c2232f16470f9d0d8e385798e37184 \ - --hash=sha256:4c54466f642d2116686268c3e5f35ebb10e49b0d48d41a847f0e171c785f7ac7 \ - --hash=sha256:4d9740e3783c748521e77d3c397dc0662062c88fd93600a3c2087d3d627cd5e5 \ - --hash=sha256:4f88cae635bfe4bbbdc29d479a297bb525a94889184bb69fa9560c2d4834ddb9 \ - --hash=sha256:51669b60ee2a4ad6c7fc17539a43ffffc8ef69fd5dbed186a38a79c0ac1f5db7 \ - --hash=sha256:5db46659cfe4e321158de74c6f71617e65dc92e54980086823a207f1c1c0e24b \ - --hash=sha256:5f37e31291bf99a63328668bb83b0669f2688f329c4c0d80643acee6e63cd933 \ - --hash=sha256:6bb5ea9076e0e39defa2c325fc086593ae582088e91c0746bee7a5a197be3da0 \ - --hash=sha256:748015d6f28f704e7d95cd3c808b483c5fb87fd3eefe172a9da54746ad56bfb6 \ - --hash=sha256:7bbbf8174501285049e64d174e29f9578495e1b3b16c07c31910d55ad57683d8 \ - --hash=sha256:884ef38a5a2fd47b0c1291647b15f4e88b9de5338ffa24ee52c77d52b4dfd09c \ - --hash=sha256:8da417431bfc9885a505e86ba706f03f598c85f5a9c54f67d63e84b9948ce590 \ - --hash=sha256:95e974d70238fc2be5f444fa91f6347191d0e914d5d8ae002c9aa189572cc215 \ - --hash=sha256:9648518ef687ba818db3fcc5d9aae27a369253ac09a81ed25c3867e8657a0680 \ - --hash=sha256:9a2f0aa6ca7c9bc1058a9d0b35483d4216e0c1bbe3962bc62ce112749954c7b8 \ - --hash=sha256:9c36da88422e0270fbc7fd959dc9749d31a958506c1d000e16703c2fce43e3d0 \ - --hash=sha256:9c60ecfa62839f7184f741d0509b5c039d391c3aff71dc5bc57b87cc305cff3b \ - --hash=sha256:9f727c3e3d08fd25352ed76cc3cb61486f8ed3f46109edf39e5a60fc9fecf6ca \ - --hash=sha256:a7a06f8d95b7496e53af80d974d63516ffb263a468e614978f3899a6df52d4b3 \ - --hash=sha256:ad0b3f6342cfa14be996971ea2b28b125ad681c6277c4cd0fbdb50340220dfb6 \ - --hash=sha256:b2adca1b46d69dce4a37eecc096fe01a65d81a2f5c13b25ad54d5430ae430b13 \ - --hash=sha256:b84a1c00f832feb9d0585ca8432fba104c819e42ff685fcce83537e2e7e91204 \ - --hash=sha256:bb6d2f8ef81ea076877d76acfb6f9534a9c5f31dc94ba70ad001267ac3a8e56f \ - --hash=sha256:bf11e2cca121df35e295bd34b309046c29476ee739753bc6bc9d5050de319273 \ - --hash=sha256:d21099b411e2006d3c3e1f9aaf339e12037dbf7bf9337faf0e93ec915991f43b \ - --hash=sha256:d4071bd1c183b8d0b368cc9ed3c07a0f6eb1bdfc4941c4c024c49a35429ac7cd \ - --hash=sha256:e117a92b07407a061cde48158c03587ab97e74e7d73cb65e6aadb17af191162a \ - --hash=sha256:f7a58eb5e736d7cf198eee94844b81c9573102ae5989ebcaa1d1a37acd04b33d \ - --hash=sha256:fe9b1ec799b6086460a7480e0f55c447b1aca0a4eecc53e444f639e967348896 -gitdb==4.0.11 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ - --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b -gitpython==3.1.40 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4 \ - --hash=sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a -identify==2.5.30 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54 \ - --hash=sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d -idna==3.4 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ - --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 -imagesize==1.4.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ - --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a -importlib-metadata==6.8.0 ; python_version >= "3.9" and python_version < "3.10" \ - --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ - --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 -importlib-resources==6.1.0 ; python_version >= "3.9" and python_version < "3.10" \ - --hash=sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9 \ - --hash=sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83 -iniconfig==2.0.0 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ - --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 -isort==5.12.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504 \ - --hash=sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6 -isort[colors]==5.12.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504 \ - --hash=sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6 -jinja2==3.1.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 -kiwisolver==1.4.5 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf \ - --hash=sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e \ - --hash=sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af \ - --hash=sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f \ - --hash=sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046 \ - --hash=sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3 \ - --hash=sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5 \ - --hash=sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71 \ - --hash=sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee \ - --hash=sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3 \ - --hash=sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9 \ - --hash=sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b \ - --hash=sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985 \ - --hash=sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea \ - --hash=sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16 \ - --hash=sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89 \ - --hash=sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c \ - --hash=sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9 \ - --hash=sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712 \ - --hash=sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342 \ - --hash=sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a \ - --hash=sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958 \ - --hash=sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d \ - --hash=sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a \ - --hash=sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130 \ - --hash=sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff \ - --hash=sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898 \ - --hash=sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b \ - --hash=sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f \ - --hash=sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265 \ - --hash=sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93 \ - --hash=sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929 \ - --hash=sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635 \ - --hash=sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709 \ - --hash=sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b \ - --hash=sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb \ - --hash=sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a \ - --hash=sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920 \ - --hash=sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e \ - --hash=sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544 \ - --hash=sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45 \ - --hash=sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390 \ - --hash=sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77 \ - --hash=sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355 \ - --hash=sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff \ - --hash=sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4 \ - --hash=sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7 \ - --hash=sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20 \ - --hash=sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c \ - --hash=sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162 \ - --hash=sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228 \ - --hash=sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437 \ - --hash=sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc \ - --hash=sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a \ - --hash=sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901 \ - --hash=sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4 \ - --hash=sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770 \ - --hash=sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525 \ - --hash=sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad \ - --hash=sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a \ - --hash=sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29 \ - --hash=sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90 \ - --hash=sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250 \ - --hash=sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d \ - --hash=sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3 \ - --hash=sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54 \ - --hash=sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f \ - --hash=sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1 \ - --hash=sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da \ - --hash=sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238 \ - --hash=sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa \ - --hash=sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523 \ - --hash=sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0 \ - --hash=sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205 \ - --hash=sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3 \ - --hash=sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4 \ - --hash=sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac \ - --hash=sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9 \ - --hash=sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb \ - --hash=sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced \ - --hash=sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd \ - --hash=sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0 \ - --hash=sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da \ - --hash=sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18 \ - --hash=sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9 \ - --hash=sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276 \ - --hash=sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333 \ - --hash=sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b \ - --hash=sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db \ - --hash=sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126 \ - --hash=sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9 \ - --hash=sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09 \ - --hash=sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0 \ - --hash=sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec \ - --hash=sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7 \ - --hash=sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff \ - --hash=sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9 \ - --hash=sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192 \ - --hash=sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8 \ - --hash=sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d \ - --hash=sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6 \ - --hash=sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797 \ - --hash=sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892 \ - --hash=sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f -markdown-it-py==3.0.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ - --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb -markupsafe==2.1.3 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ - --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ - --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ - --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ - --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ - --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ - --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ - --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ - --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ - --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ - --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ - --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ - --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ - --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ - --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ - --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ - --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ - --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ - --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ - --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ - --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ - --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ - --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ - --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ - --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ - --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ - --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ - --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ - --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ - --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ - --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ - --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ - --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ - --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ - --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ - --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ - --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ - --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ - --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ - --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ - --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ - --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ - --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ - --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ - --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ - --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ - --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ - --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ - --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ - --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ - --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ - --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ - --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ - --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ - --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ - --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ - --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ - --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ - --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ - --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 -matplotlib==3.8.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:061ee58facb3580cd2d046a6d227fb77e9295599c5ec6ad069f06b5821ad1cfc \ - --hash=sha256:0b11f354aae62a2aa53ec5bb09946f5f06fc41793e351a04ff60223ea9162955 \ - --hash=sha256:0d5ee602ef517a89d1f2c508ca189cfc395dd0b4a08284fb1b97a78eec354644 \ - --hash=sha256:0e723f5b96f3cd4aad99103dc93e9e3cdc4f18afdcc76951f4857b46f8e39d2d \ - --hash=sha256:23ed11654fc83cd6cfdf6170b453e437674a050a452133a064d47f2f1371f8d3 \ - --hash=sha256:2ea6886e93401c22e534bbfd39201ce8931b75502895cfb115cbdbbe2d31f287 \ - --hash=sha256:31e793c8bd4ea268cc5d3a695c27b30650ec35238626961d73085d5e94b6ab68 \ - --hash=sha256:36eafe2128772195b373e1242df28d1b7ec6c04c15b090b8d9e335d55a323900 \ - --hash=sha256:3cc3776836d0f4f22654a7f2d2ec2004618d5cf86b7185318381f73b80fd8a2d \ - --hash=sha256:5dc945a9cb2deb7d197ba23eb4c210e591d52d77bf0ba27c35fc82dec9fa78d4 \ - --hash=sha256:5de39dc61ca35342cf409e031f70f18219f2c48380d3886c1cf5ad9f17898e06 \ - --hash=sha256:60a6e04dfd77c0d3bcfee61c3cd335fff1b917c2f303b32524cd1235e194ef99 \ - --hash=sha256:6c49a2bd6981264bddcb8c317b6bd25febcece9e2ebfcbc34e7f4c0c867c09dc \ - --hash=sha256:6f25ffb6ad972cdffa7df8e5be4b1e3cadd2f8d43fc72085feb1518006178394 \ - --hash=sha256:7b37b74f00c4cb6af908cb9a00779d97d294e89fd2145ad43f0cdc23f635760c \ - --hash=sha256:7f54b9fb87ca5acbcdd0f286021bedc162e1425fa5555ebf3b3dfc167b955ad9 \ - --hash=sha256:87df75f528020a6299f76a1d986c0ed4406e3b2bd44bc5e306e46bca7d45e53e \ - --hash=sha256:90d74a95fe055f73a6cd737beecc1b81c26f2893b7a3751d52b53ff06ca53f36 \ - --hash=sha256:a33bd3045c7452ca1fa65676d88ba940867880e13e2546abb143035fa9072a9d \ - --hash=sha256:c3499c312f5def8f362a2bf761d04fa2d452b333f3a9a3f58805273719bf20d9 \ - --hash=sha256:c4940bad88a932ddc69734274f6fb047207e008389489f2b6f77d9ca485f0e7a \ - --hash=sha256:d670b9348e712ec176de225d425f150dc8e37b13010d85233c539b547da0be39 \ - --hash=sha256:dae97fdd6996b3a25da8ee43e3fc734fff502f396801063c6b76c20b56683196 \ - --hash=sha256:dd386c80a98b5f51571b9484bf6c6976de383cd2a8cd972b6a9562d85c6d2087 \ - --hash=sha256:df8505e1c19d5c2c26aff3497a7cbd3ccfc2e97043d1e4db3e76afa399164b69 \ - --hash=sha256:eee482731c8c17d86d9ddb5194d38621f9b0f0d53c99006275a12523ab021732 \ - --hash=sha256:f691b4ef47c7384d0936b2e8ebdeb5d526c81d004ad9403dfb9d4c76b9979a93 \ - --hash=sha256:f8b5a1bf27d078453aa7b5b27f52580e16360d02df6d3dc9504f3d2ce11f6309 -mccabe==0.7.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ - --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e -mdurl==0.1.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba -mypy-extensions==0.4.4 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd -mypy==0.991 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d \ - --hash=sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6 \ - --hash=sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf \ - --hash=sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f \ - --hash=sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813 \ - --hash=sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33 \ - --hash=sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad \ - --hash=sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05 \ - --hash=sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297 \ - --hash=sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06 \ - --hash=sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd \ - --hash=sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243 \ - --hash=sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305 \ - --hash=sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476 \ - --hash=sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711 \ - --hash=sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70 \ - --hash=sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5 \ - --hash=sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461 \ - --hash=sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab \ - --hash=sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c \ - --hash=sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d \ - --hash=sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135 \ - --hash=sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93 \ - --hash=sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648 \ - --hash=sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a \ - --hash=sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb \ - --hash=sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3 \ - --hash=sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372 \ - --hash=sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb \ - --hash=sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef -nodeenv==1.8.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2 \ - --hash=sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec -numpy==1.25.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2 \ - --hash=sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55 \ - --hash=sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf \ - --hash=sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01 \ - --hash=sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca \ - --hash=sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901 \ - --hash=sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d \ - --hash=sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4 \ - --hash=sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf \ - --hash=sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380 \ - --hash=sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044 \ - --hash=sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545 \ - --hash=sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f \ - --hash=sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f \ - --hash=sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3 \ - --hash=sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364 \ - --hash=sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9 \ - --hash=sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418 \ - --hash=sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f \ - --hash=sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295 \ - --hash=sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3 \ - --hash=sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187 \ - --hash=sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926 \ - --hash=sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357 \ - --hash=sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760 -packaging==23.2 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ - --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 -pathspec==0.11.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20 \ - --hash=sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3 -pbr==5.11.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ - --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 -pillow==10.1.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d \ - --hash=sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de \ - --hash=sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616 \ - --hash=sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839 \ - --hash=sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099 \ - --hash=sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a \ - --hash=sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219 \ - --hash=sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106 \ - --hash=sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b \ - --hash=sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412 \ - --hash=sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b \ - --hash=sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7 \ - --hash=sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2 \ - --hash=sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7 \ - --hash=sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14 \ - --hash=sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f \ - --hash=sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27 \ - --hash=sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57 \ - --hash=sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262 \ - --hash=sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28 \ - --hash=sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610 \ - --hash=sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172 \ - --hash=sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273 \ - --hash=sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e \ - --hash=sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d \ - --hash=sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818 \ - --hash=sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f \ - --hash=sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9 \ - --hash=sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01 \ - --hash=sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7 \ - --hash=sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651 \ - --hash=sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312 \ - --hash=sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80 \ - --hash=sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666 \ - --hash=sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061 \ - --hash=sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b \ - --hash=sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992 \ - --hash=sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593 \ - --hash=sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4 \ - --hash=sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db \ - --hash=sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba \ - --hash=sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd \ - --hash=sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e \ - --hash=sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212 \ - --hash=sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb \ - --hash=sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2 \ - --hash=sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34 \ - --hash=sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256 \ - --hash=sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f \ - --hash=sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2 \ - --hash=sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38 \ - --hash=sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996 \ - --hash=sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a \ - --hash=sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793 -platformdirs==3.11.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ - --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e -pluggy==1.3.0 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12 \ - --hash=sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7 -pre-commit==2.21.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658 \ - --hash=sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad -py==1.11.0 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ - --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 -pydata-sphinx-theme==0.13.3 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:827f16b065c4fd97e847c11c108bf632b7f2ff53a3bca3272f63f3f3ff782ecc \ - --hash=sha256:bf41ca6c1c6216e929e28834e404bfc90e080b51915bbe7563b5e6fda70354f0 -pydocstyle==6.3.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019 \ - --hash=sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1 -pygments==2.16.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ - --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 -pylint==3.0.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0d4c286ef6d2f66c8bfb527a7f8a629009e42c99707dec821a03e1b51a4c1496 \ - --hash=sha256:60ed5f3a9ff8b61839ff0348b3624ceeb9e6c2a92c514d81c9cc273da3b6bcda -pyparsing==3.1.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb \ - --hash=sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db -pyserial==3.5 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb \ - --hash=sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0 -pytest-cov==3.0.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6 \ - --hash=sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470 -pytest-html==3.2.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:868c08564a68d8b2c26866f1e33178419bb35b1e127c33784a28622eb827f3f3 \ - --hash=sha256:c4e2f4bb0bffc437f51ad2174a8a3e71df81bbc2f6894604e604af18fbe687c3 -pytest-metadata==2.0.4 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:acb739f89fabb3d798c099e9e0c035003062367a441910aaaf2281bc1972ee14 \ - --hash=sha256:fcc653f65fe3035b478820b5284fbf0f52803622ee3f60a2faed7a7d3ba1f41e -pytest-mock==3.12.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f \ - --hash=sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9 -pytest==6.2.5 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 \ - --hash=sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134 -python-dateutil==2.8.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 -pyupgrade==2.38.4 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:1eb43a49f416752929741ba4d706bf3f33593d3cac9bdc217fc1ef55c047c1f4 \ - --hash=sha256:944ff993c396ddc2b9012eb3de4cda138eb4c149b22c6c560d4c8bfd0e180982 -pyyaml==6.0.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ - --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ - --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ - --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ - --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ - --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ - --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \ - --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ - --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ - --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ - --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ - --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ - --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ - --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ - --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ - --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ - --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ - --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \ - --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ - --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ - --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ - --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ - --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ - --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ - --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ - --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ - --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ - --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ - --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ - --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ - --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \ - --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \ - --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \ - --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \ - --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \ - --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \ - --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \ - --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \ - --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ - --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ - --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ - --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ - --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ - --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ - --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ - --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ - --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ - --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \ - --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ - --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f -requests==2.31.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 -rich==13.6.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245 \ - --hash=sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef -ruamel-yaml-clib==0.2.8 ; platform_python_implementation == "CPython" and python_version < "3.13" and python_version >= "3.9" \ - --hash=sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001 \ - --hash=sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462 \ - --hash=sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615 \ - --hash=sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15 \ - --hash=sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b \ - --hash=sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675 \ - --hash=sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1 \ - --hash=sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7 \ - --hash=sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312 \ - --hash=sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f \ - --hash=sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91 \ - --hash=sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa \ - --hash=sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b \ - --hash=sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3 \ - --hash=sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5 \ - --hash=sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe \ - --hash=sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3 \ - --hash=sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed \ - --hash=sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337 \ - --hash=sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248 \ - --hash=sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d \ - --hash=sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279 \ - --hash=sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf \ - --hash=sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512 \ - --hash=sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069 \ - --hash=sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb \ - --hash=sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942 \ - --hash=sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d \ - --hash=sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31 \ - --hash=sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92 \ - --hash=sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd \ - --hash=sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5 \ - --hash=sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1 \ - --hash=sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2 \ - --hash=sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875 \ - --hash=sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412 -ruamel-yaml==0.17.40 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:6024b986f06765d482b5b07e086cc4b4cd05dd22ddcbc758fa23d54873cf313d \ - --hash=sha256:b16b6c3816dff0a93dca12acf5e70afd089fa5acb80604afd1ffa8b465b7722c -safety==2.3.4 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:6224dcd9b20986a2b2c5e7acfdfba6bca42bb11b2783b24ed04f32317e5167ea \ - --hash=sha256:b9e74e794e82f54d11f4091c5d820c4d2d81de9f953bf0b4f33ac8bc402ae72c -setuptools-scm==8.0.4 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:b47844cd2a84b83b3187a5782c71128c28b4c94cad8bfb871da2784a5cb54c4f \ - --hash=sha256:b5f43ff6800669595193fd09891564ee9d1d7dcb196cab4b2506d53a2e1c95c7 -setuptools==68.2.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ - --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a -six==1.16.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 -smbus2==0.4.3 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:36f2288a8e1a363cb7a7b2244ec98d880eb5a728a2494ac9c71e9de7bf6a803a \ - --hash=sha256:a2fc29cfda4081ead2ed61ef2c4fc041d71dd40a8d917e85216f44786fca2d1d -smmap==5.0.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ - --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da -snowballstemmer==2.2.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ - --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a -soupsieve==2.5 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690 \ - --hash=sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7 -sphinx-book-theme==1.0.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:927b399a6906be067e49c11ef1a87472f1b1964075c9eea30fb82c64b20aedee \ - --hash=sha256:d15f8248b3718a9a6be0ba617a32d1591f9fa39c614469bface777ba06a73b75 -sphinx==4.5.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6 \ - --hash=sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226 -sphinxcontrib-applehelp==1.0.4 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228 \ - --hash=sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e -sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \ - --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4 -sphinxcontrib-htmlhelp==2.0.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff \ - --hash=sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903 -sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ - --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 -sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ - --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 -sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \ - --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952 -stevedore==5.1.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d \ - --hash=sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c -tokenize-rt==4.2.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:08a27fa032a81cf45e8858d0ac706004fcd523e8463415ddf1442be38e204ea8 \ - --hash=sha256:0d4f69026fed520f8a1e0103aa36c406ef4661417f20ca643f913e33531b3b94 -toml==0.10.2 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f -tomli==2.0.1 ; python_version >= "3.9" and python_full_version < "3.11.0a7" \ - --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ - --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f -tomlkit==0.12.1 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86 \ - --hash=sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899 -tornado==6.3.3 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f \ - --hash=sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5 \ - --hash=sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d \ - --hash=sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3 \ - --hash=sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2 \ - --hash=sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a \ - --hash=sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16 \ - --hash=sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a \ - --hash=sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17 \ - --hash=sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0 \ - --hash=sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe -typing-extensions==4.8.0 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ - --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef -urllib3==2.0.7 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \ - --hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e -virtualenv==20.24.5 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b \ - --hash=sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752 -wheel==0.41.2 ; python_version >= "3.9" and python_full_version < "4.0.0" \ - --hash=sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985 \ - --hash=sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8 -zipp==3.17.0 ; python_version >= "3.9" and python_version < "3.10" \ - --hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \ - --hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0 diff --git a/docs/tutorials/adding_joints.rst b/docs/tutorials/adding_joints.rst deleted file mode 100644 index 3e7899e1..00000000 --- a/docs/tutorials/adding_joints.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. _adding_joints_tutorial: - -Adding an Actuator -================== -In this tutorial, we'll show you how to add a joint to your open-source leg using the ``opensourceleg`` library. - -Import the OpenSourceLeg Class ------------------------------- - -To get started, we need to import the ``OpenSourceLeg`` class, which provides an interface for controlling the open-source leg. - -.. code-block:: python - - from opensourceleg.osl import OpenSourceLeg - -Create an instance of the OpenSourceLeg Class ---------------------------------------------- - -Next, we need to create an ``OpenSourceLeg`` object. This object represents the open-source leg, provides methods for controlling its joint, and provides methods for a variety of other tasks. - -.. code-block:: python - - osl = OpenSourceLeg(frequency=200, file_name="getting_started.log") - -This will create an ``OpenSourceLeg`` object with a `frequency of 200 Hz` and a log file named ``getting_started.log``. - -Add a Joint Object -------------------- - -To add a joint to the open-source leg, we can use the ``add_joint`` method of the ``OpenSourceLeg`` object. - -.. code-block:: python - - osl.add_joint(name="knee", gear_ratio=41.99, has_loadcell=False) - -This will add a joint object named ``knee`` with a gear ratio of ``41.99`` to the `osl` object. You can also specify the **port** the joint is connected to using the ``port`` parameter -of the ``add_joint`` method. If you don't specify a port, the joint will be connected to the first available port. - - -.. Note:: - - The ``has_loadcell`` parameter indicates whether or not the actuator has a load cell connected to it via an FFC cable. This feature is only supported by the Dephy actuators. If you - are using a TMotor actuator, this parameter should always be set to ``False``. We'll discuss the loadcell in more detail in a later tutorial. - -You can also add the ankle joint to the open-source leg by calling the ``add_joint`` method again. If the ``port`` parameter is not specified, the joint will use the next available port. - -.. code-block:: python - - osl.add_joint(name="ankle", gear_ratio=41.99, has_loadcell=False) - -.. Warning:: - Please ensure that you are powering-on the actuators in the order of initialization, i.e. if you are initializing the knee joint first, then the knee joint should be powered-on first. - - -Code for this tutorial ----------------------- - -.. literalinclude:: ../../tutorials/adding_joints.py - :language: python - :linenos: diff --git a/docs/tutorials/adding_loadcell.rst b/docs/tutorials/adding_loadcell.rst deleted file mode 100644 index 4c5e3439..00000000 --- a/docs/tutorials/adding_loadcell.rst +++ /dev/null @@ -1,69 +0,0 @@ -.. _adding_loadcell_tutorial: - -Adding a Loadcell -================= -In this tutorial, we'll show you how to add a load cell to your open-source leg using the ``opensourceleg`` library. - -Create an OpenSourceLeg Object ------------------------------- - -First, we need to create an ``OpenSourceLeg`` object. This object represents your open-source leg. - -.. code-block:: python - - osl = OpenSourceLeg(frequency=200, file_name="getting_started.log") - -This will create an ``OpenSourceLeg`` object with a `frequency of 200 Hz` and a log file named ``getting_started.log``. - -Add a Load Cell Object ----------------------- - -To add a load cell to the ``osl`` object, we can use the ``add_loadcell`` method. - -.. code-block:: python - - LOADCELL_MATRIX = np.array( - [ - (-38.72600, -1817.74700, 9.84900, 43.37400, -44.54000, 1824.67000), - (-8.61600, 1041.14900, 18.86100, -2098.82200, 31.79400, 1058.6230), - ( - -1047.16800, - 8.63900, - -1047.28200, - -20.70000, - -1073.08800, - -8.92300, - ), - (20.57600, -0.04000, -0.24600, 0.55400, -21.40800, -0.47600), - (-12.13400, -1.10800, 24.36100, 0.02300, -12.14100, 0.79200), - (-0.65100, -28.28700, 0.02200, -25.23000, 0.47300, -27.3070), - ] - ) - - osl.add_loadcell(dephy_mode=False, loadcell_matrix=LOADCELL_MATRIX) - - -This will add a loadcell to the ``osl`` object with the specified ``loadcell_matrix``, which is the calibration matrix for the loadcell. This calibration matrix is unique to each load cell and can be found in the loadcell's datasheet. -The ``dephy_mode`` argument is set to ``False``, which means that the loadcell is connected to the Raspberry Pi via the GPIO pins and not to the Dephy actuator using an FFC cable. If you are using a loadcell connected to the Dephy actuator, you should set this argument to ``True``. - -Here is an example of how you would add a load cell to the `osl` object if you were using a loadcell connected to the Dephy actuator: - -.. code-block:: python - - osl.add_joint(name="knee", gear_ratio=41.99, has_loadcell=True) - osl.add_loadcell(dephy_mode=True, joint=osl.knee, loadcell_matrix=LOADCELL_MATRIX) - -This method requires a joint to be added to the `osl` object first. This is because the loadcell is connected to the Dephy actuator, which reads the loadcell data and streams it to the Raspberry Pi. This joint object is passed to the ``add_loadcell`` method so that the loadcell data can be read from the joint object. - -.. Note:: - If you are using a different loadcell amplifier, your amplifier gain and excitation voltage may be different. You can change these values by passing the ``amp_gain`` and ``exc`` arguments to the ``add_loadcell`` method. The default values for these arguments are ``amp_gain=125`` and ``exc=5``. - -Code for this tutorial ----------------------- - -.. literalinclude:: ../../tutorials/adding_loadcell.py - :language: python - :linenos: - - -That's it! You've now added a loadcell to your open-source leg using the ``opensourceleg`` library. diff --git a/docs/tutorials/compiled_control.rst b/docs/tutorials/compiled_control.rst deleted file mode 100644 index e8926b15..00000000 --- a/docs/tutorials/compiled_control.rst +++ /dev/null @@ -1,127 +0,0 @@ -.. _compiled_controller_tutorial_doc: - -Compiled Controllers -======================= -The ``opensourceleg.control`` module provides functionality for using controllers written in languages other than python via the ``CompiledController`` class. -This class wraps a function from a compiled dynamic library and allows it to be called from your main python code. -Using a compiled controller written in another language can be beneficial, especially when speed is a concern. - -Make an Example library -------------------------- -In this tutorial, we will write an example linear algebra library in ``c++`` that provides a function to compute the dot product between two 3D vectors. -This very simple function will allow us to show how to define input and output structures and call the controller. More complex examples using actual controllers in both ``c++`` and MATLAB implementations are provided in the examples folder. - -The ``CompiledController`` class assumes that you have a compiled dynamic library (with extension ``*.so`` on linux) with the function prototype ``myFunction(Inputs* inputs, Outputs* outputs)``, where ``Inputs`` and ``Outputs`` are structures holding all of the input and output data. Thus, we first need to write and compile our library. - -.. literalinclude:: ../../tutorials/compiled_control/dot_product_3d.cpp - :language: cpp - :linenos: - -.. note:: - - The ``extern "C"`` linkage-specification is important to prevent the `C++` compiler from name mangling. - Under the hood, our library uses a `C` style calling convention, which expects to be able to find the library functions with their standard names. - - -First, navigate to the directory ``opensourceleg/tutorials/compiled_control/``. Then run ``make`` to build the library. If succesful, a new library named ``lin_alg.so`` should be created. - -Load the Example Library -------------------------- -Next, we need to write a python script to call our newly compiled library. First, we import the library. We also import ``os`` to get the path of the current directory. - -.. code-block:: python - - import os - - from opensourceleg.control.compiled_controller import CompiledController - -Then we'll make an instance of the ``CompiledController`` wrapper and have it load our linear algebra library. -We need to pass it both the name of the library (without an extension) and the directory in which to find the library. -We also give it the name of our main function as well as any initialization and cleanup functions. - -.. code-block:: python - - my_linalg = CompiledController( - library_name="lin_alg.so", - library_path=os.path.dirname(__file__), - main_function_name="dot_product_3d", - initialization_function_name=None, - cleanup_function_name=None, - ) - -.. Note:: - - If your library provides initialization and cleanup functions, they will be called upon loading and cleanup, respectively. - If your library does not need these functions, pass the default argument of `None`. - -Define Custom Datatypes ---------------------------- -Our library uses a `Vector3D` structure, which we need to define so that the python code can pass the data to the library -in the right format. Every structure is built using basic types from ``my_linalg.types``, -such as `c_double`, `c_bool`, `c_int16`, etc. -We therefore can add `Vector3D` to the list of known types using the ``define_type()`` method, which -takes two arguments: (1) a name of the new type definition, and (2) a list of tuples where the first entry is the name -of the field and the second entry is the type. For example, the code to define `Vector3D` is - -.. code-block:: python - - my_linalg.define_type( - "Vector3D", - [ - ("x", my_linalg.types.c_double), - ("y", my_linalg.types.c_double), - ("z", my_linalg.types.c_double), - ], - ) - -Now the wrapper knows how `Vector3D` is defined, we can use it in other type definitions, the same way as any other basic type. -After all necessary types are defined, we need to define the input and output structures using the ``define_inputs()`` and ``define_outputs()`` methods. -These methods are similar to ``define_type()``, but are special because they tell the wrapper which objects to pass to and from -the compiled library. We define the inputs as two `Vector3D` objects and the output as one double titled result. - -.. code-block:: python - - my_linalg.define_inputs([("vector1", my_linalg.types.Vector3D), - ("vector2", my_linalg.types.Vector3D)]) - my_linalg.define_outputs([("result", my_linalg.types.c_double)]) - -Populate Inputs and Test the Function ---------------------------------------- -Now that the input structure has been defined, we can write to the inputs structure at ``my_linalg.inputs``. -First, we declare two vectors and populate their fields with the appropriate values. - -.. code-block:: python - - vector1 = my_linalg.types.Vector3D() - vector2 = my_linalg.types.Vector3D() - vector1.x = 0.6651 - vector1.y = 0.7395 - vector1.z = 0.1037 - vector2.x = -0.7395 - vector2.y = 0.6716 - vector2.z = -0.0460 - -Then we can assign those vectors to the input structure. - -.. code-block:: python - - my_linalg.inputs.vector1 = vector1 - my_linalg.inputs.vector2 = vector2 - -Finally, we can run the dot product function and print the result from the output structure. -As our input vectors were orthogonal, we get the expected result of zero. - -.. code-block:: python - - outputs = my_linalg.run() - - print(f"Dot product: {outputs.result}") - - Dot product: 3.6549999999971154e-05 - -Code for this tutorial ----------------------- - -.. literalinclude:: ../../tutorials/compiled_control/compiled_control.py - :language: python - :linenos: diff --git a/docs/tutorials/current_mode.rst b/docs/tutorials/current_mode.rst deleted file mode 100644 index cf8af4ec..00000000 --- a/docs/tutorials/current_mode.rst +++ /dev/null @@ -1,54 +0,0 @@ -.. _commanding_current_tutorial: - -Commanding Current -================== - -In this tutorial, we'll show you how to use the ``opensourceleg`` library to control the current of a joint. - -Import the OpenSourceLeg Class ------------------------------- - -To use the ``OpenSourceLeg`` class, we first need to import it from the ``opensourceleg.osl`` module: - -.. code-block:: python - - from opensourceleg.osl import OpenSourceLeg - -Add a Joint Object -------------------- - -Once we have imported the ``OpenSourceLeg`` class, we can create an instance of the class with the desired frequency and joint configuration: - -.. code-block:: python - - osl = OpenSourceLeg(frequency=200) # 200 Hz - osl.add_joint(gear_ratio=9.0) - -In this code, we create an ``OpenSourceLeg`` object named `osl` with a frequency of 200 Hz and a joint with a gear ratio of 9.0. - -Controlling Joint Current --------------------------- - -To control the current of a joint, we can use a ``with`` block to ensure that the ``OpenSourceLeg`` object is properly opened and cleaned up after use: - -.. code-block:: python - - with osl: - osl.knee.set_mode(osl.knee.control_modes.current) - - for t in osl.clock: - osl.knee.set_current(400) # 400 mA - osl.log.info(osl.knee.motor_position) - osl.update() - -In this code, we enter a ``with`` block that sets the mode of the ``knee`` joint to "current". We then loop over the ``osl.clock``, which generates a sequence of timestamps at the specified frequency, and set the current of the ``knee`` joint to 400 mA. We then log the motor position of the ``knee`` joint to the console at each timestamp. We then call the ``osl.update`` method to update the state of the ``OpenSourceLeg`` object. - -.. warning:: - This code assumes that the ``OpenSourceLeg`` object is properly configured and calibrated, and that the joint is properly connected and functioning. - -Code for this tutorial: ------------------------ - -.. literalinclude:: ../../tutorials/current_mode.py - :language: python - :linenos: diff --git a/docs/tutorials/getting_started.rst b/docs/tutorials/getting_started.rst deleted file mode 100644 index ce6a98a3..00000000 --- a/docs/tutorials/getting_started.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. _getting_started: - -Getting Started -===================== - -There are a few concepts that almost every OSL script will reqiure. -Before anything else, we suggest going through the following basic tutorials: - -- :ref:`adding_joints_tutorial` -- :ref:`adding_loadcell_tutorial` -- :ref:`reading_from_sensors_tutorial` -- :ref:`using_osl_clock_tutorial` -- :ref:`commanding_current_tutorial` - -Once you're comfortable with these, you should be ready to tackle the rest of the Tutorials and the Examples. - diff --git a/docs/tutorials/impedance_mode.rst b/docs/tutorials/impedance_mode.rst deleted file mode 100644 index 78e52b17..00000000 --- a/docs/tutorials/impedance_mode.rst +++ /dev/null @@ -1,61 +0,0 @@ -Commanding Impedance -==================== - -In this tutorial, we'll show you how to use the ``opensourceleg`` library to control the impedance of a joint. - -Import the OpenSourceLeg Class ------------------------------- - -To use the ``OpenSourceLeg`` class, we first need to import it from the ``opensourceleg.osl`` module: - -.. code-block:: python - - from opensourceleg.osl import OpenSourceLeg - -Add a Joint Object -------------------- - -Once we have imported the ``OpenSourceLeg`` class, we can create an instance of the class with the desired frequency and joint configuration: - -.. code-block:: python - - osl = OpenSourceLeg(frequency=200) # 200 Hz - osl.add_joint(gear_ratio=9.0) - -Controlling Joint Impedance ---------------------------- - -To control the impedance of a joint, we can use a ``with`` block to ensure that the ``OpenSourceLeg`` object is properly opened and cleaned up after use: - -.. code-block:: python - - test_stiffness_value = 20 # Nm/rad - test_damping_value = 20 # Nm/rad/s - set_point = 50 # motor ticks - - with osl: - osl.knee.set_mode(osl.knee.control_modes.impedance) - osl.knee.set_output_impedance(K=test_stiffness_value, B=test_damping_value) - osl.knee.set_motor_position(osl.knee.motor_position + set_point) - - for t in osl.clock: - osl.log.info(osl.knee.motor_position) - osl.update() - -In this code, we enter a ``with`` block that sets the mode of the ``knee`` joint to `impedance` and set the motor position of the ``knee`` joint to the current position plus a set point of 50 degrees. We then set the impedance of the ``knee`` joint using the ``set_output_impedance`` method, with a stiffness of 20 Nm/rad and a damping of 20 Nm/rad/s. We then loop over the ``osl.clock`` generator, which generates a sequence of timestamps at the specified frequency, and log the motor position of the ``knee`` joint to the console at each timestamp. We then call the ``osl.update`` method to update the state of the ``OpenSourceLeg`` object. - -.. Note:: - When setting the impedance gains, we can also use the ``set_motor_impedance`` method to set the impedance of the motor instead of the joint. The difference between the two methods is that the ``set_output_impedance`` method first divides the initial value by the gear ratio squared then sets the gains, while the ``set_motor_impedance`` method simply sets the gains. The impedance of the joint is the impedance of the motor plus the impedance of the joint. - -.. Note:: - We can also set the gains directly using "convert" methods. We can set the impedance gains using the ``set_impedance_gains`` method and the ``convert_to_joint_impedance`` method for joint stiffness and damping using real units. Alternatively, we can set impedance gains using the ``set_impedance_gains`` method and the ``convert_to_motor_impedance`` method for setting the motor stiffness and damping values with real units. Finally, we can set the impedance gains using the ``set_impedance_gains`` method and the ``convert_to_pid_impedance`` method for setting the motor sitffness and damping using the built-in PID controller. - -.. Warning:: - This code assumes that the ``OpenSourceLeg`` object is properly configured and calibrated, and that the joint is properly connected and functioning. - -Code for this tutorial: ------------------------ - -.. literalinclude:: ../../tutorials/impedance_mode.py - :language: python - :linenos: diff --git a/docs/tutorials/logger_tutorial.rst b/docs/tutorials/logger_tutorial.rst deleted file mode 100644 index 5d54ae25..00000000 --- a/docs/tutorials/logger_tutorial.rst +++ /dev/null @@ -1,97 +0,0 @@ -Logger Tutorial -==================== - -This tutorial demonstrates the usage of the ``Logger`` class from the ``opensourceleg.tools.logger`` module to log attributes from class instances to a CSV file. - -Import the Logger Class ------------------------ - -To use the ``Logger`` class, import it from the ``opensourceleg.tools.logger`` module: - -.. code-block:: python - - from opensourceleg.tools.logger import Logger - -Initialization --------------- - -To begin logging, create an instance of the ``Logger`` class: - -.. code-block:: python - - local_logger = Logger(file_path="./test_log") - -- `file_path` (optional): Specify the path and filename for the log file. The default is "./test_log". - -Setting Logging Levels ----------------------- - -Set different logging levels for the file and stream handlers. Available levels are "DEBUG," "INFO," "WARNING," "ERROR," and "CRITICAL." - -.. code-block:: python - - local_logger.set_file_level(level="DEBUG") - local_logger.set_stream_level(level="INFO") - -- ``set_file_level``: Set the logging level for the file handler. -- ``set_stream_level``: Set the logging level for the stream handler. - -Creating a Sample Class ------------------------ - -Define a sample class with attributes to be logged: - -.. code-block:: python - - class SimpleClass: - def __init__(self): - self.a = 1 - self.b = 2 - self.c = 3 - - simple_class = SimpleClass() - -Adding Attributes for Logging ------------------------------- - -Use the ``add_attributes`` method to specify the class instance and attributes to log: - -.. code-block:: python - - local_logger.add_attributes(container=simple_class, attributes=["a", "b", "c"]) - -- ``container``: Pass the object (instance of a class) or a dictionary containing the attributes to be logged. -- ``attributes``: Provide a list of attributes to log. - -Logging Data ------------- - -Once attributes are added, log data using the ``data`` method: - -.. code-block:: python - - local_logger.data() - -- ``data``: Logs the attributes of the class instance to the CSV file. - -Logging a Debug Message ------------------------ - -Log a debug message using the ``debug`` method: - -.. code-block:: python - - local_logger.debug("message") - -- ``debug``: Logs a debug message. - -Closing the Logger ------------------- - -After logging is complete, close the CSV file using the ``close`` method: - -.. code-block:: python - - local_logger.close() - -- ``close``: Closes the CSV file. diff --git a/docs/tutorials/osl_clock.rst b/docs/tutorials/osl_clock.rst deleted file mode 100644 index 6e9580b0..00000000 --- a/docs/tutorials/osl_clock.rst +++ /dev/null @@ -1,42 +0,0 @@ -.. _using_osl_clock_tutorial: - -Using the OSL Clock -====================== - -The ``OpenSourceLeg`` class contains a ``clock`` object that can be used to control the execution of infinite loops. -The ``clock`` is an iterable object that returns a new value (the time since instantiation in seconds) at a specified rate. -Using ``osl.clock`` in a ``for`` loop will create an infinite loop that executes at a fixed frequency. That is, - -.. code-block:: python - - for t in osl.clock: - # this code will execute repeatedly at a fixed rate - -.. note:: - The frequency that ``osl.clock`` updates at is the same as the frequency selected when instantiating the ``OpenSourceLeg`` object. - -The clock will continue ticking until one of three conditions are met: - -1. You call ``osl.clock.stop`` -2. An exception occurs -3. You press ctrl+c on the keyboard. - -We normally use option 3 to end our scripts, allowing the ctrl+c keyboard combination to basically act as a stop button. -In general, we suggest using the following overall structure for your code: - -.. code-block:: python - - # Imports - - # Configuration/Initialization Code - osl = OpenSourceLeg(frequency=loop_frequency) - - with osl: - osl.home() - - for t in osl.clock: - # Main loop code - osl.update() - - # Cleanup code - diff --git a/docs/tutorials/position_mode.rst b/docs/tutorials/position_mode.rst deleted file mode 100644 index fab62b48..00000000 --- a/docs/tutorials/position_mode.rst +++ /dev/null @@ -1,54 +0,0 @@ -Commanding Position -=================== -In this tutorial, we'll show you how to control your open-source leg in position mode using the ``opensourceleg`` library. - -Import the OpenSourceLeg Class ------------------------------- - -To use the ``OpenSourceLeg`` class, we first need to import it from the ``opensourceleg.osl`` module: - -.. code-block:: python - - import opensourceleg.tools.units as units - from opensourceleg.osl import OpenSourceLeg - -Add a Joint Object -------------------- - -Once we have imported the ``OpenSourceLeg`` class, we can create an instance of the class with the desired frequency and joint configuration: - -.. code-block:: python - - osl = OpenSourceLeg(frequency=200) # 200 Hz - osl.add_joint(gear_ratio=9.0) - -In this code, we create an ``OpenSourceLeg`` object named ``osl`` with a frequency of 200 Hz and a joint with a gear ratio of 9.0. - -Controlling Joint Position --------------------------- - -To control the position of a joint, we can use a ``with`` block to ensure that the ``OpenSourceLeg`` object is properly opened and cleaned up after use: - -.. code-block:: python - - set_point = units.convert_to_default(45, units.position.deg) - - with osl: - osl.knee.set_mode(osl.knee.control_modes.position) - osl.knee.set_motor_position(osl.knee.motor_position + set_point) - - for t in osl.clock: - osl.log.info(osl.knee.motor_position) - osl.update() - -In this code, we enter a ``with`` block that sets the mode of the ``knee`` joint to "position", and set the motor position of the ``knee`` joint to the current position plus the `set_point` value. We then loop over the ``osl.clock`` generator, which generates a sequence of timestamps at the specified frequency, and log the motor position of the ``knee`` joint to the console at each timestamp. We then call the ``osl.update`` method to update the state of the ``OpenSourceLeg`` object. - -.. warning:: - This code assumes that the ``OpenSourceLeg`` object is properly configured and calibrated, and that the joint is properly connected and functioning. - -Code for this tutorial: ------------------------ - -.. literalinclude:: ../../tutorials/position_mode.py - :language: python - :linenos: diff --git a/docs/tutorials/reading_from_sensors.rst b/docs/tutorials/reading_from_sensors.rst deleted file mode 100644 index 0fe06bfe..00000000 --- a/docs/tutorials/reading_from_sensors.rst +++ /dev/null @@ -1,64 +0,0 @@ -.. _reading_from_sensors_tutorial: - -Reading from Sensors -==================== - -In this tutorial, we'll show you how to read from the sensors of your open-source leg using the ``opensourceleg`` library. - -Import the OpenSourceLeg Class ------------------------------- - -To use the ``OpenSourceLeg`` class, we first need to import it from the ``opensourceleg.osl`` module: - -.. code-block:: python - - from opensourceleg.osl import OpenSourceLeg - -Add a Joint Object ------------------- - -Once we have imported the ``OpenSourceLeg`` class, we can create an instance of the class with the desired frequency and joint configuration: - -.. code-block:: python - - osl = OpenSourceLeg(frequency=200) - osl.add_joint(gear_ratio=9.0) - -In this code, we create an ``OpenSourceLeg`` object named ``osl`` with a frequency of 200 and a joint with a gear ratio of 9.0. - -.. rubric:: Step 3: Setting Units for the position Attribute - -We can set the units for the ``position`` attribute of the ``osl`` object using the ``units`` dictionary: - -.. code-block:: python - - osl.units["position"] = "deg" - osl.log.info(osl.units) - -In this code, we set the units for the ``position`` attribute to "deg" and log the units to the console. - -Reading from Sensors --------------------- - -To read from sensors, we can use a ``with`` block to ensure that the ``OpenSourceLeg`` object is properly opened and cleaned up after use: - -.. code-block:: python - - with osl: - osl.knee.set_mode(osl.knee.control_modes.voltage) - - for t in osl.clock: - osl.log.info(osl.knee.motor_position) - osl.update() - -In this code, we enter a ``with`` block that sets the mode of the ``knee`` joint to "voltage". We then loop over the ``osl.clock`` generator, which generates a sequence of timestamps at the specified frequency, and log the motor position of the ``knee`` joint to the console at each timestamp. We then call the ``osl.update`` method to update the state of the ``OpenSourceLeg`` object. - -.. warning:: - This code assumes that the ``OpenSourceLeg`` object is properly configured and calibrated, and that the sensors are properly connected and functioning. - -Code for this tutorial ----------------------- - -.. literalinclude:: ../../tutorials/reading_from_sensors.py - :language: python - :linenos: diff --git a/docs/tutorials/voltage_mode.rst b/docs/tutorials/voltage_mode.rst deleted file mode 100644 index 508a823a..00000000 --- a/docs/tutorials/voltage_mode.rst +++ /dev/null @@ -1,52 +0,0 @@ -Commanding Voltage -================== - -In this tutorial, we'll show you how to use the ``opensourceleg`` library to control the voltage of a joint. - -Import the OpenSourceLeg Class ------------------------------- - -To use the ``OpenSourceLeg`` class, we first need to import it from the ``opensourceleg.osl`` module: - -.. code-block:: python - - from opensourceleg.osl import OpenSourceLeg - -Add a Joint object ------------------- - -Once we have imported the ``OpenSourceLeg`` class, we can create an instance of the class with the desired frequency and joint configuration: - -.. code-block:: python - - osl = OpenSourceLeg(frequency=200) # 200 Hz - osl.add_joint(gear_ratio=9.0) - -In this code, we create an ``OpenSourceLeg`` object named ``osl`` with a frequency of 200 Hz and a joint with a gear ratio of 9.0. - -Commanding Joint Voltage -------------------------- - -To control the voltage of a joint, we can use a ``with`` block to ensure that the ``OpenSourceLeg`` object is properly opened and cleaned up after use: - -.. code-block:: python - - with osl: - osl.knee.set_mode(osl.knee.control_modes.voltage) - - for t in osl.clock: - osl.knee.set_voltage(1000) # mV - osl.log.info(osl.knee.motor_position) - osl.update() - -In this code, we enter a ``with`` block that sets the mode of the ``knee`` joint to "voltage". We then loop over the ``osl.clock`` generator, which generates a sequence of timestamps at the specified frequency, and set the voltage of the ``knee`` joint to 1000 mV. We then log the motor position of the ``knee`` joint to the console at each timestamp. We then call the ``osl.update`` method to update the state of the ``OpenSourceLeg`` object. - -.. warning:: - This code assumes that the ``OpenSourceLeg`` object is properly configured and calibrated, and that the joint is properly connected and functioning. - -Code for this tutorial: ------------------------ - -.. literalinclude:: ../../tutorials/voltage_mode.py - :language: python - :linenos: diff --git a/examples/basic_motion.py b/examples/basic_motion.py index 2044bb83..335f0012 100644 --- a/examples/basic_motion.py +++ b/examples/basic_motion.py @@ -69,12 +69,7 @@ def make_periodic_trajectory(period, minimum, maximum): ankle.set_output_position(ankle_setpoint) print( - "Ankle Desired {:+.2f} rad, Ankle Actual {:+.2f} rad, Knee Desired {:+.2f} rad, Ankle Desired {:+.2f} rad".format( - ankle_setpoint, - ankle.output_position, - knee_setpoint, - knee.output_position, - ), + f"Ankle Desired {ankle_setpoint:+.2f} rad, Ankle Actual {ankle.output_position:+.2f} rad, Knee Desired {knee_setpoint:+.2f} rad, Ankle Desired {knee.output_position:+.2f} rad", end="\r", ) diff --git a/examples/fsm_walking_compiled_controller.py b/examples/fsm_walking_compiled_controller.py index 9768d2f3..6146f1ef 100644 --- a/examples/fsm_walking_compiled_controller.py +++ b/examples/fsm_walking_compiled_controller.py @@ -42,16 +42,14 @@ actuators = [knee, ankle] -LOADCELL_MATRIX = np.array( - [ - (-38.72600, -1817.74700, 9.84900, 43.37400, -44.54000, 1824.67000), - (-8.61600, 1041.14900, 18.86100, -2098.82200, 31.79400, 1058.6230), - (-1047.16800, 8.63900, -1047.28200, -20.70000, -1073.08800, -8.92300), - (20.57600, -0.04000, -0.24600, 0.55400, -21.40800, -0.47600), - (-12.13400, -1.10800, 24.36100, 0.02300, -12.14100, 0.79200), - (-0.65100, -28.28700, 0.02200, -25.23000, 0.47300, -27.3070), - ] -) +LOADCELL_MATRIX = np.array([ + (-38.72600, -1817.74700, 9.84900, 43.37400, -44.54000, 1824.67000), + (-8.61600, 1041.14900, 18.86100, -2098.82200, 31.79400, 1058.6230), + (-1047.16800, 8.63900, -1047.28200, -20.70000, -1073.08800, -8.92300), + (20.57600, -0.04000, -0.24600, 0.55400, -21.40800, -0.47600), + (-12.13400, -1.10800, 24.36100, 0.02300, -12.14100, 0.79200), + (-0.65100, -28.28700, 0.02200, -25.23000, 0.47300, -27.3070), +]) loadcell = SRILoadcell( calibration_matrix=LOADCELL_MATRIX, @@ -110,21 +108,17 @@ ) controller.define_type("sensors", controller.DEFAULT_SENSOR_LIST) -controller.define_inputs( - [ - ("parameters", controller.types.UserParameters), - ("sensors", controller.types.sensors), - ("time", controller.types.c_double), - ] -) -controller.define_outputs( - [ - ("current_state", controller.types.c_int), - ("time_in_current_state", controller.types.c_double), - ("knee_impedance", controller.types.impedance_param_type), - ("ankle_impedance", controller.types.impedance_param_type), - ] -) +controller.define_inputs([ + ("parameters", controller.types.UserParameters), + ("sensors", controller.types.sensors), + ("time", controller.types.c_double), +]) +controller.define_outputs([ + ("current_state", controller.types.c_int), + ("time_in_current_state", controller.types.c_double), + ("knee_impedance", controller.types.impedance_param_type), + ("ankle_impedance", controller.types.impedance_param_type), +]) # Populate Controller inputs as needed controller.inputs.parameters.knee_impedance.early_stance.stiffness = 99.372 # type: ignore @@ -181,8 +175,12 @@ units.convert_from_default(knee.output_position, units.position.deg) ) controller.inputs.sensors.ankle_angle = units.convert_from_default(ankle.output_position, units.position.deg) # type: ignore - controller.inputs.sensors.knee_velocity = units.convert_from_default(knee.output_velocity, units.velocity.deg_per_s) # type: ignore - controller.inputs.sensors.ankle_velocity = units.convert_from_default(ankle.output_velocity, units.velocity.deg_per_s) # type: ignore + controller.inputs.sensors.knee_velocity = units.convert_from_default( + knee.output_velocity, units.velocity.deg_per_s + ) # type: ignore + controller.inputs.sensors.ankle_velocity = units.convert_from_default( + ankle.output_velocity, units.velocity.deg_per_s + ) # type: ignore controller.inputs.sensors.Fz = loadcell.fz # type: ignore # Update any control inputs that change every loop @@ -193,42 +191,20 @@ # Test print to ensure external library call works print( - "Current time in state {}: {:.2f} seconds, Knee Eq {:.2f}, Ankle Eq {:.2f}, Fz {:.2f}".format( - outputs.current_state, - outputs.time_in_current_state, - outputs.knee_impedance.eq_angle, - outputs.ankle_impedance.eq_angle, - loadcell.fz, - ), + f"Current time in state {outputs.current_state}: {outputs.time_in_current_state:.2f} seconds, Knee Eq {outputs.knee_impedance.eq_angle:.2f}, Ankle Eq {outputs.ankle_impedance.eq_angle:.2f}, Fz {loadcell.fz:.2f}", end="\r", ) # Write to the hardware knee.set_output_impedance( - k=units.convert_to_default( - outputs.knee_impedance.stiffness, units.stiffness.N_m_per_rad - ), - b=units.convert_to_default( - outputs.knee_impedance.damping, units.damping.N_m_per_rad_per_s - ), - ) - knee.set_output_position( - value=units.convert_to_default( - outputs.knee_impedance.eq_angle, units.position.deg - ) + k=units.convert_to_default(outputs.knee_impedance.stiffness, units.stiffness.N_m_per_rad), + b=units.convert_to_default(outputs.knee_impedance.damping, units.damping.N_m_per_rad_per_s), ) + knee.set_output_position(value=units.convert_to_default(outputs.knee_impedance.eq_angle, units.position.deg)) ankle.set_output_impedance( - k=units.convert_to_default( - outputs.ankle_impedance.stiffness, units.stiffness.N_m_per_rad - ), - b=units.convert_to_default( - outputs.ankle_impedance.damping, units.damping.N_m_per_rad_per_s - ), - ) - ankle.set_output_position( - value=units.convert_to_default( - outputs.ankle_impedance.eq_angle, units.position.deg - ) + k=units.convert_to_default(outputs.ankle_impedance.stiffness, units.stiffness.N_m_per_rad), + b=units.convert_to_default(outputs.ankle_impedance.damping, units.damping.N_m_per_rad_per_s), ) + ankle.set_output_position(value=units.convert_to_default(outputs.ankle_impedance.eq_angle, units.position.deg)) print("\n") diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..c55c7d07 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,54 @@ +site_name: opensourceleg +repo_url: https://github.com/neurobionics/opensourceleg +site_url: https://neurobionics.github.io/opensourceleg +site_description: An open-source software library for numerical computation, data acquisition, and control of lower-limb robotic prostheses. +site_author: Open-Source Leg +edit_uri: edit/main/docs/ +repo_name: neurobionics/opensourceleg +copyright: Maintained by Florian. + +nav: + - Home: index.md + - Modules: modules.md +plugins: + - search + - mkdocstrings: + handlers: + python: + setup_commands: + - import sys + - sys.path.append('../') +theme: + name: material + feature: + tabs: true + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: white + accent: deep orange + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: deep orange + toggle: + icon: material/brightness-4 + name: Switch to light mode + icon: + repo: fontawesome/brands/github + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/neurobionics/opensourceleg + - icon: fontawesome/brands/python + link: https://pypi.org/project/opensourceleg + +markdown_extensions: + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true diff --git a/opensourceleg/actuators/README.md b/opensourceleg/actuators/README.md index fcfb8199..49ded55e 100644 --- a/opensourceleg/actuators/README.md +++ b/opensourceleg/actuators/README.md @@ -48,22 +48,22 @@ The documentation mainly focus on how to develop a custom **actuator module**. S pass ``` - Index from `class ControlModesBase` is needed if constructing the Actuator Object from `base.ActuatorBase`. For example: - ```Python - @dataclass(init=False) - class ActuatorControlModes(ControlModesBase): + Index from `class ControlModesBase` is needed if constructing the Actuator Object from `base.ActuatorBase`. For example: - def __init__(self, actuator: "Actuator") -> None: + ```Python + @dataclass(init=False) + class ActuatorControlModes(ControlModesBase): + + def __init__(self, actuator: "Actuator") -> None: - self.VOLTAGE = VoltageMode(actuator=actuator) - self.CURRENT = CurrentMode(actuator=actuator) - self.POSITION = PositionMode(actuator=actuator) - self.IMPEDANCE = ImpedanceMode(actuator=actuator) + self.VOLTAGE = VoltageMode(actuator=actuator) + self.CURRENT = CurrentMode(actuator=actuator) + self.POSITION = PositionMode(actuator=actuator) + self.IMPEDANCE = ImpedanceMode(actuator=actuator) ``` 3. Customize the actuator object from `base.ActuatorBase` - ```Python class Motor(base.ActuatorBase): @@ -132,7 +132,6 @@ The documentation mainly focus on how to develop a custom **actuator module**. S ![](./images/Class%20Diagram%20Base%20Lib.svg) - ### [DephyActpack](./dephy.py) Support #### Known Issues @@ -141,7 +140,7 @@ The documentation mainly focus on how to develop a custom **actuator module**. S ### [Moteus Controller](./moteus.py) Support -#### Known Issues [Check [Issue Page](https://github.com/neurobionics/opensourceleg/issues) for Details] +#### Known Issues [Check [Issue Page](https://github.com/neurobionics/opensourceleg/issues) for Details] - Missing Joint Properties (e.g. `homing`) diff --git a/opensourceleg/actuators/__init__.py b/opensourceleg/actuators/__init__.py index e69de29b..a56df426 100644 --- a/opensourceleg/actuators/__init__.py +++ b/opensourceleg/actuators/__init__.py @@ -0,0 +1,5 @@ +from .base import * +from .decorators import * +from .dephy import * +from .moteus import * +from .tmotor import * diff --git a/opensourceleg/actuators/base.py b/opensourceleg/actuators/base.py index 248554e8..a31346e5 100644 --- a/opensourceleg/actuators/base.py +++ b/opensourceleg/actuators/base.py @@ -1,23 +1,19 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from enum import Enum +from functools import partial from typing import ( Any, Callable, - Dict, - List, NamedTuple, Optional, Protocol, - Set, TypeVar, Union, cast, runtime_checkable, ) -from abc import ABC, abstractmethod -from dataclasses import dataclass -from enum import Enum -from functools import partial - import numpy as np from opensourceleg.logging.logger import LOGGER @@ -36,9 +32,7 @@ class MOTOR_CONSTANTS: def __post_init__(self): if any(x <= 0 for x in self.__dict__.values()): - raise ValueError( - "All values in MOTOR_CONSTANTS must be non-zero and positive." - ) + raise ValueError("All values in MOTOR_CONSTANTS must be non-zero and positive.") @property def RAD_PER_COUNT(self) -> float: @@ -119,9 +113,9 @@ def decorator(func: T) -> T: raise TypeError("All arguments to 'requires' must be of type CONTROL_MODES") if not hasattr(func, "_required_modes"): - setattr(func, "_required_modes", set(modes)) + func._required_modes = set(modes) else: - getattr(func, "_required_modes").update(modes) + func._required_modes.update(modes) return func @@ -182,18 +176,14 @@ def _set_original_methods(self): if callable(method) and hasattr(method, "_required_modes"): self._original_methods[method_name] = method except AttributeError: - LOGGER.debug( - msg=f"[{self.tag}] {method_name}() is not implemented in {self.tag}." - ) + LOGGER.debug(msg=f"[{self.tag}] {method_name}() is not implemented in {self.tag}.") def _set_mutated_methods(self): for method_name, method in self._original_methods.items(): if self._mode in method._required_modes: setattr(self, method_name, method) else: - setattr( - self, method_name, partial(self._restricted_method, method_name) - ) + setattr(self, method_name, partial(self._restricted_method, method_name)) @property @abstractmethod @@ -212,16 +202,13 @@ def stop(self) -> None: def update(self) -> None: pass - def _get_control_mode_config( - self, mode: CONTROL_MODES - ) -> Optional[ControlModeConfig]: + def _get_control_mode_config(self, mode: CONTROL_MODES) -> Optional[ControlModeConfig]: return cast( Optional[ControlModeConfig], getattr(self._CONTROL_MODE_CONFIGS, mode.name), ) def set_control_mode(self, mode: CONTROL_MODES) -> None: - if self.mode == mode: LOGGER.debug(msg=f"[{self.tag}] Already in {self.mode.name} control mode.") return @@ -281,9 +268,7 @@ def set_position_gains(self, kp: float, ki: float, kd: float, ff: float) -> None @abstractmethod @requires(CONTROL_MODES.IMPEDANCE) - def set_impedance_gains( - self, kp: float, ki: float, kd: float, k: float, b: float, ff: float - ) -> None: + def set_impedance_gains(self, kp: float, ki: float, kd: float, k: float, b: float, ff: float) -> None: pass @abstractmethod diff --git a/opensourceleg/actuators/dephy.py b/opensourceleg/actuators/dephy.py index a194522c..f0894d1b 100644 --- a/opensourceleg/actuators/dephy.py +++ b/opensourceleg/actuators/dephy.py @@ -1,10 +1,6 @@ -from typing import Any, Callable, Union, overload - import os import time from ctypes import c_int -from dataclasses import dataclass -from unittest.mock import Mock import numpy as np from flexsea.device import Device @@ -24,9 +20,7 @@ ) from opensourceleg.logging import LOGGER from opensourceleg.logging.decorators import ( - deprecated, deprecated_with_routing, - deprecated_with_suggestion, ) from opensourceleg.math import ThermalModel from opensourceleg.safety import ThermalLimitException @@ -185,7 +179,7 @@ def start(self) -> None: try: self.open() self._is_open = True - except OSError as e: + except OSError: print("\n") LOGGER.error( msg=f"[{self.__repr__()}] Need admin previleges to open the port '{self.port}'. \n\nPlease run the script with 'sudo' command or add the user to the dialout group.\n" @@ -202,7 +196,6 @@ def start(self) -> None: @check_actuator_stream @check_actuator_open def stop(self) -> None: - self.stop_motor() self.set_control_mode(mode=CONTROL_MODES.IDLE) self._is_streaming = False @@ -210,7 +203,6 @@ def stop(self) -> None: self.close() def update(self) -> None: - self._data = self.read() self._thermal_model.T_c = self.case_temperature @@ -264,15 +256,11 @@ def home( """ is_homing = True - homing_frequency = ( - homing_frequency if homing_frequency is not None else self.frequency - ) + homing_frequency = homing_frequency if homing_frequency is not None else self.frequency self.set_control_mode(mode=CONTROL_MODES.VOLTAGE) - self.set_motor_voltage( - value=homing_direction * homing_voltage - ) # mV, negative for counterclockwise + self.set_motor_voltage(value=homing_direction * homing_voltage) # mV, negative for counterclockwise _motor_encoder_array = [] _joint_encoder_array = [] @@ -287,10 +275,7 @@ def home( _motor_encoder_array.append(self.motor_position) _joint_encoder_array.append(self.joint_position) - if ( - abs(self.output_velocity) <= velocity_threshold - or abs(self.motor_current) >= current_threshold - ): + if abs(self.output_velocity) <= velocity_threshold or abs(self.motor_current) >= current_threshold: self.set_motor_voltage(value=0) is_homing = False @@ -339,15 +324,11 @@ def make_encoder_map(self, overwrite=False) -> None: """ if not self.is_homed: - LOGGER.warning( - msg=f"[{self.__repr__()}] Please home the {self.tag} joint before making the encoder map." - ) + LOGGER.warning(msg=f"[{self.__repr__()}] Please home the {self.tag} joint before making the encoder map.") return if os.path.exists(f"./{self.tag}_encoder_map.npy") and not overwrite: - LOGGER.info( - msg=f"[{self.__repr__()}] Encoder map exists. Skipping encoder map creation." - ) + LOGGER.info(msg=f"[{self.__repr__()}] Encoder map exists. Skipping encoder map creation.") return self.set_control_mode(mode=CONTROL_MODES.CURRENT) @@ -382,9 +363,7 @@ def make_encoder_map(self, overwrite=False) -> None: LOGGER.warning(msg="Encoder map interrupted.") return - LOGGER.info( - msg=f"[{self.__repr__()}] You may now stop moving the {self.tag} joint." - ) + LOGGER.info(msg=f"[{self.__repr__()}] You may now stop moving the {self.tag} joint.") _power = np.arange(4.0) _a_mat = np.array(_joint_encoder_array).reshape(-1, 1) ** _power @@ -394,9 +373,7 @@ def make_encoder_map(self, overwrite=False) -> None: self.set_encoder_map(np.polynomial.polynomial.Polynomial(coef=_coeffs)) np.save(file=f"./{self.tag}_encoder_map.npy", arr=_coeffs) - LOGGER.info( - msg=f"[{self.__repr__()}] Encoder map saved to './{self.tag}_encoder_map.npy'." - ) + LOGGER.info(msg=f"[{self.__repr__()}] Encoder map saved to './{self.tag}_encoder_map.npy'.") def set_motor_torque(self, value: float) -> None: """ @@ -456,7 +433,6 @@ def set_motor_voltage(self, value: float) -> None: @deprecated_with_routing(alternative_func=set_motor_voltage) def set_voltage(self, value: float) -> None: - self.command_motor_voltage(value=int(value)) def set_motor_position(self, value: float) -> None: @@ -469,8 +445,7 @@ def set_motor_position(self, value: float) -> None: """ self.command_motor_position( value=int( - (value + self.motor_zero_position + self.motor_position_offset) - / self.MOTOR_CONSTANTS.RAD_PER_COUNT + (value + self.motor_zero_position + self.motor_position_offset) / self.MOTOR_CONSTANTS.RAD_PER_COUNT ), ) @@ -627,9 +602,7 @@ def encoder_map(self): if getattr(self, "_encoder_map", None) is not None: return self._encoder_map else: - LOGGER.warning( - msg="Encoder map is not set. Please call the make_encoder_map method to create one." - ) + LOGGER.warning(msg="Encoder map is not set. Please call the make_encoder_map method to create one.") return None @property @@ -909,51 +882,35 @@ def thermal_scaling_factor(self) -> float: def _dephy_legacy_voltage_mode_entry(dephy_actuator: "DephyActuator") -> None: - LOGGER.debug( - msg=f"[{dephy_actuator.tag}] Entering {CONTROL_MODES.VOLTAGE.name} control mode." - ) + LOGGER.debug(msg=f"[{dephy_actuator.tag}] Entering {CONTROL_MODES.VOLTAGE.name} control mode.") def _dephy_legacy_current_mode_entry(dephy_actuator: "DephyActuator") -> None: - LOGGER.debug( - msg=f"[{dephy_actuator.tag}] Entering {CONTROL_MODES.CURRENT.name} control mode." - ) + LOGGER.debug(msg=f"[{dephy_actuator.tag}] Entering {CONTROL_MODES.CURRENT.name} control mode.") def _dephy_legacy_position_mode_entry(dephy_actuator: "DephyActuator") -> None: - LOGGER.debug( - msg=f"[{dephy_actuator.tag}] Entering {CONTROL_MODES.POSITION.name} control mode." - ) + LOGGER.debug(msg=f"[{dephy_actuator.tag}] Entering {CONTROL_MODES.POSITION.name} control mode.") def _dephy_legacy_impedance_mode_entry(dephy_actuator: "DephyActuator") -> None: - LOGGER.debug( - msg=f"[{dephy_actuator.tag}] Entering {CONTROL_MODES.IMPEDANCE.name} control mode." - ) + LOGGER.debug(msg=f"[{dephy_actuator.tag}] Entering {CONTROL_MODES.IMPEDANCE.name} control mode.") def _dephy_legacy_voltage_mode_exit(dephy_actuator: "DephyActuator") -> None: - LOGGER.debug( - msg=f"[{dephy_actuator.tag}] Exiting {CONTROL_MODES.VOLTAGE.name} control mode." - ) + LOGGER.debug(msg=f"[{dephy_actuator.tag}] Exiting {CONTROL_MODES.VOLTAGE.name} control mode.") def _dephy_legacy_current_mode_exit(dephy_actuator: "DephyActuator") -> None: - LOGGER.debug( - msg=f"[{dephy_actuator.tag}] Exiting {CONTROL_MODES.CURRENT.name} control mode." - ) + LOGGER.debug(msg=f"[{dephy_actuator.tag}] Exiting {CONTROL_MODES.CURRENT.name} control mode.") def _dephy_legacy_position_mode_exit(dephy_actuator: "DephyActuator") -> None: - LOGGER.debug( - msg=f"[{dephy_actuator.tag}] Exiting {CONTROL_MODES.POSITION.name} control mode." - ) + LOGGER.debug(msg=f"[{dephy_actuator.tag}] Exiting {CONTROL_MODES.POSITION.name} control mode.") def _dephy_legacy_impedance_mode_exit(dephy_actuator: "DephyActuator") -> None: - LOGGER.debug( - msg=f"[{dephy_actuator.tag}] Exiting {CONTROL_MODES.IMPEDANCE.name} control mode." - ) + LOGGER.debug(msg=f"[{dephy_actuator.tag}] Exiting {CONTROL_MODES.IMPEDANCE.name} control mode.") DEPHY_LEGACY_CONTROL_MODE_CONFIGS = CONTROL_MODE_CONFIGS( @@ -1013,7 +970,6 @@ def __init__( self._is_streaming: bool = False self._is_open: bool = False else: - # def set_is_streaming(self, value): # self._is_streaming = value @@ -1050,7 +1006,7 @@ def start(self) -> None: log_level=self._debug_level, log_enabled=self._dephy_log, ) - except OSError as e: + except OSError: print("\n") LOGGER.error( msg=f"[{self.__repr__()}] Need admin previleges to open the port '{self.port}'. \n\nPlease run the script with 'sudo' command or add the user to the dialout group.\n" @@ -1074,7 +1030,6 @@ def stop(self) -> None: self.close() def update(self) -> None: - self._data = self.read() self._thermal_model.T_c = self.case_temperature @@ -1128,7 +1083,6 @@ def set_motor_voltage(self, value: float) -> None: @deprecated_with_routing(alternative_func=set_motor_voltage) def set_voltage(self, value: float) -> None: - self.send_motor_command(ctrl_mode=c_int(self.mode.value), value=int(value)) def set_motor_position(self, value: float) -> None: @@ -1142,8 +1096,7 @@ def set_motor_position(self, value: float) -> None: self.send_motor_command( ctrl_mode=c_int(self.mode.value), value=int( - (value + self.motor_zero_position + self.motor_position_offset) - / self.MOTOR_CONSTANTS.RAD_PER_COUNT + (value + self.motor_zero_position + self.motor_position_offset) / self.MOTOR_CONSTANTS.RAD_PER_COUNT ), ) diff --git a/opensourceleg/actuators/images/Class Diagram Base Lib.svg b/opensourceleg/actuators/images/Class Diagram Base Lib.svg index 18f3613a..a41de5b3 100644 --- a/opensourceleg/actuators/images/Class Diagram Base Lib.svg +++ b/opensourceleg/actuators/images/Class Diagram Base Lib.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/opensourceleg/actuators/moteus.py b/opensourceleg/actuators/moteus.py index be253123..5eb1e15a 100644 --- a/opensourceleg/actuators/moteus.py +++ b/opensourceleg/actuators/moteus.py @@ -1,14 +1,9 @@ -ο»Ώfrom typing import Any, Union - import math import os -import time -from dataclasses import dataclass import numpy as np -from moteus import Command, Controller +from moteus import Command, Controller, Stream from moteus import Register as MoteusRegister -from moteus import Stream from moteus import multiplex as mp from opensourceleg.actuators.base import ( @@ -56,7 +51,6 @@ class MoteusQueryResolution: - mode = mp.INT8 position = mp.F32 velocity = mp.F32 @@ -139,10 +133,9 @@ def __init__(self): pass def __repr__(self): - return f"MoteusInterface" + return "MoteusInterface" def _add2map(self, servo_id, bus_id) -> None: - if bus_id in self.bus_map.keys(): self.bus_map[bus_id].append(servo_id) else: @@ -227,7 +220,7 @@ async def start(self) -> None: self._is_open = True self._is_streaming = True - except OSError as e: + except OSError: print("\n") LOGGER.error( msg=f"[{self.__repr__()}] Need admin previleges to open the port. \n\nPlease run the script with 'sudo' command or add the user to the dialout group.\n" @@ -239,9 +232,7 @@ async def start(self) -> None: default_mode_config.entry_callback(self) if (await self._interface.transport.cycle([self.make_stop(query=True)])) == []: - LOGGER.error( - msg=f"[{self.__repr__()}] Could not start the actuator. Please check the connection." - ) + LOGGER.error(msg=f"[{self.__repr__()}] Could not start the actuator. Please check the connection.") self._is_streaming = False self._is_open = False # Keep the default command as query -- reading sensor data @@ -256,7 +247,6 @@ async def stop(self) -> None: self._command = self.make_query() async def update(self): - self._data = await self._interface.transport.cycle([self._command]) self._thermal_model.T_c = self.case_temperature @@ -314,15 +304,12 @@ def set_motor_current( self, value: float, ): - LOGGER.info(f"Current Mode Not Implemented") + LOGGER.info("Current Mode Not Implemented") def set_motor_velocity(self, value: float) -> None: self._command = self.make_position( position=math.nan, - velocity=value - / ( - np.pi * 2 - ), # TODO: Verify this conversion, are we converting from rad/s to rev/s? + velocity=value / (np.pi * 2), # TODO: Verify this conversion, are we converting from rad/s to rev/s? query=True, watchdog_timeout=math.nan, ) @@ -334,7 +321,7 @@ def set_motor_voltage(self, value: float) -> None: Args: value (float): The voltage to set in mV. """ - LOGGER.info(f"Voltage Mode Not Implemented") + LOGGER.info("Voltage Mode Not Implemented") def set_motor_position(self, value: float) -> None: """ @@ -345,9 +332,7 @@ def set_motor_position(self, value: float) -> None: value (float): The position to set """ self._command = self.make_position( - position=float( - (value) / (2 * np.pi) - ), # TODO: Verify this conversion, are we converting from rad to rev? + position=float((value) / (2 * np.pi)), # TODO: Verify this conversion, are we converting from rad to rev? query=True, watchdog_timeout=math.nan, ) @@ -460,10 +445,7 @@ def motor_current(self) -> float: @property def motor_torque(self) -> float: if self._data is not None: - return ( - float(self.motor_current * self.MOTOR_CONSTANTS.NM_PER_MILLIAMP) - / self.gear_ratio - ) + return float(self.motor_current * self.MOTOR_CONSTANTS.NM_PER_MILLIAMP) / self.gear_ratio else: LOGGER.warning( msg="Actuator data is none, please ensure that the actuator is connected and streaming. Returning 0.0." diff --git a/opensourceleg/actuators/tmotor.py b/opensourceleg/actuators/tmotor.py index 3482906b..d4d9c4b9 100644 --- a/opensourceleg/actuators/tmotor.py +++ b/opensourceleg/actuators/tmotor.py @@ -17,7 +17,6 @@ CONTROL_MODES, MOTOR_CONSTANTS, ActuatorBase, - ControlGains, ControlModeConfig, ) from opensourceleg.actuators.decorators import ( @@ -25,7 +24,6 @@ check_actuator_open, check_actuator_stream, ) -from opensourceleg.logging import LOGGER from opensourceleg.math import ThermalModel from opensourceleg.time import SoftRealtimeLoop @@ -179,9 +177,7 @@ def start(self): self._entered = True self.is_streaming = True if not self.check_can_connection(): - raise RuntimeError( - "Device not connected: " + str(self.device_info_string()) - ) + raise RuntimeError("Device not connected: " + str(self.device_info_string())) return self @check_actuator_stream @@ -207,23 +203,16 @@ def update(self): # check that the motor is safely turned on if not self._entered: raise RuntimeError( - "Tried to update motor state before safely powering on for device: " - + self.device_info_string() + "Tried to update motor state before safely powering on for device: " + self.device_info_string() ) if self.case_temperature > self.max_temp: - raise RuntimeError( - "Temperature greater than {}C for device: {}".format( - self.max_temp, self.device_info_string() - ) - ) + raise RuntimeError(f"Temperature greater than {self.max_temp}C for device: {self.device_info_string()}") # check that the motor data is recent # print(self._command_sent) now = time.time() - if (now - self._last_command_time) < 0.25 and ( - (now - self._last_update_time) > 0.1 - ): + if (now - self._last_command_time) < 0.25 and ((now - self._last_update_time) > 0.1): # print("State update requested but no data recieved from motor. Delay longer after zeroing, decrease frequency, or check connection.") warnings.warn( "State update requested but no data from motor. Delay longer after zeroing, decrease frequency, or check connection. " @@ -235,9 +224,7 @@ def update(self): # artificially extending the range of the position, current, and velocity that we track P_max = MIT_Params[self.type]["P_max"] + 0.01 - I_max = ( - self.TMotor_current_to_qaxis_current(MIT_Params[self.type]["T_max"]) + 1.0 - ) + I_max = self.TMotor_current_to_qaxis_current(MIT_Params[self.type]["T_max"]) + 1.0 V_max = MIT_Params[self.type]["V_max"] + 0.01 if self._old_pos is None: @@ -259,64 +246,40 @@ def update(self): actual_current = new_curr # The TMotor will wrap around to -max at the limits for all values it returns!! Account for this - if (thresh_pos <= new_pos and new_pos <= P_max) and ( - -P_max <= old_pos and old_pos <= -thresh_pos - ): + if (thresh_pos <= new_pos and new_pos <= P_max) and (-P_max <= old_pos and old_pos <= -thresh_pos): self._times_past_position_limit -= 1 - elif (thresh_pos <= old_pos and old_pos <= P_max) and ( - -P_max <= new_pos and new_pos <= -thresh_pos - ): + elif (thresh_pos <= old_pos and old_pos <= P_max) and (-P_max <= new_pos and new_pos <= -thresh_pos): self._times_past_position_limit += 1 # current is basically the same as position, but if you instantly command a switch it can actually change fast enough # to throw this off, so that is accounted for too. We just put a hard limit on the current to solve current jitter problems. - if (thresh_curr <= new_curr and new_curr <= I_max) and ( - -I_max <= old_curr and old_curr <= -thresh_curr - ): + if (thresh_curr <= new_curr and new_curr <= I_max) and (-I_max <= old_curr and old_curr <= -thresh_curr): # self._old_current_zone = -1 # if (thresh_curr <= curr_command and curr_command <= I_max): # self._times_past_current_limit -= 1 if curr_command > 0: - actual_current = self.TMotor_current_to_qaxis_current( - MIT_Params[self.type]["T_max"] - ) + actual_current = self.TMotor_current_to_qaxis_current(MIT_Params[self.type]["T_max"]) elif curr_command < 0: - actual_current = -self.TMotor_current_to_qaxis_current( - MIT_Params[self.type]["T_max"] - ) + actual_current = -self.TMotor_current_to_qaxis_current(MIT_Params[self.type]["T_max"]) else: - actual_current = -self.TMotor_current_to_qaxis_current( - MIT_Params[self.type]["T_max"] - ) + actual_current = -self.TMotor_current_to_qaxis_current(MIT_Params[self.type]["T_max"]) new_curr = actual_current - elif (thresh_curr <= old_curr and old_curr <= I_max) and ( - -I_max <= new_curr and new_curr <= -thresh_curr - ): + elif (thresh_curr <= old_curr and old_curr <= I_max) and (-I_max <= new_curr and new_curr <= -thresh_curr): # self._old_current_zone = 1 # if not (-I_max <= curr_command and curr_command <= -thresh_curr): # self._times_past_current_limit += 1 if curr_command > 0: - actual_current = self.TMotor_current_to_qaxis_current( - MIT_Params[self.type]["T_max"] - ) + actual_current = self.TMotor_current_to_qaxis_current(MIT_Params[self.type]["T_max"]) elif curr_command < 0: - actual_current = -self.TMotor_current_to_qaxis_current( - MIT_Params[self.type]["T_max"] - ) + actual_current = -self.TMotor_current_to_qaxis_current(MIT_Params[self.type]["T_max"]) else: - actual_current = self.TMotor_current_to_qaxis_current( - MIT_Params[self.type]["T_max"] - ) + actual_current = self.TMotor_current_to_qaxis_current(MIT_Params[self.type]["T_max"]) new_curr = actual_current # velocity should work the same as position - if (thresh_vel <= new_vel and new_vel <= V_max) and ( - -V_max <= old_vel and old_vel <= -thresh_vel - ): + if (thresh_vel <= new_vel and new_vel <= V_max) and (-V_max <= old_vel and old_vel <= -thresh_vel): self._times_past_velocity_limit -= 1 - elif (thresh_vel <= old_vel and old_vel <= V_max) and ( - -V_max <= new_vel and new_vel <= -thresh_vel - ): + elif (thresh_vel <= old_vel and old_vel <= V_max) and (-V_max <= new_vel and new_vel <= -thresh_vel): self._times_past_velocity_limit += 1 # update expanded state variables @@ -325,13 +288,9 @@ def update(self): self._old_vel = new_vel self._motor_state.set_state_obj(self._motor_state_async) - self._motor_state.position += ( - self._times_past_position_limit * 2 * MIT_Params[self.type]["P_max"] - ) + self._motor_state.position += self._times_past_position_limit * 2 * MIT_Params[self.type]["P_max"] self._motor_state.current = actual_current - self._motor_state.velocity += ( - self._times_past_velocity_limit * 2 * MIT_Params[self.type]["V_max"] - ) + self._motor_state.velocity += self._times_past_velocity_limit * 2 * MIT_Params[self.type]["V_max"] # send current motor command self._send_command() @@ -387,9 +346,7 @@ def _send_command(self): 0.0, ) else: - raise RuntimeError( - "UNDEFINED STATE for device " + self.device_info_string() - ) + raise RuntimeError("UNDEFINED STATE for device " + self.device_info_string()) self._last_command_time = time.time() # getters for motor state @@ -455,11 +412,7 @@ def output_torque(self): Returns: the most recently updated output torque in Nm """ - return ( - self.motor_current - * MIT_Params[self.type]["Kt_actual"] - * MIT_Params[self.type]["GEAR_RATIO"] - ) + return self.motor_current * MIT_Params[self.type]["Kt_actual"] * MIT_Params[self.type]["GEAR_RATIO"] # uses plain impedance mode, will send 0.0 for current command. def set_impedance_gains(self, kp=0, ki=0, K=0.08922, B=0.0038070, ff=0): @@ -473,16 +426,8 @@ def set_impedance_gains(self, kp=0, ki=0, K=0.08922, B=0.0038070, ff=0): B: The damping in Nm/(rad/s) ff: A dummy argument for backward compatibility with the dephy library. """ - assert ( - isfinite(K) - and MIT_Params[self.type]["Kp_min"] <= K - and K <= MIT_Params[self.type]["Kp_max"] - ) - assert ( - isfinite(B) - and MIT_Params[self.type]["Kd_min"] <= B - and B <= MIT_Params[self.type]["Kd_max"] - ) + assert isfinite(K) and MIT_Params[self.type]["Kp_min"] <= K and MIT_Params[self.type]["Kp_max"] >= K + assert isfinite(B) and MIT_Params[self.type]["Kd_min"] <= B and MIT_Params[self.type]["Kd_max"] >= B self._command.kp = K self._command.kd = B self._command.velocity = 0.0 @@ -587,11 +532,7 @@ def set_joint_torque(self, value): Args: value: The desired output torque in Nm. """ - self.set_motor_current( - value - / MIT_Params[self.type]["Kt_actual"] - / MIT_Params[self.type]["GEAR_RATIO"] - ) + self.set_motor_current(value / MIT_Params[self.type]["Kt_actual"] / MIT_Params[self.type]["GEAR_RATIO"]) # motor-side functions to account for the gear ratio def set_motor_torque(self, value): diff --git a/opensourceleg/benchmarks/decorators.py b/opensourceleg/benchmarks/decorators.py index ee670565..daf561ab 100644 --- a/opensourceleg/benchmarks/decorators.py +++ b/opensourceleg/benchmarks/decorators.py @@ -8,9 +8,7 @@ def decorator(func): def wrapper(*args, **kwargs): timer = timeit.Timer(lambda: func(*args, **kwargs)) execution_time = timer.timeit(number=iterations) - print( - f"Execution time for {func.__name__} over {iterations} iterations: {execution_time} seconds" - ) + print(f"Execution time for {func.__name__} over {iterations} iterations: {execution_time} seconds") return None return wrapper diff --git a/opensourceleg/benchmarks/threads.py b/opensourceleg/benchmarks/threads.py index 1b7197c5..037fd318 100644 --- a/opensourceleg/benchmarks/threads.py +++ b/opensourceleg/benchmarks/threads.py @@ -1,4 +1,3 @@ -import asyncio import threading import time diff --git a/opensourceleg/collections/validators.py b/opensourceleg/collections/validators.py index 31d171d4..2abf68cd 100644 --- a/opensourceleg/collections/validators.py +++ b/opensourceleg/collections/validators.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from dataclasses import dataclass class Validator(ABC): diff --git a/opensourceleg/control/compiled_controller.py b/opensourceleg/control/compiled_controller.py index ffc48085..1ec943a8 100644 --- a/opensourceleg/control/compiled_controller.py +++ b/opensourceleg/control/compiled_controller.py @@ -1,6 +1,5 @@ -from typing import Any - import ctypes +from typing import Any import numpy.ctypeslib as ctl @@ -46,7 +45,7 @@ def __init__( self.init_function = self._load_function(initialization_function_name) # Note if requested function name is None, returned handle is also none - if not self.init_function == None: + if self.init_function != None: self.init_function() # This alias makes defining types from top script easier without second import @@ -66,11 +65,11 @@ def __init__( self.outputs = None def __del__(self): - if not self.cleanup_func == None: + if self.cleanup_func != None: self.cleanup_func() def __repr__(self): - return f"CompiledController" + return "CompiledController" def _load_function(self, function_name): if function_name == None: @@ -79,9 +78,7 @@ def _load_function(self, function_name): try: function_handle = getattr(self.lib, function_name) except AttributeError: - raise AttributeError( - f"Function {function_name} not found in library {self.lib}" - ) + raise AttributeError(f"Function {function_name} not found in library {self.lib}") return function_handle def define_inputs(self, input_list: list[Any]) -> None: @@ -164,13 +161,9 @@ def run(self): ValueError: If define_inputs() or define_outputs() have not been called. """ if self.inputs is None: - raise ValueError( - "Must define input type before calling controller.run(). Use define_inputs() method." - ) + raise ValueError("Must define input type before calling controller.run(). Use define_inputs() method.") if self.outputs is None: - raise ValueError( - "Must define output type before calling controller.run(). Use define_outputs() method." - ) + raise ValueError("Must define output type before calling controller.run(). Use define_outputs() method.") self.main_function(ctypes.byref(self.inputs), ctypes.byref(self.outputs)) return self.outputs diff --git a/opensourceleg/control/state_machine.py b/opensourceleg/control/state_machine.py index 78516755..9fbcf92f 100644 --- a/opensourceleg/control/state_machine.py +++ b/opensourceleg/control/state_machine.py @@ -1,10 +1,8 @@ #!/usr/bin/python3 # A simple and scalable Finite State Machine module -from typing import Any, Callable, List, Optional - import time -from dataclasses import dataclass, field +from typing import Any, Callable, Optional """ The state_machine module provides classes for implementing a finite state machine (FSM). @@ -56,7 +54,6 @@ def __init__( ankle_equilibrium_angle: float = 0.0, minimum_time_in_state: float = 2.0, ) -> None: - self._name: str = name self._is_knee_active: bool = is_knee_active @@ -326,9 +323,7 @@ def __call__(self, data: Any) -> Any: raise NotImplementedError def __repr__(self) -> str: - return ( - f"Transition[{self._source_state.name} -> {self._destination_state.name}]" - ) + return f"Transition[{self._source_state.name} -> {self._destination_state.name}]" def add_criteria(self, callback: Callable[[Any], bool]) -> None: self._criteria = callback @@ -357,19 +352,14 @@ def __init__( destination: State, callback: Callable[[Any], bool] = None, ) -> None: - super().__init__( - event=event, source=source, destination=destination, callback=callback - ) + super().__init__(event=event, source=source, destination=destination, callback=callback) self._from: State = source self._to: State = destination def __call__(self, data: Any, spoof: bool = False) -> State: if spoof: - if ( - self._from.current_time_in_state - > self._from.minimum_time_spent_in_state - ): + if self._from.current_time_in_state > self._from.minimum_time_spent_in_state: if self._action: self._action(data) @@ -435,7 +425,7 @@ def __init__(self, osl=None, spoof: bool = False) -> None: self._spoof: bool = spoof def __repr__(self) -> str: - return f"StateMachine" + return "StateMachine" def add_state(self, state: State, initial_state: bool = False) -> None: """ @@ -482,14 +472,8 @@ def add_transition( """ transition = None - if ( - source in self._states - and destination in self._states - and event in self._events - ): - transition = FromToTransition( - event=event, source=source, destination=destination, callback=callback - ) + if source in self._states and destination in self._states and event in self._events: + transition = FromToTransition(event=event, source=source, destination=destination, callback=callback) self._transitions.append(transition) return transition diff --git a/opensourceleg/logging/exceptions.py b/opensourceleg/logging/exceptions.py index 5a8f11d7..3d50f6d6 100644 --- a/opensourceleg/logging/exceptions.py +++ b/opensourceleg/logging/exceptions.py @@ -8,9 +8,7 @@ class ActuatorStreamException(Exception): """ def __init__(self, tag: str) -> None: - super().__init__( - f"{tag} is not streaming, please call start() method before sending commands" - ) + super().__init__(f"{tag} is not streaming, please call start() method before sending commands") class ActuatorConnectionException(Exception): diff --git a/opensourceleg/logging/logger.py b/opensourceleg/logging/logger.py index c2f88c22..1aaf621b 100644 --- a/opensourceleg/logging/logger.py +++ b/opensourceleg/logging/logger.py @@ -24,8 +24,6 @@ """ -from typing import Any, Callable, Dict, List, Optional, Union - import csv import logging import os @@ -34,6 +32,7 @@ from datetime import datetime from enum import Enum from logging.handlers import RotatingFileHandler +from typing import Any, Callable, Optional, Union class LogLevel(Enum): diff --git a/opensourceleg/math/__init__.py b/opensourceleg/math/__init__.py index 519a4c13..8eab9c3b 100644 --- a/opensourceleg/math/__init__.py +++ b/opensourceleg/math/__init__.py @@ -5,12 +5,12 @@ Module Overview: -This module provides mathematical functionalities for the opensourceleg library. +This module provides mathematical functionalities for the opensourceleg library. Key Classes: - `ThermalModel` class is used to model the thermal behavior of an actuator. -- `SaturatingRamp` class is used to generate a saturating ramp signal that can be used to ramp up or down a signal. +- `SaturatingRamp` class is used to generate a saturating ramp signal that can be used to ramp up or down a signal. - `EdgeDetector` class is used to detect edges in a signal. - `clamp_within_vector_range` function is used to ensure that a value remains within a specified range. """ diff --git a/opensourceleg/math/math.py b/opensourceleg/math/math.py index fc749dbe..93ee678c 100644 --- a/opensourceleg/math/math.py +++ b/opensourceleg/math/math.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, List, Optional +from typing import Any import numpy as np @@ -53,7 +53,6 @@ def __init__( temp_limit_case: float = 80, soft_border_C_case: float = 5, ) -> None: - # The following parameters result from Jack Schuchmann's test with no fans self.C_w: float = 0.20 * 81.46202695970649 self.R_WC = 1.0702867186480716 @@ -67,9 +66,7 @@ def __init__( self.T_w: float = ambient self.T_c: float = ambient self.T_a: float = ambient - self.soft_max_temp_windings: float = ( - temp_limit_windings - soft_border_C_windings - ) + self.soft_max_temp_windings: float = temp_limit_windings - soft_border_C_windings self.abs_max_temp_windings: float = temp_limit_windings self.soft_border_windings: float = soft_border_C_windings @@ -78,7 +75,7 @@ def __init__( self.soft_border_case: float = soft_border_C_case def __repr__(self) -> str: - return f"ThermalModel" + return "ThermalModel" def update(self, dt: float = 1 / 200, motor_current: float = 0) -> None: """ @@ -100,9 +97,7 @@ def update(self, dt: float = 1 / 200, motor_current: float = 0) -> None: ) # accounts for resistance change due to temp. dTw_dt = (I2R + (self.T_c - self.T_w) / self.R_WC) / self.C_w - dTc_dt: float = ( - (self.T_w - self.T_c) / self.R_WC + (self.T_a - self.T_c) / self.R_CA - ) / self.C_c + dTc_dt: float = ((self.T_w - self.T_c) / self.R_WC + (self.T_a - self.T_c) / self.R_CA) / self.C_c self.T_w += dt * dTw_dt self.T_c += dt * dTc_dt @@ -139,16 +134,12 @@ def update_and_get_scale(self, dt, motor_current: float = 0, FOS: float = 1.0): if self.T_c > self.abs_max_temp_case: scale = 0.0 elif self.T_c > self.soft_max_temp_case: - scale *= (self.abs_max_temp_case - self.T_w) / ( - self.abs_max_temp_case - self.soft_max_temp_case - ) + scale *= (self.abs_max_temp_case - self.T_w) / (self.abs_max_temp_case - self.soft_max_temp_case) I2R = I2R_des * scale dTw_dt = (I2R + (self.T_c - self.T_w) / self.R_WC) / self.C_w - dTc_dt: float = ( - (self.T_w - self.T_c) / self.R_WC + (self.T_a - self.T_c) / self.R_CA - ) / self.C_c + dTc_dt: float = ((self.T_w - self.T_c) / self.R_WC + (self.T_a - self.T_c) / self.R_CA) / self.C_c self.T_w += dt * dTw_dt self.T_c += dt * dTc_dt @@ -176,7 +167,7 @@ def __init__(self, bool_in): self.falling_edge = False def __repr__(self) -> str: - return f"EdgeDetector" + return "EdgeDetector" def update(self, bool_in): self.rising_edge = bool_in and not self.cur_state @@ -212,7 +203,7 @@ def __init__(self, loop_frequency=100, ramp_time=1.0) -> None: self.value = 0.0 def __repr__(self) -> str: - return f"SaturatingRamp" + return "SaturatingRamp" def update(self, enable_ramp=False): """ diff --git a/opensourceleg/robots/base.py b/opensourceleg/robots/base.py index c0bbb978..2d86ca02 100644 --- a/opensourceleg/robots/base.py +++ b/opensourceleg/robots/base.py @@ -1,6 +1,5 @@ -from typing import Any, Dict, Generic, TypeVar - from abc import ABC, abstractmethod +from typing import Generic, TypeVar from opensourceleg.actuators.base import ActuatorBase from opensourceleg.logging import LOGGER diff --git a/opensourceleg/robots/osl.py b/opensourceleg/robots/osl.py index fec013b8..f143b932 100644 --- a/opensourceleg/robots/osl.py +++ b/opensourceleg/robots/osl.py @@ -1,6 +1,5 @@ -from typing import Union - import time +from typing import Union import numpy as np @@ -8,7 +7,7 @@ from opensourceleg.actuators.dephy import DephyLegacyActuator from opensourceleg.logging import LOGGER from opensourceleg.robots.base import RobotBase, TActuator, TSensor -from opensourceleg.sensors.base import IMUBase, LoadcellBase, SensorBase +from opensourceleg.sensors.base import LoadcellBase, SensorBase from opensourceleg.sensors.imu import LordMicrostrainIMU from opensourceleg.sensors.loadcell import SRILoadcell @@ -35,9 +34,7 @@ def knee(self) -> Union[TActuator, ActuatorBase]: try: return self.actuators["knee"] except KeyError: - LOGGER.error( - "Knee actuator not found. Please check for `knee` key in the actuators dictionary." - ) + LOGGER.error("Knee actuator not found. Please check for `knee` key in the actuators dictionary.") exit(1) @property @@ -45,9 +42,7 @@ def ankle(self) -> Union[TActuator, ActuatorBase]: try: return self.actuators["ankle"] except KeyError: - LOGGER.error( - "Ankle actuator not found. Please check for `ankle` key in the actuators dictionary." - ) + LOGGER.error("Ankle actuator not found. Please check for `ankle` key in the actuators dictionary.") exit(1) @property @@ -55,9 +50,7 @@ def loadcell(self) -> Union[TSensor, LoadcellBase]: try: return self.sensors["loadcell"] except KeyError: - LOGGER.error( - "Loadcell sensor not found. Please check for `loadcell` key in the sensors dictionary." - ) + LOGGER.error("Loadcell sensor not found. Please check for `loadcell` key in the sensors dictionary.") exit(1) @property @@ -84,23 +77,19 @@ def joint_encoder_ankle(self) -> Union[TSensor, SensorBase]: if __name__ == "__main__": frequency = 200 - LOADCELL_MATRIX = np.array( - [ - (-5.45598, -1317.68604, 27.14505, 22.8468, -11.1176, 1283.02856), - (-20.23942, 773.01343, -9.44841, -1546.70923, 21.78232, 744.52325), - (-811.76398, -16.82792, -825.67261, 2.93904, -829.06409, 7.45233), - (16.38306, 0.22658, -0.50331, -0.23233, -17.0822, -0.03632), - (-9.81471, -0.03671, 19.47362, -0.161, -9.76819, 0.25571), - (-0.51744, -20.6571, 0.18245, -20.42393, 0.01944, -20.38067), - ] - ) + LOADCELL_MATRIX = np.array([ + (-5.45598, -1317.68604, 27.14505, 22.8468, -11.1176, 1283.02856), + (-20.23942, 773.01343, -9.44841, -1546.70923, 21.78232, 744.52325), + (-811.76398, -16.82792, -825.67261, 2.93904, -829.06409, 7.45233), + (16.38306, 0.22658, -0.50331, -0.23233, -17.0822, -0.03632), + (-9.81471, -0.03671, 19.47362, -0.161, -9.76819, 0.25571), + (-0.51744, -20.6571, 0.18245, -20.42393, 0.01944, -20.38067), + ]) osl = OpenSourceLeg[DephyLegacyActuator, SensorBase]( tag="opensourceleg", actuators={ - "knee": DephyLegacyActuator( - "knee", offline=False, frequency=frequency, gear_ratio=9 * (83 / 18) - ), + "knee": DephyLegacyActuator("knee", offline=False, frequency=frequency, gear_ratio=9 * (83 / 18)), }, sensors={ "imu": LordMicrostrainIMU(frequency=frequency, port="/dev/ttyS0"), diff --git a/opensourceleg/safety/safety.py b/opensourceleg/safety/safety.py index 646c363f..28c53d41 100644 --- a/opensourceleg/safety/safety.py +++ b/opensourceleg/safety/safety.py @@ -1,7 +1,6 @@ -from typing import Callable, List - from collections import deque from dataclasses import dataclass +from typing import Callable import numpy as np @@ -50,9 +49,7 @@ def wrapper(instance, *args, **kwargs): current_std = np.std(list(history)) if current_std < threshold: if proxy_attribute_name is not None: - print( - f"{attribute_name} isn't stable, returning {proxy_attribute_name}" - ) + print(f"{attribute_name} isn't stable, returning {proxy_attribute_name}") if not hasattr(instance, proxy_key): setattr(instance, proxy_key, True) return getattr(instance, proxy_attribute_name) @@ -194,9 +191,7 @@ def wrapper(instance, *args, **kwargs): if value < min_value: if clamp: return min_value - raise ValueError( - f"Value must be greater than or equal to {min_value}" - ) + raise ValueError(f"Value must be greater than or equal to {min_value}") else: if value <= min_value: if clamp: @@ -258,7 +253,7 @@ def decorator(func): def wrapper(instance, *args, **kwargs): value = func(instance, *args, **kwargs) if not criteria(value): - raise ValueError(f"Value does not meet custom criteria") + raise ValueError("Value does not meet custom criteria") return value return wrapper @@ -301,9 +296,7 @@ def add_safety(self, instance: object, attribute: str, decorator: Callable): # """ if not hasattr(instance, attribute): - print( - f"Error: The attribute '{attribute}' does not exist in the given object." - ) + print(f"Error: The attribute '{attribute}' does not exist in the given object.") return original_attribute = getattr(instance.__class__, attribute, None) @@ -326,11 +319,8 @@ def start(self): Applies all decorators to the properties of the objects in the safe_objects dictionary. """ for container, safe_attributes in self.safe_objects.items(): - container_subclass = type( - f"{container.__class__.__name__}:SAFE", (container.__class__,), {} - ) + container_subclass = type(f"{container.__class__.__name__}:SAFE", (container.__class__,), {}) for attribute_name, attribute_decorators in safe_attributes.items(): - original_property = getattr(container.__class__, attribute_name) decorated_getter = original_property.fget for attribute_decorator in reversed(attribute_decorators): @@ -339,9 +329,7 @@ def start(self): setattr( container_subclass, attribute_name, - property( - decorated_getter, original_property.fset, original_property.fdel - ), + property(decorated_getter, original_property.fset, original_property.fdel), ) container.__class__ = container_subclass diff --git a/opensourceleg/sensors/adc.py b/opensourceleg/sensors/adc.py index 78d6179a..eaaa0314 100644 --- a/opensourceleg/sensors/adc.py +++ b/opensourceleg/sensors/adc.py @@ -1,5 +1,3 @@ -from typing import List - import math from time import sleep @@ -78,13 +76,9 @@ def __init__( """ if len(channel_gains) != num_channels: - raise ValueError( - "Length of channel_gains does not equal number of channels" - ) + raise ValueError("Length of channel_gains does not equal number of channels") if gain_error != [] and len(gain_error) != num_channels: - raise ValueError( - "Length of channel_gains does not equal number of channels" - ) + raise ValueError("Length of channel_gains does not equal number of channels") self._spi_bus = spi_bus self._spi_chip = spi_chip @@ -110,7 +104,7 @@ def __init__( self._data = [0.0] * num_channels def __repr__(self) -> str: - return f"ADS131M0x" + return "ADS131M0x" def start(self) -> None: """Opens SPI port, calibrates ADC, and begins streaming ADC data.""" @@ -131,9 +125,7 @@ def stop(self) -> None: def reset(self) -> None: """Resets all register values.""" - self._spi.xfer2( - self._RESET_WORD + self._BLANK_WORD * (self._words_per_frame - 1) - ) + self._spi.xfer2(self._RESET_WORD + self._BLANK_WORD * (self._words_per_frame - 1)) def update(self) -> None: """Reads ADC data.""" @@ -193,9 +185,7 @@ def _spi_message(self, bytes: list[int]) -> list[int]: The response to the message sent, including the entire frame following the response. """ self._spi.xfer2(bytes) - return (list[int])( - self._spi.readbytes(self._BYTES_PER_WORD * self._words_per_frame) - ) + return (list[int])(self._spi.readbytes(self._BYTES_PER_WORD * self._words_per_frame)) def _channel_enable(self, state: bool) -> None: """Enables or disables streaming on all channels. @@ -217,14 +207,10 @@ def _set_device_state(self, state: int) -> None: 1 -- Continuous Conversion Mode """ if state == 0: - self._spi.xfer2( - self._STANDBY_WORD + self._BLANK_WORD * (self._words_per_frame - 1) - ) + self._spi.xfer2(self._STANDBY_WORD + self._BLANK_WORD * (self._words_per_frame - 1)) self._streaming = False elif state == 1: - self._spi.xfer2( - self._WAKEUP_WORD + self._BLANK_WORD * (self._words_per_frame - 1) - ) + self._spi.xfer2(self._WAKEUP_WORD + self._BLANK_WORD * (self._words_per_frame - 1)) self._streaming = True def _set_voltage_source(self, source: int) -> None: @@ -290,8 +276,7 @@ def _ready_to_read(self) -> bool: def _read_data_millivolts(self) -> list[float]: """Returns a List representing the voltage in millivolts for all channels of the ADC""" mV = [ - 1000 * ((dat) / (2 ** (self._RESOLUTION - 1)) * self._voltage_reference) - for dat in self._read_data_counts() + 1000 * ((dat) / (2 ** (self._RESOLUTION - 1)) * self._voltage_reference) for dat in self._read_data_counts() ] return mV diff --git a/opensourceleg/sensors/base.py b/opensourceleg/sensors/base.py index 5ca8b6ee..02778456 100644 --- a/opensourceleg/sensors/base.py +++ b/opensourceleg/sensors/base.py @@ -1,8 +1,6 @@ -ο»Ώfrom abc import ABC, abstractmethod +from abc import ABC, abstractmethod from functools import wraps -import numpy as np - class SensorNotStreamingException(Exception): def __init__(self, sensor_name: str = "Sensor") -> None: @@ -26,7 +24,7 @@ def __init__(self) -> None: pass def __repr__(self) -> str: - return f"SensorBase" + return "SensorBase" @property @abstractmethod @@ -63,7 +61,7 @@ def __init__(self) -> None: super().__init__() def __repr__(self) -> str: - return f"ADCBase" + return "ADCBase" def reset(self) -> None: pass @@ -77,7 +75,7 @@ def __init__(self) -> None: super().__init__() def __repr__(self) -> str: - return f"EncoderBase" + return "EncoderBase" @property @abstractmethod @@ -95,7 +93,7 @@ def __init__(self) -> None: pass def __repr__(self) -> str: - return f"LoadcellBase" + return "LoadcellBase" @abstractmethod def calibrate(self) -> None: @@ -146,7 +144,7 @@ def __init__(self) -> None: pass def __repr__(self) -> str: - return f"IMU" + return "IMU" @property @abstractmethod diff --git a/opensourceleg/sensors/imu.py b/opensourceleg/sensors/imu.py index 2d812cb3..0b7faac2 100644 --- a/opensourceleg/sensors/imu.py +++ b/opensourceleg/sensors/imu.py @@ -1,9 +1,3 @@ -ο»Ώfrom typing import List, Union - -import os -import time -from dataclasses import dataclass - from opensourceleg.logging import LOGGER from opensourceleg.sensors.base import IMUBase, check_sensor_stream @@ -52,7 +46,6 @@ def __init__( baud_rate: int = 921600, frequency: int = 200, ): - self._port = port self._baud_rate = baud_rate self._frequency = frequency @@ -90,12 +83,9 @@ def _configure_mip_channels(self): return channels def start(self): - self._connection = mscl.Connection.Serial(self.port, self.baud_rate) self._node = mscl.InertialNode(self._connection) - self._node.setActiveChannelFields( - mscl.MipTypes.CLASS_ESTFILTER, self._configure_mip_channels() - ) + self._node.setActiveChannelFields(mscl.MipTypes.CLASS_ESTFILTER, self._configure_mip_channels()) self._node.enableDataStream(mscl.MipTypes.CLASS_ESTFILTER) self._is_streaming = True @@ -113,15 +103,11 @@ def ping(self): else: LOGGER.error(f"Failed to ping the IMU at {self.port}") - def update( - self, timeout: int = 500, max_packets: int = 1, return_packets: bool = False - ): + def update(self, timeout: int = 500, max_packets: int = 1, return_packets: bool = False): """ Get IMU data from the Lord Microstrain IMU """ - data_packets = self._node.getDataPackets( - timeout=timeout, maxPackets=max_packets - ) + data_packets = self._node.getDataPackets(timeout=timeout, maxPackets=max_packets) data_points = data_packets[-1].data() self._data = {data.channelName(): data.as_float() for data in data_points} @@ -129,7 +115,7 @@ def update( return data_packets def __repr__(self) -> str: - return f"IMULordMicrostrain" + return "IMULordMicrostrain" @property def port(self) -> str: @@ -246,13 +232,13 @@ def __init__( self._is_streaming = False def __repr__(self) -> str: - return f"BNO055_IMU" + return "BNO055_IMU" def start(self): i2c = busio.I2C(board.SCL, board.SDA) try: self._adafruit_imu = adafruit_bno055.BNO055_I2C(i2c, address=self._address) - except ValueError as ve: + except ValueError: print("BNO055 IMU Not Found on i2c bus! Check wiring!") self.configure_IMU_settings() diff --git a/opensourceleg/sensors/loadcell.py b/opensourceleg/sensors/loadcell.py index 23363cb2..cc6ee4fb 100644 --- a/opensourceleg/sensors/loadcell.py +++ b/opensourceleg/sensors/loadcell.py @@ -1,8 +1,6 @@ -from typing import Any, Callable, Union - import time -from dataclasses import dataclass from enum import Enum +from typing import Callable import numpy as np import numpy.typing as npt @@ -49,19 +47,13 @@ def __init__( """ # Check that parameters are set correctly: if calibration_matrix.shape != (6, 6): - LOGGER.info( - f"[{self.__repr__()}] calibration_matrix must be a 6x6 array of np.double." - ) + LOGGER.info(f"[{self.__repr__()}] calibration_matrix must be a 6x6 array of np.double.") raise TypeError("calibration_matrix must be a 6x6 array of np.double.") if amp_gain <= 0: - LOGGER.info( - f"[{self.__repr__()}] amp_gain must be a floating point value greater than 0." - ) + LOGGER.info(f"[{self.__repr__()}] amp_gain must be a floating point value greater than 0.") raise ValueError("amp_gain must be a floating point value greater than 0.") if exc <= 0: - LOGGER.info( - f"[{self.__repr__()}] exc must be a floating point value greater than 0." - ) + LOGGER.info(f"[{self.__repr__()}] exc must be a floating point value greater than 0.") raise ValueError("exc must be a floating point value greater than 0.") self._amp_gain: float = amp_gain @@ -76,9 +68,7 @@ def __init__( self._prev_data: npt.NDArray[np.double] = self._data self._failed_reads = 0 - self._calibration_offset: npt.NDArray[np.double] = np.zeros( - shape=(1, 6), dtype=np.double - ) + self._calibration_offset: npt.NDArray[np.double] = np.zeros(shape=(1, 6), dtype=np.double) self._zero_calibration_offset: npt.NDArray[np.double] = self._calibration_offset self._is_calibrated: bool = False self._is_streaming: bool = False @@ -109,10 +99,7 @@ def update( signed_data = ((data - self.OFFSET) / self.ADC_RANGE) * self._exc coupled_data = signed_data * 1000 / (self._exc * self._amp_gain) - self._data = ( - np.transpose(a=self._calibration_matrix.dot(b=np.transpose(a=coupled_data))) - - calibration_offset - ) + self._data = np.transpose(a=self._calibration_matrix.dot(b=np.transpose(a=coupled_data))) - calibration_offset def calibrate( self, @@ -140,9 +127,7 @@ def calibrate( data_callback=data_callback, ) iterative_calibration_offset = self._data - self._calibration_offset = ( - iterative_calibration_offset + self._calibration_offset - ) / 2.0 + self._calibration_offset = (iterative_calibration_offset + self._calibration_offset) / 2.0 self._is_calibrated = True LOGGER.info(f"[{self.__repr__()}] Calibration routine complete.") @@ -164,11 +149,9 @@ def stop(self) -> None: def _read_compressed_strain(self): """Used for more recent versions of strain amp firmware""" try: - data = self._smbus.read_i2c_block_data( - self._i2c_address, MEMORY_CHANNELS.CH1_H, 10 - ) + data = self._smbus.read_i2c_block_data(self._i2c_address, MEMORY_CHANNELS.CH1_H, 10) self.failed_reads = 0 - except OSError as e: + except OSError: self.failed_reads += 1 if self.failed_reads >= 5: diff --git a/opensourceleg/time/time.py b/opensourceleg/time/time.py index 47e0851d..6cfcd001 100644 --- a/opensourceleg/time/time.py +++ b/opensourceleg/time/time.py @@ -34,7 +34,7 @@ def __init__(self, fade_time=0.0): self._soft_kill_time = None def __repr__(self) -> str: - return f"LoopKiller" + return "LoopKiller" def handle_signal(self, signum, frame): self.kill_now = True @@ -111,7 +111,7 @@ def __init__(self, dt=0.001, report=False, fade=0.0): self.report = report def __repr__(self) -> str: - return f"SoftRealtimeLoop" + return "SoftRealtimeLoop" def __del__(self): if self.report: @@ -121,10 +121,7 @@ def __del__(self): "\tstddev error: %.3f milliseconds" % (1e3 * sqrt((self.sum_var - self.sum_err**2 / self.n) / (self.n - 1))) ) - print( - "\tpercent of time sleeping: %.1f %%" - % (self.sleep_t_agg / self.time() * 100.0) - ) + print("\tpercent of time sleeping: %.1f %%" % (self.sleep_t_agg / self.time() * 100.0)) @property def fade(self): @@ -139,9 +136,7 @@ def run(self, function_in_loop, dt=None): if ret == 0: self.stop() while time.monotonic() < self.t1 and not self.killer.kill_now: - if signal.sigtimedwait( - [signal.SIGTERM, signal.SIGINT, signal.SIGHUP], 0 - ): + if signal.sigtimedwait([signal.SIGTERM, signal.SIGINT, signal.SIGHUP], 0): self.stop() self.t1 += dt @@ -162,21 +157,14 @@ def __next__(self): if self.killer.kill_now: raise StopIteration - while ( - time.monotonic() < self.t1 - 2 * PRECISION_OF_SLEEP - and not self.killer.kill_now - ): + while time.monotonic() < self.t1 - 2 * PRECISION_OF_SLEEP and not self.killer.kill_now: t_pre_sleep = time.monotonic() - time.sleep( - max(PRECISION_OF_SLEEP, self.t1 - time.monotonic() - PRECISION_OF_SLEEP) - ) + time.sleep(max(PRECISION_OF_SLEEP, self.t1 - time.monotonic() - PRECISION_OF_SLEEP)) self.sleep_t_agg += time.monotonic() - t_pre_sleep while time.monotonic() < self.t1 and not self.killer.kill_now: try: - if signal.sigtimedwait( - [signal.SIGTERM, signal.SIGINT, signal.SIGHUP], 0 - ): + if signal.sigtimedwait([signal.SIGTERM, signal.SIGINT, signal.SIGHUP], 0): self.stop() except AttributeError: pass diff --git a/opensourceleg/units/units.py b/opensourceleg/units/units.py index 54c8837a..a22590ed 100644 --- a/opensourceleg/units/units.py +++ b/opensourceleg/units/units.py @@ -1,5 +1,4 @@ # Global Units Dictionary -import enum from dataclasses import dataclass """ @@ -23,7 +22,7 @@ class force: kgf = 9.80665 def __repr__(self) -> str: - return f"force" + return "force" @dataclass @@ -33,7 +32,7 @@ class torque: kgf_cm = 0.0980665 def __repr__(self) -> str: - return f"torque" + return "torque" @dataclass @@ -42,7 +41,7 @@ class stiffness: N_m_per_deg = 0.017453292519943295 def __repr__(self) -> str: - return f"stiffness" + return "stiffness" @dataclass @@ -51,7 +50,7 @@ class damping: N_m_per_deg_per_s = 0.017453292519943295 def __repr__(self) -> str: - return f"damping" + return "damping" @dataclass @@ -61,7 +60,7 @@ class length: inch = 0.0254 def __repr__(self) -> str: - return f"length" + return "length" @dataclass @@ -70,7 +69,7 @@ class position: deg = 0.017453292519943295 def __repr__(self) -> str: - return f"position" + return "position" @dataclass @@ -80,7 +79,7 @@ class mass: lb = 0.45359237 def __repr__(self) -> str: - return f"mass" + return "mass" @dataclass @@ -90,7 +89,7 @@ class velocity: rpm = 0.10471975511965977 def __repr__(self) -> str: - return f"velocity" + return "velocity" @dataclass @@ -99,7 +98,7 @@ class acceleration: deg_per_s2 = 0.017453292519943295 def __repr__(self) -> str: - return f"acceleration" + return "acceleration" @dataclass @@ -108,7 +107,7 @@ class time: ms = 0.001 def __repr__(self) -> str: - return f"time" + return "time" @dataclass @@ -117,7 +116,7 @@ class current: A = 1000 def __repr__(self) -> str: - return f"current" + return "current" @dataclass @@ -126,7 +125,7 @@ class voltage: V = 1000 def __repr__(self) -> str: - return f"voltage" + return "voltage" def convert_to_default(value: float, from_unit: float) -> float: diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..79748eb1 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2916 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "adafruit-blinka" +version = "8.50.0" +description = "CircuitPython APIs for non-CircuitPython versions of Python such as CPython on Linux and MicroPython." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "Adafruit_Blinka-8.50.0-py3-none-any.whl", hash = "sha256:a0832316ac4155df956a1e35561c75e37e6f8d8e2f28b58859c80207e7819351"}, + {file = "adafruit_blinka-8.50.0.tar.gz", hash = "sha256:64feecaa7f806fdd02b39521b5b400ebfb629a06a27a3360644375af0bbe67c2"}, +] + +[package.dependencies] +adafruit-circuitpython-typing = "*" +Adafruit-PlatformDetect = ">=3.70.1" +Adafruit-PureIO = ">=1.1.7" +binho-host-adapter = ">=0.1.6" +pyftdi = ">=0.40.0" +sysv-ipc = {version = ">=1.1.0", markers = "sys_platform == \"linux\" and platform_machine != \"mips\""} + +[[package]] +name = "adafruit-circuitpython-bno055" +version = "5.4.14" +description = "CircuitPython library for BNO055 9-DOF absolute orientation sensor." +optional = false +python-versions = "*" +files = [ + {file = "adafruit_circuitpython_bno055-5.4.14-py3-none-any.whl", hash = "sha256:a939bc00b2f74cccfb32cb8adae50dc8f6182cb959e279942348acd90ef33b95"}, + {file = "adafruit_circuitpython_bno055-5.4.14.tar.gz", hash = "sha256:eb559c9fa593f07feed8109085309f62048105f2123e6ce6f74becaf35f8e5fd"}, +] + +[package.dependencies] +Adafruit-Blinka = "*" +adafruit-circuitpython-busdevice = "*" +adafruit-circuitpython-register = "*" + +[[package]] +name = "adafruit-circuitpython-busdevice" +version = "5.2.10" +description = "CircuitPython bus device classes to manage bus sharing." +optional = false +python-versions = "*" +files = [ + {file = "adafruit_circuitpython_busdevice-5.2.10-py3-none-any.whl", hash = "sha256:9e37e303b9cced482aec004eb37a6480c5ccac60d3cfe6ea4d9c4c0a818f1fc0"}, + {file = "adafruit_circuitpython_busdevice-5.2.10.tar.gz", hash = "sha256:b3797242cb2aeb27ed6382f7946d62ab4d6dbb16631e71e50b12ba07afec0e87"}, +] + +[package.dependencies] +Adafruit-Blinka = ">=7.0.0" +adafruit-circuitpython-typing = "*" + +[[package]] +name = "adafruit-circuitpython-connectionmanager" +version = "3.1.2" +description = "A urllib3.poolmanager/urllib3.connectionpool-like library for managing sockets and connections" +optional = false +python-versions = "*" +files = [ + {file = "adafruit_circuitpython_connectionmanager-3.1.2-py3-none-any.whl", hash = "sha256:1d3af302e587d4ab0827f4382d2eeb400a152688a2eb14272189f971d7ff477b"}, + {file = "adafruit_circuitpython_connectionmanager-3.1.2.tar.gz", hash = "sha256:330485ac00da6c5f1ca567e77493cddb55ab9674afed341eddf5f9b85bd22fdb"}, +] + +[package.dependencies] +Adafruit-Blinka = "*" + +[[package]] +name = "adafruit-circuitpython-lis3dh" +version = "5.2.3" +description = "CircuitPython library for LIS3DH accelerometer." +optional = false +python-versions = "*" +files = [ + {file = "adafruit_circuitpython_lis3dh-5.2.3-py3-none-any.whl", hash = "sha256:83743264020fab01aa7ced227b24bc71b437422f8915dbe7527b132754f80e2a"}, + {file = "adafruit_circuitpython_lis3dh-5.2.3.tar.gz", hash = "sha256:f4d83215141b1965562f024fca4036703f41a930106354cb3af7ac92f6859701"}, +] + +[package.dependencies] +Adafruit-Blinka = "*" +adafruit-circuitpython-busdevice = "*" + +[[package]] +name = "adafruit-circuitpython-register" +version = "1.10.1" +description = "CircuitPython data descriptor classes to represent hardware registers on I2C and SPI devices." +optional = false +python-versions = "*" +files = [ + {file = "adafruit_circuitpython_register-1.10.1-py3-none-any.whl", hash = "sha256:872b0da50a4fe18464d1125002194fddf2fa8627f11650e90547e519c7f846d8"}, + {file = "adafruit_circuitpython_register-1.10.1.tar.gz", hash = "sha256:452aeedd515fb26405b100291bfc69df639d82dfac02087367508566123490a3"}, +] + +[package.dependencies] +Adafruit-Blinka = "*" +adafruit-circuitpython-busdevice = "*" +adafruit-circuitpython-typing = ">=1.3.1,<2.dev0" +typing-extensions = ">=4.0,<5.0" + +[[package]] +name = "adafruit-circuitpython-requests" +version = "4.1.8" +description = "A requests-like library for web interfacing" +optional = false +python-versions = "*" +files = [ + {file = "adafruit_circuitpython_requests-4.1.8-py3-none-any.whl", hash = "sha256:6ab3744175f1ddaf2ecfb2510c9ea2cf717c21848695849fcdf28de6e856fd04"}, + {file = "adafruit_circuitpython_requests-4.1.8.tar.gz", hash = "sha256:50d787bc05d953318f4ee654230195e631eb379e33179c465a68f763d4638914"}, +] + +[package.dependencies] +Adafruit-Blinka = "*" +Adafruit-Circuitpython-ConnectionManager = "*" + +[package.extras] +optional = ["requests"] + +[[package]] +name = "adafruit-circuitpython-typing" +version = "1.11.1" +description = "Types needed for type annotation that are not in `typing`" +optional = false +python-versions = ">=3.8" +files = [ + {file = "adafruit_circuitpython_typing-1.11.1-py3-none-any.whl", hash = "sha256:c223b56538c9535e6ecc45fb39b23acab6840fecf068a85c6a340cb42368d413"}, + {file = "adafruit_circuitpython_typing-1.11.1.tar.gz", hash = "sha256:efcadbf39b70a6ea1b7561a10551b665ad5306b31340c55ece4a42328da3c998"}, +] + +[package.dependencies] +Adafruit-Blinka = "*" +adafruit-circuitpython-busdevice = "*" +adafruit-circuitpython-requests = "*" +typing-extensions = ">=4.0,<5.0" + +[[package]] +name = "adafruit-platformdetect" +version = "3.75.0" +description = "Platform detection for use by libraries like Adafruit-Blinka." +optional = false +python-versions = "*" +files = [ + {file = "Adafruit_PlatformDetect-3.75.0-py3-none-any.whl", hash = "sha256:f523a118bfe270b17c3e1eb1f6115a75dfc8f8f029a67ae51f4934571229c5b8"}, + {file = "adafruit_platformdetect-3.75.0.tar.gz", hash = "sha256:e40c2f9f3c34b6066145756c8533aecdeadd39127ee50087dcf0c1ee96e3081d"}, +] + +[[package]] +name = "adafruit-pureio" +version = "1.1.11" +description = "Pure python (i.e. no native extensions) access to Linux IO including I2C and SPI. Drop in replacement for smbus and spidev modules." +optional = false +python-versions = ">=3.5.0" +files = [ + {file = "Adafruit_PureIO-1.1.11-py3-none-any.whl", hash = "sha256:281ab2099372cc0decc26326918996cbf21b8eed694ec4764d51eefa029d324e"}, + {file = "Adafruit_PureIO-1.1.11.tar.gz", hash = "sha256:c4cfbb365731942d1f1092a116f47dfdae0aef18c5b27f1072b5824ad5ea8c7c"}, +] + +[[package]] +name = "babel" +version = "2.16.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +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 = "binho-host-adapter" +version = "0.1.6" +description = "Python Libraries for Binho Multi-Protocol USB Host Adapters" +optional = false +python-versions = "*" +files = [ + {file = "binho-host-adapter-0.1.6.tar.gz", hash = "sha256:1e6da7a84e208c13b5f489066f05774bff1d593d0f5bf1ca149c2b8e83eae856"}, + {file = "binho_host_adapter-0.1.6-py3-none-any.whl", hash = "sha256:f71ca176c1e2fc1a5dce128beb286da217555c6c7c805f2ed282a6f3507ec277"}, +] + +[package.dependencies] +pyserial = "*" + +[[package]] +name = "board" +version = "1.0" +description = "Standard Board mechanism for Dojo tasks" +optional = false +python-versions = "*" +files = [ + {file = "board-1.0-py2.py3-none-any.whl", hash = "sha256:b97c986d0190a2a5181a7fe28959cd8515520b87f672796e21e1b0d32cc63e2f"}, + {file = "board-1.0.tar.gz", hash = "sha256:cd718c3322a126d86455e24ae0cebb59a567cbdbaf03696391dccf88c8f456d6"}, +] + +[[package]] +name = "boto3" +version = "1.35.59" +description = "The AWS SDK for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "boto3-1.35.59-py3-none-any.whl", hash = "sha256:8f8ff97cb9cb2e1ec7374209d0c09c1926b75604d6464c34bafaffd6d6cf0529"}, + {file = "boto3-1.35.59.tar.gz", hash = "sha256:81f4d8d6eff3e26b82cabd42eda816cfac9482821fdef353f18d2ba2f6e75f2d"}, +] + +[package.dependencies] +botocore = ">=1.35.59,<1.36.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.10.0,<0.11.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.35.59" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.8" +files = [ + {file = "botocore-1.35.59-py3-none-any.whl", hash = "sha256:bcd66d7f55c8d1b6020eb86f2d87893fe591fb4be6a7d2a689c18be586452334"}, + {file = "botocore-1.35.59.tar.gz", hash = "sha256:de0ce655fedfc02c87869dfaa3b622488a17ff37da316ef8106cbe1573b83c98"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.22.0)"] + +[[package]] +name = "cachetools" +version = "5.5.0" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "contourpy" +version = "1.3.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +files = [ + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[[package]] +name = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "deptry" +version = "0.16.2" +description = "A command line utility to check for unused, missing and transitive dependencies in a Python project." +optional = false +python-versions = ">=3.8" +files = [ + {file = "deptry-0.16.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:24bfbae07bd6533c852c795e8d88d05a8ad0801bec0d3662e1a37db763c52540"}, + {file = "deptry-0.16.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc881688a2eaeafe51c0617d32a6535057bccdb74559cc667109f48f81cd976e"}, + {file = "deptry-0.16.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fed4b692f556e4c80acb42cec93e3b5fdc7fc2323049c2a0cfd9dfc4a9c7033e"}, + {file = "deptry-0.16.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ec508a932d8f06c3bd1aa7a4548d5dbec92c3060d42eedcda3be9729bd7c3b"}, + {file = "deptry-0.16.2-cp38-abi3-win_amd64.whl", hash = "sha256:eb92e9aacde66cfe001d6318eb0851ae0ca26fea441defed4765a47644daf8bb"}, + {file = "deptry-0.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dfdceca2fbc87f4bce04df4207914a5eb37e67fb2107579ad2e88107c22d2456"}, + {file = "deptry-0.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:96ab62dd5f4658735aac72d0e49f6d896eabf50a0e4e2cdecb436a1362aa696b"}, + {file = "deptry-0.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e4408fa5a8d146b55bc40f0829fb875efef33174a2679bd9954ce988b9bc0d7"}, + {file = "deptry-0.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af976afc2a0583f48dc25f616d2566fecd7af5080675c8eccb161def88d93503"}, + {file = "deptry-0.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd86c9d34aa75b91fb72b34110f0660b2277bf9a95fe9cae3ead36d465bc44ac"}, + {file = "deptry-0.16.2.tar.gz", hash = "sha256:f0f752cf6f5e9f7445a79fcf195b772cd2d4b889cd260e23867dd8013caa74c1"}, +] + +[package.dependencies] +click = ">=8.0.0,<9" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[[package]] +name = "distlib" +version = "0.3.9" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +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 = "filelock" +version = "3.16.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +typing = ["typing-extensions (>=4.12.2)"] + +[[package]] +name = "flexsea" +version = "8.0.1" +description = "Dephy's Actuator Package API library" +optional = false +python-versions = ">= 3.7.*" +files = [ + {file = "flexsea-8.0.1-py3-none-any.whl", hash = "sha256:6b728a7cd8c74b1184cea4dad13138f8e843a2f58d4f2291ff24f14ca0d0e7aa"}, +] + +[package.dependencies] +matplotlib = "==3.*" +numpy = "==1.*" +PyYAML = "==6.*" +tornado = "==6.*" + +[[package]] +name = "flexsea" +version = "12.0.4" +description = "" +optional = false +python-versions = "<4.0,>=3.11" +files = [ + {file = "flexsea-12.0.4-py3-none-any.whl", hash = "sha256:b8dad7a8bd8e930b82aef1e266add3d17bc2871617c70b93693cc887abb60fa4"}, + {file = "flexsea-12.0.4.tar.gz", hash = "sha256:d45ecbbb7318a6c0a7fa22026211db8f9d7f2f7d5fef3d3036298f56fba5d2f3"}, +] + +[package.dependencies] +boto3 = ">=1.26.110,<2.0.0" +pendulum = [ + {version = ">=2.1.2,<3.0.0", markers = "python_version < \"3.12\""}, + {version = ">=3.0.0,<4.0.0", markers = "python_version >= \"3.12\""}, +] +pyudev = {version = ">=0.24.1,<0.25.0", markers = "sys_platform == \"linux\""} +pyyaml = ">=6.0,<7.0" +semantic-version = ">=2.10.0,<3.0.0" + +[[package]] +name = "fonttools" +version = "4.54.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, + {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, + {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, + {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, + {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, + {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, + {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, + {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, + {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, + {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, + {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, + {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, + {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, + {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, + {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, + {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, + {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, + {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "griffe" +version = "1.4.0" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.8" +files = [ + {file = "griffe-1.4.0-py3-none-any.whl", hash = "sha256:e589de8b8c137e99a46ec45f9598fc0ac5b6868ce824b24db09c02d117b89bc5"}, + {file = "griffe-1.4.0.tar.gz", hash = "sha256:8fccc585896d13f1221035d32c50dec65830c87d23f9adb9b1e6f3d63574f7f5"}, +] + +[package.dependencies] +colorama = ">=0.4" + +[[package]] +name = "grpcio" +version = "1.67.1" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.8" +files = [ + {file = "grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f"}, + {file = "grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d"}, + {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f"}, + {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0"}, + {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa"}, + {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292"}, + {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311"}, + {file = "grpcio-1.67.1-cp310-cp310-win32.whl", hash = "sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed"}, + {file = "grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e"}, + {file = "grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb"}, + {file = "grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e"}, + {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f"}, + {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc"}, + {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96"}, + {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f"}, + {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970"}, + {file = "grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744"}, + {file = "grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5"}, + {file = "grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953"}, + {file = "grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb"}, + {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0"}, + {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af"}, + {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e"}, + {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75"}, + {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38"}, + {file = "grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78"}, + {file = "grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc"}, + {file = "grpcio-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b"}, + {file = "grpcio-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1"}, + {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af"}, + {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955"}, + {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8"}, + {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62"}, + {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb"}, + {file = "grpcio-1.67.1-cp313-cp313-win32.whl", hash = "sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121"}, + {file = "grpcio-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba"}, + {file = "grpcio-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65"}, + {file = "grpcio-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426"}, + {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab"}, + {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085"}, + {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3"}, + {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8"}, + {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce"}, + {file = "grpcio-1.67.1-cp38-cp38-win32.whl", hash = "sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46"}, + {file = "grpcio-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771"}, + {file = "grpcio-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335"}, + {file = "grpcio-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e"}, + {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8"}, + {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d"}, + {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04"}, + {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8"}, + {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f"}, + {file = "grpcio-1.67.1-cp39-cp39-win32.whl", hash = "sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e"}, + {file = "grpcio-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98"}, + {file = "grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.67.1)"] + +[[package]] +name = "grpcio-tools" +version = "1.67.1" +description = "Protobuf code generator for gRPC" +optional = false +python-versions = ">=3.8" +files = [ + {file = "grpcio_tools-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:c701aaa51fde1f2644bd94941aa94c337adb86f25cd03cf05e37387aaea25800"}, + {file = "grpcio_tools-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:6a722bba714392de2386569c40942566b83725fa5c5450b8910e3832a5379469"}, + {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0c7415235cb154e40b5ae90e2a172a0eb8c774b6876f53947cf0af05c983d549"}, + {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4c459098c4934f9470280baf9ff8b38c365e147f33c8abc26039a948a664a5"}, + {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e89bf53a268f55c16989dab1cf0b32a5bff910762f138136ffad4146129b7a10"}, + {file = "grpcio_tools-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f09cb3e6bcb140f57b878580cf3b848976f67faaf53d850a7da9bfac12437068"}, + {file = "grpcio_tools-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:616dd0c6686212ca90ff899bb37eb774798677e43dc6f78c6954470782d37399"}, + {file = "grpcio_tools-1.67.1-cp310-cp310-win32.whl", hash = "sha256:58a66dbb3f0fef0396737ac09d6571a7f8d96a544ce3ed04c161f3d4fa8d51cc"}, + {file = "grpcio_tools-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:89ee7c505bdf152e67c2cced6055aed4c2d4170f53a2b46a7e543d3b90e7b977"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:6d80ddd87a2fb7131d242f7d720222ef4f0f86f53ec87b0a6198c343d8e4a86e"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b655425b82df51f3bd9fd3ba1a6282d5c9ce1937709f059cb3d419b224532d89"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:250241e6f9d20d0910a46887dfcbf2ec9108efd3b48f3fb95bb42d50d09d03f8"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6008f5a5add0b6f03082edb597acf20d5a9e4e7c55ea1edac8296c19e6a0ec8d"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5eff9818c3831fa23735db1fa39aeff65e790044d0a312260a0c41ae29cc2d9e"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:262ab7c40113f8c3c246e28e369661ddf616a351cb34169b8ba470c9a9c3b56f"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1eebd8c746adf5786fa4c3056258c21cc470e1eca51d3ed23a7fb6a697fe4e81"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-win32.whl", hash = "sha256:3eff92fb8ca1dd55e3af0ef02236c648921fb7d0e8ca206b889585804b3659ae"}, + {file = "grpcio_tools-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:1ed18281ee17e5e0f9f6ce0c6eb3825ca9b5a0866fc1db2e17fab8aca28b8d9f"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:bd5caef3a484e226d05a3f72b2d69af500dca972cf434bf6b08b150880166f0b"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:48a2d63d1010e5b218e8e758ecb2a8d63c0c6016434e9f973df1c3558917020a"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:baa64a6aa009bffe86309e236c81b02cd4a88c1ebd66f2d92e84e9b97a9ae857"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ab318c40b5e3c097a159035fc3e4ecfbe9b3d2c9de189e55468b2c27639a6ab"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50eba3e31f9ac1149463ad9182a37349850904f142cffbd957cd7f54ec320b8e"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:de6fbc071ecc4fe6e354a7939202191c1f1abffe37fbce9b08e7e9a5b93eba3d"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:db9e87f6ea4b0ce99b2651203480585fd9e8dd0dd122a19e46836e93e3a1b749"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-win32.whl", hash = "sha256:6a595a872fb720dde924c4e8200f41d5418dd6baab8cc1a3c1e540f8f4596351"}, + {file = "grpcio_tools-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:92eebb9b31031604ae97ea7657ae2e43149b0394af7117ad7e15894b6cc136dc"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:9a3b9510cc87b6458b05ad49a6dee38df6af37f9ee6aa027aa086537798c3d4a"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e4c9b9fa9b905f15d414cb7bd007ba7499f8907bdd21231ab287a86b27da81a"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:e11a98b41af4bc88b7a738232b8fa0306ad82c79fa5d7090bb607f183a57856f"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de0fcfe61c26679d64b1710746f2891f359593f76894fcf492c37148d5694f00"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae3b3e2ee5aad59dece65a613624c46a84c9582fc3642686537c6dfae8e47dc"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:9a630f83505b6471a3094a7a372a1240de18d0cd3e64f4fbf46b361bac2be65b"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d85a1fcbacd3e08dc2b3d1d46b749351a9a50899fa35cf2ff040e1faf7d405ad"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-win32.whl", hash = "sha256:778470f025f25a1fca5a48c93c0a18af395b46b12dd8df7fca63736b85181f41"}, + {file = "grpcio_tools-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:6961da86e9856b4ddee0bf51ef6636b4bf9c29c0715aa71f3c8f027c45d42654"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:c088dfbbe289bb171ca9c98fabbf7ecc8c1c51af2ba384ef32a4fdcb784b17e9"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11ce546daf8f8c04ee8d4a1673b4754cda4a0a9d505d820efd636e37f46b50c5"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:83fecb2f6119ef0eea68a091964898418c1969375d399956ff8d1741beb7b081"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d39c1aa6b26e2602d815b9cfa37faba48b2889680ae6baa002560cf0f0c69fac"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e975dc9fb61a77d88e739eb17b3361f369d03cc754217f02dd83ec7cfac32e38"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6c6e5c5b15f2eedc2a81268d588d14a79a52020383bf87b3c7595df7b571504a"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a974e0ce01806adba718e6eb8c385defe6805b18969b6914da7db55fb055ae45"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-win32.whl", hash = "sha256:35e9b0a82be9f425aa67ee1dc69ba02cf135aeee3f22c0455c5d1b01769bbdb4"}, + {file = "grpcio_tools-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:0436c97f29e654d2eccd7419907ee019caf7eea6bdc6ae91d98011f6c5f44f17"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:718fbb6d68a3d000cb3cf381642660eade0e8c1b0bf7472b84b3367f5b56171d"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:062887d2e9cb8bc261c21a2b8da714092893ce62b4e072775eaa9b24dcbf3b31"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:59dbf14a1ce928bf03a58fa157034374411159ab5d32ad83cf146d9400eed618"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac552fc9c76d50408d7141e1fd1eae69d85fbf7ae71da4d8877eaa07127fbe74"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6583773400e441dc62d08b5a32357babef1a9f9f73c3ac328a75af550815a9"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:862108f90f2f6408908e5ea4584c5104f7caf419c6d73aa3ff36bf8284cca224"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:587c6326425f37dca2291f46b93e446c07ee781cea27725865b806b7a049ec56"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-win32.whl", hash = "sha256:d7d46a4405bd763525215b6e073888386587aef9b4a5ec125bf97ba897ac757d"}, + {file = "grpcio_tools-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:e2fc7980e8bab3ee5ab98b6fdc2a8fbaa4785f196d897531346176fda49a605c"}, + {file = "grpcio_tools-1.67.1.tar.gz", hash = "sha256:d9657f5ddc62b52f58904e6054b7d8a8909ed08a1e28b734be3a707087bcf004"}, +] + +[package.dependencies] +grpcio = ">=1.67.1" +protobuf = ">=5.26.1,<6.0dev" +setuptools = "*" + +[[package]] +name = "identify" +version = "2.6.1" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, + {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.7" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.8" +files = [ + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, + {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, +] + +[[package]] +name = "markdown" +version = "3.7" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, + {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "matplotlib" +version = "3.9.2" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ + {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, + {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, + {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, + {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, + {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, + {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, + {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, + {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, + {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, + {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, + {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, + {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, + {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" +packaging = ">=20.5" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autorefs" +version = "1.2.0" +description = "Automatically link across pages in MkDocs." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"}, + {file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"}, +] + +[package.dependencies] +Markdown = ">=3.3" +markupsafe = ">=2.0.1" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-material" +version = "9.5.44" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material-9.5.44-py3-none-any.whl", hash = "sha256:47015f9c167d58a5ff5e682da37441fc4d66a1c79334bfc08d774763cacf69ca"}, + {file = "mkdocs_material-9.5.44.tar.gz", hash = "sha256:f3a6c968e524166b3f3ed1fb97d3ed3e0091183b0545cedf7156a2a6804c56c0"}, +] + +[package.dependencies] +babel = ">=2.10,<3.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.0,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +regex = ">=2022.4" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + +[[package]] +name = "mkdocstrings" +version = "0.26.1" +description = "Automatic documentation from sources, for MkDocs." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocstrings-0.26.1-py3-none-any.whl", hash = "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf"}, + {file = "mkdocstrings-0.26.1.tar.gz", hash = "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33"}, +] + +[package.dependencies] +click = ">=7.0" +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.11.1" +Markdown = ">=3.6" +MarkupSafe = ">=1.1" +mkdocs = ">=1.4" +mkdocs-autorefs = ">=1.2" +mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} +platformdirs = ">=2.2" +pymdown-extensions = ">=6.3" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "1.11.1" +description = "A Python handler for mkdocstrings." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocstrings_python-1.11.1-py3-none-any.whl", hash = "sha256:a21a1c05acef129a618517bb5aae3e33114f569b11588b1e7af3e9d4061a71af"}, + {file = "mkdocstrings_python-1.11.1.tar.gz", hash = "sha256:8824b115c5359304ab0b5378a91f6202324a849e1da907a3485b59208b797322"}, +] + +[package.dependencies] +griffe = ">=0.49" +mkdocs-autorefs = ">=1.2" +mkdocstrings = ">=0.26" + +[[package]] +name = "moteus" +version = "0.3.74" +description = "moteus brushless controller library and tools" +optional = false +python-versions = "<4,>=3.7" +files = [ + {file = "moteus-0.3.74-py3-none-any.whl", hash = "sha256:18542d598081b70ba39b97bb82e0de6c19bdad9c6f828796b5575f32b747e13a"}, +] + +[package.dependencies] +importlib-metadata = ">=3.6" +numpy = "<2" +pyelftools = ">=0.26" +pyserial = ">=3.5" +python-can = ">=3.3" +pywin32 = {version = "*", markers = "platform_system == \"Windows\""} +scipy = ">=1.8.0" + +[[package]] +name = "moteus-pi3hat" +version = "0.3.30" +description = "moteus brushless controller library and tools" +optional = false +python-versions = ">=3.7" +files = [ + {file = "moteus_pi3hat-0.3.30-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:cf318ec718b45b6af4f2141ce28d32a78cc5774417beb3c55152299f8b05b659"}, + {file = "moteus_pi3hat-0.3.30-cp310-cp310-manylinux_2_17_armv7l.whl", hash = "sha256:293899d804a21050c0f99455ee1b12d396e17b167b9c3f1c0c688a906ae3a0c7"}, + {file = "moteus_pi3hat-0.3.30-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b5ffc7f0c78995072b2a3e33e3d67f427fc3f870683baa256d9c3bf02712685f"}, + {file = "moteus_pi3hat-0.3.30-cp311-cp311-manylinux_2_17_armv7l.whl", hash = "sha256:3e83e017559ca81fc76559a551cb68c07c365a5d30005d55bd797a186fe941f7"}, + {file = "moteus_pi3hat-0.3.30-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:c75bbf96f88439aef062414bcc9cde78227d1eb10b47d5a2c7df7e3cae299c0e"}, + {file = "moteus_pi3hat-0.3.30-cp312-cp312-manylinux_2_17_armv7l.whl", hash = "sha256:0c18f4c9a97507e0e3907af5eb2b31a8aced9ab51986dc185ef7b7e6f783e35d"}, + {file = "moteus_pi3hat-0.3.30-cp37-abi3-linux_armv7l.whl", hash = "sha256:180d07cdc1e4ad168602725239c219de0efa0c4ef7909e971b9da1da9cba2fa1"}, + {file = "moteus_pi3hat-0.3.30-cp37-abi3-manylinux_2_17_aarch64.whl", hash = "sha256:a1c88daa3b7d019ba49adfa88ac3f10cb1357a063928b3e90dcb7dc1314b105b"}, + {file = "moteus_pi3hat-0.3.30-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:13d3925e86070226c1e01538375b4a8a7ae46ae7ec021e18c0b8e25c798f9d99"}, + {file = "moteus_pi3hat-0.3.30-cp39-cp39-manylinux_2_17_armv7l.whl", hash = "sha256:d73b698b86c008f0bf9d841e26ad0e964aed24401f34d4fd8104db88e8c3a876"}, +] + +[package.dependencies] +moteus = ">=0.3.22" + +[[package]] +name = "msgpack" +version = "1.0.8" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, + {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, + {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, + {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, + {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, + {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, + {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, + {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, + {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, +] + +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +] + +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pendulum" +version = "2.1.2" +description = "Python datetimes made easy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"}, + {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"}, + {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"}, + {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"}, + {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"}, + {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"}, + {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"}, + {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"}, + {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"}, + {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"}, + {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"}, + {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"}, + {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"}, + {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"}, + {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"}, + {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"}, + {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"}, + {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"}, + {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"}, + {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"}, + {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"}, +] + +[package.dependencies] +python-dateutil = ">=2.6,<3.0" +pytzdata = ">=2020.1" + +[[package]] +name = "pendulum" +version = "3.0.0" +description = "Python datetimes made easy" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pendulum-3.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2cf9e53ef11668e07f73190c805dbdf07a1939c3298b78d5a9203a86775d1bfd"}, + {file = "pendulum-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb551b9b5e6059377889d2d878d940fd0bbb80ae4810543db18e6f77b02c5ef6"}, + {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c58227ac260d5b01fc1025176d7b31858c9f62595737f350d22124a9a3ad82d"}, + {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60fb6f415fea93a11c52578eaa10594568a6716602be8430b167eb0d730f3332"}, + {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b69f6b4dbcb86f2c2fe696ba991e67347bcf87fe601362a1aba6431454b46bde"}, + {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138afa9c373ee450ede206db5a5e9004fd3011b3c6bbe1e57015395cd076a09f"}, + {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:83d9031f39c6da9677164241fd0d37fbfc9dc8ade7043b5d6d62f56e81af8ad2"}, + {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c2308af4033fa534f089595bcd40a95a39988ce4059ccd3dc6acb9ef14ca44a"}, + {file = "pendulum-3.0.0-cp310-none-win_amd64.whl", hash = "sha256:9a59637cdb8462bdf2dbcb9d389518c0263799189d773ad5c11db6b13064fa79"}, + {file = "pendulum-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3725245c0352c95d6ca297193192020d1b0c0f83d5ee6bb09964edc2b5a2d508"}, + {file = "pendulum-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c035f03a3e565ed132927e2c1b691de0dbf4eb53b02a5a3c5a97e1a64e17bec"}, + {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597e66e63cbd68dd6d58ac46cb7a92363d2088d37ccde2dae4332ef23e95cd00"}, + {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99a0f8172e19f3f0c0e4ace0ad1595134d5243cf75985dc2233e8f9e8de263ca"}, + {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d8839e20f54706aed425bec82a83b4aec74db07f26acd039905d1237a5e1d4"}, + {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afde30e8146292b059020fbc8b6f8fd4a60ae7c5e6f0afef937bbb24880bdf01"}, + {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:660434a6fcf6303c4efd36713ca9212c753140107ee169a3fc6c49c4711c2a05"}, + {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dee9e5a48c6999dc1106eb7eea3e3a50e98a50651b72c08a87ee2154e544b33e"}, + {file = "pendulum-3.0.0-cp311-none-win_amd64.whl", hash = "sha256:d4cdecde90aec2d67cebe4042fd2a87a4441cc02152ed7ed8fb3ebb110b94ec4"}, + {file = "pendulum-3.0.0-cp311-none-win_arm64.whl", hash = "sha256:773c3bc4ddda2dda9f1b9d51fe06762f9200f3293d75c4660c19b2614b991d83"}, + {file = "pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7"}, + {file = "pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc"}, + {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37"}, + {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319"}, + {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5"}, + {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f"}, + {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518"}, + {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9"}, + {file = "pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5"}, + {file = "pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f"}, + {file = "pendulum-3.0.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d4e2512f4e1a4670284a153b214db9719eb5d14ac55ada5b76cbdb8c5c00399d"}, + {file = "pendulum-3.0.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3d897eb50883cc58d9b92f6405245f84b9286cd2de6e8694cb9ea5cb15195a32"}, + {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e169cc2ca419517f397811bbe4589cf3cd13fca6dc38bb352ba15ea90739ebb"}, + {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17c3084a4524ebefd9255513692f7e7360e23c8853dc6f10c64cc184e1217ab"}, + {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:826d6e258052715f64d05ae0fc9040c0151e6a87aae7c109ba9a0ed930ce4000"}, + {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2aae97087872ef152a0c40e06100b3665d8cb86b59bc8471ca7c26132fccd0f"}, + {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac65eeec2250d03106b5e81284ad47f0d417ca299a45e89ccc69e36130ca8bc7"}, + {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a5346d08f3f4a6e9e672187faa179c7bf9227897081d7121866358af369f44f9"}, + {file = "pendulum-3.0.0-cp37-none-win_amd64.whl", hash = "sha256:235d64e87946d8f95c796af34818c76e0f88c94d624c268693c85b723b698aa9"}, + {file = "pendulum-3.0.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:6a881d9c2a7f85bc9adafcfe671df5207f51f5715ae61f5d838b77a1356e8b7b"}, + {file = "pendulum-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d7762d2076b9b1cb718a6631ad6c16c23fc3fac76cbb8c454e81e80be98daa34"}, + {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8e36a8130819d97a479a0e7bf379b66b3b1b520e5dc46bd7eb14634338df8c"}, + {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dc843253ac373358ffc0711960e2dd5b94ab67530a3e204d85c6e8cb2c5fa10"}, + {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a78ad3635d609ceb1e97d6aedef6a6a6f93433ddb2312888e668365908c7120"}, + {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a137e9e0d1f751e60e67d11fc67781a572db76b2296f7b4d44554761049d6"}, + {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c95984037987f4a457bb760455d9ca80467be792236b69d0084f228a8ada0162"}, + {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29c6e578fe0f893766c0d286adbf0b3c726a4e2341eba0917ec79c50274ec16"}, + {file = "pendulum-3.0.0-cp38-none-win_amd64.whl", hash = "sha256:deaba8e16dbfcb3d7a6b5fabdd5a38b7c982809567479987b9c89572df62e027"}, + {file = "pendulum-3.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b11aceea5b20b4b5382962b321dbc354af0defe35daa84e9ff3aae3c230df694"}, + {file = "pendulum-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a90d4d504e82ad236afac9adca4d6a19e4865f717034fc69bafb112c320dcc8f"}, + {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:825799c6b66e3734227756fa746cc34b3549c48693325b8b9f823cb7d21b19ac"}, + {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad769e98dc07972e24afe0cff8d365cb6f0ebc7e65620aa1976fcfbcadc4c6f3"}, + {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6fc26907eb5fb8cc6188cc620bc2075a6c534d981a2f045daa5f79dfe50d512"}, + {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c717eab1b6d898c00a3e0fa7781d615b5c5136bbd40abe82be100bb06df7a56"}, + {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3ddd1d66d1a714ce43acfe337190be055cdc221d911fc886d5a3aae28e14b76d"}, + {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:822172853d7a9cf6da95d7b66a16c7160cb99ae6df55d44373888181d7a06edc"}, + {file = "pendulum-3.0.0-cp39-none-win_amd64.whl", hash = "sha256:840de1b49cf1ec54c225a2a6f4f0784d50bd47f68e41dc005b7f67c7d5b5f3ae"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b1f74d1e6ffe5d01d6023870e2ce5c2191486928823196f8575dcc786e107b1"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:729e9f93756a2cdfa77d0fc82068346e9731c7e884097160603872686e570f07"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e586acc0b450cd21cbf0db6bae386237011b75260a3adceddc4be15334689a9a"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22e7944ffc1f0099a79ff468ee9630c73f8c7835cd76fdb57ef7320e6a409df4"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fa30af36bd8e50686846bdace37cf6707bdd044e5cb6e1109acbad3277232e04"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:440215347b11914ae707981b9a57ab9c7b6983ab0babde07063c6ee75c0dc6e7"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:314c4038dc5e6a52991570f50edb2f08c339debdf8cea68ac355b32c4174e820"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5acb1d386337415f74f4d1955c4ce8d0201978c162927d07df8eb0692b2d8533"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a789e12fbdefaffb7b8ac67f9d8f22ba17a3050ceaaa635cd1cc4645773a4b1e"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:860aa9b8a888e5913bd70d819306749e5eb488e6b99cd6c47beb701b22bdecf5"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5ebc65ea033ef0281368217fbf59f5cb05b338ac4dd23d60959c7afcd79a60a0"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9fef18ab0386ef6a9ac7bad7e43ded42c83ff7ad412f950633854f90d59afa8"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c134ba2f0571d0b68b83f6972e2307a55a5a849e7dac8505c715c531d2a8795"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:385680812e7e18af200bb9b4a49777418c32422d05ad5a8eb85144c4a285907b"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eec91cd87c59fb32ec49eb722f375bd58f4be790cae11c1b70fac3ee4f00da0"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4386bffeca23c4b69ad50a36211f75b35a4deb6210bdca112ac3043deb7e494a"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dfbcf1661d7146d7698da4b86e7f04814221081e9fe154183e34f4c5f5fa3bf8"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:04a1094a5aa1daa34a6b57c865b25f691848c61583fb22722a4df5699f6bf74c"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5b0ec85b9045bd49dd3a3493a5e7ddfd31c36a2a60da387c419fa04abcaecb23"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0a15b90129765b705eb2039062a6daf4d22c4e28d1a54fa260892e8c3ae6e157"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:bb8f6d7acd67a67d6fedd361ad2958ff0539445ef51cbe8cd288db4306503cd0"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd69b15374bef7e4b4440612915315cc42e8575fcda2a3d7586a0d88192d0c88"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc00f8110db6898360c53c812872662e077eaf9c75515d53ecc65d886eec209a"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:83a44e8b40655d0ba565a5c3d1365d27e3e6778ae2a05b69124db9e471255c4a"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1a3604e9fbc06b788041b2a8b78f75c243021e0f512447806a6d37ee5214905d"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:92c307ae7accebd06cbae4729f0ba9fa724df5f7d91a0964b1b972a22baa482b"}, + {file = "pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e"}, +] + +[package.dependencies] +python-dateutil = ">=2.6" +tzdata = ">=2020.1" + +[package.extras] +test = ["time-machine (>=2.6.0)"] + +[[package]] +name = "pillow" +version = "11.0.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, + {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, + {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, + {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, + {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, + {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, + {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, + {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, + {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, + {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, + {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, + {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, + {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, + {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, + {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, + {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, + {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, + {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, + {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, + {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, + {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +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 = "pre-commit" +version = "3.5.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "protobuf" +version = "5.28.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-5.28.3-cp310-abi3-win32.whl", hash = "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24"}, + {file = "protobuf-5.28.3-cp310-abi3-win_amd64.whl", hash = "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868"}, + {file = "protobuf-5.28.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687"}, + {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584"}, + {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"}, + {file = "protobuf-5.28.3-cp38-cp38-win32.whl", hash = "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548"}, + {file = "protobuf-5.28.3-cp38-cp38-win_amd64.whl", hash = "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b"}, + {file = "protobuf-5.28.3-cp39-cp39-win32.whl", hash = "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535"}, + {file = "protobuf-5.28.3-cp39-cp39-win_amd64.whl", hash = "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36"}, + {file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"}, + {file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"}, +] + +[[package]] +name = "pyelftools" +version = "0.31" +description = "Library for analyzing ELF files and DWARF debugging information" +optional = false +python-versions = "*" +files = [ + {file = "pyelftools-0.31-py3-none-any.whl", hash = "sha256:f52de7b3c7e8c64c8abc04a79a1cf37ac5fb0b8a49809827130b858944840607"}, + {file = "pyelftools-0.31.tar.gz", hash = "sha256:c774416b10310156879443b81187d182d8d9ee499660380e645918b50bc88f99"}, +] + +[[package]] +name = "pyftdi" +version = "0.55.4" +description = "FTDI device driver (pure Python)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyftdi-0.55.4-py3-none-any.whl", hash = "sha256:64a804ea1c652e6935897df7867dbba18910848cad0a4927ccdb06813c072c85"}, + {file = "pyftdi-0.55.4.tar.gz", hash = "sha256:cb2770b606507024a65e0a520bf892f239a36cec25cab45338ce3e3027de53c8"}, +] + +[package.dependencies] +pyserial = ">=3.0" +pyusb = ">=1.0.0,<1.2.0 || >1.2.0" + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pymdown-extensions" +version = "10.12" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pymdown_extensions-10.12-py3-none-any.whl", hash = "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77"}, + {file = "pymdown_extensions-10.12.tar.gz", hash = "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7"}, +] + +[package.dependencies] +markdown = ">=3.6" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.12)"] + +[[package]] +name = "pyparsing" +version = "3.2.0" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, + {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyproject-api" +version = "1.8.0" +description = "API to interact with the python pyproject.toml based projects" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"}, + {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"}, +] + +[package.dependencies] +packaging = ">=24.1" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "pytest (>=8.3.3)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] + +[[package]] +name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +optional = false +python-versions = "*" +files = [ + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, +] + +[package.extras] +cp2110 = ["hidapi"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-can" +version = "4.4.2" +description = "Controller Area Network interface module for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python_can-4.4.2-py3-none-any.whl", hash = "sha256:e956d781b45563c244c1f3c8fe001e292d1857519cff663d7c184673a68879f9"}, + {file = "python_can-4.4.2.tar.gz", hash = "sha256:1c46c0935f39f7a9c3e76b03249af0580689ebf7a1844195e92f87257f009df5"}, +] + +[package.dependencies] +msgpack = {version = ">=1.0.0,<1.1.0", markers = "platform_system != \"Windows\""} +packaging = ">=23.1" +pywin32 = {version = ">=305", markers = "platform_system == \"Windows\" and platform_python_implementation == \"CPython\""} +typing-extensions = ">=3.10.0.0" +wrapt = ">=1.10,<2.0" + +[package.extras] +canalystii = ["canalystii (>=0.1.0)"] +canine = ["python-can-canine (>=0.2.2)"] +cantact = ["cantact (>=0.0.7)"] +cvector = ["python-can-cvector"] +gs-usb = ["gs-usb (>=0.2.1)"] +lint = ["black (==24.4.*)", "mypy (==1.10.*)", "pylint (==3.2.*)", "ruff (==0.4.8)"] +mf4 = ["asammdf (>=6.0.0)"] +neovi = ["filelock", "python-ics (>=2.12)"] +nixnet = ["nixnet (>=0.3.2)"] +pcan = ["uptime (>=3.0.1,<3.1.0)"] +remote = ["python-can-remote"] +seeedstudio = ["pyserial (>=3.0)"] +serial = ["pyserial (>=3.0,<4.0)"] +sontheim = ["python-can-sontheim (>=0.1.2)"] +viewer = ["windows-curses"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytzdata" +version = "2020.1" +description = "The Olson timezone database for Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"}, + {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"}, +] + +[[package]] +name = "pyudev" +version = "0.24.3" +description = "A libudev binding" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyudev-0.24.3-py3-none-any.whl", hash = "sha256:e8246f0a014fe370119ba2bc781bfbe62c0298d0d6b39c94e83102a8a3f56960"}, + {file = "pyudev-0.24.3.tar.gz", hash = "sha256:2e945427a21674893bb97632401db62139d91cea1ee96137cc7b07ad22198fc7"}, +] + +[[package]] +name = "pyusb" +version = "1.2.1" +description = "Python USB access module" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "pyusb-1.2.1-py3-none-any.whl", hash = "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36"}, + {file = "pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9"}, +] + +[[package]] +name = "pywin32" +version = "308" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, + {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, + {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, + {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, + {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, + {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, + {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, + {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, + {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, + {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, + {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, + {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, + {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, + {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, + {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, + {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, + {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, + {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +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 = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "regex" +version = "2024.11.6" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[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 = "s3transfer" +version = "0.10.3" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">=3.8" +files = [ + {file = "s3transfer-0.10.3-py3-none-any.whl", hash = "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d"}, + {file = "s3transfer-0.10.3.tar.gz", hash = "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c"}, +] + +[package.dependencies] +botocore = ">=1.33.2,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] + +[[package]] +name = "scipy" +version = "1.13.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, + {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, + {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, + {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, + {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, + {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, + {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, + {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, + {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, + {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, +] + +[package.dependencies] +numpy = ">=1.22.4,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "semantic-version" +version = "2.10.0" +description = "A library implementing the 'SemVer' scheme." +optional = false +python-versions = ">=2.7" +files = [ + {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, + {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, +] + +[package.extras] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "setuptools" +version = "75.5.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +files = [ + {file = "setuptools-75.5.0-py3-none-any.whl", hash = "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"}, + {file = "setuptools-75.5.0.tar.gz", hash = "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +core = ["importlib-metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "smbus2" +version = "0.5.0" +description = "smbus2 is a drop-in replacement for smbus-cffi/smbus-python in pure Python" +optional = false +python-versions = "*" +files = [ + {file = "smbus2-0.5.0-py2.py3-none-any.whl", hash = "sha256:1a15c3b9fa69357beb038cc0b5d37939702f8bfde1ddc89ca9f17d8461dbe949"}, + {file = "smbus2-0.5.0.tar.gz", hash = "sha256:4a5946fd82277870c2878befdb1a29bb28d15cda14ea4d8d2d54cf3d4bdcb035"}, +] + +[package.extras] +docs = ["sphinx (>=1.5.3)"] +qa = ["flake8"] + +[[package]] +name = "sysv-ipc" +version = "1.1.0" +description = "System V IPC primitives (semaphores, shared memory and message queues) for Python" +optional = false +python-versions = "*" +files = [ + {file = "sysv_ipc-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ff6e3c98d8aa3e1ae5378d86869c4699c76f94318705d08085c97fb010770a31"}, + {file = "sysv_ipc-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:30611dccecd8fa0dc9f83d4d465e5047692df0ec9a095abef8b90ccc64afbdaf"}, + {file = "sysv_ipc-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4bbbe4e5ad5ab859e6d21fc8ff3e3b01082b959fa5c2cd76289dbcd56350c5f9"}, + {file = "sysv_ipc-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:61190b934b0277f7a189092dcabceee8d9736fd73de90fd9b50ea69f0bac4723"}, + {file = "sysv_ipc-1.1.0.tar.gz", hash = "sha256:0f063cbd36ec232032e425769ebc871f195a7d183b9af32f9901589ea7129ac3"}, +] + +[[package]] +name = "tomli" +version = "2.1.0" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, +] + +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + +[[package]] +name = "tox" +version = "4.23.2" +description = "tox is a generic virtualenv management and test command line tool" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tox-4.23.2-py3-none-any.whl", hash = "sha256:452bc32bb031f2282881a2118923176445bac783ab97c874b8770ab4c3b76c38"}, + {file = "tox-4.23.2.tar.gz", hash = "sha256:86075e00e555df6e82e74cfc333917f91ecb47ffbc868dcafbd2672e332f4a2c"}, +] + +[package.dependencies] +cachetools = ">=5.5" +chardet = ">=5.2" +colorama = ">=0.4.6" +filelock = ">=3.16.1" +packaging = ">=24.1" +platformdirs = ">=4.3.6" +pluggy = ">=1.5" +pyproject-api = ">=1.8" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""} +virtualenv = ">=20.26.6" + +[package.extras] +test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.3)", "pytest-mock (>=3.14)"] + +[[package]] +name = "types-protobuf" +version = "4.25.0.20240417" +description = "Typing stubs for protobuf" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-protobuf-4.25.0.20240417.tar.gz", hash = "sha256:c34eff17b9b3a0adb6830622f0f302484e4c089f533a46e3f147568313544352"}, + {file = "types_protobuf-4.25.0.20240417-py3-none-any.whl", hash = "sha256:e9b613227c2127e3d4881d75d93c93b4d6fd97b5f6a099a0b654a05351c8685d"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +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 = "tzdata" +version = "2024.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.27.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +files = [ + {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"}, + {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "watchdog" +version = "4.0.2" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, + {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, + {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, + {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, + {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "zipp" +version = "3.20.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[extras] +communication = ["smbus2"] +dephy = ["flexsea", "flexsea"] +messaging = ["grpcio", "grpcio-tools", "types-protobuf"] +moteus = ["moteus", "moteus-pi3hat"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9,<4.0" +content-hash = "158043d67b428d2fbf5f67571f4d310d9d0b3009899985eb62b2953e31c25d60" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 00000000..ab1033bd --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/pyproject.toml b/pyproject.toml index e8b63000..389569bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,38 +1,17 @@ -# Poetry pyproject.toml: https://python-poetry.org/docs/pyproject/ -[build-system] -requires = ["poetry_core>=1.0.0"] -build-backend = "poetry.core.masonry.api" - [tool.poetry] name = "opensourceleg" version = "3.0.0" description = "An open-source software library for numerical computation, data acquisition, and control of lower-limb robotic prostheses." -readme = "README.md" -authors = ["Open-source Leg "] -license = "GNU LGPL v2.1" +authors = ["Open-Source Leg "] repository = "https://github.com/neurobionics/opensourceleg" -homepage = "https://github.com/neurobionics/opensourceleg" - - -# Keywords description https://python-poetry.org/docs/pyproject/#keywords -keywords = [] #! Update me - -# Pypi classifiers: https://pypi.org/classifiers/ -classifiers = [ #! Update me - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "Topic :: Software Development :: Libraries :: Python Modules", - "License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", +documentation = "https://neurobionics.github.io/opensourceleg/" +readme = "README.md" +packages = [ + {include = "opensourceleg"} ] -[virtualenvs] -in-project = true - [tool.poetry.dependencies] -python = ">=3.9,<4.0.0" +python = ">=3.9,<4.0" numpy = "^1.24.3" flexsea = [ {version = "^12.0.3", python = ">=3.11"}, @@ -55,118 +34,90 @@ communication = ["smbus2"] messaging = ["grpcio", "grpcio-tools", "types-protobuf"] [tool.poetry.group.dev.dependencies] -bandit = "^1.7.1" -black = {version = ">=24.3,<25.0", allow-prereleases = true} -darglint = "^1.8.1" -isort = {extras = ["colors"], version = "^5.10.1"} -mypy = ">=0.991,<1.1" -mypy-extensions = "^0.4.3" -pre-commit = "^2.15.0" -pydocstyle = "^6.1.1" -pylint = ">=2.13.7,<4.0.0" -pytest = "^8.1.1" -pytest-mock = "^3.12.0" -pyupgrade = ">=2.29.1,<4.0.0" -safety = "^2.2.0" -coverage = "^6.1.2" -coverage-badge = "^1.1.0" -pytest-html = "^4.1.1" -pytest-cov = ">=3,<6" -click = "8.1.7" -sphinx = "^7.4.7" -wheel = "^0.41.2" -sphinx-book-theme = "^1.0.1" - -tornado = "6.4.1" -[tool.bandit] -skips = ["B101"] +pytest = "^7.2.0" +pytest-cov = "^4.0.0" +deptry = "^0.16.2" +mypy = "^1.5.1" +pre-commit = "^3.4.0" +tox = "^4.11.1" + +[tool.poetry.group.docs.dependencies] +mkdocs = "^1.4.2" +mkdocs-material = "^9.2.7" +mkdocstrings = {extras = ["python"], version = "^0.26.1"} -[tool.black] -# https://github.com/psf/black -target-version = ["py39"] -line-length = 88 -color = true - -exclude = ''' -/( - \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - | env - | venv -)/ -''' - -[tool.isort] -# https://github.com/timothycrosley/isort/ -py_version = 39 -line_length = 88 - -known_typing = ["typing", "types", "typing_extensions", "mypy", "mypy_extensions"] -sections = ["FUTURE", "TYPING", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] -include_trailing_comma = true -profile = "black" -multi_line_output = 3 -indent = 4 -color_output = true +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" [tool.mypy] -# https://mypy.readthedocs.io/en/latest/config_file.html#using-a-pyproject-toml-file -python_version = 3.9 -pretty = true -show_traceback = true -color_output = true -exclude = ["tests"] +files = ["opensourceleg"] +disallow_untyped_defs = "True" +disallow_any_unimported = "True" +no_implicit_optional = "True" +check_untyped_defs = "True" +warn_return_any = "True" +warn_unused_ignores = "True" +show_error_codes = "True" -allow_redefinition = false -check_untyped_defs = false -disallow_any_generics = true -disallow_incomplete_defs = false -ignore_missing_imports = true -implicit_reexport = false -no_implicit_optional = false -show_column_numbers = true -show_error_codes = true -show_error_context = true -strict_equality = true -strict_optional = true -warn_no_return = true -warn_redundant_casts = true -warn_return_any = true -warn_unreachable = true -warn_unused_configs = true -warn_unused_ignores = true [tool.pytest.ini_options] -# https://docs.pytest.org/en/6.2.x/customize.html#pyproject-toml -# Directories that are not visited by pytest collector: -norecursedirs =["hooks", "*.egg", ".eggs", "dist", "build", "docs", ".tox", ".git", "__pycache__"] -doctest_optionflags = ["NUMBER", "NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"] - -# Extra options: -addopts = [ - "--strict-markers", - "--tb=short", - "--doctest-modules", - "--doctest-continue-on-failure", +testpaths = ["tests"] + +[tool.ruff] +target-version = "py39" +line-length = 120 +fix = true +select = [ + # flake8-2020 + "YTT", + # flake8-bandit + "S", + # flake8-bugbear + "B", + # flake8-builtins + "A", + # flake8-comprehensions + "C4", + # flake8-debugger + "T10", + # flake8-simplify + "SIM", + # isort + "I", + # mccabe + "C90", + # pycodestyle + "E", "W", + # pyflakes + "F", + # pygrep-hooks + "PGH", + # pyupgrade + "UP", + # ruff + "RUF", + # tryceratops + "TRY", +] +ignore = [ + # LineTooLong + "E501", + # DoNotAssignLambda + "E731", ] -[tool.coverage.run] -source = ["tests"] +[tool.ruff.format] +preview = true -[coverage.paths] -source = "opensourceleg" +[tool.coverage.report] +skip_empty = true -[coverage.run] +[tool.coverage.run] branch = true +source = ["opensourceleg"] + -[coverage.report] -fail_under = 50 -show_missing = true +[tool.ruff.per-file-ignores] +"tests/*" = ["S101"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index aaca9ab9..00000000 --- a/requirements.txt +++ /dev/null @@ -1,60 +0,0 @@ -adafruit-blinka==8.50.0 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-circuitpython-bno055==5.4.14 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-circuitpython-busdevice==5.2.10 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-circuitpython-connectionmanager==3.1.2 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-circuitpython-lis3dh==5.2.3 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-circuitpython-register==1.10.1 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-circuitpython-requests==4.1.8 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-circuitpython-typing==1.11.1 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-platformdetect==3.75.0 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -adafruit-pureio==1.1.11 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -binho-host-adapter==0.1.6 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -board==1.0 ; python_version >= "3.9" and python_full_version < "4.0.0" -boto3==1.35.54 ; python_version >= "3.11" and python_version < "4.0" -botocore==1.35.54 ; python_version >= "3.11" and python_version < "4.0" -contourpy==1.3.0 ; python_version >= "3.9" and python_version < "3.10" -cycler==0.12.1 ; python_version >= "3.9" and python_version < "3.10" -flexsea==12.0.4 ; python_version >= "3.11" and python_version < "4.0" -flexsea==8.0.1 ; python_version >= "3.9" and python_version < "3.10" -fonttools==4.54.1 ; python_version >= "3.9" and python_version < "3.10" -grpcio-tools==1.67.1 ; python_version >= "3.9" and python_full_version < "4.0.0" -grpcio==1.67.1 ; python_version >= "3.9" and python_full_version < "4.0.0" -importlib-metadata==8.5.0 ; python_version >= "3.9" and python_version < "4" -importlib-resources==6.4.5 ; python_version >= "3.9" and python_version < "3.10" -jmespath==1.0.1 ; python_version >= "3.11" and python_version < "4.0" -kiwisolver==1.4.7 ; python_version >= "3.9" and python_version < "3.10" -matplotlib==3.9.2 ; python_version >= "3.9" and python_version < "3.10" -moteus-pi3hat==0.3.30 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -moteus==0.3.73 ; python_version >= "3.9" and python_version < "4" -msgpack==1.0.8 ; python_version >= "3.9" and python_version < "4" and platform_system != "Windows" -numpy==1.26.4 ; python_version >= "3.9" and python_version < "4" -packaging==24.1 ; python_version >= "3.9" and python_version < "4" -pendulum==2.1.2 ; python_version >= "3.11" and python_version < "3.12" -pendulum==3.0.0 ; python_version >= "3.12" and python_version < "4.0" -pillow==11.0.0 ; python_version >= "3.9" and python_version < "3.10" -protobuf==5.28.3 ; python_version >= "3.9" and python_full_version < "4.0.0" -pyelftools==0.31 ; python_version >= "3.9" and python_version < "4" -pyftdi==0.55.4 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -pyparsing==3.2.0 ; python_version >= "3.9" and python_version < "3.10" -pyserial==3.5 ; python_version >= "3.9" and python_version < "4" -python-can==4.4.2 ; python_version >= "3.9" and python_version < "4" -python-dateutil==2.9.0.post0 ; python_version >= "3.11" and python_version < "4.0" or python_version >= "3.9" and python_version < "3.10" -pytzdata==2020.1 ; python_version >= "3.11" and python_version < "3.12" -pyudev==0.24.3 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "linux" -pyusb==1.2.1 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -pywin32==308 ; python_version >= "3.9" and python_version < "4" and platform_system == "Windows" -pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "4.0" or python_version >= "3.9" and python_version < "3.10" -s3transfer==0.10.3 ; python_version >= "3.11" and python_version < "4.0" -scipy==1.13.1 ; python_version >= "3.9" and python_version < "4" -semantic-version==2.10.0 ; python_version >= "3.11" and python_version < "4.0" -setuptools==75.3.0 ; python_version >= "3.9" and python_full_version < "4.0.0" -six==1.16.0 ; python_version >= "3.11" and python_version < "4.0" or python_version >= "3.9" and python_version < "3.10" -smbus2==0.5.0 ; python_version >= "3.9" and python_full_version < "4.0.0" -sysv-ipc==1.1.0 ; sys_platform == "linux" and platform_machine == "aarch64" and python_version >= "3.9" and python_full_version < "4.0.0" -tornado==6.4.1 ; python_version >= "3.9" and python_version < "3.10" -types-protobuf==4.25.0.20240417 ; python_version >= "3.9" and python_full_version < "4.0.0" -typing-extensions==4.12.2 ; python_version >= "3.9" and python_version < "4" -tzdata==2024.2 ; python_version >= "3.12" and python_version < "4.0" -urllib3==2.2.3 ; python_version >= "3.11" and python_version < "4.0" -wrapt==1.16.0 ; python_version >= "3.9" and python_version < "4" -zipp==3.20.2 ; python_version >= "3.9" and python_version < "4" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 4b801204..00000000 --- a/setup.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[metadata] -description-file = README.md - -[darglint] -# https://github.com/terrencepreilly/darglint -strictness = long -docstring_style = google diff --git a/tests/test_actuators/test_actuators_base.py b/tests/test_actuators/test_actuators_base.py index 7b03b705..69b0d21a 100644 --- a/tests/test_actuators/test_actuators_base.py +++ b/tests/test_actuators/test_actuators_base.py @@ -1,4 +1,3 @@ -import unittest from unittest.mock import Mock, patch import numpy as np @@ -40,8 +39,8 @@ def create_motor_constants(values): def test_motor_constants_init(non_zero_positive_values): motor_constants = create_motor_constants(non_zero_positive_values) assert list(motor_constants.__dict__.values()) == list(non_zero_positive_values) - assert motor_constants.RAD_PER_COUNT == 2 * np.pi / non_zero_positive_values[0] - assert motor_constants.NM_PER_MILLIAMP == non_zero_positive_values[1] / 1000 + assert 2 * np.pi / non_zero_positive_values[0] == motor_constants.RAD_PER_COUNT + assert non_zero_positive_values[1] / 1000 == motor_constants.NM_PER_MILLIAMP @pytest.mark.parametrize( @@ -74,8 +73,8 @@ def test_motor_constants_init_types(): ) def test_motor_constants_properties(non_zero_positive_values): motor_constants = create_motor_constants(non_zero_positive_values) - assert motor_constants.RAD_PER_COUNT == 2 * np.pi / non_zero_positive_values[0] - assert motor_constants.NM_PER_MILLIAMP == non_zero_positive_values[1] / 1000 + assert 2 * np.pi / non_zero_positive_values[0] == motor_constants.RAD_PER_COUNT + assert non_zero_positive_values[1] / 1000 == motor_constants.NM_PER_MILLIAMP def test_control_modes_default_four(): @@ -232,10 +231,10 @@ def test_control_mode_configs_init(): IMPEDANCE=default_control_mode_config, ) - assert control_mode_configs.POSITION == default_control_mode_config - assert control_mode_configs.CURRENT == default_control_mode_config - assert control_mode_configs.VOLTAGE == default_control_mode_config - assert control_mode_configs.IMPEDANCE == default_control_mode_config + assert default_control_mode_config == control_mode_configs.POSITION + assert default_control_mode_config == control_mode_configs.CURRENT + assert default_control_mode_config == control_mode_configs.VOLTAGE + assert default_control_mode_config == control_mode_configs.IMPEDANCE assert control_mode_configs.VELOCITY == None assert control_mode_configs.TORQUE == None assert control_mode_configs.IDLE == None @@ -500,14 +499,10 @@ def test_set_joint_direction(mock_actuator: MockActuator): def test_output_position_and_velocity(mock_actuator: MockActuator): - with patch.object( - MockActuator, "motor_position", new_callable=Mock(return_value=10.0) - ): + with patch.object(MockActuator, "motor_position", new_callable=Mock(return_value=10.0)): assert mock_actuator.output_position == 1.0 # 10.0 / 10.0 (gear_ratio) - with patch.object( - MockActuator, "motor_velocity", new_callable=Mock(return_value=5.0) - ): + with patch.object(MockActuator, "motor_velocity", new_callable=Mock(return_value=5.0)): assert mock_actuator.output_velocity == 0.5 # 5.0 / 10.0 (gear_ratio) @@ -518,9 +513,7 @@ def test_temperature_limits(mock_actuator: MockActuator): def test_method_restriction(mock_actuator: MockActuator): mock_actuator.set_control_mode(CONTROL_MODES.IDLE) - assert ( - mock_actuator.set_motor_voltage(5.0) is None - ) # Should be restricted in IDLE mode + assert mock_actuator.set_motor_voltage(5.0) is None # Should be restricted in IDLE mode mock_actuator.set_control_mode(CONTROL_MODES.VOLTAGE) with patch.object(mock_actuator, "set_motor_voltage") as mock_method: @@ -557,10 +550,7 @@ def test_motor_constants(mock_actuator: MockActuator): def test_motor_constants_properties(mock_actuator: MockActuator): - assert ( - pytest.approx(mock_actuator.MOTOR_CONSTANTS.RAD_PER_COUNT, 0.00001) - == 2 * 3.14159 / 1000 - ) + assert pytest.approx(mock_actuator.MOTOR_CONSTANTS.RAD_PER_COUNT, 0.00001) == 2 * 3.14159 / 1000 assert mock_actuator.MOTOR_CONSTANTS.NM_PER_MILLIAMP == 0.0001 diff --git a/tests/test_control/test_compiled_controller.py b/tests/test_control/test_compiled_controller.py index 4b06d2f2..0faab5b1 100644 --- a/tests/test_control/test_compiled_controller.py +++ b/tests/test_control/test_compiled_controller.py @@ -384,9 +384,7 @@ def test_cleanup_function_called_on_exception(monkeypatch): mock_cleanup_function = MagicMock() mock_lib.cleanup_func = mock_cleanup_function - controller = CompiledController( - "test_lib", "/path/to/lib", "main_func", cleanup_function_name="cleanup_func" - ) + controller = CompiledController("test_lib", "/path/to/lib", "main_func", cleanup_function_name="cleanup_func") # Mock main_function to raise an exception def mock_main_function(inputs, outputs): diff --git a/tests/test_control/test_state_machine.py b/tests/test_control/test_state_machine.py index 4bcf7369..1b9c1632 100644 --- a/tests/test_control/test_state_machine.py +++ b/tests/test_control/test_state_machine.py @@ -174,9 +174,7 @@ def criteria(data): def action(data): action_called.append(True) - transition = FromToTransition( - event=event, source=state1, destination=state2, callback=criteria - ) + transition = FromToTransition(event=event, source=state1, destination=state2, callback=criteria) transition.add_action(action) state1.start(data) next_state = transition(data) @@ -194,9 +192,7 @@ def test_from_to_transition_no_criteria(): def criteria(data): return False - transition = FromToTransition( - event=event, source=state1, destination=state2, callback=criteria - ) + transition = FromToTransition(event=event, source=state1, destination=state2, callback=criteria) state1.start(data) next_state = transition(data) assert next_state == state1 # Should remain in state1 diff --git a/tests/test_logging/test_logging_decorators.py b/tests/test_logging/test_logging_decorators.py index 2f788f73..1e25d7fa 100644 --- a/tests/test_logging/test_logging_decorators.py +++ b/tests/test_logging/test_logging_decorators.py @@ -13,9 +13,7 @@ def testing(x): return x assert testing(2) == 2 - LOGGER.warning.assert_called_once_with( - f"Function `{testing.__name__}` is deprecated." - ) + LOGGER.warning.assert_called_once_with(f"Function `{testing.__name__}` is deprecated.") LOGGER.warning = LOGGER.original_warning del LOGGER.original_warning diff --git a/tests/test_logging/test_logging_exceptions.py b/tests/test_logging/test_logging_exceptions.py index a95518b5..aea787a0 100644 --- a/tests/test_logging/test_logging_exceptions.py +++ b/tests/test_logging/test_logging_exceptions.py @@ -8,10 +8,7 @@ def test_actuator_stream_exception(): test_str = "test" with pytest.raises(ActuatorStreamException) as e: raise ActuatorStreamException(test_str) - assert ( - str(e.value) - == f"{test_str} is not streaming, please call start() method before sending commands" - ) + assert str(e.value) == f"{test_str} is not streaming, please call start() method before sending commands" # Test ActuatorConnectionException diff --git a/tests/test_logging/test_logging_logger.py b/tests/test_logging/test_logging_logger.py index fd5cc924..e39d0998 100644 --- a/tests/test_logging/test_logging_logger.py +++ b/tests/test_logging/test_logging_logger.py @@ -11,15 +11,13 @@ def test_log_level_default(): def test_log_level_value_logging(): - assert all( - [ - LogLevel.DEBUG.value == logging.DEBUG, - LogLevel.INFO.value == logging.INFO, - LogLevel.WARNING.value == logging.WARNING, - LogLevel.ERROR.value == logging.ERROR, - LogLevel.CRITICAL.value == logging.CRITICAL, - ] - ) + assert all([ + LogLevel.DEBUG.value == logging.DEBUG, + LogLevel.INFO.value == logging.INFO, + LogLevel.WARNING.value == logging.WARNING, + LogLevel.ERROR.value == logging.ERROR, + LogLevel.CRITICAL.value == logging.CRITICAL, + ]) def test_log_level_len(): @@ -46,28 +44,24 @@ def test_logger_new(): # Test init def test_logger_init_default(): logger = Logger() - assert all( - [ - logger._log_path == "./", - isinstance(logger.file_level, LogLevel), - isinstance(logger.stream_level, LogLevel), - logger.file_max_bytes == 0, - logger._file_backup_count == 5, - logger.buffer_size == 1000, - ] - ) + assert all([ + logger._log_path == "./", + isinstance(logger.file_level, LogLevel), + isinstance(logger.stream_level, LogLevel), + logger.file_max_bytes == 0, + logger._file_backup_count == 5, + logger.buffer_size == 1000, + ]) logger.reset() def test_logger_init_set(): logger = Logger(buffer_size=10, file_level=LogLevel.CRITICAL) - assert all( - [ - logger._log_format == "[%(asctime)s] %(levelname)s: %(message)s", - logger._buffer_size == 10, - logger._file_level == LogLevel.CRITICAL, - ] - ) + assert all([ + logger._log_format == "[%(asctime)s] %(levelname)s: %(message)s", + logger._buffer_size == 10, + logger._file_level == LogLevel.CRITICAL, + ]) logger.reset() @@ -79,13 +73,11 @@ def test_setup_logging(): stream_level=LogLevel.INFO, ) test_logger._setup_logging() - assert all( - [ - test_logger.level == LogLevel.DEBUG.value, - test_logger._stream_handler.level == LogLevel.INFO.value, - test_logger._stream_handler.formatter._fmt == "[%(levelname)s]", - ] - ) + assert all([ + test_logger.level == LogLevel.DEBUG.value, + test_logger._stream_handler.level == LogLevel.INFO.value, + test_logger._stream_handler.formatter._fmt == "[%(levelname)s]", + ]) test_logger.reset() @@ -101,16 +93,14 @@ def test_setup_file_handler(): test_logger._setup_file_handler() - assert all( - [ - test_logger._file_handler.maxBytes == 0, - test_logger._file_handler.backupCount == 20, - test_logger._file_handler.level == LogLevel.WARNING.value, - test_logger._file_handler.mode == "a", - test_logger._file_handler.formatter._fmt == "[%(levelname)s]", - hasattr(test_logger, "_file_handler"), - ] - ) + assert all([ + test_logger._file_handler.maxBytes == 0, + test_logger._file_handler.backupCount == 20, + test_logger._file_handler.level == LogLevel.WARNING.value, + test_logger._file_handler.mode == "a", + test_logger._file_handler.formatter._fmt == "[%(levelname)s]", + hasattr(test_logger, "_file_handler"), + ]) test_logger.reset() @@ -142,20 +132,16 @@ def test_func() -> list: assert test_func() == [1, 2, 3] test_logger.track_variable(test_func, "Testing") - assert all( - [ - test_func in list(test_logger._tracked_vars.values()), - "Testing" in list(test_logger._var_names.values()), - ] - ) + assert all([ + test_func in list(test_logger._tracked_vars.values()), + "Testing" in list(test_logger._var_names.values()), + ]) test_logger.untrack_variable(test_func) - assert all( - [ - not test_func in list(test_logger._tracked_vars.values()), - not "Testing" in list(test_logger._var_names.values()), - ] - ) + assert all([ + test_func not in list(test_logger._tracked_vars.values()), + "Testing" not in list(test_logger._var_names.values()), + ]) # Test repr @@ -167,83 +153,67 @@ def test_repr(test_logger: Logger): # Test set file name def test_set_file_name_str(test_logger: Logger): test_logger.set_file_name("test_file") - assert all( - [ - test_logger._user_file_name == "test_file", - test_logger._file_path == "", - test_logger._csv_path == "", - ] - ) + assert all([ + test_logger._user_file_name == "test_file", + test_logger._file_path == "", + test_logger._csv_path == "", + ]) def test_set_file_name_none(test_logger: Logger): test_logger.set_file_name(None) - assert all( - [ - test_logger._user_file_name == None, - test_logger._file_path == "", - test_logger._csv_path == "", - ] - ) + assert all([ + test_logger._user_file_name == None, + test_logger._file_path == "", + test_logger._csv_path == "", + ]) # Test set file level def test_set_file_level(test_logger: Logger): test_logger.set_file_level(LogLevel.CRITICAL) - assert all( - [ - test_logger._file_level == LogLevel.CRITICAL, - not hasattr(test_logger, "_file_handler"), - ] - ) + assert all([ + test_logger._file_level == LogLevel.CRITICAL, + not hasattr(test_logger, "_file_handler"), + ]) def test_set_file_level_has_attr(test_logger: Logger): test_logger._setup_file_handler() - assert all( - [hasattr(test_logger, "_file_handler"), test_logger._file_handler.mode == "a"] - ) + assert all([hasattr(test_logger, "_file_handler"), test_logger._file_handler.mode == "a"]) test_logger.set_file_level(LogLevel.DEBUG) - assert all( - [ - test_logger._file_level == LogLevel.DEBUG, - test_logger._file_handler.level == LogLevel.DEBUG.value, - ] - ) + assert all([ + test_logger._file_level == LogLevel.DEBUG, + test_logger._file_handler.level == LogLevel.DEBUG.value, + ]) # Test set stream level def test_set_stream_level(test_logger: Logger): test_logger.set_stream_level(LogLevel.ERROR) - assert all( - [ - hasattr(test_logger, "_stream_handler"), - test_logger._stream_level == LogLevel.ERROR, - test_logger._stream_handler.level == LogLevel.ERROR.value, - ] - ) + assert all([ + hasattr(test_logger, "_stream_handler"), + test_logger._stream_level == LogLevel.ERROR, + test_logger._stream_handler.level == LogLevel.ERROR.value, + ]) # Test set format def test_set_format(test_logger: Logger): test_logger.set_format("[%(levelname)s]") - assert all( - [ - test_logger._log_format == "[%(levelname)s]", - isinstance(test_logger._std_formatter, logging.Formatter), - test_logger._std_formatter._fmt == "[%(levelname)s]", - not hasattr(test_logger, "_file_handler"), - test_logger._stream_handler.formatter._fmt == "[%(levelname)s]", - ] - ) + assert all([ + test_logger._log_format == "[%(levelname)s]", + isinstance(test_logger._std_formatter, logging.Formatter), + test_logger._std_formatter._fmt == "[%(levelname)s]", + not hasattr(test_logger, "_file_handler"), + test_logger._stream_handler.formatter._fmt == "[%(levelname)s]", + ]) def test_set_format_has_attr(test_logger: Logger): test_logger._setup_file_handler() - assert all( - [hasattr(test_logger, "_file_handler"), test_logger._file_handler.mode == "a"] - ) + assert all([hasattr(test_logger, "_file_handler"), test_logger._file_handler.mode == "a"]) test_logger.set_format("[%(test)s]") assert test_logger._file_handler.formatter._fmt == "[%(test)s]" @@ -252,13 +222,11 @@ def test_set_format_has_attr(test_logger: Logger): # Test set buffer size def test_set_buffer_size(test_logger: Logger): test_logger.set_buffer_size(5) - assert all( - [ - test_logger._buffer_size == 5, - isinstance(test_logger._buffer, deque), - test_logger._buffer.maxlen == 5, - ] - ) + assert all([ + test_logger._buffer_size == 5, + isinstance(test_logger._buffer, deque), + test_logger._buffer.maxlen == 5, + ]) # Test update @@ -274,13 +242,11 @@ def test_func2() -> int: test_logger.update() test_logger.track_variable(test_func2, "second") test_logger.update() - assert all( - [ - test_logger._buffer[0] == ["18"], - test_logger._buffer[1] == ["18", "8"], - len(test_logger._buffer) == 2, - ] - ) + assert all([ + test_logger._buffer[0] == ["18"], + test_logger._buffer[1] == ["18", "8"], + len(test_logger._buffer) == 2, + ]) # Test update size exceeded @@ -387,24 +353,20 @@ def test_reset(test_logger: Logger): test_logger.track_variable(lambda: 2, "test") test_logger.update() test_logger._setup_file_handler() - assert all( - [ - len(test_logger._buffer) == 1, - len(test_logger._tracked_vars) == 1, - len(test_logger._var_names) == 1, - hasattr(test_logger, "_file_handler"), - ] - ) + assert all([ + len(test_logger._buffer) == 1, + len(test_logger._tracked_vars) == 1, + len(test_logger._var_names) == 1, + hasattr(test_logger, "_file_handler"), + ]) test_logger.reset() - assert all( - [ - len(test_logger._buffer) == 0, - len(test_logger._tracked_vars) == 0, - len(test_logger._var_names) == 0, - not hasattr(test_logger, "_file_handler"), - ] - ) + assert all([ + len(test_logger._buffer) == 0, + len(test_logger._tracked_vars) == 0, + len(test_logger._var_names) == 0, + not hasattr(test_logger, "_file_handler"), + ]) def test_reset_header(test_logger: Logger): diff --git a/tests/test_math/test_math.py b/tests/test_math/test_math.py index 0293cdbc..0f587e79 100644 --- a/tests/test_math/test_math.py +++ b/tests/test_math/test_math.py @@ -4,7 +4,6 @@ def test_edge_detector_init(): - edi = EdgeDetector(bool_in=False) assert edi.cur_state == False assert edi.rising_edge == False @@ -12,7 +11,6 @@ def test_edge_detector_init(): def test_edge_detector_update(): - edu = EdgeDetector(bool_in=False) edu.update(bool_in=True) assert edu.rising_edge == True @@ -26,14 +24,12 @@ def test_edge_detector_update(): def test_saturating_ramp_init(): - sri = SaturatingRamp(loop_frequency=100, ramp_time=1.0) assert sri.delta_per_update == 1.0 / 100 assert sri.value == 0.0 def test_saturating_ramp_update(): - sru = SaturatingRamp(loop_frequency=100, ramp_time=1.0) sru.update(enable_ramp=True) assert sru.value == 0.01 @@ -113,14 +109,7 @@ def test_update(): # Testing the default ThermalModel update method with motor_current arg test_model_default.update(motor_current=1000) default_T_w_update1 = ( - 21 - + ( - ((1000 * 1e-3) ** 2) - * 0.376 - * (1 + (0.393 * 1 / 100) * (21 - 65)) - / (16.292405391941298) - ) - / 200 + 21 + (((1000 * 1e-3) ** 2) * 0.376 * (1 + (0.393 * 1 / 100) * (21 - 65)) / (16.292405391941298)) / 200 ) assert test_model_default.T_w == default_T_w_update1 assert test_model_default.T_c == 21 @@ -131,9 +120,7 @@ def test_update(): default_T_w_update1 + ( ( - ((1000 * 1e-3) ** 2) - * 0.376 - * (1 + (0.393 * 1 / 100) * (default_T_w_update1 - 65)) + ((1000 * 1e-3) ** 2) * 0.376 * (1 + (0.393 * 1 / 100) * (default_T_w_update1 - 65)) + (21 - default_T_w_update1) / 1.0702867186480716 ) / (16.292405391941298) @@ -142,10 +129,6 @@ def test_update(): ) assert test_model_default.T_w == default_T_w_update2 assert ( - test_model_default.T_c - == (((default_T_w_update1 - 21) / 1.0702867186480716) / 512.249065845453) - * 1 - / 200 - + 21 + test_model_default.T_c == (((default_T_w_update1 - 21) / 1.0702867186480716) / 512.249065845453) * 1 / 200 + 21 ) assert test_model_default.T_a == 21 diff --git a/tests/test_robots/test_robots_base.py b/tests/test_robots/test_robots_base.py index 9c3e3acb..cb48fb24 100644 --- a/tests/test_robots/test_robots_base.py +++ b/tests/test_robots/test_robots_base.py @@ -59,18 +59,14 @@ def test_robot_base_init(): test_actuators = {"actuator": test_tactuator} test_tsensor = MockSensor() test_sensors = {"sensor": test_tsensor} - sample_robot = MockRobot( - tag=tag_name, actuators=test_actuators, sensors=test_sensors - ) - assert all( - [ - sample_robot.tag == tag_name, - sample_robot.actuators == test_actuators, - sample_robot.sensors == test_sensors, - type(sample_robot.actuators) == dict, - type(sample_robot.sensors) == dict, - ] - ) + sample_robot = MockRobot(tag=tag_name, actuators=test_actuators, sensors=test_sensors) + assert all([ + sample_robot.tag == tag_name, + sample_robot.actuators == test_actuators, + sample_robot.sensors == test_sensors, + type(sample_robot.actuators) == dict, + type(sample_robot.sensors) == dict, + ]) # Test enter @@ -101,9 +97,9 @@ def test_robot_base_start(mock_robot: MockRobot): mock_robot.start() - file = open(LOGGER._file_path, "r") + file = open(LOGGER._file_path) contents = file.read() - assert (f"DEBUG: Calling start method of act_tag") in contents + assert ("DEBUG: Calling start method of act_tag") in contents assert "DEBUG: Calling start method of SensorBase" in contents file.close() @@ -122,7 +118,7 @@ def test_robot_base_stop(mock_robot: MockRobot): mock_robot.stop() - file = open(LOGGER._file_path, "r") + file = open(LOGGER._file_path) contents = file.read() assert "DEBUG: Calling stop method of act_tag" in contents assert "DEBUG: Calling stop method of SensorBase" in contents diff --git a/tests/test_safety/test_safety.py b/tests/test_safety/test_safety.py index 6271aeb2..34d5d742 100644 --- a/tests/test_safety/test_safety.py +++ b/tests/test_safety/test_safety.py @@ -1,5 +1,5 @@ from io import StringIO -from unittest.mock import Mock, patch +from unittest.mock import patch import pytest @@ -37,9 +37,7 @@ def test_Thermal_Limit_Exception_initialization(): def test_Thermal_Limit_Exception_initialization_with_default(): thermal_limit_exception = ThermalLimitException() - assert ( - thermal_limit_exception.message == "Software thermal limit exceeded. Exiting." - ) + assert thermal_limit_exception.message == "Software thermal limit exceeded. Exiting." # Test def is_changing & decorator @@ -117,9 +115,7 @@ def test_changing_point1(instance): with patch("sys.stdout", new=StringIO()) as temp_out: test_changing_point1(instance) - assert ( - temp_out.getvalue() == f"{att} isn't stable, returning {proxy_att_name}\n" - ) + assert temp_out.getvalue() == f"{att} isn't stable, returning {proxy_att_name}\n" def test_is_changing_parameters(): @@ -386,9 +382,7 @@ def test_is_within_range_max_less_min(): # Test maximum value greater than minimum value for range min = 4 max = 1 - with pytest.raises( - ValueError, match=f"Maximum value must be greater than minimum value of range" - ): + with pytest.raises(ValueError, match="Maximum value must be greater than minimum value of range"): @is_within_range(min, max) def test_for_min_greater_than_max(instance: object): @@ -463,9 +457,7 @@ def test_is_greater_equality_false_out(): def test_less_with_false_clamp(instance: object): return 1 - with pytest.raises( - ValueError, match=f"Value must be greater than or equal to {min}" - ): + with pytest.raises(ValueError, match=f"Value must be greater than or equal to {min}"): test_less_with_false_clamp({}) @@ -687,9 +679,7 @@ def test_add_safety_att_doesnt_exist(mock_print): attribute = "wrong" test_manager.add_safety(samp, attribute, SafetyDecorators.is_positive()) - mock_print.assert_called_once_with( - f"Error: The attribute '{attribute}' does not exist in the given object." - ) + mock_print.assert_called_once_with(f"Error: The attribute '{attribute}' does not exist in the given object.") @patch("builtins.print") diff --git a/tests/test_sensors/test_imu.py b/tests/test_sensors/test_imu.py index 69b3113f..8edbbcc6 100644 --- a/tests/test_sensors/test_imu.py +++ b/tests/test_sensors/test_imu.py @@ -1,5 +1,3 @@ -from unittest.mock import Mock - import pytest from opensourceleg.logging import LOGGER @@ -134,17 +132,15 @@ def sample_imu_init(): # Test init def test_init_default(sample_imu: MockLordMicrostrainIMU): - assert all( - [ - sample_imu._port == "/dev/ttyUSB0", - sample_imu._baud_rate == 921600, - sample_imu._frequency == 200, - sample_imu._is_streaming == False, - sample_imu._connection == None, - sample_imu._data == {}, - type(sample_imu._data) == dict, - ] - ) + assert all([ + sample_imu._port == "/dev/ttyUSB0", + sample_imu._baud_rate == 921600, + sample_imu._frequency == 200, + sample_imu._is_streaming == False, + sample_imu._connection == None, + sample_imu._data == {}, + type(sample_imu._data) == dict, + ]) def test_init_set(): @@ -152,13 +148,11 @@ def test_init_set(): baud = 10 freq = 20 sample_imu = MockLordMicrostrainIMU(port=port_name, baud_rate=baud, frequency=freq) - assert all( - [ - sample_imu._port == port_name, - sample_imu._baud_rate == baud, - sample_imu._frequency == freq, - ] - ) + assert all([ + sample_imu._port == port_name, + sample_imu._baud_rate == baud, + sample_imu._frequency == freq, + ]) # Test configure mip channels @@ -166,32 +160,26 @@ def test_configure_mip_channels(sample_imu: MockLordMicrostrainIMU): channels = sample_imu._configure_mip_channels() assert len(channels) == 4 for i in range(0, 4): - assert all( - [channels[i].miptype in MockTypes, type(channels[i]) == MockMipChannel] - ) + assert all([channels[i].miptype in MockTypes, type(channels[i]) == MockMipChannel]) # Test start def test_start(sample_imu: MockLordMicrostrainIMU): - assert all( - [ - sample_imu._connection == None, - not hasattr(sample_imu, "_node"), - sample_imu._is_streaming == False, - ] - ) + assert all([ + sample_imu._connection == None, + not hasattr(sample_imu, "_node"), + sample_imu._is_streaming == False, + ]) sample_imu.start() - assert all( - [ - type(sample_imu._connection) == MockConnection, - type(sample_imu._node) == MockNode, - sample_imu._is_streaming == True, - sample_imu._node.type == "mocktype", - sample_imu._node.datastream == "mocktype", - ] - ) + assert all([ + type(sample_imu._connection) == MockConnection, + type(sample_imu._node) == MockNode, + sample_imu._is_streaming == True, + sample_imu._node.type == "mocktype", + sample_imu._node.datastream == "mocktype", + ]) # Test stop diff --git a/tests/test_sensors/test_loadcell.py b/tests/test_sensors/test_loadcell.py index 76d8a9ce..01e8b626 100644 --- a/tests/test_sensors/test_loadcell.py +++ b/tests/test_sensors/test_loadcell.py @@ -1,12 +1,8 @@ # Global Units Dictionary -import enum -import time -from dataclasses import dataclass import numpy as np import pytest from numpy import typing as npt -from smbus2 import SMBus from opensourceleg.sensors import loadcell @@ -17,7 +13,6 @@ def test_SRILoadcell_init(): - invalid_cal_matrix = np.ones(shape=(5, 6), dtype=np.double) with pytest.raises(TypeError): SRI = loadcell.SRILoadcell(calibration_matrix=invalid_cal_matrix) @@ -56,17 +51,13 @@ def test_SRILoadcell_init(): def test_SRILoadcell_reset(): - SRI = loadcell.SRILoadcell(calibration_matrix=DEFAULT_CAL_MATRIX) SRI._calibration_offset == np.ones(shape=(1, 6), dtype=np.double) SRI.reset() - assert np.array_equal( - SRI._calibration_offset, np.zeros(shape=(1, 6), dtype=np.double) - ) + assert np.array_equal(SRI._calibration_offset, np.zeros(shape=(1, 6), dtype=np.double)) def test_SRILoadcell_update(): - # Test basic call execution SRI = loadcell.SRILoadcell(calibration_matrix=DEFAULT_CAL_MATRIX) SRI.update(data_callback=_read_data) @@ -105,10 +96,7 @@ def _update_calculations(SRI: loadcell.SRILoadcell, calibration_offset: float): test_data = _read_data() signed_data = ((test_data - SRI.OFFSET) / SRI.ADC_RANGE) * SRI._exc coupled_data = signed_data * 1000 / (SRI._exc * SRI._amp_gain) - data = ( - np.transpose(a=SRI._calibration_matrix.dot(b=np.transpose(a=coupled_data))) - - calibration_offset - ) + data = np.transpose(a=SRI._calibration_matrix.dot(b=np.transpose(a=coupled_data))) - calibration_offset return data diff --git a/tests/test_time/test_time.py b/tests/test_time/test_time.py index 2da06317..1c3e0560 100644 --- a/tests/test_time/test_time.py +++ b/tests/test_time/test_time.py @@ -137,7 +137,6 @@ def test_softrealtimeloop_iter(patch_time_time2): def test_softrealtimeloop_fade_prop(patch_time_time3): - srtlf = SoftRealtimeLoop(fade=1.0) assert srtlf.fade == 1.0 srtlf.killer._kill_soon = True diff --git a/tests/test_units/test_units.py b/tests/test_units/test_units.py index fc2727f5..c7e930f3 100644 --- a/tests/test_units/test_units.py +++ b/tests/test_units/test_units.py @@ -1,6 +1,4 @@ # Global Units Dictionary -import enum -from dataclasses import dataclass import numpy as np import pytest @@ -54,7 +52,6 @@ def test_convert_to_from_default(): def test_force(): - assert force.N == 1.0 assert force.lbf == 4.4482216152605 assert force.kgf == 9.80665 @@ -62,7 +59,6 @@ def test_force(): def test_torque(): - assert torque.N_m == 1.0 assert torque.lbf_inch == 0.1129848290276167 assert torque.kgf_cm == 0.0980665 @@ -70,7 +66,6 @@ def test_torque(): def test_stiffness(): - assert stiffness.N_m_per_rad == 1.0 assert stiffness.N_m_per_deg == 0.017453292519943295 @@ -78,7 +73,6 @@ def test_stiffness(): def test_damping(): - assert damping.N_m_per_rad_per_s == 1.0 assert damping.N_m_per_deg_per_s == 0.017453292519943295 @@ -86,7 +80,6 @@ def test_damping(): def test_length(): - assert length.m == 1.0 assert length.cm == 0.01 assert length.inch == 0.0254 @@ -95,7 +88,6 @@ def test_length(): def test_position(): - assert position.rad == 1.0 assert position.deg == 0.017453292519943295 @@ -103,7 +95,6 @@ def test_position(): def test_mass(): - assert mass.kg == 1.0 assert mass.g == 0.001 assert mass.lb == 0.45359237 @@ -112,7 +103,6 @@ def test_mass(): def test_velocity(): - assert velocity.rad_per_s == 1.0 assert velocity.deg_per_s == 0.017453292519943295 assert velocity.rpm == 0.10471975511965977 @@ -121,7 +111,6 @@ def test_velocity(): def test_acceleration(): - assert acceleration.rad_per_s2 == 1.0 assert acceleration.deg_per_s2 == 0.017453292519943295 @@ -129,7 +118,6 @@ def test_acceleration(): def test_time(): - assert time.s == 1.0 assert time.ms == 0.001 @@ -137,7 +125,6 @@ def test_time(): def test_current(): - assert current.mA == 1 assert current.A == 1000 @@ -145,7 +132,6 @@ def test_current(): def test_voltage(): - assert voltage.mV == 1 assert voltage.V == 1000 diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..a44a21bd --- /dev/null +++ b/tox.ini @@ -0,0 +1,18 @@ +[tox] +skipsdist = true +envlist = py38, py39, py310, py311 + +[gh-actions] +python = + 3.8: py38 + 3.9: py39 + 3.10: py310 + 3.11: py311 + +[testenv] +passenv = PYTHON_VERSION +allowlist_externals = poetry +commands = + poetry install -v + pytest --doctest-modules tests --cov --cov-config=pyproject.toml --cov-report=xml + mypy diff --git a/tutorials/actuators/README.md b/tutorials/actuators/README.md index b945a090..d2fd82ac 100644 --- a/tutorials/actuators/README.md +++ b/tutorials/actuators/README.md @@ -1,13 +1,13 @@ # Reference - Performance Evaluation for `DephyActpack` and `moteus-r4` -## Torque / Current Control +## Torque / Current Control ![](./images/torque_comp.png) -## Position Control +## Position Control ![](./images/position_comp.png) ## Velocity (`moteus-r4` only) -![](./images/velocity_moteus.png) \ No newline at end of file +![](./images/velocity_moteus.png) diff --git a/tutorials/actuators/dephy/current_control.py b/tutorials/actuators/dephy/current_control.py index ebdefb15..8c308965 100644 --- a/tutorials/actuators/dephy/current_control.py +++ b/tutorials/actuators/dephy/current_control.py @@ -1,4 +1,4 @@ -ο»Ώimport time +import time import pandas as pd @@ -17,16 +17,13 @@ def main(): port="/dev/ttyACM0", gear_ratio=1.0, ) - current_data = pd.DataFrame( - { - "Time": [], - "Output_Current": [], - "Command_Current": [], - } - ) + current_data = pd.DataFrame({ + "Time": [], + "Output_Current": [], + "Command_Current": [], + }) clock = SoftRealtimeLoop(dt=DT) with actpack: - try: actpack.set_control_mode(mode=CONTROL_MODES.CURRENT) actpack.set_current_gains( @@ -34,7 +31,6 @@ def main(): ) for t in clock: - if t > TIME_TO_STEP: command_current = 275 actpack.set_motor_current(value=command_current) # in mA @@ -54,13 +50,11 @@ def main(): current_data = pd.concat( [ current_data, - pd.DataFrame( - { - "Time": [t], - "Output_Current": [actpack.motor_current], - "Command_Current": [command_current], - } - ), + pd.DataFrame({ + "Time": [t], + "Output_Current": [actpack.motor_current], + "Command_Current": [command_current], + }), ], ignore_index=True, ) diff --git a/tutorials/actuators/dephy/impedance_control.py b/tutorials/actuators/dephy/impedance_control.py index 3e3fb0b9..19b30d84 100644 --- a/tutorials/actuators/dephy/impedance_control.py +++ b/tutorials/actuators/dephy/impedance_control.py @@ -1,4 +1,4 @@ -ο»Ώimport time +import time import numpy as np diff --git a/tutorials/actuators/dephy/position_control.py b/tutorials/actuators/dephy/position_control.py index 6aa20bcb..97fbd415 100644 --- a/tutorials/actuators/dephy/position_control.py +++ b/tutorials/actuators/dephy/position_control.py @@ -1,4 +1,4 @@ -ο»Ώimport time +import time import numpy as np import pandas as pd @@ -18,13 +18,11 @@ def main(): gear_ratio=1.0, ) - position_data = pd.DataFrame( - { - "Time": [], - "Output_Position": [], - "Command_Position": [], - } - ) + position_data = pd.DataFrame({ + "Time": [], + "Output_Position": [], + "Command_Position": [], + }) clock = SoftRealtimeLoop(dt=DT) with actpack: try: @@ -39,7 +37,6 @@ def main(): current_position = actpack.output_position for t in clock: - if t > TIME_TO_STEP: command_position = current_position + np.pi actpack.set_output_position(value=command_position) @@ -59,13 +56,11 @@ def main(): position_data = pd.concat( [ position_data, - pd.DataFrame( - { - "Time": [t], - "Output_Position": [actpack.output_position], - "Command_Position": [command_position], - } - ), + pd.DataFrame({ + "Time": [t], + "Output_Position": [actpack.output_position], + "Command_Position": [command_position], + }), ], ignore_index=True, ) diff --git a/tutorials/actuators/dephy/sensor_reading.py b/tutorials/actuators/dephy/sensor_reading.py index c815300f..08d2bd47 100644 --- a/tutorials/actuators/dephy/sensor_reading.py +++ b/tutorials/actuators/dephy/sensor_reading.py @@ -1,4 +1,3 @@ -ο»Ώimport sys import time import opensourceleg.actuators.dephy as Dephy diff --git a/tutorials/actuators/dephy/voltage_control.py b/tutorials/actuators/dephy/voltage_control.py index fea4ef84..8bef9fee 100644 --- a/tutorials/actuators/dephy/voltage_control.py +++ b/tutorials/actuators/dephy/voltage_control.py @@ -1,4 +1,4 @@ -ο»Ώ# import time +# import time # import sys # from opensourceleg.actuators.base import CONTROL_MODES # import opensourceleg.actuators.dephy as Dephy @@ -45,21 +45,17 @@ def main(): port="/dev/ttyACM0", gear_ratio=1.0, ) - voltage_data = pd.DataFrame( - { - "Time": [], - "Output_Voltage": [], - "Command_Voltage": [], - } - ) + voltage_data = pd.DataFrame({ + "Time": [], + "Output_Voltage": [], + "Command_Voltage": [], + }) clock = SoftRealtimeLoop(dt=DT) with actpack: - try: actpack.set_control_mode(mode=CONTROL_MODES.VOLTAGE) for t in clock: - if t > TIME_TO_STEP: command_voltage = 3000 actpack.set_motor_voltage(value=command_voltage) # in mV @@ -79,13 +75,11 @@ def main(): voltage_data = pd.concat( [ voltage_data, - pd.DataFrame( - { - "Time": [t], - "Output_Voltage": [actpack.motor_voltage], - "Command_Voltage": [command_voltage], - } - ), + pd.DataFrame({ + "Time": [t], + "Output_Voltage": [actpack.motor_voltage], + "Command_Voltage": [command_voltage], + }), ], ignore_index=True, ) diff --git a/tutorials/actuators/moteus/position_control.py b/tutorials/actuators/moteus/position_control.py index 718848e4..be638fdc 100644 --- a/tutorials/actuators/moteus/position_control.py +++ b/tutorials/actuators/moteus/position_control.py @@ -1,8 +1,7 @@ -ο»Ώimport asyncio +import asyncio import numpy as np import pandas as pd -from moteus import Register from opensourceleg.actuators.moteus import MoteusActuator from opensourceleg.logging.logger import LOGGER @@ -22,13 +21,11 @@ async def main(): gear_ratio=9.0, ) - position_data = pd.DataFrame( - { - "Time": [], - "Output_Position": [], - "Command_Position": [], - } - ) + position_data = pd.DataFrame({ + "Time": [], + "Output_Position": [], + "Command_Position": [], + }) clock = SoftRealtimeLoop(dt=DT) @@ -44,7 +41,6 @@ async def main(): await mc1.update() for t in clock: - # current_time = time.monotonic() if t > TIME_TO_STEP: @@ -56,29 +52,22 @@ async def main(): else: command_position = position - print(f"######") - LOGGER.info( - "".join( - f"Motor Position: {mc1.output_position}\t" - + f"Command_Position: {command_position}\t" - ) - ) + print("######") + LOGGER.info("".join(f"Motor Position: {mc1.output_position}\t" + f"Command_Position: {command_position}\t")) position_data = pd.concat( [ position_data, - pd.DataFrame( - { - "Time": [t], - "Output_Position": [mc1.output_position], - "Command_Position": [command_position], - } - ), + pd.DataFrame({ + "Time": [t], + "Output_Position": [mc1.output_position], + "Command_Position": [command_position], + }), ], ignore_index=True, ) - print(f"------") + print("------") await asyncio.sleep(DT) finally: diff --git a/tutorials/actuators/moteus/sensor_readings.py b/tutorials/actuators/moteus/sensor_readings.py index 8fd63723..c2c3b5c2 100644 --- a/tutorials/actuators/moteus/sensor_readings.py +++ b/tutorials/actuators/moteus/sensor_readings.py @@ -1,12 +1,9 @@ -ο»Ώimport asyncio -import math +import asyncio -import numpy as np import pandas as pd from moteus import Register from opensourceleg.actuators.moteus import MoteusActuator -from opensourceleg.logging.logger import LOGGER async def main(): @@ -14,13 +11,11 @@ async def main(): servo_id=42, bus_id=3, ) - current_data = pd.DataFrame( - { - "Time": [], - "Output_Current": [], - "Command_Current": [], - } - ) + current_data = pd.DataFrame({ + "Time": [], + "Output_Current": [], + "Command_Current": [], + }) try: await mc1.start() iter = 0 @@ -28,22 +23,18 @@ async def main(): while True: iter += 1 await mc1.update() - print(f"######") + print("######") print(f"{mc1.case_temperature}") - print(f"------") + print("------") current_data = pd.concat( [ current_data, - pd.DataFrame( - { - "Time": [iter * time_period], - "Output_Current": [mc1._data[0].values[Register.Q_CURRENT]], - "Command_Current": [ - mc1._data[0].values[Register.COMMAND_Q_CURRENT] - ], - } - ), + pd.DataFrame({ + "Time": [iter * time_period], + "Output_Current": [mc1._data[0].values[Register.Q_CURRENT]], + "Command_Current": [mc1._data[0].values[Register.COMMAND_Q_CURRENT]], + }), ], ignore_index=True, ) diff --git a/tutorials/actuators/moteus/torque_control.py b/tutorials/actuators/moteus/torque_control.py index d81cc67d..90e3cd88 100644 --- a/tutorials/actuators/moteus/torque_control.py +++ b/tutorials/actuators/moteus/torque_control.py @@ -1,7 +1,5 @@ -ο»Ώimport asyncio -import math +import asyncio -import numpy as np import pandas as pd from moteus import Register @@ -22,13 +20,11 @@ async def main(): bus_id=3, gear_ratio=9.0, ) - torque_data = pd.DataFrame( - { - "Time": [], - "Output_Torque": [], - "Command_Torque": [], - } - ) + torque_data = pd.DataFrame({ + "Time": [], + "Output_Torque": [], + "Command_Torque": [], + }) clock = SoftRealtimeLoop(dt=DT) @@ -44,7 +40,6 @@ async def main(): await mc1.update() for t in clock: - # current_time = time.monotonic() if t > TIME_TO_STEP: mc1.set_motor_torque( @@ -52,29 +47,18 @@ async def main(): ) await mc1.update() - print(f"######") - LOGGER.info( - "".join( - f"Output Torque: {mc1._data[0].values[Register.TORQUE] * mc1.gear_ratio}\t" - ) - ) + print("######") + LOGGER.info("".join(f"Output Torque: {mc1._data[0].values[Register.TORQUE] * mc1.gear_ratio}\t")) - print(f"------") + print("------") torque_data = pd.concat( [ torque_data, - pd.DataFrame( - { - "Time": [t], - "Output_Torque": [ - mc1._data[0].values[Register.TORQUE] * mc1.gear_ratio - ], - "Command_Torque": [ - mc1._data[0].values[Register.COMMAND_FEEDFORWARD_TORQUE] - * mc1.gear_ratio - ], - } - ), + pd.DataFrame({ + "Time": [t], + "Output_Torque": [mc1._data[0].values[Register.TORQUE] * mc1.gear_ratio], + "Command_Torque": [mc1._data[0].values[Register.COMMAND_FEEDFORWARD_TORQUE] * mc1.gear_ratio], + }), ], ignore_index=True, ) diff --git a/tutorials/actuators/moteus/velocity_control.py b/tutorials/actuators/moteus/velocity_control.py index 69d140ec..2697ea1c 100644 --- a/tutorials/actuators/moteus/velocity_control.py +++ b/tutorials/actuators/moteus/velocity_control.py @@ -1,4 +1,4 @@ -ο»Ώimport asyncio +import asyncio import numpy as np import pandas as pd @@ -23,13 +23,11 @@ async def main(): bus_id=3, gear_ratio=9.0, ) - velocity_data = pd.DataFrame( - { - "Time": [], - "Output_Velocity": [], - "Command_Velocity": [], - } - ) + velocity_data = pd.DataFrame({ + "Time": [], + "Output_Velocity": [], + "Command_Velocity": [], + }) clock = SoftRealtimeLoop(dt=DT) try: @@ -44,17 +42,15 @@ async def main(): await mc1.update() for t in clock: - # current_time = time.monotonic() if t > TIME_TO_STEP: - mc1.set_motor_velocity( value=np.pi * 2, ) await mc1.update() - print(f"######") + print("######") LOGGER.info( "".join( f"Motor Velocity: {mc1.motor_velocity}\t" @@ -64,23 +60,18 @@ async def main(): velocity_data = pd.concat( [ velocity_data, - pd.DataFrame( - { - "Time": [t], - "Output_Velocity": [mc1.motor_velocity], - "Command_Velocity": [ - mc1._data[0].values[Register.COMMAND_VELOCITY] - * 2 - * np.pi - / mc1.gear_ratio - ], - } - ), + pd.DataFrame({ + "Time": [t], + "Output_Velocity": [mc1.motor_velocity], + "Command_Velocity": [ + mc1._data[0].values[Register.COMMAND_VELOCITY] * 2 * np.pi / mc1.gear_ratio + ], + }), ], ignore_index=True, ) - print(f"------") + print("------") await asyncio.sleep(DT) finally: diff --git a/tutorials/compiled_control/compiled_control.py b/tutorials/compiled_control/compiled_control.py index e8a362c1..5990f3a0 100644 --- a/tutorials/compiled_control/compiled_control.py +++ b/tutorials/compiled_control/compiled_control.py @@ -18,9 +18,7 @@ ("z", my_linalg.types.c_double), ], ) -my_linalg.define_inputs( - [("vector1", my_linalg.types.Vector3D), ("vector2", my_linalg.types.Vector3D)] -) +my_linalg.define_inputs([("vector1", my_linalg.types.Vector3D), ("vector2", my_linalg.types.Vector3D)]) my_linalg.define_outputs([("result", my_linalg.types.c_double)]) vector1 = my_linalg.types.Vector3D() diff --git a/tutorials/compiled_control/dot_product_3d.cpp b/tutorials/compiled_control/dot_product_3d.cpp index 13b08b4d..e9c42eb4 100644 --- a/tutorials/compiled_control/dot_product_3d.cpp +++ b/tutorials/compiled_control/dot_product_3d.cpp @@ -1,6 +1,6 @@ /** dot_product_3d.cpp An example cpp file to demonstrate the compiled controller functionality -in the opensourceleg library. +in the opensourceleg library. Kevin Best University of Michigan October 2023 From 108f5ae74bf87e56b9f193075c947ca0fdee8af4 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Wed, 13 Nov 2024 14:38:28 -0500 Subject: [PATCH 02/19] Switching from dataclasses to enums for units. --- examples/fsm_walking_compiled_controller.py | 82 +++++----- opensourceleg/control/compiled_controller.py | 4 +- opensourceleg/logging/logger.py | 2 +- opensourceleg/safety/safety.py | 4 +- opensourceleg/units/units.py | 151 ++++++------------ tests/test_units/test_units.py | 112 +++++-------- tutorials/actuators/moteus/sensor_readings.py | 6 +- .../compiled_control/compiled_control.py | 4 +- 8 files changed, 141 insertions(+), 224 deletions(-) diff --git a/examples/fsm_walking_compiled_controller.py b/examples/fsm_walking_compiled_controller.py index 6146f1ef..4c1ec6c0 100644 --- a/examples/fsm_walking_compiled_controller.py +++ b/examples/fsm_walking_compiled_controller.py @@ -58,7 +58,7 @@ sensors = [loadcell] -currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) # type: ignore +currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) controller = CompiledController( library_name="FSMController", library_path=currentdir, @@ -121,42 +121,42 @@ ]) # Populate Controller inputs as needed -controller.inputs.parameters.knee_impedance.early_stance.stiffness = 99.372 # type: ignore -controller.inputs.parameters.knee_impedance.early_stance.damping = 3.180 # type: ignore -controller.inputs.parameters.knee_impedance.early_stance.eq_angle = 5 # type: ignore -controller.inputs.parameters.knee_impedance.late_stance.stiffness = 99.372 # type: ignore -controller.inputs.parameters.knee_impedance.late_stance.damping = 1.272 # type: ignore -controller.inputs.parameters.knee_impedance.late_stance.eq_angle = 8 # type: ignore -controller.inputs.parameters.knee_impedance.early_swing.stiffness = 39.746 # type: ignore -controller.inputs.parameters.knee_impedance.early_swing.damping = 0.063 # type: ignore -controller.inputs.parameters.knee_impedance.early_swing.eq_angle = 60 # type: ignore -controller.inputs.parameters.knee_impedance.late_swing.stiffness = 15.899 # type: ignore -controller.inputs.parameters.knee_impedance.late_swing.damping = 3.186 # type: ignore -controller.inputs.parameters.knee_impedance.late_swing.eq_angle = 5 # type: ignore -controller.inputs.parameters.ankle_impedance.early_stance.stiffness = 19.874 # type: ignore -controller.inputs.parameters.ankle_impedance.early_stance.damping = 0 # type: ignore -controller.inputs.parameters.ankle_impedance.early_stance.eq_angle = -2 # type: ignore -controller.inputs.parameters.ankle_impedance.late_stance.stiffness = 79.498 # type: ignore -controller.inputs.parameters.ankle_impedance.late_stance.damping = 0.063 # type: ignore -controller.inputs.parameters.ankle_impedance.late_stance.eq_angle = -20 # type: ignore -controller.inputs.parameters.ankle_impedance.early_swing.stiffness = 7.949 # type: ignore -controller.inputs.parameters.ankle_impedance.early_swing.damping = 0 # type: ignore -controller.inputs.parameters.ankle_impedance.early_swing.eq_angle = 25 # type: ignore -controller.inputs.parameters.ankle_impedance.late_swing.stiffness = 7.949 # type: ignore -controller.inputs.parameters.ankle_impedance.late_swing.damping = 0.0 # type: ignore -controller.inputs.parameters.ankle_impedance.late_swing.eq_angle = 15 # type: ignore +controller.inputs.parameters.knee_impedance.early_stance.stiffness = 99.372 +controller.inputs.parameters.knee_impedance.early_stance.damping = 3.180 +controller.inputs.parameters.knee_impedance.early_stance.eq_angle = 5 +controller.inputs.parameters.knee_impedance.late_stance.stiffness = 99.372 +controller.inputs.parameters.knee_impedance.late_stance.damping = 1.272 +controller.inputs.parameters.knee_impedance.late_stance.eq_angle = 8 +controller.inputs.parameters.knee_impedance.early_swing.stiffness = 39.746 +controller.inputs.parameters.knee_impedance.early_swing.damping = 0.063 +controller.inputs.parameters.knee_impedance.early_swing.eq_angle = 60 +controller.inputs.parameters.knee_impedance.late_swing.stiffness = 15.899 +controller.inputs.parameters.knee_impedance.late_swing.damping = 3.186 +controller.inputs.parameters.knee_impedance.late_swing.eq_angle = 5 +controller.inputs.parameters.ankle_impedance.early_stance.stiffness = 19.874 +controller.inputs.parameters.ankle_impedance.early_stance.damping = 0 +controller.inputs.parameters.ankle_impedance.early_stance.eq_angle = -2 +controller.inputs.parameters.ankle_impedance.late_stance.stiffness = 79.498 +controller.inputs.parameters.ankle_impedance.late_stance.damping = 0.063 +controller.inputs.parameters.ankle_impedance.late_stance.eq_angle = -20 +controller.inputs.parameters.ankle_impedance.early_swing.stiffness = 7.949 +controller.inputs.parameters.ankle_impedance.early_swing.damping = 0 +controller.inputs.parameters.ankle_impedance.early_swing.eq_angle = 25 +controller.inputs.parameters.ankle_impedance.late_swing.stiffness = 7.949 +controller.inputs.parameters.ankle_impedance.late_swing.damping = 0.0 +controller.inputs.parameters.ankle_impedance.late_swing.eq_angle = 15 # Configure state machine body_weight = 82 # kg -controller.inputs.parameters.body_weight = body_weight # type: ignore -controller.inputs.parameters.transition_parameters.min_time_in_state = 0.20 # type: ignore -controller.inputs.parameters.transition_parameters.loadLStance = -body_weight * 0.25 # type: ignore -controller.inputs.parameters.transition_parameters.ankleThetaEstanceToLstance = 6.0 # type: ignore -controller.inputs.parameters.transition_parameters.loadESwing = -body_weight * 0.15 # type: ignore -controller.inputs.parameters.transition_parameters.kneeThetaESwingToLSwing = 50 # type: ignore -controller.inputs.parameters.transition_parameters.kneeDthetaESwingToLSwing = 3 # type: ignore -controller.inputs.parameters.transition_parameters.loadEStance = -body_weight * 0.4 # type: ignore -controller.inputs.parameters.transition_parameters.kneeThetaLSwingToEStance = 30 # type: ignore +controller.inputs.parameters.body_weight = body_weight +controller.inputs.parameters.transition_parameters.min_time_in_state = 0.20 +controller.inputs.parameters.transition_parameters.loadLStance = -body_weight * 0.25 +controller.inputs.parameters.transition_parameters.ankleThetaEstanceToLstance = 6.0 +controller.inputs.parameters.transition_parameters.loadESwing = -body_weight * 0.15 +controller.inputs.parameters.transition_parameters.kneeThetaESwingToLSwing = 50 +controller.inputs.parameters.transition_parameters.kneeDthetaESwingToLSwing = 3 +controller.inputs.parameters.transition_parameters.loadEStance = -body_weight * 0.4 +controller.inputs.parameters.transition_parameters.kneeThetaLSwingToEStance = 30 with knee, ankle, loadcell: knee.home() @@ -171,20 +171,18 @@ ankle.update() loadcell.update() - controller.inputs.sensors.knee_angle = ( # type: ignore - units.convert_from_default(knee.output_position, units.position.deg) - ) - controller.inputs.sensors.ankle_angle = units.convert_from_default(ankle.output_position, units.position.deg) # type: ignore + controller.inputs.sensors.knee_angle = units.convert_from_default(knee.output_position, units.position.deg) + controller.inputs.sensors.ankle_angle = units.convert_from_default(ankle.output_position, units.position.deg) controller.inputs.sensors.knee_velocity = units.convert_from_default( knee.output_velocity, units.velocity.deg_per_s - ) # type: ignore + ) controller.inputs.sensors.ankle_velocity = units.convert_from_default( ankle.output_velocity, units.velocity.deg_per_s - ) # type: ignore - controller.inputs.sensors.Fz = loadcell.fz # type: ignore + ) + controller.inputs.sensors.Fz = loadcell.fz # Update any control inputs that change every loop - controller.inputs.time = t # type: ignore + controller.inputs.time = t # Call the controller outputs = controller.run() diff --git a/opensourceleg/control/compiled_controller.py b/opensourceleg/control/compiled_controller.py index 1ec943a8..be375eab 100644 --- a/opensourceleg/control/compiled_controller.py +++ b/opensourceleg/control/compiled_controller.py @@ -95,7 +95,7 @@ def define_inputs(self, input_list: list[Any]) -> None: All types can be accessed as CompiledController.types.(type_name) """ self._input_type = self.define_type("inputs", input_list) - self.inputs = self._input_type() # type: ignore + self.inputs = self._input_type() def define_outputs(self, output_list: list[Any]) -> None: """ @@ -111,7 +111,7 @@ def define_outputs(self, output_list: list[Any]) -> None: All types can be accessed as CompiledController.types.(type_name) """ self._output_type = self.define_type("outputs", output_list) - self.outputs = self._output_type() # type: ignore + self.outputs = self._output_type() def define_type(self, type_name: str, parameter_list: list[Any]): """ diff --git a/opensourceleg/logging/logger.py b/opensourceleg/logging/logger.py index 1aaf621b..d736354f 100644 --- a/opensourceleg/logging/logger.py +++ b/opensourceleg/logging/logger.py @@ -196,7 +196,7 @@ def flush_buffer(self): def _write_header(self) -> None: header = list(self._var_names.values()) - self._writer.writerow(header) # type: ignore + self._writer.writerow(header) self._header_written = True def _generate_file_paths(self) -> None: diff --git a/opensourceleg/safety/safety.py b/opensourceleg/safety/safety.py index 28c53d41..6fb0243b 100644 --- a/opensourceleg/safety/safety.py +++ b/opensourceleg/safety/safety.py @@ -238,7 +238,7 @@ def wrapper(instance, *args, **kwargs): return decorator -def custom_criteria(criteria: Callable): # type: ignore +def custom_criteria(criteria: Callable): """ Creates a decorator to check if a property's value meets a custom criteria. The criteria is a function that takes the property's value as an argument and returns a boolean. @@ -285,7 +285,7 @@ class SafetyManager: def __init__(self): self._safe_objects: dict[object, dict[str, list[Callable]]] = {} - def add_safety(self, instance: object, attribute: str, decorator: Callable): # type: ignore + def add_safety(self, instance: object, attribute: str, decorator: Callable): """ Adds a safety decorator to the given object's attribute. The decorator will be applied to the property's getter. diff --git a/opensourceleg/units/units.py b/opensourceleg/units/units.py index a22590ed..ea557b47 100644 --- a/opensourceleg/units/units.py +++ b/opensourceleg/units/units.py @@ -1,5 +1,5 @@ # Global Units Dictionary -from dataclasses import dataclass +from enum import Enum """ Global Units Dictionary @@ -15,117 +15,64 @@ """ -@dataclass -class force: - N = 1.0 - lbf = 4.4482216152605 - kgf = 9.80665 +class Force(float, Enum): + N: 1.0 + lbf: 4.4482216152605 + kgf: 9.80665 - def __repr__(self) -> str: - return "force" +class Torque(float, Enum): + N_m: 1.0 + lbf_inch: 0.1129848290276167 + kgf_cm: 0.0980665 -@dataclass -class torque: - N_m = 1.0 - lbf_inch = 0.1129848290276167 - kgf_cm = 0.0980665 - def __repr__(self) -> str: - return "torque" +class Stiffness(float, Enum): + N_m_per_rad: 1.0 + N_m_per_deg: 0.017453292519943295 -@dataclass -class stiffness: - N_m_per_rad = 1.0 - N_m_per_deg = 0.017453292519943295 +class Damping(float, Enum): + N_m_per_rad_per_s: 1.0 + N_m_per_deg_per_s: 0.017453292519943295 - def __repr__(self) -> str: - return "stiffness" +class Length(float, Enum): + m: 1.0 + cm: 0.01 + inch: 0.0254 -@dataclass -class damping: - N_m_per_rad_per_s = 1.0 - N_m_per_deg_per_s = 0.017453292519943295 - def __repr__(self) -> str: - return "damping" +class Position(float, Enum): + rad: 1.0 + deg: 0.017453292519943295 -@dataclass -class length: - m = 1.0 - cm = 0.01 - inch = 0.0254 +class Mass(float, Enum): + kg: 1.0 + g: 0.001 + lb: 0.45359237 - def __repr__(self) -> str: - return "length" +class Velocity(float, Enum): + rad_per_s: 1.0 + deg_per_s: 0.017453292519943295 + rpm: 0.10471975511965977 -@dataclass -class position: - rad = 1.0 - deg = 0.017453292519943295 - def __repr__(self) -> str: - return "position" +class Acceleration(float, Enum): + rad_per_s2: 1.0 + deg_per_s2: 0.017453292519943295 -@dataclass -class mass: - kg = 1.0 - g = 0.001 - lb = 0.45359237 +class Current(float, Enum): + mA: 1 + A: 1000 - def __repr__(self) -> str: - return "mass" - -@dataclass -class velocity: - rad_per_s = 1.0 - deg_per_s = 0.017453292519943295 - rpm = 0.10471975511965977 - - def __repr__(self) -> str: - return "velocity" - - -@dataclass -class acceleration: - rad_per_s2 = 1.0 - deg_per_s2 = 0.017453292519943295 - - def __repr__(self) -> str: - return "acceleration" - - -@dataclass -class time: - s = 1.0 - ms = 0.001 - - def __repr__(self) -> str: - return "time" - - -@dataclass -class current: - mA = 1 - A = 1000 - - def __repr__(self) -> str: - return "current" - - -@dataclass -class voltage: - mV = 1 - V = 1000 - - def __repr__(self) -> str: - return "voltage" +class Voltage(float, Enum): + mV: 1 + V: 1000 def convert_to_default(value: float, from_unit: float) -> float: @@ -140,10 +87,10 @@ def convert_to_default(value: float, from_unit: float) -> float: float: Converted value in default units. Example: - >>> convert_to_default(2, units.current.A) + >>> convert_to_default(2, units.Current.A) 2000 # returns value in mA, which is the default unit for current - >>> convert_to_default(10, units.voltage.V) + >>> convert_to_default(10, units.Voltage.V) 10000 # returns value in mV, which is the default unit for voltage >>> convert_to_default(45, units.position.deg) @@ -165,10 +112,10 @@ def convert_from_default(value: float, to_unit: float) -> float: Example: - >>> convert_from_default(2000, units.current.A) + >>> convert_from_default(2000, units.Current.A) 2.0 # A - >>> convert_from_default(10000, units.voltage.V) + >>> convert_from_default(10000, units.Voltage.V) 10.0 # V >>> convert_from_default(0.7853981633974483, units.position.deg) @@ -179,9 +126,9 @@ def convert_from_default(value: float, to_unit: float) -> float: if __name__ == "__main__": - print(convert_to_default(2, current.A)) - print(convert_to_default(10, voltage.V)) - print(convert_to_default(45, position.deg)) - print(convert_from_default(2000, current.A)) - print(convert_from_default(10000, voltage.V)) - print(convert_from_default(0.7853981633974483, position.deg)) + print(convert_to_default(2, Current.A)) + print(convert_to_default(10, Voltage.V)) + print(convert_to_default(45, Position.deg)) + print(convert_from_default(2000, Current.A)) + print(convert_from_default(10000, Voltage.V)) + print(convert_from_default(0.7853981633974483, Position.deg)) diff --git a/tests/test_units/test_units.py b/tests/test_units/test_units.py index c7e930f3..7e37176a 100644 --- a/tests/test_units/test_units.py +++ b/tests/test_units/test_units.py @@ -12,28 +12,27 @@ def test_convert_to_from_default(): # Testing behaviour when invalid input type is passed to convert_to_default & convert_from_default with pytest.raises(TypeError): - convert_to_default("TEST", force.kgf) + convert_to_default("TEST", Force.kgf) with pytest.raises(TypeError): - convert_to_default("T", force.kgf) + convert_to_default("T", Force.kgf) with pytest.raises(TypeError): - convert_from_default("TEST", force.N) + convert_from_default("TEST", Force.N) with pytest.raises(TypeError): - convert_from_default("T", force.N) + convert_from_default("T", Force.N) # Testing behaviour on all possible unit conversions to default & from default categories = [ - force, - torque, - stiffness, - damping, - length, - position, - mass, - velocity, - acceleration, - time, - current, - voltage, + Force, + Torque, + Stiffness, + Damping, + Length, + Position, + Mass, + Velocity, + Acceleration, + Current, + Voltage, ] units = [] # Add all units to array @@ -52,87 +51,60 @@ def test_convert_to_from_default(): def test_force(): - assert force.N == 1.0 - assert force.lbf == 4.4482216152605 - assert force.kgf == 9.80665 - # assert repr(force) == "force" + assert Force.N == 1.0 + assert Force.lbf == 4.4482216152605 + assert Force.kgf == 9.80665 def test_torque(): - assert torque.N_m == 1.0 - assert torque.lbf_inch == 0.1129848290276167 - assert torque.kgf_cm == 0.0980665 - # assert repr(torque) == "torque" + assert Torque.N_m == 1.0 + assert Torque.lbf_inch == 0.1129848290276167 + assert Torque.kgf_cm == 0.0980665 def test_stiffness(): - assert stiffness.N_m_per_rad == 1.0 - assert stiffness.N_m_per_deg == 0.017453292519943295 - - # assert repr(stiffness) == "stiffness" + assert Stiffness.N_m_per_rad == 1.0 + assert Stiffness.N_m_per_deg == 0.017453292519943295 def test_damping(): - assert damping.N_m_per_rad_per_s == 1.0 - assert damping.N_m_per_deg_per_s == 0.017453292519943295 - - # assert repr(damping) == "damping" + assert Damping.N_m_per_rad_per_s == 1.0 + assert Damping.N_m_per_deg_per_s == 0.017453292519943295 def test_length(): - assert length.m == 1.0 - assert length.cm == 0.01 - assert length.inch == 0.0254 - - # assert repr(length) == "length" + assert Length.m == 1.0 + assert Length.cm == 0.01 + assert Length.inch == 0.0254 def test_position(): - assert position.rad == 1.0 - assert position.deg == 0.017453292519943295 - - # assert repr(position) == "position" + assert Position.rad == 1.0 + assert Position.deg == 0.017453292519943295 def test_mass(): - assert mass.kg == 1.0 - assert mass.g == 0.001 - assert mass.lb == 0.45359237 - - # assert repr(mass) == "mass" + assert Mass.kg == 1.0 + assert Mass.g == 0.001 + assert Mass.lb == 0.45359237 def test_velocity(): - assert velocity.rad_per_s == 1.0 - assert velocity.deg_per_s == 0.017453292519943295 - assert velocity.rpm == 0.10471975511965977 - - # assert repr(velocity) == "velocity" + assert Velocity.rad_per_s == 1.0 + assert Velocity.deg_per_s == 0.017453292519943295 + assert Velocity.rpm == 0.10471975511965977 def test_acceleration(): - assert acceleration.rad_per_s2 == 1.0 - assert acceleration.deg_per_s2 == 0.017453292519943295 - - # assert repr(acceleration) == "acceleration" - - -def test_time(): - assert time.s == 1.0 - assert time.ms == 0.001 - - # assert repr(time) == "time" + assert Acceleration.rad_per_s2 == 1.0 + assert Acceleration.deg_per_s2 == 0.017453292519943295 def test_current(): - assert current.mA == 1 - assert current.A == 1000 - - # assert repr(current) == "current" + assert Current.mA == 1 + assert Current.A == 1000 def test_voltage(): - assert voltage.mV == 1 - assert voltage.V == 1000 - - # assert repr(voltage) == "voltage" + assert Voltage.mV == 1 + assert Voltage.V == 1000 diff --git a/tutorials/actuators/moteus/sensor_readings.py b/tutorials/actuators/moteus/sensor_readings.py index c2c3b5c2..8e7a3c4d 100644 --- a/tutorials/actuators/moteus/sensor_readings.py +++ b/tutorials/actuators/moteus/sensor_readings.py @@ -18,10 +18,10 @@ async def main(): }) try: await mc1.start() - iter = 0 + iterator = 0 time_period = 0.001 while True: - iter += 1 + iterator += 1 await mc1.update() print("######") print(f"{mc1.case_temperature}") @@ -31,7 +31,7 @@ async def main(): [ current_data, pd.DataFrame({ - "Time": [iter * time_period], + "Time": [iterator * time_period], "Output_Current": [mc1._data[0].values[Register.Q_CURRENT]], "Command_Current": [mc1._data[0].values[Register.COMMAND_Q_CURRENT]], }), diff --git a/tutorials/compiled_control/compiled_control.py b/tutorials/compiled_control/compiled_control.py index 5990f3a0..25edf689 100644 --- a/tutorials/compiled_control/compiled_control.py +++ b/tutorials/compiled_control/compiled_control.py @@ -31,8 +31,8 @@ vector2.y = 0.6716 vector2.z = -0.0460 -my_linalg.inputs.vector1 = vector1 # type: ignore -my_linalg.inputs.vector2 = vector2 # type: ignore +my_linalg.inputs.vector1 = vector1 +my_linalg.inputs.vector2 = vector2 outputs = my_linalg.run() From 6cfbcd9a1e3250af28ed14ac0370a476b4206f1b Mon Sep 17 00:00:00 2001 From: imsenthur Date: Wed, 13 Nov 2024 15:01:47 -0500 Subject: [PATCH 03/19] Commiting files without pre-commit to check workflows. --- .pre-commit-config.yaml | 2 +- opensourceleg/actuators/dephy.py | 3 +- opensourceleg/actuators/moteus.py | 2 +- opensourceleg/actuators/tmotor.py | 4 +- opensourceleg/control/compiled_controller.py | 6 +-- opensourceleg/control/state_machine.py | 21 +++------- opensourceleg/logging/logger.py | 9 ++--- opensourceleg/math/math.py | 11 +++-- opensourceleg/safety/safety.py | 8 ++-- opensourceleg/sensors/adc.py | 9 +++-- opensourceleg/sensors/loadcell.py | 6 +-- pyproject.toml | 5 +-- tests/test_actuators/test_actuators_base.py | 40 +++++++++---------- tests/test_logging/test_logging_logger.py | 9 ++--- tests/test_math/test_math.py | 18 ++++----- tests/test_safety/test_safety.py | 6 +-- tests/test_sensors/test_imu.py | 16 ++++---- tests/test_time/test_time.py | 42 ++++++++++---------- 18 files changed, 102 insertions(+), 115 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1f33da27..9f008c8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: rev: "v0.5.2" hooks: - id: ruff - args: [--exit-non-zero-on-fix] + args: [--exit-non-zero-on-fix, --unsafe-fixes] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-prettier diff --git a/opensourceleg/actuators/dephy.py b/opensourceleg/actuators/dephy.py index f0894d1b..c327e600 100644 --- a/opensourceleg/actuators/dephy.py +++ b/opensourceleg/actuators/dephy.py @@ -1,6 +1,7 @@ import os import time from ctypes import c_int +from typing import Optional import numpy as np from flexsea.device import Device @@ -230,7 +231,7 @@ def update(self) -> None: def home( self, homing_voltage: int = 2000, - homing_frequency: int = None, + homing_frequency: Optional[int] = None, homing_direction: int = -1, joint_direction: int = -1, joint_position_offset: float = 0.0, diff --git a/opensourceleg/actuators/moteus.py b/opensourceleg/actuators/moteus.py index 5eb1e15a..b29b80cf 100644 --- a/opensourceleg/actuators/moteus.py +++ b/opensourceleg/actuators/moteus.py @@ -136,7 +136,7 @@ def __repr__(self): return "MoteusInterface" def _add2map(self, servo_id, bus_id) -> None: - if bus_id in self.bus_map.keys(): + if bus_id in self.bus_map: self.bus_map[bus_id].append(servo_id) else: self.bus_map[bus_id] = [servo_id] diff --git a/opensourceleg/actuators/tmotor.py b/opensourceleg/actuators/tmotor.py index d4d9c4b9..cb956ebe 100644 --- a/opensourceleg/actuators/tmotor.py +++ b/opensourceleg/actuators/tmotor.py @@ -640,13 +640,13 @@ def check_can_connection(self) -> bool: ) Listener = can.BufferedReader() self._canman.notifier.add_listener(Listener) - for i in range(10): + for _i in range(10): self.power_on() time.sleep(0.001) success = True self._is_open = True time.sleep(0.1) - for i in range(10): + for _i in range(10): if Listener.get_message(timeout=0.1) is None: success = False self._is_open = False diff --git a/opensourceleg/control/compiled_controller.py b/opensourceleg/control/compiled_controller.py index be375eab..cc6991f2 100644 --- a/opensourceleg/control/compiled_controller.py +++ b/opensourceleg/control/compiled_controller.py @@ -45,7 +45,7 @@ def __init__( self.init_function = self._load_function(initialization_function_name) # Note if requested function name is None, returned handle is also none - if self.init_function != None: + if self.init_function is not None: self.init_function() # This alias makes defining types from top script easier without second import @@ -65,14 +65,14 @@ def __init__( self.outputs = None def __del__(self): - if self.cleanup_func != None: + if self.cleanup_func is not None: self.cleanup_func() def __repr__(self): return "CompiledController" def _load_function(self, function_name): - if function_name == None: + if function_name is None: return None else: try: diff --git a/opensourceleg/control/state_machine.py b/opensourceleg/control/state_machine.py index 9fbcf92f..3d420310 100644 --- a/opensourceleg/control/state_machine.py +++ b/opensourceleg/control/state_machine.py @@ -77,10 +77,7 @@ def __init__( self._exit_callbacks: list[Callable[[Any], None]] = [] def __eq__(self, __o) -> bool: - if __o.name == self._name: - return True - else: - return False + return __o.name == self._name def __ne__(self, __o) -> bool: return not self.__eq__(__o) @@ -284,10 +281,7 @@ def __init__(self, name) -> None: self._name = name def __eq__(self, __o) -> bool: - if __o.name == self._name: - return True - else: - return False + return __o.name == self._name def __ne__(self, __o) -> bool: return not self.__eq__(__o) # TODO: Check this fix @@ -310,7 +304,7 @@ def __init__( event: Event, source: State, destination: State, - callback: Callable[[Any], bool] = None, + callback: Optional[Callable[[Any], bool]] = None, ) -> None: self._event: Event = event self._source_state: State = source @@ -350,7 +344,7 @@ def __init__( event: Event, source: State, destination: State, - callback: Callable[[Any], bool] = None, + callback: Optional[Callable[[Any], bool]] = None, ) -> None: super().__init__(event=event, source=source, destination=destination, callback=callback) @@ -454,7 +448,7 @@ def add_transition( source: State, destination: State, event: Event, - callback: Callable[[Any], bool] = None, + callback: Optional[Callable[[Any], bool]] = None, ) -> Optional[Transition]: """ Add a transition to the state machine. @@ -518,10 +512,7 @@ def stop(self, data: Any = None) -> None: self._exited = True def is_on(self) -> bool: - if self._current_state and self._current_state != self._exit_state: - return True - else: - return False + return bool(self._current_state and self._current_state != self._exit_state) def spoof(self, spoof: bool) -> None: self._spoof = spoof diff --git a/opensourceleg/logging/logger.py b/opensourceleg/logging/logger.py index d736354f..25119556 100644 --- a/opensourceleg/logging/logger.py +++ b/opensourceleg/logging/logger.py @@ -167,7 +167,7 @@ def update(self) -> None: return data = [] - for var_id, get_value in self._tracked_vars.items(): + for _var_id, get_value in self._tracked_vars.items(): value = get_value() data.append(str(value)) @@ -204,10 +204,7 @@ def _generate_file_paths(self) -> None: timestamp = now.strftime("%Y%m%d_%H%M%S") script_name = os.path.basename(__file__).split(".")[0] - if self._user_file_name: - base_name = self._user_file_name - else: - base_name = f"{script_name}_{timestamp}" + base_name = self._user_file_name if self._user_file_name else f"{script_name}_{timestamp}" file_path = os.path.join(self._log_path, base_name) self._file_path = file_path + ".log" @@ -308,7 +305,7 @@ def update(self): my_logger.track_variable(lambda: y, "y") LOGGER.track_variable(lambda: test.a, "A") - for i in range(1000): + for _i in range(1000): x += 0.1 y = x**2 diff --git a/opensourceleg/math/math.py b/opensourceleg/math/math.py index 93ee678c..c0edfcd5 100644 --- a/opensourceleg/math/math.py +++ b/opensourceleg/math/math.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Optional import numpy as np @@ -47,13 +47,15 @@ class ThermalModel: def __init__( self, ambient: float = 21, - params: dict[Any, Any] = {}, + params: Optional[dict[Any, Any]] = None, temp_limit_windings: float = 115, soft_border_C_windings: float = 15, temp_limit_case: float = 80, soft_border_C_case: float = 5, ) -> None: # The following parameters result from Jack Schuchmann's test with no fans + if params is None: + params = {} self.C_w: float = 0.20 * 81.46202695970649 self.R_WC = 1.0702867186480716 self.C_c = 512.249065845453 @@ -220,10 +222,7 @@ def update(self, enable_ramp=False): Returns: value (float): Scalar between 0 and 1. """ - if enable_ramp: - delta = self.delta_per_update - else: - delta = -1 * self.delta_per_update + delta = self.delta_per_update if enable_ramp else -1 * self.delta_per_update self.value += delta self.value = min(max(self.value, 0), 1) diff --git a/opensourceleg/safety/safety.py b/opensourceleg/safety/safety.py index 6fb0243b..1e010487 100644 --- a/opensourceleg/safety/safety.py +++ b/opensourceleg/safety/safety.py @@ -1,6 +1,6 @@ from collections import deque from dataclasses import dataclass -from typing import Callable +from typing import Callable, Optional import numpy as np @@ -15,7 +15,7 @@ def is_changing( attribute_name: str, max_points: int = 10, threshold: float = 1e-6, - proxy_attribute_name: str = None, + proxy_attribute_name: Optional[str] = None, ): """ Creates a decorator to check if a property's value is changing. If the standard deviation of the last 'max_points' values is less than 'threshold', the decorator will raise an error or return a proxy attribute. @@ -306,8 +306,8 @@ def add_safety(self, instance: object, attribute: str, decorator: Callable): ) return - if instance in self._safe_objects.keys(): - if attribute in self._safe_objects[instance].keys(): + if instance in self._safe_objects: + if attribute in self._safe_objects[instance]: self._safe_objects[instance][attribute].append(decorator) else: self._safe_objects[instance][attribute] = [decorator] diff --git a/opensourceleg/sensors/adc.py b/opensourceleg/sensors/adc.py index eaaa0314..115c7442 100644 --- a/opensourceleg/sensors/adc.py +++ b/opensourceleg/sensors/adc.py @@ -1,5 +1,6 @@ import math from time import sleep +from typing import Optional import spidev @@ -57,7 +58,7 @@ def __init__( max_speed_hz: int = 8192000, channel_gains: list[int] = [32, 128] * 3, voltage_reference: float = 1.2, - gain_error: list[int] = [], + gain_error: Optional[list[int]] = None, ): """Initializes ADS131M0x class. @@ -75,6 +76,8 @@ def __init__( """ + if gain_error is None: + gain_error = [] if len(channel_gains) != num_channels: raise ValueError("Length of channel_gains does not equal number of channels") if gain_error != [] and len(gain_error) != num_channels: @@ -193,9 +196,9 @@ def _channel_enable(self, state: bool) -> None: Arg: - state(bool): sets whether or not the ADC is streaming """ - if state == True: + if state is True: self.write_register(self._CLOCK_REG, self._ENABLE_CHANNELS_CLOCK) - elif state == False: + elif state is False: self.write_register(self._CLOCK_REG, self._DISABLE_CHANNELS_CLOCK) def _set_device_state(self, state: int) -> None: diff --git a/opensourceleg/sensors/loadcell.py b/opensourceleg/sensors/loadcell.py index cc6ee4fb..f78507e5 100644 --- a/opensourceleg/sensors/loadcell.py +++ b/opensourceleg/sensors/loadcell.py @@ -1,6 +1,6 @@ import time from enum import Enum -from typing import Callable +from typing import Callable, Optional import numpy as np import numpy.typing as npt @@ -85,7 +85,7 @@ def reset(self): def update( self, calibration_offset: npt.NDArray[np.double] = None, - data_callback: Callable[..., npt.NDArray[np.uint8]] = None, + data_callback: Optional[Callable[..., npt.NDArray[np.uint8]]] = None, ) -> None: """ Queries the loadcell for the latest data. @@ -105,7 +105,7 @@ def calibrate( self, number_of_iterations: int = 2000, reset: bool = False, - data_callback: Callable[[], npt.NDArray[np.uint8]] = None, + data_callback: Optional[Callable[[], npt.NDArray[np.uint8]]] = None, ) -> None: """ Obtains the initial loadcell reading (aka) loadcell_zero. diff --git a/pyproject.toml b/pyproject.toml index 389569bb..c9fd21eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,6 @@ warn_unused_ignores = "True" show_error_codes = "True" - [tool.pytest.ini_options] testpaths = ["tests"] @@ -102,10 +101,10 @@ select = [ "TRY", ] ignore = [ - # LineTooLong - "E501", # DoNotAssignLambda "E731", + "TRY003", + "SIM115", ] [tool.ruff.format] diff --git a/tests/test_actuators/test_actuators_base.py b/tests/test_actuators/test_actuators_base.py index 69b0d21a..64ffe874 100644 --- a/tests/test_actuators/test_actuators_base.py +++ b/tests/test_actuators/test_actuators_base.py @@ -55,15 +55,15 @@ def test_motor_constants_init(non_zero_positive_values): ) def test_motor_constants_init_values(zero_values, non_zero_negative_values): with pytest.raises(ValueError): - motor_constants = create_motor_constants(zero_values) + create_motor_constants(zero_values) with pytest.raises(ValueError): - motor_constants = create_motor_constants(non_zero_negative_values) + create_motor_constants(non_zero_negative_values) def test_motor_constants_init_types(): with pytest.raises(TypeError): - motor_constants = MOTOR_CONSTANTS(1, 2) + MOTOR_CONSTANTS(1, 2) @pytest.mark.parametrize( @@ -138,7 +138,7 @@ def test_control_gains_init_partial(default_value): @pytest.mark.parametrize("default_value", DEFAULT_VALUES) def test_control_gains_init_invalid(default_value): with pytest.raises(TypeError): - control_gains = ControlGains(kt=default_value) + ControlGains(kt=default_value) # Python3.9 does not support @dataclass(kw_only=True) # with pytest.raises(TypeError): @@ -162,22 +162,22 @@ def test_control_mode_config_init_default(): entry_callback=lambda _: None, exit_callback=lambda _: None, ) - assert control_mode_config.has_gains == False - assert control_mode_config.max_gains == None + assert control_mode_config.has_gains is False + assert control_mode_config.max_gains is None def test_control_mode_config_init_invalid(): with pytest.raises(TypeError): - control_mode_config = ControlModeConfig() + ControlModeConfig() with pytest.raises(TypeError): - control_mode_config = ControlModeConfig( + ControlModeConfig( control_mode=CONTROL_MODES.POSITION, gains=ControlGains(), ) with pytest.raises(TypeError): - control_mode_config = ControlModeConfig( + ControlModeConfig( has_gains=True, ) @@ -206,13 +206,13 @@ def test_control_mode_config_init(): def test_control_mode_configs_init_default(): control_mode_configs = CONTROL_MODE_CONFIGS() - assert control_mode_configs.POSITION == None - assert control_mode_configs.CURRENT == None - assert control_mode_configs.VOLTAGE == None - assert control_mode_configs.IMPEDANCE == None - assert control_mode_configs.VELOCITY == None - assert control_mode_configs.TORQUE == None - assert control_mode_configs.IDLE == None + assert control_mode_configs.POSITION is None + assert control_mode_configs.CURRENT is None + assert control_mode_configs.VOLTAGE is None + assert control_mode_configs.IMPEDANCE is None + assert control_mode_configs.VELOCITY is None + assert control_mode_configs.TORQUE is None + assert control_mode_configs.IDLE is None def test_control_mode_configs_init(): @@ -235,14 +235,14 @@ def test_control_mode_configs_init(): assert default_control_mode_config == control_mode_configs.CURRENT assert default_control_mode_config == control_mode_configs.VOLTAGE assert default_control_mode_config == control_mode_configs.IMPEDANCE - assert control_mode_configs.VELOCITY == None - assert control_mode_configs.TORQUE == None - assert control_mode_configs.IDLE == None + assert control_mode_configs.VELOCITY is None + assert control_mode_configs.TORQUE is None + assert control_mode_configs.IDLE is None def test_control_mode_methods(): assert type(CONTROL_MODE_METHODS) == list - assert all([type(x) == str for x in CONTROL_MODE_METHODS]) + assert all(type(x) == str for x in CONTROL_MODE_METHODS) assert len(CONTROL_MODE_METHODS) >= 12 diff --git a/tests/test_logging/test_logging_logger.py b/tests/test_logging/test_logging_logger.py index e39d0998..eb8e2875 100644 --- a/tests/test_logging/test_logging_logger.py +++ b/tests/test_logging/test_logging_logger.py @@ -163,7 +163,7 @@ def test_set_file_name_str(test_logger: Logger): def test_set_file_name_none(test_logger: Logger): test_logger.set_file_name(None) assert all([ - test_logger._user_file_name == None, + test_logger._user_file_name is None, test_logger._file_path == "", test_logger._csv_path == "", ]) @@ -299,7 +299,7 @@ def test_write_header(test_logger: Logger): test_logger._writer = csv.writer(test_logger._file) test_logger._write_header() test_logger.close() - assert test_logger._header_written == True + assert test_logger._header_written is True # Ensure expected output was written file = open(test_logger._csv_path) @@ -373,10 +373,10 @@ def test_reset_header(test_logger: Logger): test_logger.track_variable(lambda: 2, "test") test_logger.update() test_logger.flush_buffer() - assert test_logger._header_written == True + assert test_logger._header_written is True test_logger.reset() - assert test_logger._header_written == False + assert test_logger._header_written is False # Test close @@ -487,7 +487,6 @@ def test_file_path(test_logger: Logger): test_logger._generate_file_paths = Mock() test_logger._file_path = "" - filename = test_logger.file_path test_logger._generate_file_paths.assert_called_once() test_logger._generate_file_paths = original_generate diff --git a/tests/test_math/test_math.py b/tests/test_math/test_math.py index 0f587e79..d041f2a1 100644 --- a/tests/test_math/test_math.py +++ b/tests/test_math/test_math.py @@ -5,22 +5,22 @@ def test_edge_detector_init(): edi = EdgeDetector(bool_in=False) - assert edi.cur_state == False - assert edi.rising_edge == False - assert edi.falling_edge == False + assert edi.cur_state is False + assert edi.rising_edge is False + assert edi.falling_edge is False def test_edge_detector_update(): edu = EdgeDetector(bool_in=False) edu.update(bool_in=True) - assert edu.rising_edge == True - assert edu.falling_edge == False - assert edu.cur_state == True + assert edu.rising_edge is True + assert edu.falling_edge is False + assert edu.cur_state is True edu2 = EdgeDetector(bool_in=True) edu2.update(bool_in=False) - assert edu2.rising_edge == False - assert edu2.falling_edge == True - assert edu2.cur_state == False + assert edu2.rising_edge is False + assert edu2.falling_edge is True + assert edu2.cur_state is False def test_saturating_ramp_init(): diff --git a/tests/test_safety/test_safety.py b/tests/test_safety/test_safety.py index 34d5d742..02804344 100644 --- a/tests/test_safety/test_safety.py +++ b/tests/test_safety/test_safety.py @@ -601,9 +601,7 @@ def test_for_type_error(): # Test def custom_criteria & decorator. Will return true if x > 2 def custom_func(x: float): - if x ^ 2 > 4: - return True - return False + return x ^ 2 > 4 def test_custom_criteria_def_in(): @@ -729,7 +727,7 @@ def test_start(): test_manager.start() with pytest.raises(ValueError, match="Value must be negative"): - test = samp.test + pass # Test update & safe objects diff --git a/tests/test_sensors/test_imu.py b/tests/test_sensors/test_imu.py index 8edbbcc6..4672ea78 100644 --- a/tests/test_sensors/test_imu.py +++ b/tests/test_sensors/test_imu.py @@ -136,8 +136,8 @@ def test_init_default(sample_imu: MockLordMicrostrainIMU): sample_imu._port == "/dev/ttyUSB0", sample_imu._baud_rate == 921600, sample_imu._frequency == 200, - sample_imu._is_streaming == False, - sample_imu._connection == None, + sample_imu._is_streaming is False, + sample_imu._connection is None, sample_imu._data == {}, type(sample_imu._data) == dict, ]) @@ -166,9 +166,9 @@ def test_configure_mip_channels(sample_imu: MockLordMicrostrainIMU): # Test start def test_start(sample_imu: MockLordMicrostrainIMU): assert all([ - sample_imu._connection == None, + sample_imu._connection is None, not hasattr(sample_imu, "_node"), - sample_imu._is_streaming == False, + sample_imu._is_streaming is False, ]) sample_imu.start() @@ -176,7 +176,7 @@ def test_start(sample_imu: MockLordMicrostrainIMU): assert all([ type(sample_imu._connection) == MockConnection, type(sample_imu._node) == MockNode, - sample_imu._is_streaming == True, + sample_imu._is_streaming is True, sample_imu._node.type == "mocktype", sample_imu._node.datastream == "mocktype", ]) @@ -194,11 +194,11 @@ def test_stop_not_started(sample_imu: MockLordMicrostrainIMU): def test_stop(sample_imu: MockLordMicrostrainIMU): sample_imu.start() - assert sample_imu._is_streaming == True + assert sample_imu._is_streaming is True sample_imu.stop() - assert all([sample_imu._is_streaming == False, sample_imu._node.idle == True]) + assert all([sample_imu._is_streaming is False, sample_imu._node.idle is True]) # Test ping @@ -285,7 +285,7 @@ def test_frequency(sample_imu: MockLordMicrostrainIMU): # Test is_streaming def test_is_streaming(sample_imu: MockLordMicrostrainIMU): sample_imu._is_streaming = True - assert sample_imu.is_streaming == True + assert sample_imu.is_streaming is True # Test roll diff --git a/tests/test_time/test_time.py b/tests/test_time/test_time.py index 1c3e0560..ca5ab2b4 100644 --- a/tests/test_time/test_time.py +++ b/tests/test_time/test_time.py @@ -15,12 +15,12 @@ def test_loopkiller_init(): lk = LoopKiller() assert lk._fade_time == 0.0 - assert lk._soft_kill_time == None + assert lk._soft_kill_time is None lk1 = LoopKiller(fade_time=1.0) assert lk1._fade_time == 1.0 - assert lk1._soft_kill_time == None - assert lk1._kill_now == False - assert lk1._kill_soon == False + assert lk1._soft_kill_time is None + assert lk1._kill_now is False + assert lk1._kill_soon is False def test_loopkiller_handle_signal(): @@ -32,9 +32,9 @@ def test_loopkiller_handle_signal(): """ lkhs = LoopKiller() - assert lkhs.kill_now == False + assert lkhs.kill_now is False lkhs.handle_signal(signum=None, frame=None) - assert lkhs.kill_now == True + assert lkhs.kill_now is True @pytest.fixture @@ -70,11 +70,11 @@ def test_loopkiller_kill_now_prop(patch_time_time2): lkknp = LoopKiller(fade_time=1.0) lkknp._kill_soon = True lkknp._soft_kill_time = 0.0 - assert lkknp.kill_now == False + assert lkknp.kill_now is False lkknp._soft_kill_time = 0.5 - assert lkknp.kill_now == False - assert lkknp.kill_now == True - assert lkknp.kill_now == True + assert lkknp.kill_now is False + assert lkknp.kill_now is True + assert lkknp.kill_now is True def test_loopkiller_kill_now_setter(patch_time_time2): @@ -83,19 +83,19 @@ def test_loopkiller_kill_now_setter(patch_time_time2): lkkns._kill_soon = True lkkns._soft_kill_time = 0.0 lkkns.kill_now = False - assert lkkns._kill_now == False - assert lkkns._kill_soon == False - assert lkkns._soft_kill_time == None + assert lkkns._kill_now is False + assert lkkns._kill_soon is False + assert lkkns._soft_kill_time is None lkkns.kill_now = True - assert lkkns._kill_soon == True + assert lkkns._kill_soon is True assert lkkns._soft_kill_time == 0.0 - assert lkkns._kill_now == False + assert lkkns._kill_now is False lkkns.kill_now = True - assert lkkns._kill_now == True + assert lkkns._kill_now is True lkkns.kill_now = False lkkns._fade_time = 0.0 lkkns.kill_now = True - assert lkkns._kill_now == True + assert lkkns._kill_now is True def test_softrealtimeloop_init(patch_time_time2): @@ -103,12 +103,12 @@ def test_softrealtimeloop_init(patch_time_time2): assert srtl.t0 == 0.0 assert srtl.t1 == 0.0 assert isinstance(srtl.killer, LoopKiller) - assert srtl.ttarg == None + assert srtl.ttarg is None assert srtl.sum_err == 0.0 assert srtl.sum_var == 0.0 assert srtl.sleep_t_agg == 0.0 assert srtl.n == 0 - assert srtl.report == False + assert srtl.report is False @pytest.fixture @@ -148,9 +148,9 @@ def test_softrealtimeloop_fade_prop(patch_time_time3): def test_softrealtimeloop_stop(patch_time_time2): srtls = SoftRealtimeLoop() srtls.killer._kill_soon = True - assert srtls.killer._kill_now == False + assert srtls.killer._kill_now is False srtls.stop() - assert srtls.killer.kill_now == True + assert srtls.killer.kill_now is True def test_softrealtimeloop_time(patch_time_time2): From 73d383b67744dfaff92d4e6c1b87a78d7ae8c8e3 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Wed, 13 Nov 2024 15:13:03 -0500 Subject: [PATCH 04/19] Removing old workflows. --- .github/.stale.yml | 17 --------- .github/ISSUE_TEMPLATE/bug_report.md | 42 ----------------------- .github/ISSUE_TEMPLATE/config.yml | 3 -- .github/ISSUE_TEMPLATE/feature_request.md | 23 ------------- .github/ISSUE_TEMPLATE/question.md | 25 -------------- .github/PULL_REQUEST_TEMPLATE.md | 28 --------------- .github/dependabot.yml | 35 ------------------- .gitignore | 2 ++ opensourceleg/actuators/__init__.py | 2 -- 9 files changed, 2 insertions(+), 175 deletions(-) delete mode 100644 .github/.stale.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/question.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/dependabot.yml diff --git a/.github/.stale.yml b/.github/.stale.yml deleted file mode 100644 index dc90e5a1..00000000 --- a/.github/.stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - security -# Label to use when marking an issue as stale -staleLabel: wontfix -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index d4ee3806..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: πŸ› Bug report -about: If something isn't working πŸ”§ -title: "" -labels: bug -assignees: ---- - -## πŸ› Bug Report - - - -## πŸ”¬ How To Reproduce - -Steps to reproduce the behavior: - -1. ... - -### Code sample - - - -### Environment - -- OS: [e.g. Linux / Windows / macOS] -- Python version, get it with: - -```bash -python --version -``` - -### Screenshots - - - -## πŸ“ˆ Expected behavior - - - -## πŸ“Ž Additional context - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 8f2da548..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Configuration: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository - -blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 7ddaee21..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: πŸš€ Feature request -about: Suggest an idea for this project πŸ– -title: "" -labels: enhancement -assignees: ---- - -## πŸš€ Feature Request - - - -## πŸ”ˆ Motivation - - - -## πŸ›° Alternatives - - - -## πŸ“Ž Additional context - - diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 2a7ea6b7..00000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: ❓ Question -about: Ask a question about this project πŸŽ“ -title: "" -labels: question -assignees: ---- - -## Checklist - - - -- [ ] I've searched the project's [`issues`](https://github.com/imsenthur/opensourceleg/issues?q=is%3Aissue). - -## ❓ Question - - - -How can I [...]? - -Is it possible to [...]? - -## πŸ“Ž Additional context - - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index ee09bfcf..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,28 +0,0 @@ -## Description - - - -## Related Issue - - - -## Type of Change - - - -- [ ] πŸ“š Examples / docs / tutorials / dependencies update -- [ ] πŸ”§ Bug fix (non-breaking change which fixes an issue) -- [ ] πŸ₯‚ Improvement (non-breaking change which improves an existing feature) -- [ ] πŸš€ New feature (non-breaking change which adds functionality) -- [ ] πŸ’₯ Breaking change (fix or feature that would cause existing functionality to change) -- [ ] πŸ” Security fix - -## Checklist - - - -- [ ] I've read the [`CODE_OF_CONDUCT.md`](https://github.com/imsenthur/opensourceleg/blob/master/CODE_OF_CONDUCT.md) document. -- [ ] I've read the [`CONTRIBUTING.md`](https://github.com/imsenthur/opensourceleg/blob/master/CONTRIBUTING.md) guide. -- [ ] I've updated the code style using `make codestyle`. -- [ ] I've written tests for all new methods and classes that I created. -- [ ] I've written the docstring in Google format for all the methods and classes that I used. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index a95f5e32..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Configuration: https://dependabot.com/docs/config-file/ -# Docs: https://docs.github.com/en/github/administering-a-repository/keeping-your-dependencies-updated-automatically - -version: 2 - -updates: - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "monthly" - allow: - - dependency-type: "all" - commit-message: - prefix: ":arrow_up:" - open-pull-requests-limit: 3 - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - allow: - - dependency-type: "all" - commit-message: - prefix: ":arrow_up:" - open-pull-requests-limit: 3 - - - package-ecosystem: "docker" - directory: "/docker" - schedule: - interval: "monthly" - allow: - - dependency-type: "all" - commit-message: - prefix: ":arrow_up:" - open-pull-requests-limit: 3 diff --git a/.gitignore b/.gitignore index f64e26aa..de874fc1 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +*.csv diff --git a/opensourceleg/actuators/__init__.py b/opensourceleg/actuators/__init__.py index a56df426..e21b3c14 100644 --- a/opensourceleg/actuators/__init__.py +++ b/opensourceleg/actuators/__init__.py @@ -1,5 +1,3 @@ from .base import * from .decorators import * from .dephy import * -from .moteus import * -from .tmotor import * From 7047a538c1d20b00251bd91c45e839fc0f0e4ed8 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Wed, 13 Nov 2024 15:19:54 -0500 Subject: [PATCH 05/19] Adding all feature branches to main workflow --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a3c723a..c9dedb70 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - "feature/**" pull_request: types: [opened, synchronize, reopened, ready_for_review] From 46e9f2139a67a95f7f3bd476c3afe3541996eea1 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Wed, 13 Nov 2024 15:32:17 -0500 Subject: [PATCH 06/19] Removed py3.8 from workflows and added docs-deploy. --- .github/workflows/main.yml | 15 ++++++++++++++- Makefile | 4 ++++ mkdocs.yml | 4 ++-- tox.ini | 3 +-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c9dedb70..9ed7ab89 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12"] fail-fast: false defaults: run: @@ -65,3 +65,16 @@ jobs: - name: Check if documentation can be built run: poetry run mkdocs build -s + + deploy-docs: + runs-on: ubuntu-latest + needs: check-docs + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up the environment + uses: ./.github/actions/setup-poetry-env + + - name: Deploy documentation + run: poetry run mkdocs gh-deploy diff --git a/Makefile b/Makefile index 2157cf71..1df79e5d 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,10 @@ docs-test: ## Test if documentation can be built without warnings or errors docs: ## Build and serve the documentation @poetry run mkdocs serve +.PHONY: docs-deploy +docs-deploy: ## Deploy the documentation to GitHub pages + @poetry run mkdocs gh-deploy + .PHONY: help help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/mkdocs.yml b/mkdocs.yml index c55c7d07..cbb96a6d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,10 +5,10 @@ site_description: An open-source software library for numerical computation, dat site_author: Open-Source Leg edit_uri: edit/main/docs/ repo_name: neurobionics/opensourceleg -copyright: Maintained by Florian. +copyright: Maintained by Senthur Ayyappan. nav: - - Home: index.md + - Overview: index.md - Modules: modules.md plugins: - search diff --git a/tox.ini b/tox.ini index a44a21bd..a3d6929e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,9 @@ [tox] skipsdist = true -envlist = py38, py39, py310, py311 +envlist = py39, py310, py311 [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 From 195da869cf5b78bc20c1d9cf70a3f2f109c45898 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 14 Nov 2024 15:29:55 -0500 Subject: [PATCH 07/19] Fixed some ruff errors. --- tests/test_logging/test_logging_exceptions.py | 19 +- tests/test_logging/test_logging_logger.py | 7 +- tests/test_math/test_math.py | 7 +- tests/test_robots/test_robots_base.py | 12 +- tests/test_robots/test_robots_osl.py | 20 +- tests/test_safety/test_safety.py | 218 +++++++++--------- tests/test_sensors/test_imu.py | 28 +-- tests/test_sensors/test_loadcell.py | 2 +- tests/test_sensors/test_sensors_base.py | 27 ++- tests/test_units/test_units.py | 16 +- .../actuators/moteus/velocity_control.py | 3 +- 11 files changed, 206 insertions(+), 153 deletions(-) diff --git a/tests/test_logging/test_logging_exceptions.py b/tests/test_logging/test_logging_exceptions.py index aea787a0..87ae6cde 100644 --- a/tests/test_logging/test_logging_exceptions.py +++ b/tests/test_logging/test_logging_exceptions.py @@ -1,6 +1,13 @@ import pytest -from opensourceleg.logging.exceptions import * +from opensourceleg.logging.exceptions import ( + ActuatorConnectionException, + ActuatorIsNoneException, + ActuatorKeyException, + ActuatorStreamException, + ControlModeException, + VoltageModeMissingException, +) # Test ActuatorStreamException @@ -24,9 +31,9 @@ def test_actuator_is_none_exception(): test_str = "test" with pytest.raises(ActuatorIsNoneException) as e: raise ActuatorIsNoneException(test_str) - assert ( - str(e.value) - == f"Actuator is None in {test_str} mode, please pass the actuator instance to the mode during initialization or set the actuator instance using set_actuator method." + assert str(e.value) == ( + f"Actuator is None in {test_str} mode, please pass the actuator instance to the mode during initialization " + "or set the actuator instance using set_actuator method." ) @@ -58,6 +65,6 @@ def test_actuator_key_exception(): with pytest.raises(ActuatorKeyException) as e: raise ActuatorKeyException(test_str, test_key) assert ( - str(e.value) - == f"{test_str} does not have {test_key} key in the actuators dictionary. Please check the actuators dictionary for the `{test_key}` key." + str(e.value) == f"{test_str} does not have {test_key} key in the actuators dictionary. " + f"Please check the actuators dictionary for the `{test_key}` key." ) diff --git a/tests/test_logging/test_logging_logger.py b/tests/test_logging/test_logging_logger.py index eb8e2875..e4a389f6 100644 --- a/tests/test_logging/test_logging_logger.py +++ b/tests/test_logging/test_logging_logger.py @@ -1,13 +1,16 @@ +import csv +import logging +from collections import deque from unittest.mock import Mock import pytest -from opensourceleg.logging.logger import * +from opensourceleg.logging.logger import LOGGER, Logger, LogLevel # Test LogLevel class def test_log_level_default(): - {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} <= {e.name for e in LogLevel} + assert {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} <= {e.name for e in LogLevel} def test_log_level_value_logging(): diff --git a/tests/test_math/test_math.py b/tests/test_math/test_math.py index d041f2a1..43f350b6 100644 --- a/tests/test_math/test_math.py +++ b/tests/test_math/test_math.py @@ -56,7 +56,7 @@ def test_saturating_ramp_update(): (test_model_default, "R_WC", 1.0702867186480716), (test_model_default, "C_c", 512.249065845453), (test_model_default, "R_CA", 1.9406620046327363), - (test_model_default, "Ξ±", 0.393 * 1 / 100), + (test_model_default, "Ξ±", 0.393 * 1 / 100), # noqa: RUF001 (test_model_default, "R_T_0", 65), (test_model_default, "T_w", 21), (test_model_default, "T_c", 21), @@ -71,7 +71,7 @@ def test_saturating_ramp_update(): (test_model_specified, "R_WC", 1.0702867186480716), (test_model_specified, "C_c", 512.249065845453), (test_model_specified, "R_CA", 1.9406620046327363), - (test_model_specified, "Ξ±", 0.393 * 1 / 100), + (test_model_specified, "Ξ±", 0.393 * 1 / 100), # noqa: RUF001 (test_model_specified, "R_T_0", 65), (test_model_specified, "T_w", 10), (test_model_specified, "T_c", 10), @@ -97,7 +97,8 @@ def test_update(): """ Tests the ThermalModel update method\n Calls the update method with no arguments and asserts the class variables are equal to the expected values. - Then calls the update method with a motor_current argument and asserts the class variables are equal to the expected values. + Then calls the update method with a motor_current argument and asserts the class variables + are equal to the expected values. This is repeated once more to test the update method with a motor_current argument. """ diff --git a/tests/test_robots/test_robots_base.py b/tests/test_robots/test_robots_base.py index cb48fb24..460c8340 100644 --- a/tests/test_robots/test_robots_base.py +++ b/tests/test_robots/test_robots_base.py @@ -3,7 +3,7 @@ import pytest from opensourceleg.logging import LOGGER -from opensourceleg.robots.base import * +from opensourceleg.robots.base import RobotBase from tests.test_actuators.test_actuators_base import MOTOR_CONSTANTS, MockActuator from tests.test_sensors.test_sensors_base import MockSensor @@ -64,8 +64,8 @@ def test_robot_base_init(): sample_robot.tag == tag_name, sample_robot.actuators == test_actuators, sample_robot.sensors == test_sensors, - type(sample_robot.actuators) == dict, - type(sample_robot.sensors) == dict, + type(sample_robot.actuators) is dict, + type(sample_robot.sensors) is dict, ]) @@ -88,7 +88,8 @@ def test_robot_base_exit(mock_robot: MockRobot): # Test start def test_robot_base_start(mock_robot: MockRobot): - # "pass" is implementation for start function in both Actuator and Sensor base classes so just testing if called for that part of function + # "pass" is implementation for start function in both Actuator and Sensor base classes + # so just testing if called for that part of function MockActuator.start = Mock() MockSensor.start = Mock() @@ -109,7 +110,8 @@ def test_robot_base_start(mock_robot: MockRobot): # Test stop def test_robot_base_stop(mock_robot: MockRobot): - # "pass" is implementation for stop function in both Actuator and Sensor base classes so just testing if called for that part of function + # "pass" is implementation for stop function in both Actuator and Sensor base classes + # so just testing if called for that part of function MockActuator.stop = Mock() MockSensor.stop = Mock() diff --git a/tests/test_robots/test_robots_osl.py b/tests/test_robots/test_robots_osl.py index 277db6eb..579c16b0 100644 --- a/tests/test_robots/test_robots_osl.py +++ b/tests/test_robots/test_robots_osl.py @@ -50,7 +50,7 @@ def test_knee(sample_osl: OpenSourceLeg): with pytest.raises(SystemExit) as e: sample_osl.knee() - assert e.type == SystemExit + assert e.type is SystemExit with open(LOGGER.file_path) as f: output = f.read() @@ -86,7 +86,7 @@ def test_ankle(sample_osl: OpenSourceLeg): with pytest.raises(SystemExit) as e: sample_osl.ankle() - assert e.type == SystemExit + assert e.type is SystemExit with open(LOGGER.file_path) as f: output = f.read() @@ -122,7 +122,7 @@ def test_loadcell(sample_osl: OpenSourceLeg): with pytest.raises(SystemExit) as e: sample_osl.loadcell() - assert e.type == SystemExit + assert e.type is SystemExit with open(LOGGER.file_path) as f: output = f.read() @@ -139,7 +139,10 @@ def test_loadcell(sample_osl: OpenSourceLeg): # Test joint encoder knee def test_joint_encoder_knee(sample_osl: OpenSourceLeg): # Test error case - expected_error = "ERROR: Knee joint encoder sensor not found. Please check for `joint_encoder_knee` key in the sensors dictionary.\n" + expected_error = ( + "ERROR: Knee joint encoder sensor not found. " + "Please check for `joint_encoder_knee` key in the sensors dictionary.\n" + ) with open(LOGGER.file_path) as f: output = f.read() @@ -147,7 +150,7 @@ def test_joint_encoder_knee(sample_osl: OpenSourceLeg): with pytest.raises(SystemExit) as e: sample_osl.joint_encoder_knee() - assert e.type == SystemExit + assert e.type is SystemExit with open(LOGGER.file_path) as f: output = f.read() @@ -164,7 +167,10 @@ def test_joint_encoder_knee(sample_osl: OpenSourceLeg): # Test joint encoder ankle def test_joint_encoder_ankle(sample_osl: OpenSourceLeg): # Test error case - expected_error = "ERROR: Ankle joint encoder sensor not found. Please check for `joint_encoder_ankle` key in the sensors dictionary.\n" + expected_error = ( + "ERROR: Ankle joint encoder sensor not found. " + "Please check for `joint_encoder_ankle` key in the sensors dictionary.\n" + ) with open(LOGGER.file_path) as f: output = f.read() @@ -172,7 +178,7 @@ def test_joint_encoder_ankle(sample_osl: OpenSourceLeg): with pytest.raises(SystemExit) as e: sample_osl.joint_encoder_ankle() - assert e.type == SystemExit + assert e.type is SystemExit with open(LOGGER.file_path) as f: output = f.read() diff --git a/tests/test_safety/test_safety.py b/tests/test_safety/test_safety.py index 02804344..7461b67f 100644 --- a/tests/test_safety/test_safety.py +++ b/tests/test_safety/test_safety.py @@ -3,7 +3,19 @@ import pytest -from opensourceleg.safety.safety import * +from opensourceleg.safety.safety import ( + SafetyDecorators, + SafetyManager, + ThermalLimitException, + custom_criteria, + is_changing, + is_greater_than, + is_less_than, + is_negative, + is_positive, + is_within_range, + is_zero, +) # Created class for testing purposes @@ -60,13 +72,12 @@ def test_is_changing_four_points_not_less_than_threshold(): max_points = 4 instance = Sample() - for x in range(6, 10): - - @is_changing(att, max_points) - def test_changing_point(instance): - return x + def test_changing_point(instance, x): + return x - assert test_changing_point(instance) == x + for x in range(6, 10): + wrapped_func = is_changing(att, max_points)(test_changing_point) + assert wrapped_func(instance, x) == x def test_is_changing_four_points_less_than_threshold(): @@ -76,13 +87,13 @@ def test_is_changing_four_points_less_than_threshold(): max_points = 4 test_threshold = 2 instance = Sample() - for x in range(6, 9): - @is_changing(att, max_points, test_threshold) - def test_changing_point(instance): - return x + def test_changing_point(instance, x): + return x - assert test_changing_point(instance) == x + for x in range(6, 9): + wrapped_func = is_changing(att, max_points, test_threshold)(test_changing_point) + assert wrapped_func(instance, x) == x @is_changing(att, max_points, test_threshold) def test_changing_point1(instance): @@ -101,13 +112,14 @@ def test_is_changing_four_points_less_than_threshold_with_proxy_att_name(): test_threshold = 2 proxy_att_name = "test_proxy_att_name" instance = Sample() - for x in range(6, 9): - @is_changing(att, max_points, test_threshold, proxy_att_name) - def test_changing_point(instance): - return x + @is_changing(att, max_points, test_threshold, proxy_att_name) + def test_changing_point(instance): + return x - assert test_changing_point(instance) == x + for x in range(6, 9): + wrapped_func = is_changing(att, max_points, test_threshold, proxy_att_name)(test_changing_point) + assert wrapped_func(instance) == x @is_changing(att, max_points, test_threshold, proxy_att_name) def test_changing_point1(instance): @@ -294,10 +306,10 @@ def test_for_type_error(): # Test def is_within_range & decorator def test_is_within_range_def_in(): # Test clamp default = False, with property value = 2 in range - min = 1 - max = 4 + value_min = 1 + value_max = 4 - @is_within_range(min, max) + @is_within_range(value_min, value_max) def test_within_range_with_default_clamp(instance: object): return 2 @@ -306,52 +318,52 @@ def test_within_range_with_default_clamp(instance: object): def test_is_within_range_def_out(): # Test clamp default = False, with property value = 5 out of range - min = 1 - max = 4 + value_min = 1 + value_max = 4 - @is_within_range(min, max) + @is_within_range(value_min, value_max) def test_out_of_range_with_default_clamp(instance: object): return 5 - with pytest.raises(ValueError, match=f"Value must be within {min} and {max}"): + with pytest.raises(ValueError, match=f"Value must be within {value_min} and {value_max}"): test_out_of_range_with_default_clamp({}) def test_is_within_range_false_out(): # Test clamp explicitly set to False, with property value = 0 out of range - min = 1 - max = 4 + value_min = 1 + value_max = 4 - @is_within_range(min, max, False) + @is_within_range(value_min, value_max, False) def test_out_of_range_with_false_clamp(instance: object): return 0 - with pytest.raises(ValueError, match=f"Value must be within {min} and {max}"): + with pytest.raises(ValueError, match=f"Value must be within {value_min} and {value_max}"): test_out_of_range_with_false_clamp({}) def test_is_within_range_true_smaller(): - # Test clamp explicitly set to True, with property value = -2 lower than min - min = 1 - max = 4 + # Test clamp explicitly set to True, with property value = -2 lower than value_min + value_min = 1 + value_max = 4 - @is_within_range(min, max, True) + @is_within_range(value_min, value_max, True) def test_lower_with_true_clamp(instance: object): return -2 - assert test_lower_with_true_clamp({}) == min + assert test_lower_with_true_clamp({}) == value_min def test_is_within_range_true_larger(): - # Test clamp explicitly set to True, with property value = 6 higher than max - min = 1 - max = 4 + # Test clamp explicitly set to True, with property value = 6 higher than value_max + value_min = 1 + value_max = 4 - @is_within_range(min, max, True) + @is_within_range(value_min, value_max, True) def test_higher_with_true_clamp(instance: object): return 5 - assert test_higher_with_true_clamp({}) == max + assert test_higher_with_true_clamp({}) == value_max def test_is_within_range_parameter(): @@ -368,10 +380,10 @@ def test_for_type_error(instance: object): def test_is_within_range_parameter2(): # Test without parameter instance in func def with pytest.raises(TypeError): - min = 1 - max = 4 + value_min = 1 + value_max = 4 - @is_within_range(min, max) + @is_within_range(value_min, value_max) def test_for_type_error(): return 0 @@ -380,11 +392,11 @@ def test_for_type_error(): def test_is_within_range_max_less_min(): # Test maximum value greater than minimum value for range - min = 4 - max = 1 + value_min = 4 + value_max = 1 with pytest.raises(ValueError, match="Maximum value must be greater than minimum value of range"): - @is_within_range(min, max) + @is_within_range(value_min, value_max) def test_for_min_greater_than_max(instance: object): return 0 @@ -393,10 +405,10 @@ def test_for_min_greater_than_max(instance: object): # Test def is_greater_than & decorator def test_is_greater_def_in(): - # Test clamp default = False, with value greater than min - min = 2 + # Test clamp default = False, with value greater than value_min + value_min = 2 - @is_greater_than(min) + @is_greater_than(value_min) def test_greater_with_default_clamp(instance: object): return 4 @@ -404,72 +416,72 @@ def test_greater_with_default_clamp(instance: object): def test_is_greater_def_out(): - # Test clamp default = False, with value less than min - min = 2 + # Test clamp default = False, with value less than value_min + value_min = 2 - @is_greater_than(min) + @is_greater_than(value_min) def test_less_with_default_clamp(instance: object): return 1 - with pytest.raises(ValueError, match=f"Value must be greater than {min}"): + with pytest.raises(ValueError, match=f"Value must be greater than {value_min}"): test_less_with_default_clamp({}) def test_is_greater_false_out(): - # Test clamp explicitly set to False, with value less than min - min = 2 + # Test clamp explicitly set to False, with value less than value_min + value_min = 2 - @is_greater_than(min, False) + @is_greater_than(value_min, False) def test_less_with_false_clamp(instance: object): return 1 - with pytest.raises(ValueError, match=f"Value must be greater than {min}"): + with pytest.raises(ValueError, match=f"Value must be greater than {value_min}"): test_less_with_false_clamp({}) def test_is_greater_true_out(): - # Test clamp explicitly set to True, with value less than min - min = 2 + # Test clamp explicitly set to True, with value less than value_min + value_min = 2 - @is_greater_than(min, True) + @is_greater_than(value_min, True) def test_less_with_true_clamp(instance: object): return -2 - assert test_less_with_true_clamp({}) == min + assert test_less_with_true_clamp({}) == value_min def test_is_greater_equality_false_equal(): - # Test clamp explicitly set to False, with value equal to min and equality set to True - min = 2 + # Test clamp explicitly set to False, with value equal to value_min and equality set to True + value_min = 2 - @is_greater_than(min, False, True) + @is_greater_than(value_min, False, True) def test_equal_with_false_clamp(instance: object): return 2 - assert test_equal_with_false_clamp({}) == min + assert test_equal_with_false_clamp({}) == value_min def test_is_greater_equality_false_out(): - # Test clamp explicitly set to False, with value less than min and equality set to True - min = 2 + # Test clamp explicitly set to False, with value less than value_min and equality set to True + value_min = 2 - @is_greater_than(min, False, True) + @is_greater_than(value_min, False, True) def test_less_with_false_clamp(instance: object): return 1 - with pytest.raises(ValueError, match=f"Value must be greater than or equal to {min}"): + with pytest.raises(ValueError, match=f"Value must be greater than or equal to {value_min}"): test_less_with_false_clamp({}) def test_is_greater_equality_true_out(): - # Test clamp explicitly set to True, with value less than min and equality set to True - min = 2 + # Test clamp explicitly set to True, with value less than value_min and equality set to True + value_min = 2 - @is_greater_than(min, True, True) + @is_greater_than(value_min, True, True) def test_less_with_true_clamp(instance: object): return 1 - assert test_less_with_true_clamp({}) == min + assert test_less_with_true_clamp({}) == value_min def test_is_greater_parameter(): @@ -486,9 +498,9 @@ def test_for_type_error(instance: object): def test_is_greater_parameter2(): # Test without parameter instance in func def with pytest.raises(TypeError): - min = 2 + value_min = 2 - @is_greater_than(min) + @is_greater_than(value_min) def test_for_type_error(): return 0 @@ -497,10 +509,10 @@ def test_for_type_error(): # Test def is_less_than & decorator def test_is_less_def_in(): - # Test clamp default = False, with value less than max - max = 4 + # Test clamp default = False, with value less than value_max + value_max = 4 - @is_less_than(max) + @is_less_than(value_max) def test_less_with_default_clamp(instance: object): return 2 @@ -508,72 +520,72 @@ def test_less_with_default_clamp(instance: object): def test_is_less_def_out(): - # Test clamp default = False, with value greater than max - max = 2 + # Test clamp default = False, with value greater than value_max + value_max = 2 - @is_less_than(max) + @is_less_than(value_max) def test_greater_with_default_clamp(instance: object): return 4 - with pytest.raises(ValueError, match=f"Value must be less than {max}"): + with pytest.raises(ValueError, match=f"Value must be less than {value_max}"): test_greater_with_default_clamp({}) def test_is_less_false_out(): - # Test clamp explicitly set to False, with value greater than max - max = 2 + # Test clamp explicitly set to False, with value greater than value_max + value_max = 2 - @is_less_than(max, False) + @is_less_than(value_max, False) def test_greater_with_false_clamp(instance: object): return 4 - with pytest.raises(ValueError, match=f"Value must be less than {max}"): + with pytest.raises(ValueError, match=f"Value must be less than {value_max}"): test_greater_with_false_clamp({}) def test_is_less_true_out(): - # Test clamp explicitly set to True, with value greater than max - max = 2 + # Test clamp explicitly set to True, with value greater than value_max + value_max = 2 - @is_less_than(max, True) + @is_less_than(value_max, True) def test_greater_with_true_clamp(instance: object): return 5 - assert test_greater_with_true_clamp({}) == max + assert test_greater_with_true_clamp({}) == value_max def test_is_less_equality_false_equal(): - # Test clamp explicitly set to False, with value equal to max and equality set to True - max = 2 + # Test clamp explicitly set to False, with value equal to value_max and equality set to True + value_max = 2 - @is_less_than(max, False, True) + @is_less_than(value_max, False, True) def test_equal_with_false_clamp(instance: object): return 2 - assert test_equal_with_false_clamp({}) == max + assert test_equal_with_false_clamp({}) == value_max def test_is_less_equality_false_out(): - # Test clamp explicitly set to False, with value greater than max and equality set to True - max = 2 + # Test clamp explicitly set to False, with value greater than value_max and equality set to True + value_max = 2 - @is_less_than(max, False, True) + @is_less_than(value_max, False, True) def test_less_with_false_clamp(instance: object): return 3 - with pytest.raises(ValueError, match=f"Value must be less than or equal to {max}"): + with pytest.raises(ValueError, match=f"Value must be less than or equal to {value_max}"): test_less_with_false_clamp({}) def test_is_less_equality_true_out(): - # Test clamp explicitly set to True, with value greater than max and equality set to True - max = 2 + # Test clamp explicitly set to True, with value greater than value_max and equality set to True + value_max = 2 - @is_less_than(max, True, True) + @is_less_than(value_max, True, True) def test_less_with_true_clamp(instance: object): return 3 - assert test_less_with_true_clamp({}) == max + assert test_less_with_true_clamp({}) == value_max def test_is_less_parameter(): @@ -590,9 +602,9 @@ def test_for_type_error(instance: object): def test_is_less_parameter2(): # Test without parameter instance in func def with pytest.raises(TypeError): - max = 2 + value_max = 2 - @is_less_than(max) + @is_less_than(value_max) def test_for_type_error(): return 0 @@ -665,7 +677,7 @@ def test_safetys_init_default(): # Test init def test_safety_manager_init(): test_manager = SafetyManager() - assert type(test_manager._safe_objects) == dict + assert isinstance(test_manager._safe_objects, dict) assert test_manager._safe_objects == {} diff --git a/tests/test_sensors/test_imu.py b/tests/test_sensors/test_imu.py index 4672ea78..3fa85069 100644 --- a/tests/test_sensors/test_imu.py +++ b/tests/test_sensors/test_imu.py @@ -1,8 +1,8 @@ import pytest from opensourceleg.logging import LOGGER -from opensourceleg.sensors.base import * -from opensourceleg.sensors.imu import * +from opensourceleg.sensors.base import SensorNotStreamingException +from opensourceleg.sensors.imu import LordMicrostrainIMU # Patched classes from mscl module @@ -139,7 +139,7 @@ def test_init_default(sample_imu: MockLordMicrostrainIMU): sample_imu._is_streaming is False, sample_imu._connection is None, sample_imu._data == {}, - type(sample_imu._data) == dict, + isinstance(sample_imu._data, dict), ]) @@ -160,7 +160,7 @@ def test_configure_mip_channels(sample_imu: MockLordMicrostrainIMU): channels = sample_imu._configure_mip_channels() assert len(channels) == 4 for i in range(0, 4): - assert all([channels[i].miptype in MockTypes, type(channels[i]) == MockMipChannel]) + assert all([channels[i].miptype in MockTypes, isinstance(channels[i], MockMipChannel)]) # Test start @@ -174,8 +174,8 @@ def test_start(sample_imu: MockLordMicrostrainIMU): sample_imu.start() assert all([ - type(sample_imu._connection) == MockConnection, - type(sample_imu._node) == MockNode, + isinstance(sample_imu._connection, MockConnection), + isinstance(sample_imu._node, MockNode), sample_imu._is_streaming is True, sample_imu._node.type == "mocktype", sample_imu._node.datastream == "mocktype", @@ -186,9 +186,9 @@ def test_start(sample_imu: MockLordMicrostrainIMU): def test_stop_not_started(sample_imu: MockLordMicrostrainIMU): with pytest.raises(SensorNotStreamingException) as e: sample_imu.stop() - assert ( - str(e.value) - == f"{sample_imu.__repr__()} is not streaming, please ensure that the connections are intact, power is on, and the start method is called." + assert str(e.value) == ( + f"{sample_imu.__repr__()} is not streaming, please ensure that the connections " + "are intact, power is on, and the start method is called." ) @@ -205,9 +205,9 @@ def test_stop(sample_imu: MockLordMicrostrainIMU): def test_ping_not_started(sample_imu: MockLordMicrostrainIMU): with pytest.raises(SensorNotStreamingException) as e: sample_imu.ping() - assert ( - str(e.value) - == f"{sample_imu.__repr__()} is not streaming, please ensure that the connections are intact, power is on, and the start method is called." + assert str(e.value) == ( + f"{sample_imu.__repr__()} is not streaming, please ensure that the connections " + "are intact, power is on, and the start method is called." ) @@ -248,12 +248,12 @@ def test_update(sample_imu: MockLordMicrostrainIMU): sample_imu.start() assert sample_imu._data == {} sample_imu.update(500, 2, False) - assert sample_imu._data == {"mockdata": 10, "mockdata": 20} + assert sample_imu._data == {"mockdata": [10, 20]} return_val = sample_imu.update(400, 3, True) assert len(return_val) == 3 for i in range(0, 3): - assert type(return_val[i]) == MockDataPockets + assert isinstance(return_val[i], MockDataPockets) # Test LordMicrostrainIMU repr diff --git a/tests/test_sensors/test_loadcell.py b/tests/test_sensors/test_loadcell.py index 01e8b626..3d963f2f 100644 --- a/tests/test_sensors/test_loadcell.py +++ b/tests/test_sensors/test_loadcell.py @@ -52,7 +52,7 @@ def test_SRILoadcell_init(): def test_SRILoadcell_reset(): SRI = loadcell.SRILoadcell(calibration_matrix=DEFAULT_CAL_MATRIX) - SRI._calibration_offset == np.ones(shape=(1, 6), dtype=np.double) + SRI._calibration_offset = np.ones(shape=(1, 6), dtype=np.double) SRI.reset() assert np.array_equal(SRI._calibration_offset, np.zeros(shape=(1, 6), dtype=np.double)) diff --git a/tests/test_sensors/test_sensors_base.py b/tests/test_sensors/test_sensors_base.py index 6448f074..2074bc6d 100644 --- a/tests/test_sensors/test_sensors_base.py +++ b/tests/test_sensors/test_sensors_base.py @@ -2,7 +2,14 @@ import pytest -from opensourceleg.sensors.base import * +from opensourceleg.sensors.base import ( + EncoderBase, + IMUBase, + LoadcellBase, + SensorBase, + SensorNotStreamingException, + check_sensor_stream, +) # Test SensorNotStreamingException init @@ -10,16 +17,16 @@ def test_actuator_stream_exception(): test_str = "test" with pytest.raises(SensorNotStreamingException) as e: raise SensorNotStreamingException(test_str) - assert ( - str(e.value) - == f"{test_str} is not streaming, please ensure that the connections are intact, power is on, and the start method is called." + assert str(e.value) == ( + f"{test_str} is not streaming, please ensure that the connections are " + "intact, power is on, and the start method is called." ) with pytest.raises(SensorNotStreamingException) as e: raise SensorNotStreamingException() - assert ( - str(e.value) - == "Sensor is not streaming, please ensure that the connections are intact, power is on, and the start method is called." + assert str(e.value) == ( + "Sensor is not streaming, please ensure that the connections are intact, " + "power is on, and the start method is called." ) @@ -61,9 +68,9 @@ def mock_sensor(): def test_check_sensor_stream(mock_sensor: MockSensor): with pytest.raises(SensorNotStreamingException) as e: mock_sensor.test_sensor_stream() - assert ( - str(e.value) - == f"{mock_sensor.__repr__()} is not streaming, please ensure that the connections are intact, power is on, and the start method is called." + assert str(e.value) == ( + f"{mock_sensor.__repr__()} is not streaming, please ensure that the " + "connections are intact, power is on, and the start method is called." ) mock_sensor.set_is_streaming() diff --git a/tests/test_units/test_units.py b/tests/test_units/test_units.py index 7e37176a..bd0cf0c1 100644 --- a/tests/test_units/test_units.py +++ b/tests/test_units/test_units.py @@ -3,7 +3,21 @@ import numpy as np import pytest -from opensourceleg.units import * +from opensourceleg.units import ( + Acceleration, + Current, + Damping, + Force, + Length, + Mass, + Position, + Stiffness, + Torque, + Velocity, + Voltage, + convert_from_default, + convert_to_default, +) # Values to iterate over for testing VALUES = np.append(np.array([0, 1, -1, 1000, -1000]), np.random.random(5)) diff --git a/tutorials/actuators/moteus/velocity_control.py b/tutorials/actuators/moteus/velocity_control.py index 2697ea1c..39ecdd85 100644 --- a/tutorials/actuators/moteus/velocity_control.py +++ b/tutorials/actuators/moteus/velocity_control.py @@ -54,7 +54,8 @@ async def main(): LOGGER.info( "".join( f"Motor Velocity: {mc1.motor_velocity}\t" - + f"Motor Velocity Command: {mc1._data[0].values[Register.COMMAND_VELOCITY] * 2 * np.pi / mc1.gear_ratio}\t" + + "Motor Velocity Command: " + + f"{mc1._data[0].values[Register.COMMAND_VELOCITY] * 2 * np.pi / mc1.gear_ratio}\t" ) ) velocity_data = pd.concat( From a2945a795c1b7add51feb4ce7ba906ac6ad07cd1 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 14 Nov 2024 16:52:20 -0500 Subject: [PATCH 08/19] Fixed all ruff errors. --- examples/basic_motion.py | 3 +- examples/fsm_walking_compiled_controller.py | 5 +- opensourceleg/__init__.py | 6 ++- opensourceleg/actuators/__init__.py | 6 +-- opensourceleg/actuators/dephy.py | 40 ++++++++++------ opensourceleg/actuators/moteus.py | 17 +++++-- opensourceleg/actuators/tmotor.py | 48 +++++++++---------- opensourceleg/collections/validators.py | 10 ++-- opensourceleg/control/compiled_controller.py | 17 +++++-- opensourceleg/control/state_machine.py | 11 +++-- opensourceleg/logging/__init__.py | 2 +- opensourceleg/logging/decorators.py | 3 +- opensourceleg/logging/exceptions.py | 6 ++- opensourceleg/math/__init__.py | 2 +- opensourceleg/math/math.py | 3 +- opensourceleg/robots/osl.py | 6 ++- opensourceleg/safety/__init__.py | 2 +- opensourceleg/safety/safety.py | 41 +++++++++------- opensourceleg/sensors/adc.py | 32 +++++++------ opensourceleg/sensors/base.py | 6 +-- opensourceleg/sensors/imu.py | 4 +- opensourceleg/sensors/loadcell.py | 5 +- opensourceleg/time/__init__.py | 3 +- opensourceleg/time/time.py | 8 ++-- opensourceleg/units/__init__.py | 2 +- opensourceleg/units/units.py | 19 +++++++- tests/test_actuators/test_actuators_base.py | 32 +++++++------ tests/test_logging/test_logging_decorators.py | 7 ++- 28 files changed, 210 insertions(+), 136 deletions(-) diff --git a/examples/basic_motion.py b/examples/basic_motion.py index 335f0012..24ed96d8 100644 --- a/examples/basic_motion.py +++ b/examples/basic_motion.py @@ -69,7 +69,8 @@ def make_periodic_trajectory(period, minimum, maximum): ankle.set_output_position(ankle_setpoint) print( - f"Ankle Desired {ankle_setpoint:+.2f} rad, Ankle Actual {ankle.output_position:+.2f} rad, Knee Desired {knee_setpoint:+.2f} rad, Ankle Desired {knee.output_position:+.2f} rad", + f"Ankle Desired {ankle_setpoint:+.2f} rad, Ankle Actual {ankle.output_position:+.2f} rad, \ + Knee Desired {knee_setpoint:+.2f} rad, Ankle Desired {knee.output_position:+.2f} rad", end="\r", ) diff --git a/examples/fsm_walking_compiled_controller.py b/examples/fsm_walking_compiled_controller.py index 4c1ec6c0..3c5050ba 100644 --- a/examples/fsm_walking_compiled_controller.py +++ b/examples/fsm_walking_compiled_controller.py @@ -189,7 +189,10 @@ # Test print to ensure external library call works print( - f"Current time in state {outputs.current_state}: {outputs.time_in_current_state:.2f} seconds, Knee Eq {outputs.knee_impedance.eq_angle:.2f}, Ankle Eq {outputs.ankle_impedance.eq_angle:.2f}, Fz {loadcell.fz:.2f}", + f"Current time in state {outputs.current_state}: {outputs.time_in_current_state:.2f} seconds, \ + Knee Eq {outputs.knee_impedance.eq_angle:.2f}, \ + Ankle Eq {outputs.ankle_impedance.eq_angle:.2f}, \ + Fz {loadcell.fz:.2f}", end="\r", ) diff --git a/opensourceleg/__init__.py b/opensourceleg/__init__.py index 34c80411..12e3b6e6 100644 --- a/opensourceleg/__init__.py +++ b/opensourceleg/__init__.py @@ -1,6 +1,8 @@ -"""An open-source software library for numerical computation, data acquisition, and control of lower-limb robotic prostheses.""" +""" +An open-source software library for numerical computation, data acquisition, +and control of lower-limb robotic prostheses. +""" -import sys from importlib import metadata as importlib_metadata diff --git a/opensourceleg/actuators/__init__.py b/opensourceleg/actuators/__init__.py index e21b3c14..7806a20b 100644 --- a/opensourceleg/actuators/__init__.py +++ b/opensourceleg/actuators/__init__.py @@ -1,3 +1,3 @@ -from .base import * -from .decorators import * -from .dephy import * +from .base import * # noqa: F403 +from .decorators import * # noqa: F403 +from .dephy import * # noqa: F403 diff --git a/opensourceleg/actuators/dephy.py b/opensourceleg/actuators/dephy.py index c327e600..dd9e1db8 100644 --- a/opensourceleg/actuators/dephy.py +++ b/opensourceleg/actuators/dephy.py @@ -183,7 +183,8 @@ def start(self) -> None: except OSError: print("\n") LOGGER.error( - msg=f"[{self.__repr__()}] Need admin previleges to open the port '{self.port}'. \n\nPlease run the script with 'sudo' command or add the user to the dialout group.\n" + msg=f"[{self.__repr__()}] Need admin previleges to open the port '{self.port}'. \n\n \ + Please run the script with 'sudo' command or add the user to the dialout group.\n" ) os._exit(status=1) @@ -213,14 +214,16 @@ def update(self) -> None: ) if self.case_temperature >= self.max_case_temperature: LOGGER.error( - msg=f"[{str.upper(self.tag)}] Case thermal limit {self.max_case_temperature} reached. Stopping motor." + msg=f"[{str.upper(self.tag)}] Case thermal limit {self.max_case_temperature} reached. \ + Stopping motor." ) # self.stop() raise ThermalLimitException() if self.winding_temperature >= self.max_winding_temperature: LOGGER.error( - msg=f"[{str.upper(self.tag)}] Winding thermal limit {self.max_winding_temperature} reached. Stopping motor." + msg=f"[{str.upper(self.tag)}] Winding thermal limit {self.max_winding_temperature} reached. \ + Stopping motor." ) raise ThermalLimitException() # Check for thermal fault, bit 2 of the execute status byte @@ -252,8 +255,10 @@ def home( joint_direction (int): Direction to move the joint during homing. Default is -1. joint_position_offset (float): Offset in radians to add to the joint position. Default is 0.0. motor_position_offset (float): Offset in radians to add to the motor position. Default is 0.0. - current_threshold (int): Current threshold in mA to stop homing the joint or actuator. This is used to detect if the actuator or joint has hit a hard stop. Default is 5000 mA. - velocity_threshold (float): Velocity threshold in rad/s to stop homing the joint or actuator. This is also used to detect if the actuator or joint has hit a hard stop. Default is 0.001 rad/s. + current_threshold (int): Current threshold in mA to stop homing the joint or actuator. + This is used to detect if the actuator or joint has hit a hard stop. Default is 5000 mA. + velocity_threshold (float): Velocity threshold in rad/s to stop homing the joint or actuator. + This is also used to detect if the actuator or joint has hit a hard stop. Default is 0.001 rad/s. """ is_homing = True @@ -305,7 +310,8 @@ def home( self.set_encoder_map(np.polynomial.polynomial.Polynomial(coef=coefficients)) else: LOGGER.debug( - msg=f"[{self.__repr__()}] No encoder map found. Please call the make_encoder_map method to create one. The encoder map is used to estimate joint position more accurately." + msg=f"[{self.__repr__()}] No encoder map found. Please call the make_encoder_map method \ + to create one. The encoder map is used to estimate joint position more accurately." ) def make_encoder_map(self, overwrite=False) -> None: @@ -345,7 +351,8 @@ def make_encoder_map(self, overwrite=False) -> None: _output_position_array = [] LOGGER.info( - msg=f"[{self.__repr__()}] Please manually move the {self.tag} joint numerous times through its full range of motion for 10 seconds. \n{input('Press any key when you are ready to start.')}" + msg=f"[{self.__repr__()}] Please manually move the {self.tag} joint numerous times through \ + its full range of motion for 10 seconds. \n{input('Press any key when you are ready to start.')}" ) _start_time: float = time.time() @@ -353,7 +360,8 @@ def make_encoder_map(self, overwrite=False) -> None: try: while time.time() - _start_time < 10: LOGGER.info( - msg=f"[{self.__repr__()}] Mapping the {self.tag} joint encoder: {10 - time.time() + _start_time} seconds left." + msg=f"[{self.__repr__()}] Mapping the {self.tag} \ + joint encoder: {10 - time.time() + _start_time} seconds left." ) self.update() _joint_encoder_array.append(self.joint_encoder_counts) @@ -876,8 +884,8 @@ def gyroz(self) -> float: def thermal_scaling_factor(self) -> float: """ Scale factor to use in torque control, in [0,1]. - If you scale the torque command by this factor, the motor temperature will never exceed max allowable temperature. - For a proof, see paper referenced in thermal model. + If you scale the torque command by this factor, the motor temperature will never + exceed max allowable temperature. For a proof, see paper referenced in thermal model. """ return self._thermal_scale @@ -1010,7 +1018,8 @@ def start(self) -> None: except OSError: print("\n") LOGGER.error( - msg=f"[{self.__repr__()}] Need admin previleges to open the port '{self.port}'. \n\nPlease run the script with 'sudo' command or add the user to the dialout group.\n" + msg=f"[{self.__repr__()}] Need admin previleges to open the port '{self.port}'. \n\n \ + Please run the script with 'sudo' command or add the user to the dialout group.\n" ) os._exit(status=1) @@ -1040,20 +1049,23 @@ def update(self) -> None: ) if self.case_temperature >= self.max_case_temperature: LOGGER.error( - msg=f"[{str.upper(self.tag)}] Case thermal limit {self.max_case_temperature} reached. Stopping motor." + msg=f"[{str.upper(self.tag)}] Case thermal limit {self.max_case_temperature} reached. \ + Stopping motor." ) raise ThermalLimitException() if self.winding_temperature >= self.max_winding_temperature: LOGGER.error( - msg=f"[{str.upper(self.tag)}] Winding thermal limit {self.max_winding_temperature} reached. Stopping motor." + msg=f"[{str.upper(self.tag)}] Winding thermal limit {self.max_winding_temperature} reached. \ + Stopping motor." ) raise ThermalLimitException() # Check for thermal fault, bit 2 of the execute status byte if self._data.status_ex & 0b00000010 == 0b00000010: LOGGER.error( - msg=f"[{str.upper(self.tag)}] Thermal Fault: Winding temperature: {self.winding_temperature}; Case temperature: {self.case_temperature}." + msg=f"[{str.upper(self.tag)}] Thermal Fault: Winding temperature: {self.winding_temperature}; \ + Case temperature: {self.case_temperature}." ) raise ThermalLimitException("Internal thermal limit tripped.") diff --git a/opensourceleg/actuators/moteus.py b/opensourceleg/actuators/moteus.py index b29b80cf..f772ac8a 100644 --- a/opensourceleg/actuators/moteus.py +++ b/opensourceleg/actuators/moteus.py @@ -1,5 +1,6 @@ import math import os +from typing import ClassVar, Optional import numpy as np from moteus import Command, Controller, Stream @@ -73,7 +74,7 @@ class MoteusQueryResolution: aux1_gpio = mp.IGNORE aux2_gpio = mp.IGNORE - _extra = { + _extra: ClassVar = { MoteusRegister.COMMAND_POSITION: mp.F32, MoteusRegister.COMMAND_VELOCITY: mp.F32, MoteusRegister.COMMAND_FEEDFORWARD_TORQUE: mp.F32, @@ -166,8 +167,11 @@ def __init__( gear_ratio: float = 1.0, frequency: int = 500, offline: bool = False, - query: MoteusQueryResolution = MoteusQueryResolution(), + query: Optional[MoteusQueryResolution] = None, ) -> None: + if query is None: + query = MoteusQueryResolution() + self._servo_id = servo_id self._bus_id = bus_id super().__init__( @@ -223,7 +227,8 @@ async def start(self) -> None: except OSError: print("\n") LOGGER.error( - msg=f"[{self.__repr__()}] Need admin previleges to open the port. \n\nPlease run the script with 'sudo' command or add the user to the dialout group.\n" + msg=f"[{self.__repr__()}] Need admin previleges to open the port. \n\n \ + Please run the script with 'sudo' command or add the user to the dialout group.\n" ) os._exit(status=1) @@ -262,7 +267,8 @@ async def update(self): if self.winding_temperature >= self.max_winding_temperature: LOGGER.error( - msg=f"[{str.upper(self.tag)}] Winding thermal limit {self.max_winding_temperature} reached. Stopping motor." + msg=f"[{str.upper(self.tag)}] Winding thermal limit {self.max_winding_temperature} reached. \ + Stopping motor." ) raise ThermalLimitException() @@ -531,7 +537,8 @@ def winding_temperature(self) -> float: def thermal_scaling_factor(self) -> float: """ Scale factor to use in torque control, in [0,1]. - If you scale the torque command by this factor, the motor temperature will never exceed max allowable temperature. + If you scale the torque command by this factor, the motor temperature will + never exceed max allowable temperature. For a proof, see paper referenced in thermal model. """ return self._thermal_scale diff --git a/opensourceleg/actuators/tmotor.py b/opensourceleg/actuators/tmotor.py index cb956ebe..f04d949d 100644 --- a/opensourceleg/actuators/tmotor.py +++ b/opensourceleg/actuators/tmotor.py @@ -192,9 +192,7 @@ def stop(self): def home(self): pass - # this method is called by the user to synchronize the current state used by the controller - # with the most recent message recieved - def update(self): + def update(self): # noqa: C901 """ This method is called by the user to synchronize the current state used by the controller with the most recent message recieved, as well as to send the current command. @@ -213,11 +211,12 @@ def update(self): # print(self._command_sent) now = time.time() if (now - self._last_command_time) < 0.25 and ((now - self._last_update_time) > 0.1): - # print("State update requested but no data recieved from motor. Delay longer after zeroing, decrease frequency, or check connection.") warnings.warn( - "State update requested but no data from motor. Delay longer after zeroing, decrease frequency, or check connection. " + "State update requested but no data from motor. Delay longer after zeroing, \ + decrease frequency, or check connection. " + self.device_info_string(), RuntimeWarning, + stacklevel=2, ) else: self._command_sent = False @@ -251,8 +250,10 @@ def update(self): elif (thresh_pos <= old_pos and old_pos <= P_max) and (-P_max <= new_pos and new_pos <= -thresh_pos): self._times_past_position_limit += 1 - # current is basically the same as position, but if you instantly command a switch it can actually change fast enough - # to throw this off, so that is accounted for too. We just put a hard limit on the current to solve current jitter problems. + # current is basically the same as position, but if you instantly + # command a switch it can actually change fast enough + # to throw this off, so that is accounted for too. We just put a hard limit on the current + # to solve current jitter problems. if (thresh_curr <= new_curr and new_curr <= I_max) and (-I_max <= old_curr and old_curr <= -thresh_curr): # self._old_current_zone = -1 # if (thresh_curr <= curr_command and curr_command <= I_max): @@ -294,11 +295,6 @@ def update(self): # send current motor command self._send_command() - - # # writing to log file - # if self.csv_file_name is not None: - # self.csv_writer.writerow([self._last_update_time - self._start_time] + [self.LOG_FUNCTIONS[var]() for var in self.log_vars]) - self._updated = False # sends a command to the motor depending on whats controlm mode the motor is in @@ -311,8 +307,6 @@ def _send_command(self): This allows control based on actual q-axis current, rather than estimated torque, which doesn't account for friction losses. """ - # if self._control_state == _TMotorManState.FULL_STATE: - # self._canman.MIT_controller(self.ID,self.type, self._command.position, self._command.velocity, self._command.kp, self._command.kd, self.qaxis_current_to_TMotor_current(self._command.current)) if self.mode == CONTROL_MODES.IMPEDANCE: self._canman.MIT_controller( self.ID, @@ -426,8 +420,18 @@ def set_impedance_gains(self, kp=0, ki=0, K=0.08922, B=0.0038070, ff=0): B: The damping in Nm/(rad/s) ff: A dummy argument for backward compatibility with the dephy library. """ - assert isfinite(K) and MIT_Params[self.type]["Kp_min"] <= K and MIT_Params[self.type]["Kp_max"] >= K - assert isfinite(B) and MIT_Params[self.type]["Kd_min"] <= B and MIT_Params[self.type]["Kd_max"] >= B + if not (isfinite(K) and MIT_Params[self.type]["Kp_min"] <= K and MIT_Params[self.type]["Kp_max"] >= K): + raise ValueError( + f"K must be finite and between \ + {MIT_Params[self.type]['Kp_min']} and {MIT_Params[self.type]['Kp_max']}" + ) + + if not (isfinite(B) and MIT_Params[self.type]["Kd_min"] <= B and MIT_Params[self.type]["Kd_max"] >= B): + raise ValueError( + f"B must be finite and between \ + {MIT_Params[self.type]['Kd_min']} and {MIT_Params[self.type]['Kd_max']}" + ) + self._command.kp = K self._command.kd = B self._command.velocity = 0.0 @@ -470,9 +474,6 @@ def set_output_position(self, value): Raises: RuntimeError: If the position command is outside the range of the motor. """ - # position commands must be within a certain range :/ - # pos = (np.abs(pos) % MIT_Params[self.type]["P_max"])*np.sign(pos) # this doesn't work because it will unwind itself! - # CANNOT Control using impedance mode for angles greater than 12.5 rad!! if np.abs(value) >= MIT_Params[self.type]["P_max"]: raise RuntimeError( "Cannot control using impedance mode for angles with magnitude greater than " @@ -480,8 +481,6 @@ def set_output_position(self, value): + "rad!" ) - # if self._control_state not in [_TMotorManState.IMPEDANCE, _TMotorManState.FULL_STATE]: - # raise RuntimeError("Attempted to send position command without gains for device " + self.device_info_string()) self._command.position = value def set_output_velocity(self, value): @@ -503,8 +502,6 @@ def set_output_velocity(self, value): + "rad/s!" ) - # if self._control_state not in [_TMotorManState.SPEED, _TMotorManState.FULL_STATE]: - # raise RuntimeError("Attempted to send speed command without gains for device " + self.device_info_string()) self._command.velocity = value # used for either current MIT mode to set current @@ -518,8 +515,6 @@ def set_motor_current(self, value): Args: value: the desired current in amps. """ - # if self._control_state not in [_TMotorManState.CURRENT, _TMotorManState.FULL_STATE]: - # raise RuntimeError("Attempted to send current command before entering current mode for device " + self.device_info_string()) self._command.current = value # used for either current or MIT Mode to set current, based on desired torque @@ -636,7 +631,8 @@ def check_can_connection(self) -> bool: """ if not self._entered: raise RuntimeError( - "Tried to check_can_connection before entering motor control! Enter control using the __enter__ method, or instantiating the TMotorManager in a with block." + "Tried to check_can_connection before entering motor control! \ + Enter control using the __enter__ method, or instantiating the TMotorManager in a with block." ) Listener = can.BufferedReader() self._canman.notifier.add_listener(Listener) diff --git a/opensourceleg/collections/validators.py b/opensourceleg/collections/validators.py index 2abf68cd..88360a0c 100644 --- a/opensourceleg/collections/validators.py +++ b/opensourceleg/collections/validators.py @@ -5,12 +5,12 @@ class Validator(ABC): def __set_name__(self, owner, name): self.private_name = f"_{name}" - def __get__(self, object, objtype=None): - return getattr(object, self.private_name) + def __get__(self, instance, objtype=None): + return getattr(instance, self.private_name) - def __set__(self, object, value): + def __set__(self, instance, value): self.validate(value) - setattr(object, self.private_name, value) + setattr(instance, self.private_name, value) @abstractmethod def validate(self, value): @@ -24,7 +24,7 @@ def __init__(self, min_value=None, max_value=None) -> None: def validate(self, value): if not isinstance(value, (int, float)): - raise ValueError("Value must be an int or float") + raise TypeError("Value must be an int or float") if self.min_value is not None and value < self.min_value: raise ValueError(f"Value must be at least {self.min_value}") diff --git a/opensourceleg/control/compiled_controller.py b/opensourceleg/control/compiled_controller.py index cc6991f2..d2239e9b 100644 --- a/opensourceleg/control/compiled_controller.py +++ b/opensourceleg/control/compiled_controller.py @@ -3,6 +3,8 @@ import numpy.ctypeslib as ctl +from opensourceleg.logging.logger import LOGGER + class CompiledController: """ @@ -16,10 +18,15 @@ class CompiledController: Parameters: ----------- library_name (string): The name of the compiled library file, without the *.so - library_path (string): The path to the directory containing the library. See examples for how to get working directory of parent script. - main_function_name (string): Name of the main function to call within the library. This is the function that will get called via the run() method - initialization_function_name (string): Name of an initialization function for your library. This gets called only once when the library is loaded. If you don't have an initialization function, pass None. - cleanup_function_name (string): Name of a cleanup function for your library. This gets called when the CompiledController class has gone out of scope and is garbage collected. Again, pass None if you don't need this functionality. + library_path (string): The path to the directory containing the library. + See examples for how to get working directory of parent script. + main_function_name (string): Name of the main function to call within the library. + This is the function that will get called via the run() method + initialization_function_name (string): Name of an initialization function for your library. + This gets called only once when the library is loaded. If you don't have an initialization function, pass None. + cleanup_function_name (string): Name of a cleanup function for your library. + This gets called when the CompiledController class has gone out of scope and is garbage collected. + Again, pass None if you don't need this functionality. Authors: -------- @@ -78,7 +85,7 @@ def _load_function(self, function_name): try: function_handle = getattr(self.lib, function_name) except AttributeError: - raise AttributeError(f"Function {function_name} not found in library {self.lib}") + LOGGER.warning(f"Function {function_name} not found in library {self.lib}") return function_handle def define_inputs(self, input_list: list[Any]) -> None: diff --git a/opensourceleg/control/state_machine.py b/opensourceleg/control/state_machine.py index 3d420310..04a27508 100644 --- a/opensourceleg/control/state_machine.py +++ b/opensourceleg/control/state_machine.py @@ -4,6 +4,8 @@ import time from typing import Any, Callable, Optional +from opensourceleg.logging.logger import LOGGER + """ The state_machine module provides classes for implementing a finite state machine (FSM). It includes the State, Idle, Event, Transition, FromToTransition, and StateMachine classes. @@ -462,7 +464,8 @@ def add_transition( event : Event The event that triggers the transition. callback : Callable[[Any], bool], optional - A callback function that returns a boolean value, which determines whether the transition is valid, by default None + A callback function that returns a boolean value, + which determines whether the transition is valid, by default None """ transition = None @@ -492,8 +495,10 @@ def update(self, data: Any = None) -> None: break if not validity: - assert self._osl is not None - self._osl.log.debug(f"Event isn't valid at {self._current_state.name}") + if self._osl is None: + raise ValueError("OSL object not set.") + + LOGGER.debug(f"Event isn't valid at {self._current_state.name}") def start(self, data: Any = None) -> None: if not self._initial_state: diff --git a/opensourceleg/logging/__init__.py b/opensourceleg/logging/__init__.py index 82a7cbc9..46406def 100644 --- a/opensourceleg/logging/__init__.py +++ b/opensourceleg/logging/__init__.py @@ -1 +1 @@ -from opensourceleg.logging.logger import * +from .logger import * # noqa: F403 diff --git a/opensourceleg/logging/decorators.py b/opensourceleg/logging/decorators.py index 23dbb06f..fcfdf238 100644 --- a/opensourceleg/logging/decorators.py +++ b/opensourceleg/logging/decorators.py @@ -44,7 +44,8 @@ def decorator(func): @wraps(func) def wrapper(*args, **kwargs): LOGGER.warning( - f"Function `{func.__name__}` is deprecated. Please use `{alternative_func.__name__}` instead, which will be called automatically now." + f"Function `{func.__name__}` is deprecated. Please use `{alternative_func.__name__}` instead," + "which will be called automatically now." ) return alternative_func(*args, **kwargs) diff --git a/opensourceleg/logging/exceptions.py b/opensourceleg/logging/exceptions.py index 3d50f6d6..2cdacae0 100644 --- a/opensourceleg/logging/exceptions.py +++ b/opensourceleg/logging/exceptions.py @@ -35,7 +35,8 @@ class ActuatorIsNoneException(Exception): def __init__(self, mode: str) -> None: super().__init__( - f"Actuator is None in {mode} mode, please pass the actuator instance to the mode during initialization or set the actuator instance using set_actuator method." + f"Actuator is None in {mode} mode, please pass the actuator instance to the mode during" + "initialization or set the actuator instance using set_actuator method." ) @@ -88,5 +89,6 @@ class ActuatorKeyException(Exception): def __init__(self, tag: str, key: str) -> None: super().__init__( - f"{tag} does not have {key} key in the actuators dictionary. Please check the actuators dictionary for the `{key}` key." + f"{tag} does not have {key} key in the actuators dictionary." + f"Please check the actuators dictionary for the `{key}` key." ) diff --git a/opensourceleg/math/__init__.py b/opensourceleg/math/__init__.py index 8eab9c3b..9ee9d10f 100644 --- a/opensourceleg/math/__init__.py +++ b/opensourceleg/math/__init__.py @@ -1,4 +1,4 @@ -from opensourceleg.math.math import * +from .math import * # noqa: F403 """ Math module for opensourceleg library. diff --git a/opensourceleg/math/math.py b/opensourceleg/math/math.py index c0edfcd5..c4ee8cc6 100644 --- a/opensourceleg/math/math.py +++ b/opensourceleg/math/math.py @@ -105,7 +105,8 @@ def update(self, dt: float = 1 / 200, motor_current: float = 0) -> None: def update_and_get_scale(self, dt, motor_current: float = 0, FOS: float = 1.0): """ - Updates the temperature of the winding and the case based on the current and the ambient temperature and returns the scale factor for the torque. + Updates the temperature of the winding and the case based on the current and + the ambient temperature and returns the scale factor for the torque. Args: dt (float): Time step in seconds. diff --git a/opensourceleg/robots/osl.py b/opensourceleg/robots/osl.py index f143b932..f89fc7fd 100644 --- a/opensourceleg/robots/osl.py +++ b/opensourceleg/robots/osl.py @@ -59,7 +59,8 @@ def joint_encoder_knee(self) -> Union[TSensor, SensorBase]: return self.sensors["joint_encoder_knee"] except KeyError: LOGGER.error( - "Knee joint encoder sensor not found. Please check for `joint_encoder_knee` key in the sensors dictionary." + "Knee joint encoder sensor not found." + "Please check for `joint_encoder_knee` key in the sensors dictionary." ) exit(1) @@ -69,7 +70,8 @@ def joint_encoder_ankle(self) -> Union[TSensor, SensorBase]: return self.sensors["joint_encoder_ankle"] except KeyError: LOGGER.error( - "Ankle joint encoder sensor not found. Please check for `joint_encoder_ankle` key in the sensors dictionary." + "Ankle joint encoder sensor not found." + "Please check for `joint_encoder_ankle` key in the sensors dictionary." ) exit(1) diff --git a/opensourceleg/safety/__init__.py b/opensourceleg/safety/__init__.py index 36fd8c45..3e698f77 100644 --- a/opensourceleg/safety/__init__.py +++ b/opensourceleg/safety/__init__.py @@ -1,4 +1,4 @@ -from opensourceleg.safety.safety import * +from .safety import * # noqa: F403 """ Safety module for opensourceleg library. diff --git a/opensourceleg/safety/safety.py b/opensourceleg/safety/safety.py index 1e010487..71a656f9 100644 --- a/opensourceleg/safety/safety.py +++ b/opensourceleg/safety/safety.py @@ -18,17 +18,20 @@ def is_changing( proxy_attribute_name: Optional[str] = None, ): """ - Creates a decorator to check if a property's value is changing. If the standard deviation of the last 'max_points' values is less than 'threshold', the decorator will raise an error or return a proxy attribute. + Creates a decorator to check if a property's value is changing. + If the standard deviation of the last 'max_points' values is less than 'threshold', + the decorator will raise an error or return a proxy attribute. Args: - attribute_name (str): Name of the attribute. - max_points (int): Number of points to consider. Defaults to 10. - threshold (float): Threshold for the standard deviation. Defaults to 1e-6. - proxy_attribute_name (str): Name of the proxy attribute to return if the property is not changing. Defaults to None. + attribute_name: Name of the attribute. + max_points: Number of points to consider. Defaults to 10. + threshold: Threshold for the standard deviation. Defaults to 1e-6. + proxy_attribute_name: Name of the proxy attribute to return if the property is not changing. Defaults to None. Returns: Callable: Decorator function. """ + history_key = f"_{attribute_name}_history" proxy_key = f"_{attribute_name}_proxy" @@ -142,9 +145,9 @@ def is_within_range(min_value: float, max_value: float, clamp: bool = False): Creates a decorator to check if a property's value is within a given range. Args: - min_value (float): Minimum value of the range. - max_value (float): Maximum value of the range. - clamp (bool): If True, the decorator will return the clamped value instead of raising an error. Defaults to False. + min_value: Minimum value of the range. + max_value: Maximum value of the range. + clamp: If True, the decorator will return the clamped value instead of raising an error. Defaults to False. Raises: ValueError: If the maximum value is less than or equal to the minimum value. @@ -176,9 +179,9 @@ def is_greater_than(min_value: float, clamp: bool = False, equality: bool = Fals choice to implement is_greater_than_or_equal_to with equality bool Args: - min_value (float): Minimum value to check against. - clamp (bool): If True, the decorator will return the clamped value instead of raising an error. Defaults to False. - equality (bool): If True, the decorator will check for is greater than or equal to, instead of is greater than. + min_value: Minimum value to check against. + clamp: If True, the decorator will return the clamped value instead of raising an error. Defaults to False. + equality: If True, the decorator will check for is greater than or equal to, instead of is greater than. Returns: Callable: Decorator function. @@ -210,9 +213,9 @@ def is_less_than(max_value: float, clamp: bool = False, equality: bool = False): choice to implement is_less_than_or_equal_to with equality bool Args: - max_value (float): Maximum value to check against. - clamp (bool): If True, the decorator will return the clamped value instead of raising an error. Defaults to False. - equality (bool): If True, the decorator will check for is less than or equal to, instead of is less than. + max_value: Maximum value to check against. + clamp: If True, the decorator will return the clamped value instead of raising an error. Defaults to False. + equality: If True, the decorator will check for is less than or equal to, instead of is less than. Returns: Callable: Decorator function. @@ -240,7 +243,8 @@ def wrapper(instance, *args, **kwargs): def custom_criteria(criteria: Callable): """ - Creates a decorator to check if a property's value meets a custom criteria. The criteria is a function that takes the property's value as an argument and returns a boolean. + Creates a decorator to check if a property's value meets a custom criteria. The criteria is a + function that takes the property's value as an argument and returns a boolean. Args: criteria (Callable): Custom criteria function. @@ -279,7 +283,12 @@ class SafetyDecorators: class SafetyManager: """ - The SafetyManager class enables the addition of safety decorators to an object's properties, specifically to their getters. When the 'start' method is invoked, these decorators are applied to the properties of the objects stored in the 'safe_objects' dictionary. The original objects are then replaced with subclasses that incorporate the decorated properties. Invoking the 'update' method accesses the properties of the objects in the 'safe_objects' dictionary, thereby triggering the decorators. + The SafetyManager class enables the addition of safety decorators to an object's properties, + specifically to their getters. When the 'start' method is invoked, these decorators are applied + to the properties of the objects stored in the 'safe_objects' dictionary. The original objects + are then replaced with subclasses that incorporate the decorated properties. + Invoking the 'update' method accesses the properties of the objects in the 'safe_objects' dictionary, + thereby triggering the decorators. """ def __init__(self): diff --git a/opensourceleg/sensors/adc.py b/opensourceleg/sensors/adc.py index 115c7442..26951949 100644 --- a/opensourceleg/sensors/adc.py +++ b/opensourceleg/sensors/adc.py @@ -1,6 +1,6 @@ import math from time import sleep -from typing import Optional +from typing import ClassVar, Optional import spidev @@ -19,11 +19,11 @@ class ADS131M0x(ADCBase): _RESOLUTION = 24 _SPI_MODE = 1 - _BLANK_WORD = [0x00, 0x00, 0x00] + _BLANK_WORD: ClassVar = [0x00, 0x00, 0x00] # SPI Commands - _RESET_WORD = [0x00, 0x11, 0x00] - _STANDBY_WORD = [0x00, 0x22, 0x00] - _WAKEUP_WORD = [0x00, 0x33, 0x00] + _RESET_WORD: ClassVar = [0x00, 0x11, 0x00] + _STANDBY_WORD: ClassVar = [0x00, 0x22, 0x00] + _WAKEUP_WORD: ClassVar = [0x00, 0x33, 0x00] _RREG_PREFIX = 0b101 _WREG_PREFIX = 0b011 @@ -42,11 +42,11 @@ class ADS131M0x(ADCBase): _MODE_CFG = 0x0110 # Channel specific setting register addresses - _OCAL_MSB_ADDRS = [0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x23, 0x28, 0x2D] - _OCAL_LSB_ADDRS = [0x0B, 0x10, 0x15, 0x1A, 0x1F, 0x24, 0x29, 0x2E] - _GCAL_MSB_ADDRS = [0x0C, 0x11, 0x16, 0x1B, 0x20, 0x25, 0x2A, 0x2F] - _GCAL_LSB_ADDRS = [0x0D, 0x12, 0x17, 0x1C, 0x21, 0x26, 0x2B, 0x30] - _CHANNEL_CFG_ADDRS = [0x09, 0x0E, 0x13, 0x18, 0x1D, 0x22, 0x27, 0x2C] + _OCAL_MSB_ADDRS: ClassVar = [0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x23, 0x28, 0x2D] + _OCAL_LSB_ADDRS: ClassVar = [0x0B, 0x10, 0x15, 0x1A, 0x1F, 0x24, 0x29, 0x2E] + _GCAL_MSB_ADDRS: ClassVar = [0x0C, 0x11, 0x16, 0x1B, 0x20, 0x25, 0x2A, 0x2F] + _GCAL_LSB_ADDRS: ClassVar = [0x0D, 0x12, 0x17, 0x1C, 0x21, 0x26, 0x2B, 0x30] + _CHANNEL_CFG_ADDRS: ClassVar = [0x09, 0x0E, 0x13, 0x18, 0x1D, 0x22, 0x27, 0x2C] _GCAL_STEP_SIZE = 1.19e-7 @@ -69,10 +69,12 @@ def __init__( - max_speed_hz(int): Maximum clock frequency of the SPI communication. Default: 8192000 - channel_gains(List[int]): Gains of the programmable gain amplifier for all channels. Default: [32,128] * 3 - voltage_reference(float): Reference voltage used by the ADC. Default: 1.2 - - gain_error(List[int]): User-calculated integers used for correcting the gain of each channel for additional precision. Default: [] + - gain_error(List[int]): User-calculated integers used for correcting the gain of each channel for + additional precision. Default: [] Raises: - ValueError: If length of channel_gains is not equal to number of channels, or if gain is not a power of 2 between 1 and 128. + ValueError: If length of channel_gains is not equal to number of channels, or if gain is not a power of 2 + between 1 and 128. """ @@ -179,15 +181,15 @@ def gains(self) -> list[int]: def data(self): return self._data - def _spi_message(self, bytes: list[int]) -> list[int]: + def _spi_message(self, message: list[int]) -> list[int]: """Send SPI message to ADS131M0x. Args: - - bytes(List[int]): message to be sent to the ADS131M0x separated into bytes. + - message(List[int]): message to be sent to the ADS131M0x separated into message. Returns: The response to the message sent, including the entire frame following the response. """ - self._spi.xfer2(bytes) + self._spi.xfer2(message) return (list[int])(self._spi.readbytes(self._BYTES_PER_WORD * self._words_per_frame)) def _channel_enable(self, state: bool) -> None: diff --git a/opensourceleg/sensors/base.py b/opensourceleg/sensors/base.py index 02778456..4b80581e 100644 --- a/opensourceleg/sensors/base.py +++ b/opensourceleg/sensors/base.py @@ -5,7 +5,8 @@ class SensorNotStreamingException(Exception): def __init__(self, sensor_name: str = "Sensor") -> None: super().__init__( - f"{sensor_name} is not streaming, please ensure that the connections are intact, power is on, and the start method is called." + f"{sensor_name} is not streaming, please ensure that the connections are intact, " + f"power is on, and the start method is called." ) @@ -20,9 +21,6 @@ def wrapper(self, *args, **kwargs): class SensorBase(ABC): - def __init__(self) -> None: - pass - def __repr__(self) -> str: return "SensorBase" diff --git a/opensourceleg/sensors/imu.py b/opensourceleg/sensors/imu.py index 0b7faac2..f203876e 100644 --- a/opensourceleg/sensors/imu.py +++ b/opensourceleg/sensors/imu.py @@ -8,7 +8,9 @@ import mscl except ImportError: LOGGER.error( - "Failed to import mscl. Please install the MSCL library from Lord Microstrain and append the path to the PYTHONPATH or sys.path. Checkout https://github.com/LORD-MicroStrain/MSCL/tree/master and https://lord-microstrain.github.io/MSCL/Documentation/MSCL%20API%20Documentation/index.html" + "Failed to import mscl. Please install the MSCL library from Lord Microstrain and append the path" + "to the PYTHONPATH or sys.path. Checkout https://github.com/LORD-MicroStrain/MSCL/tree/master" + "and https://lord-microstrain.github.io/MSCL/Documentation/MSCL%20API%20Documentation/index.html" ) try: diff --git a/opensourceleg/sensors/loadcell.py b/opensourceleg/sensors/loadcell.py index f78507e5..d50ab817 100644 --- a/opensourceleg/sensors/loadcell.py +++ b/opensourceleg/sensors/loadcell.py @@ -138,7 +138,8 @@ def calibrate( else: LOGGER.info( - f"[{self.__repr__()}] Loadcell has already been zeroed. To recalibrate, set reset=True in the calibrate method or call reset() first." + f"[{self.__repr__()}] Loadcell has already been zeroed. " + "To recalibrate, set reset=True in the calibrate method or call reset() first." ) def stop(self) -> None: @@ -155,7 +156,7 @@ def _read_compressed_strain(self): self.failed_reads += 1 if self.failed_reads >= 5: - raise Exception("Load cell unresponsive.") + raise LoadcellNotRespondingException("Load cell unresponsive.") from None return self._unpack_compressed_strain(data) diff --git a/opensourceleg/time/__init__.py b/opensourceleg/time/__init__.py index 6c5a5d6e..fa11fcb5 100644 --- a/opensourceleg/time/__init__.py +++ b/opensourceleg/time/__init__.py @@ -9,7 +9,8 @@ Key Classes: -- `SoftRealtimeLoop` class is used to create a soft real-time loop that runs at a specified frequency. It also handles signal interruptions gracefully. +- `SoftRealtimeLoop` class is used to create a soft real-time loop that runs at a specified frequency. + It also handles signal interruptions gracefully. """ __all__ = ["SoftRealtimeLoop"] diff --git a/opensourceleg/time/time.py b/opensourceleg/time/time.py index 6cfcd001..e51d8e4f 100644 --- a/opensourceleg/time/time.py +++ b/opensourceleg/time/time.py @@ -15,8 +15,8 @@ class LoopKiller: Typically, it detects the CTRL-C from your keyboard, which sends a SIGTERM signal. the function_in_loop argument to the Soft Realtime Loop's blocking_loop method is the function to be run every loop. - A typical usage would set function_in_loop to be a method of an object, so that the object could store program state. - See the 'ifmain' for two examples. + A typical usage would set function_in_loop to be a method of an object, so that the object + could store program state. See the 'ifmain' for two examples. # This library will soon be hosted as a PIP module and added as a python dependency. # https://github.com/UM-LoCoLab/NeuroLocoMiddleware/blob/main/SoftRealtimeLoop.py @@ -88,8 +88,8 @@ class SoftRealtimeLoop: Typically, it detects the CTRL-C from your keyboard, which sends a SIGTERM signal. the function_in_loop argument to the Soft Realtime Loop's blocking_loop method is the function to be run every loop. - A typical usage would set function_in_loop to be a method of an object, so that the object could store program state. - See the 'ifmain' for two examples. + A typical usage would set function_in_loop to be a method of an object, so that the object could store + program state. See the 'ifmain' for two examples. This library will soon be hosted as a PIP module and added as a python dependency. https://github.com/UM-LoCoLab/NeuroLocoMiddleware/blob/main/SoftRealtimeLoop.py diff --git a/opensourceleg/units/__init__.py b/opensourceleg/units/__init__.py index 46758c47..b624d670 100644 --- a/opensourceleg/units/__init__.py +++ b/opensourceleg/units/__init__.py @@ -1,4 +1,4 @@ -from opensourceleg.units.units import * +from .units import * # noqa: F403 """ Units module for opensourceleg library. diff --git a/opensourceleg/units/units.py b/opensourceleg/units/units.py index ea557b47..4f67c63e 100644 --- a/opensourceleg/units/units.py +++ b/opensourceleg/units/units.py @@ -10,10 +10,27 @@ Usage: 1. Utilize the `force`, `torque`, etc., classes to represent quantities and configure their unit conversion factors. -2. Use the `convert_to_default` function to convert a value from a user unit to the default unit for the corresponding quantity. +2. Use the `convert_to_default` function to convert a value from a user unit to the default unit + for the corresponding quantity. 3. Use the `convert_from_default` function to convert a value from the default unit to a user-specified unit. """ +__all__ = [ + "Force", + "Torque", + "Stiffness", + "Damping", + "Length", + "Position", + "Mass", + "Velocity", + "Acceleration", + "Current", + "Voltage", + "convert_to_default", + "convert_from_default", +] + class Force(float, Enum): N: 1.0 diff --git a/tests/test_actuators/test_actuators_base.py b/tests/test_actuators/test_actuators_base.py index 64ffe874..8209120d 100644 --- a/tests/test_actuators/test_actuators_base.py +++ b/tests/test_actuators/test_actuators_base.py @@ -1,10 +1,21 @@ +from typing import ClassVar from unittest.mock import Mock, patch import numpy as np import pytest -from opensourceleg.actuators.base import * -from opensourceleg.actuators.base import MOTOR_CONSTANTS +from opensourceleg.actuators.base import ( + CONTROL_MODE_CONFIGS, + CONTROL_MODE_METHODS, + CONTROL_MODES, + MOTOR_CONSTANTS, + ActuatorBase, + ControlGains, + ControlModeConfig, + MethodWithRequiredModes, + T, + requires, +) DEFAULT_VALUES = [0, 1, 1000, -1000] @@ -78,7 +89,7 @@ def test_motor_constants_properties(non_zero_positive_values): def test_control_modes_default_four(): - {"POSITION", "CURRENT", "VOLTAGE", "IMPEDANCE"} <= {e.name for e in CONTROL_MODES} + assert {"POSITION", "CURRENT", "VOLTAGE", "IMPEDANCE"} <= {e.name for e in CONTROL_MODES} def test_control_modes_dephy_order(): @@ -241,8 +252,8 @@ def test_control_mode_configs_init(): def test_control_mode_methods(): - assert type(CONTROL_MODE_METHODS) == list - assert all(type(x) == str for x in CONTROL_MODE_METHODS) + assert type(CONTROL_MODE_METHODS) is list + assert all(type(x) is str for x in CONTROL_MODE_METHODS) assert len(CONTROL_MODE_METHODS) >= 12 @@ -264,7 +275,7 @@ def test_typevar_usage_invalid(): def test_method_with_required_modes(): class TestClass(MethodWithRequiredModes): - _required_modes = {CONTROL_MODES.POSITION, CONTROL_MODES.CURRENT} + _required_modes: ClassVar = {CONTROL_MODES.POSITION, CONTROL_MODES.CURRENT} test_class_instance = TestClass() @@ -547,12 +558,3 @@ def test_motor_constants(mock_actuator: MockActuator): assert mock_actuator.MOTOR_CONSTANTS.NM_S_PER_RAD_TO_B == 0.1 assert mock_actuator.MOTOR_CONSTANTS.MAX_CASE_TEMPERATURE == 100.0 assert mock_actuator.MOTOR_CONSTANTS.MAX_WINDING_TEMPERATURE == 150.0 - - -def test_motor_constants_properties(mock_actuator: MockActuator): - assert pytest.approx(mock_actuator.MOTOR_CONSTANTS.RAD_PER_COUNT, 0.00001) == 2 * 3.14159 / 1000 - assert mock_actuator.MOTOR_CONSTANTS.NM_PER_MILLIAMP == 0.0001 - - -def test_hello_world(): - assert len("Hello World") == 11 diff --git a/tests/test_logging/test_logging_decorators.py b/tests/test_logging/test_logging_decorators.py index 1e25d7fa..579af064 100644 --- a/tests/test_logging/test_logging_decorators.py +++ b/tests/test_logging/test_logging_decorators.py @@ -1,6 +1,7 @@ from unittest.mock import Mock -from opensourceleg.logging.decorators import * +from opensourceleg.logging import LOGGER +from opensourceleg.logging.decorators import deprecated, deprecated_with_routing, deprecated_with_suggestion # test deprecated & decorator @@ -59,7 +60,9 @@ def testing(x): assert testing(2) == 6 LOGGER.warning.assert_called_once_with( - f"Function `{testing.__name__}` is deprecated. Please use `{alternate.__name__}` instead, which will be called automatically now." + f"Function `{testing.__name__}` is deprecated. " + f"Please use `{alternate.__name__}` instead, " + f"which will be called automatically now." ) LOGGER.warning = LOGGER.original_warning From 4c250ca3424ffa4c263e40aa969c6b9b5aa5809a Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 14 Nov 2024 18:20:25 -0500 Subject: [PATCH 09/19] Fixed some mypy errors. --- examples/basic_motion.py | 9 +++-- opensourceleg/actuators/dephy.py | 30 +++++++------- opensourceleg/actuators/moteus.py | 4 +- opensourceleg/collections/validators.py | 13 +++--- opensourceleg/robots/osl.py | 10 ++--- opensourceleg/safety/safety.py | 6 +-- opensourceleg/units/units.py | 54 ++++++++++++------------- 7 files changed, 64 insertions(+), 62 deletions(-) diff --git a/examples/basic_motion.py b/examples/basic_motion.py index 24ed96d8..4d6d0106 100644 --- a/examples/basic_motion.py +++ b/examples/basic_motion.py @@ -11,6 +11,7 @@ import numpy as np +from opensourceleg.actuators.base import CONTROL_MODES from opensourceleg.actuators.dephy import DephyActuator from opensourceleg.time import SoftRealtimeLoop from opensourceleg.units import units @@ -53,8 +54,8 @@ def make_periodic_trajectory(period, minimum, maximum): input("Homing complete: Press enter to continue") - knee.set_control_mode(knee.CONTROL_MODES.POSITION) - ankle.set_control_mode(ankle.CONTROL_MODES.POSITION) + knee.set_control_mode(CONTROL_MODES.POSITION) + ankle.set_control_mode(CONTROL_MODES.POSITION) knee.set_position_gains(kp=5) ankle.set_position_gains(kp=5) @@ -62,8 +63,8 @@ def make_periodic_trajectory(period, minimum, maximum): knee.update() ankle.update() - knee_setpoint = units.convert_to_default(knee_traj(t), units.position.deg) - ankle_setpoint = units.convert_to_default(ankle_traj(t), units.position.deg) + knee_setpoint = units.convert_to_default(knee_traj(t), units.Position.deg) + ankle_setpoint = units.convert_to_default(ankle_traj(t), units.Position.deg) knee.set_output_position(knee_setpoint) ankle.set_output_position(ankle_setpoint) diff --git a/opensourceleg/actuators/dephy.py b/opensourceleg/actuators/dephy.py index dd9e1db8..2945b903 100644 --- a/opensourceleg/actuators/dephy.py +++ b/opensourceleg/actuators/dephy.py @@ -314,7 +314,7 @@ def home( to create one. The encoder map is used to estimate joint position more accurately." ) - def make_encoder_map(self, overwrite=False) -> None: + def make_encoder_map(self, overwrite: bool = False) -> None: """ This method makes a lookup table to calculate the position measured by the joint encoder. This is necessary because the magnetic output encoders are nonlinear. @@ -332,11 +332,11 @@ def make_encoder_map(self, overwrite=False) -> None: if not self.is_homed: LOGGER.warning(msg=f"[{self.__repr__()}] Please home the {self.tag} joint before making the encoder map.") - return + return None if os.path.exists(f"./{self.tag}_encoder_map.npy") and not overwrite: LOGGER.info(msg=f"[{self.__repr__()}] Encoder map exists. Skipping encoder map creation.") - return + return None self.set_control_mode(mode=CONTROL_MODES.CURRENT) self.set_current_gains() @@ -370,7 +370,7 @@ def make_encoder_map(self, overwrite=False) -> None: except KeyboardInterrupt: LOGGER.warning(msg="Encoder map interrupted.") - return + return None LOGGER.info(msg=f"[{self.__repr__()}] You may now stop moving the {self.tag} joint.") @@ -418,7 +418,7 @@ def set_output_torque(self, value: float) -> None: def set_motor_current( self, value: float, - ): + ) -> None: """ Sets the motor current in mA. @@ -601,12 +601,12 @@ def set_motor_impedance( ff=ff, ) - def set_encoder_map(self, encoder_map) -> None: + def set_encoder_map(self, encoder_map: np.polynomial.polynomial.Polynomial) -> None: """Sets the joint encoder map""" - self._encoder_map = encoder_map + self._encoder_map: np.polynomial.polynomial.Polynomial = encoder_map @property - def encoder_map(self): + def encoder_map(self) -> Optional[np.polynomial.polynomial.Polynomial]: """Polynomial coefficients defining the joint encoder map from counts to radians.""" if getattr(self, "_encoder_map", None) is not None: return self._encoder_map @@ -777,7 +777,7 @@ def winding_temperature(self) -> float: return 0.0 @property - def genvars(self): + def genvars(self) -> np.ndarray: """Dephy's 'genvars' object.""" if self._data is not None: return np.array( @@ -1072,7 +1072,7 @@ def update(self) -> None: def set_motor_current( self, value: float, - ): + ) -> None: """ Sets the motor current in mA. @@ -1297,7 +1297,7 @@ def winding_temperature(self) -> float: return 0.0 @property - def genvars(self): + def genvars(self) -> np.ndarray: """Dephy's 'genvars' object.""" if self._data is not None: return np.array( @@ -1387,19 +1387,19 @@ def gyroy(self) -> float: return 0.0 @property - def is_streaming(self): + def is_streaming(self) -> bool: return self._is_streaming @is_streaming.setter - def is_streaming(self, value: bool): + def is_streaming(self, value: bool) -> None: self._is_streaming = value @property - def is_open(self): + def is_open(self) -> bool: return self._is_open @is_open.setter - def is_open(self, value: bool): + def is_open(self, value: bool) -> None: self._is_open = value @property diff --git a/opensourceleg/actuators/moteus.py b/opensourceleg/actuators/moteus.py index f772ac8a..efff6c24 100644 --- a/opensourceleg/actuators/moteus.py +++ b/opensourceleg/actuators/moteus.py @@ -274,7 +274,7 @@ async def update(self): self._command = self.make_query() - def home(self): + def home(self) -> None: # TODO: implement homing LOGGER.info(msg=f"[{self.__repr__()}] Homing not implemented.") @@ -309,7 +309,7 @@ def set_joint_torque(self, value: float) -> None: def set_motor_current( self, value: float, - ): + ) -> None: LOGGER.info("Current Mode Not Implemented") def set_motor_velocity(self, value: float) -> None: diff --git a/opensourceleg/collections/validators.py b/opensourceleg/collections/validators.py index 88360a0c..966227af 100644 --- a/opensourceleg/collections/validators.py +++ b/opensourceleg/collections/validators.py @@ -1,19 +1,20 @@ from abc import ABC, abstractmethod +from typing import Any class Validator(ABC): - def __set_name__(self, owner, name): + def __set_name__(self, owner, name) -> None: self.private_name = f"_{name}" - def __get__(self, instance, objtype=None): + def __get__(self, instance, objtype=None) -> Any: return getattr(instance, self.private_name) - def __set__(self, instance, value): + def __set__(self, instance, value) -> None: self.validate(value) setattr(instance, self.private_name, value) @abstractmethod - def validate(self, value): + def validate(self, value) -> None: pass @@ -22,7 +23,7 @@ def __init__(self, min_value=None, max_value=None) -> None: self.min_value = min_value self.max_value = max_value - def validate(self, value): + def validate(self, value) -> None: if not isinstance(value, (int, float)): raise TypeError("Value must be an int or float") @@ -38,7 +39,7 @@ def validate(self, value): class Gains: kp = Number(0, 100) - def __init__(self, price): + def __init__(self, price) -> None: self.kp = price g = Gains(200) diff --git a/opensourceleg/robots/osl.py b/opensourceleg/robots/osl.py index f89fc7fd..42d90108 100644 --- a/opensourceleg/robots/osl.py +++ b/opensourceleg/robots/osl.py @@ -13,20 +13,20 @@ class OpenSourceLeg(RobotBase[TActuator, TSensor]): - def start(self): + def start(self) -> None: super().start() - def stop(self): + def stop(self) -> None: super().stop() - def update(self): + def update(self) -> None: super().update() - def home(self): + def home(self) -> None: for actuator in self.actuators.values(): actuator.home() - def make_encoder_maps(self): + def make_encoder_maps(self) -> None: pass @property diff --git a/opensourceleg/safety/safety.py b/opensourceleg/safety/safety.py index 71a656f9..6429bde7 100644 --- a/opensourceleg/safety/safety.py +++ b/opensourceleg/safety/safety.py @@ -6,7 +6,7 @@ class ThermalLimitException(Exception): - def __init__(self, message="Software thermal limit exceeded. Exiting."): + def __init__(self, message="Software thermal limit exceeded. Exiting.") -> None: self.message = message super().__init__(self.message) @@ -16,7 +16,7 @@ def is_changing( max_points: int = 10, threshold: float = 1e-6, proxy_attribute_name: Optional[str] = None, -): +) -> Callable: """ Creates a decorator to check if a property's value is changing. If the standard deviation of the last 'max_points' values is less than 'threshold', @@ -65,7 +65,7 @@ def wrapper(instance, *args, **kwargs): return decorator -def is_negative(clamp: bool = False): +def is_negative(clamp: bool = False) -> Callable: """ Creates a decorator to check if a property's value is negative. diff --git a/opensourceleg/units/units.py b/opensourceleg/units/units.py index 4f67c63e..30768787 100644 --- a/opensourceleg/units/units.py +++ b/opensourceleg/units/units.py @@ -33,63 +33,63 @@ class Force(float, Enum): - N: 1.0 - lbf: 4.4482216152605 - kgf: 9.80665 + N = 1.0 + lbf = 4.4482216152605 + kgf = 9.80665 class Torque(float, Enum): - N_m: 1.0 - lbf_inch: 0.1129848290276167 - kgf_cm: 0.0980665 + N_m = 1.0 + lbf_inch = 0.1129848290276167 + kgf_cm = 0.0980665 class Stiffness(float, Enum): - N_m_per_rad: 1.0 - N_m_per_deg: 0.017453292519943295 + N_m_per_rad = 1.0 + N_m_per_deg = 0.017453292519943295 class Damping(float, Enum): - N_m_per_rad_per_s: 1.0 - N_m_per_deg_per_s: 0.017453292519943295 + N_m_per_rad_per_s = 1.0 + N_m_per_deg_per_s = 0.017453292519943295 class Length(float, Enum): - m: 1.0 - cm: 0.01 - inch: 0.0254 + m = 1.0 + cm = 0.01 + inch = 0.0254 class Position(float, Enum): - rad: 1.0 - deg: 0.017453292519943295 + rad = 1.0 + deg = 0.017453292519943295 class Mass(float, Enum): - kg: 1.0 - g: 0.001 - lb: 0.45359237 + kg = 1.0 + g = 0.001 + lb = 0.45359237 class Velocity(float, Enum): - rad_per_s: 1.0 - deg_per_s: 0.017453292519943295 - rpm: 0.10471975511965977 + rad_per_s = 1.0 + deg_per_s = 0.017453292519943295 + rpm = 0.10471975511965977 class Acceleration(float, Enum): - rad_per_s2: 1.0 - deg_per_s2: 0.017453292519943295 + rad_per_s2 = 1.0 + deg_per_s2 = 0.017453292519943295 class Current(float, Enum): - mA: 1 - A: 1000 + mA = 1 + A = 1000 class Voltage(float, Enum): - mV: 1 - V: 1000 + mV = 1 + V = 1000 def convert_to_default(value: float, from_unit: float) -> float: From 35c50285281f214ea1525acb8c4077ee368a6a73 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Fri, 15 Nov 2024 15:07:41 -0500 Subject: [PATCH 10/19] Fixed some more mypy errors. --- opensourceleg/actuators/tmotor.py | 48 +++++++++++----------- opensourceleg/collections/validators.py | 22 +++++----- opensourceleg/logging/decorators.py | 17 ++++---- opensourceleg/math/math.py | 22 +++++----- opensourceleg/sensors/base.py | 12 +++--- opensourceleg/time/time.py | 54 +++++++++++++------------ 6 files changed, 92 insertions(+), 83 deletions(-) diff --git a/opensourceleg/actuators/tmotor.py b/opensourceleg/actuators/tmotor.py index f04d949d..969151a1 100644 --- a/opensourceleg/actuators/tmotor.py +++ b/opensourceleg/actuators/tmotor.py @@ -345,7 +345,7 @@ def _send_command(self): # getters for motor state @property - def case_temperature(self): + def case_temperature(self) -> float: """ Returns: The most recently updated motor temperature in degrees C. @@ -364,7 +364,7 @@ def winding_temperature(self) -> float: return 0.0 @property - def motor_current(self): + def motor_current(self) -> float: """ Returns: The most recently updated qaxis current in amps @@ -372,12 +372,12 @@ def motor_current(self): return self._motor_state.current @property - def motor_voltage(self): + def motor_voltage(self) -> float: # Not implemented return 0.0 @property - def output_position(self): + def output_position(self) -> float: """ Returns: The most recently updated output angle in radians @@ -385,7 +385,7 @@ def output_position(self): return self._motor_state.position @property - def output_velocity(self): + def output_velocity(self) -> float: """ Returns: The most recently updated output velocity in radians per second @@ -393,7 +393,7 @@ def output_velocity(self): return self._motor_state.velocity @property - def output_acceleration(self): + def output_acceleration(self) -> float: """ Returns: The most recently updated output acceleration in radians per second per second @@ -401,7 +401,7 @@ def output_acceleration(self): return self._motor_state.acceleration @property - def output_torque(self): + def output_torque(self) -> float: """ Returns: the most recently updated output torque in Nm @@ -409,7 +409,7 @@ def output_torque(self): return self.motor_current * MIT_Params[self.type]["Kt_actual"] * MIT_Params[self.type]["GEAR_RATIO"] # uses plain impedance mode, will send 0.0 for current command. - def set_impedance_gains(self, kp=0, ki=0, K=0.08922, B=0.0038070, ff=0): + def set_impedance_gains(self, kp=0, ki=0, K=0.08922, B=0.0038070, ff=0) -> None: """ Uses plain impedance mode, will send 0.0 for current command in addition to position request. @@ -436,7 +436,7 @@ def set_impedance_gains(self, kp=0, ki=0, K=0.08922, B=0.0038070, ff=0): self._command.kd = B self._command.velocity = 0.0 - def set_current_gains(self, kp=40, ki=400, ff=128, spoof=False): + def set_current_gains(self, kp=40, ki=400, ff=128, spoof=False) -> None: """ Uses plain current mode, will send 0.0 for position gains in addition to requested current. @@ -448,7 +448,7 @@ def set_current_gains(self, kp=40, ki=400, ff=128, spoof=False): """ pass - def set_velocity_gains(self, kd=1.0): + def set_velocity_gains(self, kd=1.0) -> None: """ Uses plain speed mode, will send 0.0 for position gain and for feed forward current. @@ -457,12 +457,12 @@ def set_velocity_gains(self, kd=1.0): """ self._command.kd = kd - def set_position_gains(self): + def set_position_gains(self) -> None: # Not implemented pass # used for either impedance or MIT mode to set output angle - def set_output_position(self, value): + def set_output_position(self, value: float) -> None: """ Used for either impedance or full state feedback mode to set output angle command. Note, this does not send a command, it updates the TMotorManager's saved command, @@ -483,7 +483,7 @@ def set_output_position(self, value): self._command.position = value - def set_output_velocity(self, value): + def set_output_velocity(self, value: float) -> None: """ Used for either speed or full state feedback mode to set output velocity command. Note, this does not send a command, it updates the TMotorManager's saved command, @@ -506,7 +506,7 @@ def set_output_velocity(self, value): # used for either current MIT mode to set current - def set_motor_current(self, value): + def set_motor_current(self, value: float) -> None: """ Used for either current or full state feedback mode to set current command. Note, this does not send a command, it updates the TMotorManager's saved command, @@ -518,7 +518,7 @@ def set_motor_current(self, value): self._command.current = value # used for either current or MIT Mode to set current, based on desired torque - def set_joint_torque(self, value): + def set_joint_torque(self, value: float) -> None: """ Used for either current or MIT Mode to set current, based on desired torque. If a more complicated torque model is available for the motor, that will be used. @@ -530,7 +530,7 @@ def set_joint_torque(self, value): self.set_motor_current(value / MIT_Params[self.type]["Kt_actual"] / MIT_Params[self.type]["GEAR_RATIO"]) # motor-side functions to account for the gear ratio - def set_motor_torque(self, value): + def set_motor_torque(self, value: float) -> None: """ Version of set_output_torque that accounts for gear ratio to control motor-side torque @@ -539,7 +539,7 @@ def set_motor_torque(self, value): """ self.set_output_torque(value * MIT_Params[self.type]["Kt_actual"]) - def set_motor_position(self, value): + def set_motor_position(self, value: float) -> None: """ Wrapper for set_output_angle that accounts for gear ratio to control motor-side angle @@ -548,7 +548,7 @@ def set_motor_position(self, value): """ self.set_output_position(value / (MIT_Params[self.type]["GEAR_RATIO"])) - def set_motor_velocity(self, value): + def set_motor_velocity(self, value: float) -> None: """ Wrapper for set_output_velocity that accounts for gear ratio to control motor-side velocity @@ -557,12 +557,12 @@ def set_motor_velocity(self, value): """ self.set_output_velocity(value / (MIT_Params[self.type]["GEAR_RATIO"])) - def set_motor_voltage(self, value): + def set_motor_voltage(self, value: float) -> float: # Not implemented pass @property - def motor_position(self): + def motor_position(self) -> float: """ Wrapper for get_output_angle that accounts for gear ratio to get motor-side angle @@ -572,7 +572,7 @@ def motor_position(self): return self._motor_state.position * MIT_Params[self.type]["GEAR_RATIO"] @property - def motor_velocity(self): + def motor_velocity(self) -> float: """ Wrapper for get_output_velocity that accounts for gear ratio to get motor-side velocity @@ -582,7 +582,7 @@ def motor_velocity(self): return self._motor_state.velocity * MIT_Params[self.type]["GEAR_RATIO"] @property - def motor_acceleration(self): + def motor_acceleration(self) -> float: """ Wrapper for get_output_acceleration that accounts for gear ratio to get motor-side acceleration @@ -592,7 +592,7 @@ def motor_acceleration(self): return self._motor_state.acceleration * MIT_Params[self.type]["GEAR_RATIO"] @property - def motor_torque(self): + def motor_torque(self) -> float: """ Wrapper for get_output_torque that accounts for gear ratio to get motor-side torque @@ -602,7 +602,7 @@ def motor_torque(self): return self.output_torque * MIT_Params[self.type]["GEAR_RATIO"] # Pretty stuff - def __str__(self): + def __str__(self) -> str: """Prints the motor's device info and current""" return ( self.device_info_string() diff --git a/opensourceleg/collections/validators.py b/opensourceleg/collections/validators.py index 966227af..a5e00fb6 100644 --- a/opensourceleg/collections/validators.py +++ b/opensourceleg/collections/validators.py @@ -1,29 +1,31 @@ from abc import ABC, abstractmethod -from typing import Any +from typing import Any, Optional, Union class Validator(ABC): - def __set_name__(self, owner, name) -> None: + def __set_name__(self, name: str) -> None: self.private_name = f"_{name}" - def __get__(self, instance, objtype=None) -> Any: + def __get__(self, instance: Any, objtype: Any = None) -> Any: return getattr(instance, self.private_name) - def __set__(self, instance, value) -> None: + def __set__(self, instance: Any, value: Any) -> None: self.validate(value) setattr(instance, self.private_name, value) @abstractmethod - def validate(self, value) -> None: + def validate(self, value: Any) -> None: pass class Number(Validator): - def __init__(self, min_value=None, max_value=None) -> None: - self.min_value = min_value - self.max_value = max_value + def __init__( + self, min_value: Optional[Union[int, float]] = None, max_value: Optional[Union[int, float]] = None + ) -> None: + self.min_value: Optional[Union[int, float]] = min_value + self.max_value: Optional[Union[int, float]] = max_value - def validate(self, value) -> None: + def validate(self, value: Union[int, float]) -> None: if not isinstance(value, (int, float)): raise TypeError("Value must be an int or float") @@ -39,7 +41,7 @@ def validate(self, value) -> None: class Gains: kp = Number(0, 100) - def __init__(self, price) -> None: + def __init__(self, price: int) -> None: self.kp = price g = Gains(200) diff --git a/opensourceleg/logging/decorators.py b/opensourceleg/logging/decorators.py index fcfdf238..26ae92db 100644 --- a/opensourceleg/logging/decorators.py +++ b/opensourceleg/logging/decorators.py @@ -1,29 +1,30 @@ from functools import wraps +from typing import Callable from opensourceleg.logging import LOGGER -def deprecated(func): +def deprecated(func: Callable) -> Callable: """ Decorator to mark a function as deprecated. """ @wraps(func) - def wrapper(*args, **kwargs): + def wrapper(*args, **kwargs) -> Callable: LOGGER.warning(f"Function `{func.__name__}` is deprecated.") return func(*args, **kwargs) return wrapper -def deprecated_with_suggestion(alternative_func): +def deprecated_with_suggestion(alternative_func: Callable) -> Callable: """ Decorator to provide an alternative function for a deprecated function. """ - def decorator(func): + def decorator(func: Callable) -> Callable: @wraps(func) - def wrapper(*args, **kwargs): + def wrapper(*args, **kwargs) -> Callable: LOGGER.warning( f"Function `{func.__name__}` is deprecated. Please use `{alternative_func.__name__}` instead." ) @@ -34,15 +35,15 @@ def wrapper(*args, **kwargs): return decorator -def deprecated_with_routing(alternative_func): +def deprecated_with_routing(alternative_func: Callable) -> Callable: """ Decorator to provide an alternative function for a deprecated function. The alternative function will be called instead of the deprecated function. """ - def decorator(func): + def decorator(func: Callable) -> Callable: @wraps(func) - def wrapper(*args, **kwargs): + def wrapper(*args, **kwargs) -> Callable: LOGGER.warning( f"Function `{func.__name__}` is deprecated. Please use `{alternative_func.__name__}` instead," "which will be called automatically now." diff --git a/opensourceleg/math/math.py b/opensourceleg/math/math.py index c4ee8cc6..6397ba42 100644 --- a/opensourceleg/math/math.py +++ b/opensourceleg/math/math.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any, Optional, Union import numpy as np @@ -103,7 +103,7 @@ def update(self, dt: float = 1 / 200, motor_current: float = 0) -> None: self.T_w += dt * dTw_dt self.T_c += dt * dTc_dt - def update_and_get_scale(self, dt, motor_current: float = 0, FOS: float = 1.0): + def update_and_get_scale(self, dt: float, motor_current: float = 0, FOS: float = 1.0) -> float: """ Updates the temperature of the winding and the case based on the current and the ambient temperature and returns the scale factor for the torque. @@ -164,15 +164,15 @@ class EdgeDetector: https://github.com/tkevinbest """ - def __init__(self, bool_in): - self.cur_state = bool_in - self.rising_edge = False - self.falling_edge = False + def __init__(self, bool_in: bool) -> None: + self.cur_state: bool = bool_in + self.rising_edge: bool = False + self.falling_edge: bool = False def __repr__(self) -> str: return "EdgeDetector" - def update(self, bool_in): + def update(self, bool_in: bool) -> None: self.rising_edge = bool_in and not self.cur_state self.falling_edge = not bool_in and self.cur_state self.cur_state = bool_in @@ -196,7 +196,7 @@ class SaturatingRamp: https://github.com/tkevinbest """ - def __init__(self, loop_frequency=100, ramp_time=1.0) -> None: + def __init__(self, loop_frequency: float = 100, ramp_time: float = 1.0) -> None: """ Args: loop_frequency (int, optional): Rate in Hz (default 100 Hz). Defaults to 100. @@ -208,7 +208,7 @@ def __init__(self, loop_frequency=100, ramp_time=1.0) -> None: def __repr__(self) -> str: return "SaturatingRamp" - def update(self, enable_ramp=False): + def update(self, enable_ramp: bool = False) -> float: """ Updates the ramp value and returns it as a float. If enable_ramp is true, ramp value increases @@ -230,7 +230,9 @@ def update(self, enable_ramp=False): return self.value -def clamp_within_vector_range(input_value, input_vector): +def clamp_within_vector_range( + input_value: Union[float, int], input_vector: list[Union[float, int]] +) -> Union[float, int]: """ This function ensures that input_value remains within the range spanned by the input_vector. If the input_value falls outside the vector's bounds, it'll return the appropriate max or min value from the vector. diff --git a/opensourceleg/sensors/base.py b/opensourceleg/sensors/base.py index 4b80581e..078acaf5 100644 --- a/opensourceleg/sensors/base.py +++ b/opensourceleg/sensors/base.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod from functools import wraps +from typing import Any, Callable class SensorNotStreamingException(Exception): @@ -10,10 +11,11 @@ def __init__(self, sensor_name: str = "Sensor") -> None: ) -def check_sensor_stream(func): +def check_sensor_stream(func: Callable) -> Callable: @wraps(func) - def wrapper(self, *args, **kwargs): - if not self.is_streaming: + def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any: + # TODO: This could be a generic type that points to actuator, sensor, etc. + if self.is_streaming: raise SensorNotStreamingException(sensor_name=self.__repr__()) return func(self, *args, **kwargs) @@ -26,7 +28,7 @@ def __repr__(self) -> str: @property @abstractmethod - def data(self): + def data(self) -> Any: pass @abstractmethod @@ -41,7 +43,7 @@ def stop(self) -> None: def update(self) -> None: pass - def __enter__(self): + def __enter__(self) -> "SensorBase": self.start() return self diff --git a/opensourceleg/time/time.py b/opensourceleg/time/time.py index e51d8e4f..6db4a7c7 100644 --- a/opensourceleg/time/time.py +++ b/opensourceleg/time/time.py @@ -1,6 +1,7 @@ import signal import time from math import sqrt +from typing import Any, Callable, Optional PRECISION_OF_SLEEP = 0.0001 @@ -26,20 +27,20 @@ class LoopKiller: """ - def __init__(self, fade_time=0.0): + def __init__(self, fade_time: float = 0.0): signal.signal(signal.SIGTERM, self.handle_signal) signal.signal(signal.SIGINT, self.handle_signal) signal.signal(signal.SIGHUP, self.handle_signal) - self._fade_time = fade_time - self._soft_kill_time = None + self._fade_time: float = fade_time + self._soft_kill_time: float = 0.0 def __repr__(self) -> str: return "LoopKiller" - def handle_signal(self, signum, frame): + def handle_signal(self, signum: Any, frame: Any) -> None: self.kill_now = True - def get_fade(self): + def get_fade(self) -> float: # interpolates from 1 to zero with soft fade out if self._kill_soon: t = time.monotonic() - self._soft_kill_time @@ -52,7 +53,7 @@ def get_fade(self): _kill_soon = False @property - def kill_now(self): + def kill_now(self) -> bool: if self._kill_now: return True if self._kill_soon: @@ -62,7 +63,7 @@ def kill_now(self): return self._kill_now @kill_now.setter - def kill_now(self, val): + def kill_now(self, val: bool) -> None: if val: if self._kill_soon: # if you kill twice, then it becomes immediate self._kill_now = True @@ -75,7 +76,7 @@ def kill_now(self, val): else: self._kill_now = False self._kill_soon = False - self._soft_kill_time = None + self._soft_kill_time = 0.0 class SoftRealtimeLoop: @@ -99,21 +100,22 @@ class SoftRealtimeLoop: """ - def __init__(self, dt=0.001, report=False, fade=0.0): - self.t0 = self.t1 = time.monotonic() - self.killer = LoopKiller(fade_time=fade) - self.dt = dt - self.ttarg = None - self.sum_err = 0.0 - self.sum_var = 0.0 - self.sleep_t_agg = 0.0 - self.n = 0 - self.report = report + def __init__(self, dt: float = 0.001, report: bool = False, fade: float = 0.0): + self.t0: float = time.monotonic() + self.t1: float = self.t0 + self.killer: LoopKiller = LoopKiller(fade_time=fade) + self.dt: float = dt + self.ttarg: Any = None + self.sum_err: float = 0.0 + self.sum_var: float = 0.0 + self.sleep_t_agg: float = 0.0 + self.n: int = 0 + self.report: bool = report def __repr__(self) -> str: return "SoftRealtimeLoop" - def __del__(self): + def __del__(self) -> None: if self.report: print("In %d cycles at %.2f Hz:" % (self.n, 1.0 / self.dt)) print("\tavg error: %.3f milliseconds" % (1e3 * self.sum_err / self.n)) @@ -124,10 +126,10 @@ def __del__(self): print("\tpercent of time sleeping: %.1f %%" % (self.sleep_t_agg / self.time() * 100.0)) @property - def fade(self): + def fade(self) -> float: return self.killer.get_fade() - def run(self, function_in_loop, dt=None): + def run(self, function_in_loop: Callable, dt: Optional[float] = None) -> None: if dt is None: dt = self.dt self.t0 = self.t1 = time.monotonic() + dt @@ -140,20 +142,20 @@ def run(self, function_in_loop, dt=None): self.stop() self.t1 += dt - def stop(self): + def stop(self) -> None: self.killer.kill_now = True - def time(self): + def time(self) -> float: return time.monotonic() - self.t0 - def time_since(self): + def time_since(self) -> float: return time.monotonic() - self.t1 - def __iter__(self): + def __iter__(self) -> "SoftRealtimeLoop": self.t0 = self.t1 = time.monotonic() + self.dt return self - def __next__(self): + def __next__(self) -> float: if self.killer.kill_now: raise StopIteration From 554e09c7fcf422dbcde1f78c8c8e063d06d21cbb Mon Sep 17 00:00:00 2001 From: imsenthur Date: Wed, 20 Nov 2024 15:17:44 -0500 Subject: [PATCH 11/19] Fixed all mypy errors. Ignored tmotor and moteus files. --- Makefile | 2 +- opensourceleg/actuators/base.py | 20 +- opensourceleg/actuators/decorators.py | 13 +- opensourceleg/actuators/dephy.py | 2 +- opensourceleg/actuators/tmotor.py | 20 +- opensourceleg/benchmarks/decorators.py | 7 +- opensourceleg/benchmarks/threads.py | 8 +- opensourceleg/control/compiled_controller.py | 39 ++- opensourceleg/control/state_machine.py | 34 +- opensourceleg/logging/decorators.py | 14 +- opensourceleg/logging/logger.py | 34 +- opensourceleg/math/math.py | 2 +- opensourceleg/robots/base.py | 16 +- opensourceleg/robots/osl.py | 9 +- opensourceleg/safety/safety.py | 79 ++--- opensourceleg/sensors/adc.py | 4 +- opensourceleg/sensors/base.py | 2 +- opensourceleg/sensors/imu.py | 27 +- opensourceleg/sensors/loadcell.py | 26 +- poetry.lock | 343 ++++++++++++------- pyproject.toml | 3 + 21 files changed, 409 insertions(+), 295 deletions(-) diff --git a/Makefile b/Makefile index 1df79e5d..f258e2bc 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ check: ## Run code quality tools. @echo "πŸš€ Static type checking: Running mypy" @poetry run mypy @echo "πŸš€ Checking for obsolete dependencies: Running deptry" - @poetry run deptry . + @poetry run deptry . --ignore DEP002,DEP001 .PHONY: test test: ## Test the code with pytest diff --git a/opensourceleg/actuators/base.py b/opensourceleg/actuators/base.py index a31346e5..5efd2e62 100644 --- a/opensourceleg/actuators/base.py +++ b/opensourceleg/actuators/base.py @@ -30,7 +30,7 @@ class MOTOR_CONSTANTS: MAX_CASE_TEMPERATURE: float MAX_WINDING_TEMPERATURE: float - def __post_init__(self): + def __post_init__(self) -> None: if any(x <= 0 for x in self.__dict__.values()): raise ValueError("All values in MOTOR_CONSTANTS must be non-zero and positive.") @@ -107,13 +107,13 @@ class MethodWithRequiredModes(Protocol): _required_modes: set[CONTROL_MODES] -def requires(*modes: CONTROL_MODES): +def requires(*modes: CONTROL_MODES) -> Callable[[T], T]: def decorator(func: T) -> T: if not all(isinstance(mode, CONTROL_MODES) for mode in modes): raise TypeError("All arguments to 'requires' must be of type CONTROL_MODES") if not hasattr(func, "_required_modes"): - func._required_modes = set(modes) + func._required_modes = set(modes) # type: ignore[attr-defined] else: func._required_modes.update(modes) @@ -130,8 +130,8 @@ def __init__( motor_constants: MOTOR_CONSTANTS, frequency: int = 1000, offline: bool = False, - *args, - **kwargs, + *args: Any, + **kwargs: Any, ) -> None: self._MOTOR_CONSTANTS: MOTOR_CONSTANTS = motor_constants self._gear_ratio: float = gear_ratio @@ -158,18 +158,18 @@ def __init__( self._set_original_methods() self._set_mutated_methods() - def __enter__(self): + def __enter__(self) -> "ActuatorBase": self.start() return self - def __exit__(self, exc_type, exc_value, exc_traceback): + def __exit__(self, exc_type: Any, exc_value: Any, exc_traceback: Any) -> None: self.stop() - def _restricted_method(self, method_name: str, *args, **kwargs): + def _restricted_method(self, method_name: str, *args: Any, **kwargs: Any) -> None: LOGGER.error(f"{method_name}() is not available in {self._mode.name} mode.") return None - def _set_original_methods(self): + def _set_original_methods(self) -> None: for method_name in CONTROL_MODE_METHODS: try: method = getattr(self, method_name) @@ -178,7 +178,7 @@ def _set_original_methods(self): except AttributeError: LOGGER.debug(msg=f"[{self.tag}] {method_name}() is not implemented in {self.tag}.") - def _set_mutated_methods(self): + def _set_mutated_methods(self) -> None: for method_name, method in self._original_methods.items(): if self._mode in method._required_modes: setattr(self, method_name, method) diff --git a/opensourceleg/actuators/decorators.py b/opensourceleg/actuators/decorators.py index 19a716d8..df0de834 100644 --- a/opensourceleg/actuators/decorators.py +++ b/opensourceleg/actuators/decorators.py @@ -1,4 +1,5 @@ from functools import wraps +from typing import Any, Callable from opensourceleg.actuators.base import ActuatorBase from opensourceleg.logging.exceptions import ( @@ -7,9 +8,9 @@ ) -def check_actuator_connection(func): +def check_actuator_connection(func: Callable) -> Callable: @wraps(func) - def wrapper(self: ActuatorBase, *args, **kwargs): + def wrapper(self: ActuatorBase, *args: Any, **kwargs: Any) -> Any: if self.is_offline: raise ActuatorConnectionException(tag=self.tag) @@ -18,9 +19,9 @@ def wrapper(self: ActuatorBase, *args, **kwargs): return wrapper -def check_actuator_open(func): +def check_actuator_open(func: Callable) -> Callable: @wraps(func) - def wrapper(self: ActuatorBase, *args, **kwargs): + def wrapper(self: ActuatorBase, *args: Any, **kwargs: Any) -> Any: if not self.is_open: raise ActuatorConnectionException(tag=self.tag) @@ -29,9 +30,9 @@ def wrapper(self: ActuatorBase, *args, **kwargs): return wrapper -def check_actuator_stream(func): +def check_actuator_stream(func: Callable) -> Callable: @wraps(func) - def wrapper(self: ActuatorBase, *args, **kwargs): + def wrapper(self: ActuatorBase, *args: Any, **kwargs: Any) -> Any: if not self.is_streaming: raise ActuatorStreamException(tag=self.tag) diff --git a/opensourceleg/actuators/dephy.py b/opensourceleg/actuators/dephy.py index 2945b903..30fd18e5 100644 --- a/opensourceleg/actuators/dephy.py +++ b/opensourceleg/actuators/dephy.py @@ -119,7 +119,7 @@ def _dephy_impedance_mode_exit(dephy_actuator: "DephyActuator") -> None: ) -class DephyActuator(Device, ActuatorBase): +class DephyActuator(Device, ActuatorBase): # type: ignore[no-any-unimported] def __init__( self, tag: str = "DephyActuator", diff --git a/opensourceleg/actuators/tmotor.py b/opensourceleg/actuators/tmotor.py index 969151a1..b349279d 100644 --- a/opensourceleg/actuators/tmotor.py +++ b/opensourceleg/actuators/tmotor.py @@ -350,7 +350,7 @@ def case_temperature(self) -> float: Returns: The most recently updated motor temperature in degrees C. """ - return self._motor_state.temperature + return float(self._motor_state.temperature) @property def winding_temperature(self) -> float: @@ -369,7 +369,7 @@ def motor_current(self) -> float: Returns: The most recently updated qaxis current in amps """ - return self._motor_state.current + return float(self._motor_state.current) @property def motor_voltage(self) -> float: @@ -382,7 +382,7 @@ def output_position(self) -> float: Returns: The most recently updated output angle in radians """ - return self._motor_state.position + return float(self._motor_state.position) @property def output_velocity(self) -> float: @@ -390,7 +390,7 @@ def output_velocity(self) -> float: Returns: The most recently updated output velocity in radians per second """ - return self._motor_state.velocity + return float(self._motor_state.velocity) @property def output_acceleration(self) -> float: @@ -398,7 +398,7 @@ def output_acceleration(self) -> float: Returns: The most recently updated output acceleration in radians per second per second """ - return self._motor_state.acceleration + return float(self._motor_state.acceleration) @property def output_torque(self) -> float: @@ -406,7 +406,7 @@ def output_torque(self) -> float: Returns: the most recently updated output torque in Nm """ - return self.motor_current * MIT_Params[self.type]["Kt_actual"] * MIT_Params[self.type]["GEAR_RATIO"] + return float(self.motor_current * MIT_Params[self.type]["Kt_actual"] * MIT_Params[self.type]["GEAR_RATIO"]) # uses plain impedance mode, will send 0.0 for current command. def set_impedance_gains(self, kp=0, ki=0, K=0.08922, B=0.0038070, ff=0) -> None: @@ -569,7 +569,7 @@ def motor_position(self) -> float: Returns: The most recently updated motor-side angle in rad. """ - return self._motor_state.position * MIT_Params[self.type]["GEAR_RATIO"] + return float(self._motor_state.position * MIT_Params[self.type]["GEAR_RATIO"]) @property def motor_velocity(self) -> float: @@ -579,7 +579,7 @@ def motor_velocity(self) -> float: Returns: The most recently updated motor-side velocity in rad/s. """ - return self._motor_state.velocity * MIT_Params[self.type]["GEAR_RATIO"] + return float(self._motor_state.velocity * MIT_Params[self.type]["GEAR_RATIO"]) @property def motor_acceleration(self) -> float: @@ -589,7 +589,7 @@ def motor_acceleration(self) -> float: Returns: The most recently updated motor-side acceleration in rad/s/s. """ - return self._motor_state.acceleration * MIT_Params[self.type]["GEAR_RATIO"] + return float(self._motor_state.acceleration * MIT_Params[self.type]["GEAR_RATIO"]) @property def motor_torque(self) -> float: @@ -599,7 +599,7 @@ def motor_torque(self) -> float: Returns: The most recently updated motor-side torque in Nm. """ - return self.output_torque * MIT_Params[self.type]["GEAR_RATIO"] + return float(self.output_torque * MIT_Params[self.type]["GEAR_RATIO"]) # Pretty stuff def __str__(self) -> str: diff --git a/opensourceleg/benchmarks/decorators.py b/opensourceleg/benchmarks/decorators.py index daf561ab..1fbdbf80 100644 --- a/opensourceleg/benchmarks/decorators.py +++ b/opensourceleg/benchmarks/decorators.py @@ -1,11 +1,12 @@ import timeit from functools import wraps +from typing import Any, Callable -def profile_time(iterations): - def decorator(func): +def profile_time(iterations: int) -> Callable: + def decorator(func: Callable) -> Callable: @wraps(func) - def wrapper(*args, **kwargs): + def wrapper(*args: Any, **kwargs: Any) -> None: timer = timeit.Timer(lambda: func(*args, **kwargs)) execution_time = timer.timeit(number=iterations) print(f"Execution time for {func.__name__} over {iterations} iterations: {execution_time} seconds") diff --git a/opensourceleg/benchmarks/threads.py b/opensourceleg/benchmarks/threads.py index 037fd318..5c0a7e59 100644 --- a/opensourceleg/benchmarks/threads.py +++ b/opensourceleg/benchmarks/threads.py @@ -8,7 +8,7 @@ FREQ = 1000 -def core_function(): +def core_function() -> None: global counter counter += 1 # print("Counter: ", counter) @@ -16,13 +16,13 @@ def core_function(): @profile_time(iterations=PROFILING_ITERATIONS) -def basic_counter(): +def basic_counter() -> None: for _ in range(10): core_function() @profile_time(iterations=PROFILING_ITERATIONS) -def threaded_counter(): +def threaded_counter() -> None: threads = [] for _ in range(10): thread = threading.Thread(target=core_function) @@ -33,7 +33,7 @@ def threaded_counter(): thread.join() -def main(): +def main() -> None: basic_counter() threaded_counter() diff --git a/opensourceleg/control/compiled_controller.py b/opensourceleg/control/compiled_controller.py index d2239e9b..eb0f8be2 100644 --- a/opensourceleg/control/compiled_controller.py +++ b/opensourceleg/control/compiled_controller.py @@ -1,5 +1,5 @@ import ctypes -from typing import Any +from typing import Any, Callable, Optional import numpy.ctypeslib as ctl @@ -39,17 +39,16 @@ class CompiledController: def __init__( self, - library_name, - library_path, - main_function_name, - initialization_function_name=None, - cleanup_function_name=None, + library_name: str, + library_path: str, + main_function_name: str, + initialization_function_name: Optional[str] = None, + cleanup_function_name: Optional[str] = None, ) -> None: - self.cleanup_func = None - self.lib = ctl.load_library(library_name, library_path) - self.cleanup_func = self._load_function(cleanup_function_name) - self.main_function = self._load_function(main_function_name) - self.init_function = self._load_function(initialization_function_name) + self.lib: Any = ctl.load_library(library_name, library_path) + self.cleanup_func: Callable = self._load_function(cleanup_function_name) + self.main_function: Callable = self._load_function(main_function_name) + self.init_function: Callable = self._load_function(initialization_function_name) # Note if requested function name is None, returned handle is also none if self.init_function is not None: @@ -71,14 +70,14 @@ def __init__( self._output_type = None self.outputs = None - def __del__(self): + def __del__(self) -> None: if self.cleanup_func is not None: self.cleanup_func() - def __repr__(self): + def __repr__(self) -> str: return "CompiledController" - def _load_function(self, function_name): + def _load_function(self, function_name: Optional[str]) -> Any: if function_name is None: return None else: @@ -102,6 +101,10 @@ def define_inputs(self, input_list: list[Any]) -> None: All types can be accessed as CompiledController.types.(type_name) """ self._input_type = self.define_type("inputs", input_list) + + if self._input_type is None: + raise ValueError("Input type not defined properly. Check define_type() method.") + self.inputs = self._input_type() def define_outputs(self, output_list: list[Any]) -> None: @@ -118,9 +121,13 @@ def define_outputs(self, output_list: list[Any]) -> None: All types can be accessed as CompiledController.types.(type_name) """ self._output_type = self.define_type("outputs", output_list) + + if self._output_type is None: + raise ValueError("Output type not defined properly. Check define_type() method.") + self.outputs = self._output_type() - def define_type(self, type_name: str, parameter_list: list[Any]): + def define_type(self, type_name: str, parameter_list: list[Any]) -> Any: """ This method defines a new type to be used in the compiled controller. After calling this method, the datatype with name type_name will be @@ -153,7 +160,7 @@ class CustomStructure(ctypes.Structure): setattr(self.types, type_name, CustomStructure) return getattr(self.types, type_name) - def run(self): + def run(self) -> Any: """ This method calls the main controller function of the library. Under the hood, it calls library_name.main_function_name(*inputs, *outputs), diff --git a/opensourceleg/control/state_machine.py b/opensourceleg/control/state_machine.py index 04a27508..341e4fbd 100644 --- a/opensourceleg/control/state_machine.py +++ b/opensourceleg/control/state_machine.py @@ -78,10 +78,10 @@ def __init__( self._entry_callbacks: list[Callable[[Any], None]] = [] self._exit_callbacks: list[Callable[[Any], None]] = [] - def __eq__(self, __o) -> bool: - return __o.name == self._name + def __eq__(self, __o: Any) -> bool: + return bool(__o.name == self._name) - def __ne__(self, __o) -> bool: + def __ne__(self, __o: Any) -> bool: return not self.__eq__(__o) def __call__(self, data: Any) -> Any: @@ -99,7 +99,7 @@ def set_minimum_time_spent_in_state(self, time: float) -> None: """ self._min_time_in_state = time - def set_knee_impedance_paramters(self, theta, k, b) -> None: + def set_knee_impedance_paramters(self, theta: float, k: float, b: float) -> None: """ Set the knee impedance parameters @@ -117,7 +117,7 @@ def set_knee_impedance_paramters(self, theta, k, b) -> None: self._knee_stiffness = k self._knee_damping = b - def set_ankle_impedance_paramters(self, theta, k, b) -> None: + def set_ankle_impedance_paramters(self, theta: float, k: float, b: float) -> None: """ Set the ankle impedance parameters @@ -189,7 +189,7 @@ def stop(self, data: Any) -> None: for c in self._exit_callbacks: c(data) - def make_knee_active(self): + def make_knee_active(self) -> None: """ Make the knee active @@ -199,7 +199,7 @@ def make_knee_active(self): """ self._is_knee_active = True - def make_ankle_active(self): + def make_ankle_active(self) -> None: """ Make the ankle active @@ -273,26 +273,26 @@ class Event: Event class """ - def __init__(self, name) -> None: + def __init__(self, name: str) -> None: """ Parameters ---------- name : str The name of the event. """ - self._name = name + self._name: str = name - def __eq__(self, __o) -> bool: - return __o.name == self._name + def __eq__(self, __o: Any) -> bool: + return bool(__o.name == self._name) - def __ne__(self, __o) -> bool: + def __ne__(self, __o: Any) -> bool: return not self.__eq__(__o) # TODO: Check this fix def __repr__(self) -> str: return f"Event[{self._name}]" @property - def name(self): + def name(self) -> str: return self._name @@ -404,7 +404,7 @@ class StateMachine: Whether or not the state machine is spoofing the state transitions. """ - def __init__(self, osl=None, spoof: bool = False) -> None: + def __init__(self, osl: object = None, spoof: bool = False) -> None: # State Machine Variables self._states: list[State] = [] self._events: list[Event] = [] @@ -523,18 +523,18 @@ def spoof(self, spoof: bool) -> None: self._spoof = spoof @property - def current_state(self): + def current_state(self) -> State: if self._current_state is None: return self._initial_state else: return self._current_state @property - def states(self): + def states(self) -> list[str]: return [state.name for state in self._states] @property - def is_spoofing(self): + def is_spoofing(self) -> bool: return self._spoof diff --git a/opensourceleg/logging/decorators.py b/opensourceleg/logging/decorators.py index 26ae92db..ac05af30 100644 --- a/opensourceleg/logging/decorators.py +++ b/opensourceleg/logging/decorators.py @@ -1,5 +1,5 @@ from functools import wraps -from typing import Callable +from typing import Any, Callable from opensourceleg.logging import LOGGER @@ -10,7 +10,7 @@ def deprecated(func: Callable) -> Callable: """ @wraps(func) - def wrapper(*args, **kwargs) -> Callable: + def wrapper(*args: Any, **kwargs: Any) -> Any: LOGGER.warning(f"Function `{func.__name__}` is deprecated.") return func(*args, **kwargs) @@ -24,7 +24,7 @@ def deprecated_with_suggestion(alternative_func: Callable) -> Callable: def decorator(func: Callable) -> Callable: @wraps(func) - def wrapper(*args, **kwargs) -> Callable: + def wrapper(*args: Any, **kwargs: Any) -> Any: LOGGER.warning( f"Function `{func.__name__}` is deprecated. Please use `{alternative_func.__name__}` instead." ) @@ -43,7 +43,7 @@ def deprecated_with_routing(alternative_func: Callable) -> Callable: def decorator(func: Callable) -> Callable: @wraps(func) - def wrapper(*args, **kwargs) -> Callable: + def wrapper(*args: Any, **kwargs: Any) -> Any: LOGGER.warning( f"Function `{func.__name__}` is deprecated. Please use `{alternative_func.__name__}` instead," "which will be called automatically now." @@ -57,15 +57,15 @@ def wrapper(*args, **kwargs) -> Callable: if __name__ == "__main__": - def add(a, b): + def add(a: int, b: int) -> int: return a + b @deprecated_with_suggestion(add) - def add_old(a, b): + def add_old(a: int, b: int) -> int: return b @deprecated_with_routing(add) - def add_renamed(a, b): + def add_renamed(a: int, b: int) -> int: return a print(add_renamed(1, 2)) diff --git a/opensourceleg/logging/logger.py b/opensourceleg/logging/logger.py index 25119556..c4bb3227 100644 --- a/opensourceleg/logging/logger.py +++ b/opensourceleg/logging/logger.py @@ -46,7 +46,7 @@ class LogLevel(Enum): class Logger(logging.Logger): _instance = None - def __new__(cls, *args, **kwargs): + def __new__(cls, *args: Any, **kwargs: Any) -> "Logger": if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance @@ -75,7 +75,7 @@ def __init__( self._file_path: str = "" self._csv_path: str = "" self._file: Optional[Any] = None - self._writer = None + self._writer: Any = None self._is_logging = False self._header_written = False @@ -120,16 +120,16 @@ def _setup_file_handler(self) -> None: self._file_handler.setFormatter(fmt=self._std_formatter) self.addHandler(hdlr=self._file_handler) - def _ensure_file_handler(self): + def _ensure_file_handler(self) -> None: if not hasattr(self, "_file_handler"): self._setup_file_handler() - def track_variable(self, var_func: Callable[[], Any], name: str): + def track_variable(self, var_func: Callable[[], Any], name: str) -> None: var_id = id(var_func) self._tracked_vars[var_id] = var_func self._var_names[var_id] = name - def untrack_variable(self, var_func: Callable[[], Any]): + def untrack_variable(self, var_func: Callable[[], Any]) -> None: var_id = id(var_func) self._tracked_vars.pop(var_id, None) self._var_names.pop(var_id, None) @@ -176,7 +176,7 @@ def update(self) -> None: if len(self._buffer) >= self._buffer_size: self.flush_buffer() - def flush_buffer(self): + def flush_buffer(self) -> None: if not self._buffer: return @@ -213,11 +213,11 @@ def _generate_file_paths(self) -> None: def __enter__(self) -> "Logger": return self - def __exit__(self, exc_type, exc_val, exc_tb) -> None: + def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: self.flush_buffer() self.close() - def reset(self): + def reset(self) -> None: self._buffer.clear() self._tracked_vars.clear() self._var_names.clear() @@ -232,27 +232,27 @@ def close(self) -> None: self._file = None self._writer = None - def debug(self, msg, *args, **kwargs): + def debug(self, msg: object, *args: object, **kwargs: Any) -> None: self._ensure_file_handler() super().debug(msg, *args, **kwargs) - def info(self, msg, *args, **kwargs): + def info(self, msg: object, *args: object, **kwargs: Any) -> None: self._ensure_file_handler() super().info(msg, *args, **kwargs) - def warning(self, msg, *args, **kwargs): + def warning(self, msg: object, *args: object, **kwargs: Any) -> None: self._ensure_file_handler() super().warning(msg, *args, **kwargs) - def error(self, msg, *args, **kwargs): + def error(self, msg: object, *args: object, **kwargs: Any) -> None: self._ensure_file_handler() super().error(msg, *args, **kwargs) - def critical(self, msg, *args, **kwargs): + def critical(self, msg: object, *args: object, **kwargs: Any) -> None: self._ensure_file_handler() super().critical(msg, *args, **kwargs) - def log(self, level, msg, *args, **kwargs): + def log(self, level: int, msg: object, *args: object, **kwargs: Any) -> None: self._ensure_file_handler() super().log(level, msg, *args, **kwargs) @@ -289,10 +289,10 @@ def file_backup_count(self) -> int: if __name__ == "__main__": class Test: - def __init__(self): - self.a = 0 + def __init__(self) -> None: + self.a: float = 0.0 - def update(self): + def update(self) -> None: self.a += 0.2 my_logger = Logger(buffer_size=5000, file_name="my_log") diff --git a/opensourceleg/math/math.py b/opensourceleg/math/math.py index 6397ba42..ad684b0d 100644 --- a/opensourceleg/math/math.py +++ b/opensourceleg/math/math.py @@ -151,7 +151,7 @@ def update_and_get_scale(self, dt: float, motor_current: float = 0, FOS: float = if scale >= 1.0: return 1.0 - return np.sqrt(scale) # this is how much the torque should be scaled + return float(np.sqrt(scale)) # this is how much the torque should be scaled class EdgeDetector: diff --git a/opensourceleg/robots/base.py b/opensourceleg/robots/base.py index 2d86ca02..ff68ebc1 100644 --- a/opensourceleg/robots/base.py +++ b/opensourceleg/robots/base.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Generic, TypeVar +from typing import Any, Generic, TypeVar from opensourceleg.actuators.base import ActuatorBase from opensourceleg.logging import LOGGER @@ -15,20 +15,20 @@ def __init__( tag: str, actuators: dict[str, TActuator], sensors: dict[str, TSensor], - ): + ) -> None: self._tag = tag self.actuators: dict[str, TActuator] = actuators self.sensors: dict[str, TSensor] = sensors - def __enter__(self): + def __enter__(self) -> "RobotBase": self.start() return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: self.stop() @abstractmethod - def start(self): + def start(self) -> None: for actuator in self.actuators.values(): LOGGER.debug(f"Calling start method of {actuator.tag}") actuator.start() @@ -38,7 +38,7 @@ def start(self): sensor.start() @abstractmethod - def stop(self): + def stop(self) -> None: for actuator in self.actuators.values(): LOGGER.debug(f"Calling stop method of {actuator.tag}") actuator.stop() @@ -48,7 +48,7 @@ def stop(self): sensor.stop() @abstractmethod - def update(self): + def update(self) -> None: for actuator in self.actuators.values(): actuator.update() @@ -56,7 +56,7 @@ def update(self): sensor.update() @property - def tag(self): + def tag(self) -> str: return self._tag # You can pipe values from the actuators to custom @property methods diff --git a/opensourceleg/robots/osl.py b/opensourceleg/robots/osl.py index 42d90108..b1aa1c44 100644 --- a/opensourceleg/robots/osl.py +++ b/opensourceleg/robots/osl.py @@ -4,7 +4,7 @@ import numpy as np from opensourceleg.actuators.base import CONTROL_MODES, ActuatorBase -from opensourceleg.actuators.dephy import DephyLegacyActuator +from opensourceleg.actuators.dephy import DEFAULT_POSITION_GAINS, DephyLegacyActuator from opensourceleg.logging import LOGGER from opensourceleg.robots.base import RobotBase, TActuator, TSensor from opensourceleg.sensors.base import LoadcellBase, SensorBase @@ -103,7 +103,12 @@ def joint_encoder_ankle(self) -> Union[TSensor, SensorBase]: osl.update() osl.knee.set_control_mode(CONTROL_MODES.POSITION) - osl.knee.set_position_gains() + osl.knee.set_position_gains( + kp=DEFAULT_POSITION_GAINS.kp, + ki=DEFAULT_POSITION_GAINS.ki, + kd=DEFAULT_POSITION_GAINS.kd, + ff=DEFAULT_POSITION_GAINS.ff, + ) osl.knee.set_output_position(osl.knee.output_position + np.deg2rad(10)) # osl.loadcell.calibrate() diff --git a/opensourceleg/safety/safety.py b/opensourceleg/safety/safety.py index 6429bde7..11d3b9f3 100644 --- a/opensourceleg/safety/safety.py +++ b/opensourceleg/safety/safety.py @@ -1,12 +1,12 @@ from collections import deque from dataclasses import dataclass -from typing import Callable, Optional +from typing import Any, Callable, Optional import numpy as np class ThermalLimitException(Exception): - def __init__(self, message="Software thermal limit exceeded. Exiting.") -> None: + def __init__(self, message: str = "Software thermal limit exceeded. Exiting.") -> None: self.message = message super().__init__(self.message) @@ -35,15 +35,12 @@ def is_changing( history_key = f"_{attribute_name}_history" proxy_key = f"_{attribute_name}_proxy" - def decorator(func): - def wrapper(instance, *args, **kwargs): + def decorator(func: Callable) -> Callable: + def wrapper(instance: object, *args: Any, **kwargs: Any) -> Any: instance.__dict__.setdefault(history_key, deque(maxlen=max_points)) - try: - if instance.__dict__[proxy_key] is True: - return getattr(instance, proxy_attribute_name) - except KeyError: - pass + if proxy_attribute_name is not None and getattr(instance, proxy_key): + return getattr(instance, proxy_attribute_name) value = func(instance, *args, **kwargs) history = getattr(instance, history_key) @@ -76,8 +73,8 @@ def is_negative(clamp: bool = False) -> Callable: Callable: Decorator function. """ - def decorator(func): - def wrapper(instance, *args, **kwargs): + def decorator(func: Callable) -> Callable: + def wrapper(instance: object, *args: Any, **kwargs: Any) -> Any: value = func(instance, *args, **kwargs) if value >= 0: if clamp: @@ -90,7 +87,7 @@ def wrapper(instance, *args, **kwargs): return decorator -def is_positive(clamp: bool = False): +def is_positive(clamp: bool = False) -> Callable: """ Creates a decorator to check if a property's value is positive. @@ -101,8 +98,8 @@ def is_positive(clamp: bool = False): Callable: Decorator function. """ - def decorator(func): - def wrapper(instance, *args, **kwargs): + def decorator(func: Callable) -> Callable: + def wrapper(instance: object, *args: Any, **kwargs: Any) -> Any: value = func(instance, *args, **kwargs) if value <= 0: if clamp: @@ -115,7 +112,7 @@ def wrapper(instance, *args, **kwargs): return decorator -def is_zero(clamp: bool = False): +def is_zero(clamp: bool = False) -> Callable: """ Creates a decorator to check if a property's value is zero. @@ -126,8 +123,8 @@ def is_zero(clamp: bool = False): Callable: Decorator function. """ - def decorator(func): - def wrapper(instance, *args, **kwargs): + def decorator(func: Callable) -> Callable: + def wrapper(instance: object, *args: Any, **kwargs: Any) -> Any: value = func(instance, *args, **kwargs) if value != 0: if clamp: @@ -140,7 +137,7 @@ def wrapper(instance, *args, **kwargs): return decorator -def is_within_range(min_value: float, max_value: float, clamp: bool = False): +def is_within_range(min_value: float, max_value: float, clamp: bool = False) -> Callable: """ Creates a decorator to check if a property's value is within a given range. @@ -159,8 +156,8 @@ def is_within_range(min_value: float, max_value: float, clamp: bool = False): if max_value <= min_value: raise ValueError("Maximum value must be greater than minimum value of range") - def decorator(func): - def wrapper(instance, *args, **kwargs): + def decorator(func: Callable) -> Callable: + def wrapper(instance: object, *args: Any, **kwargs: Any) -> Any: value = func(instance, *args, **kwargs) if value < min_value or value > max_value: if clamp: @@ -173,7 +170,7 @@ def wrapper(instance, *args, **kwargs): return decorator -def is_greater_than(min_value: float, clamp: bool = False, equality: bool = False): +def is_greater_than(min_value: float, clamp: bool = False, equality: bool = False) -> Callable: """ Creates a decorator to check if a property's value is greater than a given value. Gives user choice to implement is_greater_than_or_equal_to with equality bool @@ -187,8 +184,8 @@ def is_greater_than(min_value: float, clamp: bool = False, equality: bool = Fals Callable: Decorator function. """ - def decorator(func): - def wrapper(instance, *args, **kwargs): + def decorator(func: Callable) -> Callable: + def wrapper(instance: object, *args: Any, **kwargs: Any) -> Any: value = func(instance, *args, **kwargs) if equality: if value < min_value: @@ -207,7 +204,7 @@ def wrapper(instance, *args, **kwargs): return decorator -def is_less_than(max_value: float, clamp: bool = False, equality: bool = False): +def is_less_than(max_value: float, clamp: bool = False, equality: bool = False) -> Callable: """ Creates a decorator to check if a property's value is less than a given value. Gives user choice to implement is_less_than_or_equal_to with equality bool @@ -221,8 +218,8 @@ def is_less_than(max_value: float, clamp: bool = False, equality: bool = False): Callable: Decorator function. """ - def decorator(func): - def wrapper(instance, *args, **kwargs): + def decorator(func: Callable) -> Callable: + def wrapper(instance: object, *args: Any, **kwargs: Any) -> Any: value = func(instance, *args, **kwargs) if equality: if value > max_value: @@ -241,7 +238,7 @@ def wrapper(instance, *args, **kwargs): return decorator -def custom_criteria(criteria: Callable): +def custom_criteria(criteria: Callable) -> Callable: """ Creates a decorator to check if a property's value meets a custom criteria. The criteria is a function that takes the property's value as an argument and returns a boolean. @@ -253,8 +250,8 @@ def custom_criteria(criteria: Callable): Callable: Decorator function. """ - def decorator(func): - def wrapper(instance, *args, **kwargs): + def decorator(func: Callable) -> Callable: + def wrapper(instance: object, *args: Any, **kwargs: Any) -> Any: value = func(instance, *args, **kwargs) if not criteria(value): raise ValueError("Value does not meet custom criteria") @@ -291,10 +288,10 @@ class SafetyManager: thereby triggering the decorators. """ - def __init__(self): + def __init__(self) -> None: self._safe_objects: dict[object, dict[str, list[Callable]]] = {} - def add_safety(self, instance: object, attribute: str, decorator: Callable): + def add_safety(self, instance: object, attribute: str, decorator: Callable) -> None: """ Adds a safety decorator to the given object's attribute. The decorator will be applied to the property's getter. @@ -323,7 +320,7 @@ def add_safety(self, instance: object, attribute: str, decorator: Callable): else: self._safe_objects[instance] = {attribute: [decorator]} - def start(self): + def start(self) -> None: """ Applies all decorators to the properties of the objects in the safe_objects dictionary. """ @@ -343,7 +340,7 @@ def start(self): container.__class__ = container_subclass - def update(self): + def update(self) -> None: """ Accesses the properties of the objects in the safe_objects dictionary, thereby triggering the decorators. """ @@ -352,31 +349,31 @@ def update(self): getattr(container, attribute_name) @property - def safe_objects(self): + def safe_objects(self) -> dict[object, dict[str, list[Callable]]]: return self._safe_objects if __name__ == "__main__": class Sensor: - def __init__(self, value): - self._value = value - self._a = 10 + def __init__(self, value: float) -> None: + self._value: float = value + self._a: float = 10 @property - def value(self): + def value(self) -> float: return self._value @value.setter - def value(self, value): + def value(self, value: float) -> None: self._value = value @property - def a(self): + def a(self) -> float: return self._a @a.setter - def a(self, value): + def a(self, value: float) -> None: self._a = value sensor = Sensor(100) diff --git a/opensourceleg/sensors/adc.py b/opensourceleg/sensors/adc.py index 26951949..37172ca1 100644 --- a/opensourceleg/sensors/adc.py +++ b/opensourceleg/sensors/adc.py @@ -1,6 +1,6 @@ import math from time import sleep -from typing import ClassVar, Optional +from typing import Any, ClassVar, Optional import spidev @@ -178,7 +178,7 @@ def gains(self) -> list[int]: return self._gains @property - def data(self): + def data(self) -> Any: return self._data def _spi_message(self, message: list[int]) -> list[int]: diff --git a/opensourceleg/sensors/base.py b/opensourceleg/sensors/base.py index 078acaf5..6402f0e3 100644 --- a/opensourceleg/sensors/base.py +++ b/opensourceleg/sensors/base.py @@ -47,7 +47,7 @@ def __enter__(self) -> "SensorBase": self.start() return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: self.stop() @property diff --git a/opensourceleg/sensors/imu.py b/opensourceleg/sensors/imu.py index f203876e..b0c08cd4 100644 --- a/opensourceleg/sensors/imu.py +++ b/opensourceleg/sensors/imu.py @@ -1,3 +1,5 @@ +from typing import Any, Union + from opensourceleg.logging import LOGGER from opensourceleg.sensors.base import IMUBase, check_sensor_stream @@ -47,7 +49,7 @@ def __init__( port: str = "/dev/ttyUSB0", baud_rate: int = 921600, frequency: int = 200, - ): + ) -> None: self._port = port self._baud_rate = baud_rate self._frequency = frequency @@ -55,7 +57,7 @@ def __init__( self._connection = None self._data: dict[str, float] = {} - def _configure_mip_channels(self): + def _configure_mip_channels(self) -> Any: channels = mscl.MipChannels() channels.append( mscl.MipChannel( @@ -84,7 +86,7 @@ def _configure_mip_channels(self): return channels - def start(self): + def start(self) -> None: self._connection = mscl.Connection.Serial(self.port, self.baud_rate) self._node = mscl.InertialNode(self._connection) self._node.setActiveChannelFields(mscl.MipTypes.CLASS_ESTFILTER, self._configure_mip_channels()) @@ -92,12 +94,12 @@ def start(self): self._is_streaming = True @check_sensor_stream - def stop(self): + def stop(self) -> None: self._node.setToIdle() self._is_streaming = False @check_sensor_stream - def ping(self): + def ping(self) -> None: response = self._node.ping() if response.success(): @@ -105,7 +107,7 @@ def ping(self): else: LOGGER.error(f"Failed to ping the IMU at {self.port}") - def update(self, timeout: int = 500, max_packets: int = 1, return_packets: bool = False): + def update(self, timeout: int = 500, max_packets: int = 1, return_packets: bool = False) -> Union[None, Any]: """ Get IMU data from the Lord Microstrain IMU """ @@ -115,6 +117,8 @@ def update(self, timeout: int = 500, max_packets: int = 1, return_packets: bool if return_packets: return data_packets + else: + return None def __repr__(self) -> str: return "IMULordMicrostrain" @@ -227,7 +231,7 @@ class BNO055(IMUBase): def __init__( self, addr: int = 40, - ): + ) -> None: self._address: int = addr self._gyro_data: list[float] = [0.0, 0.0, 0.0] self._acc_data: list[float] = [0.0, 0.0, 0.0] @@ -236,7 +240,7 @@ def __init__( def __repr__(self) -> str: return "BNO055_IMU" - def start(self): + def start(self) -> None: i2c = busio.I2C(board.SCL, board.SDA) try: self._adafruit_imu = adafruit_bno055.BNO055_I2C(i2c, address=self._address) @@ -246,15 +250,14 @@ def start(self): self.configure_IMU_settings() self._is_streaming = True - def stop(self): + def stop(self) -> None: self._is_streaming = False - pass - def update(self): + def update(self) -> None: self._acc_data = self._adafruit_imu.acceleration self._gyro_data = self._adafruit_imu.gyro - def configure_IMU_settings(self): + def configure_IMU_settings(self) -> None: """ Configure the IMU mode and different range/bandwidth settings. Hard coding settings for now. diff --git a/opensourceleg/sensors/loadcell.py b/opensourceleg/sensors/loadcell.py index d50ab817..2844484f 100644 --- a/opensourceleg/sensors/loadcell.py +++ b/opensourceleg/sensors/loadcell.py @@ -1,6 +1,6 @@ import time from enum import Enum -from typing import Callable, Optional +from typing import Any, Callable, Optional import numpy as np import numpy.typing as npt @@ -78,13 +78,13 @@ def start(self) -> None: time.sleep(1) self._is_streaming = True - def reset(self): + def reset(self) -> None: self._calibration_offset = self._zero_calibration_offset self._is_calibrated = False def update( self, - calibration_offset: npt.NDArray[np.double] = None, + calibration_offset: Optional[npt.NDArray[np.double]] = None, data_callback: Optional[Callable[..., npt.NDArray[np.uint8]]] = None, ) -> None: """ @@ -147,7 +147,7 @@ def stop(self) -> None: if hasattr(self, "_smbus"): self._smbus.close() - def _read_compressed_strain(self): + def _read_compressed_strain(self) -> Any: """Used for more recent versions of strain amp firmware""" try: data = self._smbus.read_i2c_block_data(self._i2c_address, MEMORY_CHANNELS.CH1_H, 10) @@ -158,10 +158,10 @@ def _read_compressed_strain(self): if self.failed_reads >= 5: raise LoadcellNotRespondingException("Load cell unresponsive.") from None - return self._unpack_compressed_strain(data) + return self._unpack_compressed_strain(np.array(object=data, dtype=np.uint8)) @staticmethod - def _unpack_uncompressed_strain(data): + def _unpack_uncompressed_strain(data: npt.NDArray[np.uint8]) -> npt.NDArray[np.uint16]: """Used for an older version of the strain amp firmware (at least pre-2017)""" ch1 = (data[0] << 8) | data[1] ch2 = (data[2] << 8) | data[3] @@ -172,7 +172,7 @@ def _unpack_uncompressed_strain(data): return np.array(object=[ch1, ch2, ch3, ch4, ch5, ch6]) @staticmethod - def _unpack_compressed_strain(data): + def _unpack_compressed_strain(data: npt.NDArray[np.uint8]) -> npt.NDArray[np.uint16]: """Used for more recent versions of strainamp firmware""" return np.array( object=[ @@ -203,7 +203,7 @@ def fx(self) -> float: return float(self.data[0]) @property - def fy(self): + def fy(self) -> float: """ Latest force in the y (anterior/posterior) direction in Newtons. If using the standard OSL setup, this is positive in the posterior direction. @@ -211,7 +211,7 @@ def fy(self): return float(self.data[1]) @property - def fz(self): + def fz(self) -> float: """ Latest force in the z (vertical) direction in Newtons. If using the standard OSL setup, this should be positive downwards. @@ -220,7 +220,7 @@ def fz(self): return float(self.data[2]) @property - def mx(self): + def mx(self) -> float: """ Latest moment about the x (medial/lateral) axis in Nm. If using the standard OSL setup, this axis is positive towards the user's right. @@ -228,7 +228,7 @@ def mx(self): return float(self.data[3]) @property - def my(self): + def my(self) -> float: """ Latest moment about the y (anterior/posterior) axis in Nm. If using the standard OSL setup, this axis is positive in the posterior direction. @@ -236,7 +236,7 @@ def my(self): return float(self.data[4]) @property - def mz(self): + def mz(self) -> float: """ Latest moment about the z (vertical) axis in Nm. If using the standard OSL setup, this axis is positive towards the ground. @@ -244,7 +244,7 @@ def mz(self): return float(self.data[5]) @property - def data(self): + def data(self) -> Any: """ Returns a vector of the latest loadcell data. [Fx, Fy, Fz, Mx, My, Mz] diff --git a/poetry.lock b/poetry.lock index 79748eb1..8a322451 100644 --- a/poetry.lock +++ b/poetry.lock @@ -21,13 +21,13 @@ sysv-ipc = {version = ">=1.1.0", markers = "sys_platform == \"linux\" and platfo [[package]] name = "adafruit-circuitpython-bno055" -version = "5.4.14" +version = "5.4.15" description = "CircuitPython library for BNO055 9-DOF absolute orientation sensor." optional = false python-versions = "*" files = [ - {file = "adafruit_circuitpython_bno055-5.4.14-py3-none-any.whl", hash = "sha256:a939bc00b2f74cccfb32cb8adae50dc8f6182cb959e279942348acd90ef33b95"}, - {file = "adafruit_circuitpython_bno055-5.4.14.tar.gz", hash = "sha256:eb559c9fa593f07feed8109085309f62048105f2123e6ce6f74becaf35f8e5fd"}, + {file = "adafruit_circuitpython_bno055-5.4.15-py3-none-any.whl", hash = "sha256:8df0b295684c25e032791b415546c73f43ad3fbb2a8a5a69be8bc5820bb2c78a"}, + {file = "adafruit_circuitpython_bno055-5.4.15.tar.gz", hash = "sha256:3cba6cbe0e6e0b9854de8e244d7c14115bfd8c708d2a1442573677e7fb3c154d"}, ] [package.dependencies] @@ -806,137 +806,137 @@ colorama = ">=0.4" [[package]] name = "grpcio" -version = "1.67.1" +version = "1.68.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f"}, - {file = "grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa"}, - {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292"}, - {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311"}, - {file = "grpcio-1.67.1-cp310-cp310-win32.whl", hash = "sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed"}, - {file = "grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e"}, - {file = "grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb"}, - {file = "grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96"}, - {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f"}, - {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970"}, - {file = "grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744"}, - {file = "grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5"}, - {file = "grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953"}, - {file = "grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e"}, - {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75"}, - {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38"}, - {file = "grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78"}, - {file = "grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc"}, - {file = "grpcio-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b"}, - {file = "grpcio-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8"}, - {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62"}, - {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb"}, - {file = "grpcio-1.67.1-cp313-cp313-win32.whl", hash = "sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121"}, - {file = "grpcio-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba"}, - {file = "grpcio-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65"}, - {file = "grpcio-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3"}, - {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8"}, - {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce"}, - {file = "grpcio-1.67.1-cp38-cp38-win32.whl", hash = "sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46"}, - {file = "grpcio-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771"}, - {file = "grpcio-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335"}, - {file = "grpcio-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04"}, - {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8"}, - {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f"}, - {file = "grpcio-1.67.1-cp39-cp39-win32.whl", hash = "sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e"}, - {file = "grpcio-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98"}, - {file = "grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732"}, + {file = "grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544"}, + {file = "grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1"}, + {file = "grpcio-1.68.0-cp310-cp310-win32.whl", hash = "sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b"}, + {file = "grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a"}, + {file = "grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415"}, + {file = "grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75"}, + {file = "grpcio-1.68.0-cp311-cp311-win32.whl", hash = "sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc"}, + {file = "grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27"}, + {file = "grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d"}, + {file = "grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659"}, + {file = "grpcio-1.68.0-cp312-cp312-win32.whl", hash = "sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332"}, + {file = "grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9"}, + {file = "grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e"}, + {file = "grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665"}, + {file = "grpcio-1.68.0-cp313-cp313-win32.whl", hash = "sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03"}, + {file = "grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a"}, + {file = "grpcio-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3"}, + {file = "grpcio-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161"}, + {file = "grpcio-1.68.0-cp38-cp38-win32.whl", hash = "sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78"}, + {file = "grpcio-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5"}, + {file = "grpcio-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354"}, + {file = "grpcio-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363"}, + {file = "grpcio-1.68.0-cp39-cp39-win32.whl", hash = "sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a"}, + {file = "grpcio-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490"}, + {file = "grpcio-1.68.0.tar.gz", hash = "sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.67.1)"] +protobuf = ["grpcio-tools (>=1.68.0)"] [[package]] name = "grpcio-tools" -version = "1.67.1" +version = "1.68.0" description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio_tools-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:c701aaa51fde1f2644bd94941aa94c337adb86f25cd03cf05e37387aaea25800"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:6a722bba714392de2386569c40942566b83725fa5c5450b8910e3832a5379469"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0c7415235cb154e40b5ae90e2a172a0eb8c774b6876f53947cf0af05c983d549"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4c459098c4934f9470280baf9ff8b38c365e147f33c8abc26039a948a664a5"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e89bf53a268f55c16989dab1cf0b32a5bff910762f138136ffad4146129b7a10"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f09cb3e6bcb140f57b878580cf3b848976f67faaf53d850a7da9bfac12437068"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:616dd0c6686212ca90ff899bb37eb774798677e43dc6f78c6954470782d37399"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-win32.whl", hash = "sha256:58a66dbb3f0fef0396737ac09d6571a7f8d96a544ce3ed04c161f3d4fa8d51cc"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:89ee7c505bdf152e67c2cced6055aed4c2d4170f53a2b46a7e543d3b90e7b977"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:6d80ddd87a2fb7131d242f7d720222ef4f0f86f53ec87b0a6198c343d8e4a86e"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b655425b82df51f3bd9fd3ba1a6282d5c9ce1937709f059cb3d419b224532d89"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:250241e6f9d20d0910a46887dfcbf2ec9108efd3b48f3fb95bb42d50d09d03f8"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6008f5a5add0b6f03082edb597acf20d5a9e4e7c55ea1edac8296c19e6a0ec8d"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5eff9818c3831fa23735db1fa39aeff65e790044d0a312260a0c41ae29cc2d9e"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:262ab7c40113f8c3c246e28e369661ddf616a351cb34169b8ba470c9a9c3b56f"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1eebd8c746adf5786fa4c3056258c21cc470e1eca51d3ed23a7fb6a697fe4e81"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-win32.whl", hash = "sha256:3eff92fb8ca1dd55e3af0ef02236c648921fb7d0e8ca206b889585804b3659ae"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:1ed18281ee17e5e0f9f6ce0c6eb3825ca9b5a0866fc1db2e17fab8aca28b8d9f"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:bd5caef3a484e226d05a3f72b2d69af500dca972cf434bf6b08b150880166f0b"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:48a2d63d1010e5b218e8e758ecb2a8d63c0c6016434e9f973df1c3558917020a"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:baa64a6aa009bffe86309e236c81b02cd4a88c1ebd66f2d92e84e9b97a9ae857"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ab318c40b5e3c097a159035fc3e4ecfbe9b3d2c9de189e55468b2c27639a6ab"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50eba3e31f9ac1149463ad9182a37349850904f142cffbd957cd7f54ec320b8e"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:de6fbc071ecc4fe6e354a7939202191c1f1abffe37fbce9b08e7e9a5b93eba3d"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:db9e87f6ea4b0ce99b2651203480585fd9e8dd0dd122a19e46836e93e3a1b749"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-win32.whl", hash = "sha256:6a595a872fb720dde924c4e8200f41d5418dd6baab8cc1a3c1e540f8f4596351"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:92eebb9b31031604ae97ea7657ae2e43149b0394af7117ad7e15894b6cc136dc"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:9a3b9510cc87b6458b05ad49a6dee38df6af37f9ee6aa027aa086537798c3d4a"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e4c9b9fa9b905f15d414cb7bd007ba7499f8907bdd21231ab287a86b27da81a"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:e11a98b41af4bc88b7a738232b8fa0306ad82c79fa5d7090bb607f183a57856f"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de0fcfe61c26679d64b1710746f2891f359593f76894fcf492c37148d5694f00"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae3b3e2ee5aad59dece65a613624c46a84c9582fc3642686537c6dfae8e47dc"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:9a630f83505b6471a3094a7a372a1240de18d0cd3e64f4fbf46b361bac2be65b"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d85a1fcbacd3e08dc2b3d1d46b749351a9a50899fa35cf2ff040e1faf7d405ad"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-win32.whl", hash = "sha256:778470f025f25a1fca5a48c93c0a18af395b46b12dd8df7fca63736b85181f41"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:6961da86e9856b4ddee0bf51ef6636b4bf9c29c0715aa71f3c8f027c45d42654"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:c088dfbbe289bb171ca9c98fabbf7ecc8c1c51af2ba384ef32a4fdcb784b17e9"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11ce546daf8f8c04ee8d4a1673b4754cda4a0a9d505d820efd636e37f46b50c5"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:83fecb2f6119ef0eea68a091964898418c1969375d399956ff8d1741beb7b081"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d39c1aa6b26e2602d815b9cfa37faba48b2889680ae6baa002560cf0f0c69fac"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e975dc9fb61a77d88e739eb17b3361f369d03cc754217f02dd83ec7cfac32e38"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6c6e5c5b15f2eedc2a81268d588d14a79a52020383bf87b3c7595df7b571504a"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a974e0ce01806adba718e6eb8c385defe6805b18969b6914da7db55fb055ae45"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-win32.whl", hash = "sha256:35e9b0a82be9f425aa67ee1dc69ba02cf135aeee3f22c0455c5d1b01769bbdb4"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:0436c97f29e654d2eccd7419907ee019caf7eea6bdc6ae91d98011f6c5f44f17"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:718fbb6d68a3d000cb3cf381642660eade0e8c1b0bf7472b84b3367f5b56171d"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:062887d2e9cb8bc261c21a2b8da714092893ce62b4e072775eaa9b24dcbf3b31"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:59dbf14a1ce928bf03a58fa157034374411159ab5d32ad83cf146d9400eed618"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac552fc9c76d50408d7141e1fd1eae69d85fbf7ae71da4d8877eaa07127fbe74"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6583773400e441dc62d08b5a32357babef1a9f9f73c3ac328a75af550815a9"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:862108f90f2f6408908e5ea4584c5104f7caf419c6d73aa3ff36bf8284cca224"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:587c6326425f37dca2291f46b93e446c07ee781cea27725865b806b7a049ec56"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-win32.whl", hash = "sha256:d7d46a4405bd763525215b6e073888386587aef9b4a5ec125bf97ba897ac757d"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:e2fc7980e8bab3ee5ab98b6fdc2a8fbaa4785f196d897531346176fda49a605c"}, - {file = "grpcio_tools-1.67.1.tar.gz", hash = "sha256:d9657f5ddc62b52f58904e6054b7d8a8909ed08a1e28b734be3a707087bcf004"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:9509a5c3ed3d54fa7ac20748d501cb86668f764605a0a68f275339ee0f1dc1a6"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:59a885091bf29700ba0e14a954d156a18714caaa2006a7f328b18e1ac4b1e721"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d3e678162e1d7a8720dc05fdd537fc8df082a50831791f7bb1c6f90095f8368b"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10d03e3ad4af6284fd27cb14f5a3d52045913c1253e3e24a384ed91bc8adbfcd"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1769d7f529de1cc102f7fb900611e3c0b69bdb244fca1075b24d6e5b49024586"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88640d95ee41921ac7352fa5fadca52a06d7e21fbe53e6a706a9a494f756be7d"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e903d07bc65232aa9e7704c829aec263e1e139442608e473d7912417a9908e29"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-win32.whl", hash = "sha256:66b70b37184d40806844f51c2757c6b852511d4ea46a3bf2c7e931a47b455bc6"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:b47ae076ffb29a68e517bc03552bef0d9c973f8e18adadff180b123e973a26ea"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:f65942fab440e99113ce14436deace7554d5aa554ea18358e3a5f3fc47efe322"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8fefc6d000e169a97336feded23ce614df3fb9926fc48c7a9ff8ea459d93b5b0"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:6dd69c9f3ff85eee8d1f71adf7023c638ca8d465633244ac1b7f19bc3668612d"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc5195dc02057668cc22da1ff1aea1811f6fa0deb801b3194dec1fe0bab1cf0"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849b12bec2320e49e988df104c92217d533e01febac172a4495caab36d9f0edc"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:766c2cd2e365e0fc0e559af56f2c2d144d95fd7cb8668a34d533e66d6435eb34"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2ec3a2e0afa4866ccc5ba33c071aebaa619245dfdd840cbb74f2b0591868d085"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-win32.whl", hash = "sha256:80b733014eb40d920d836d782e5cdea0dcc90d251a2ffb35ab378ef4f8a42c14"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:f95103e3e4e7fee7c6123bc9e4e925e07ad24d8d09d7c1c916fb6c8d1cb9e726"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:dd9a654af8536b3de8525bff72a245fef62d572eabf96ac946fe850e707cb27d"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0f77957e3a0916a0dd18d57ce6b49d95fc9a5cfed92310f226339c0fda5394f6"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:92a09afe64fe26696595de2036e10967876d26b12c894cc9160f00152cacebe7"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28ebdbad2ef16699d07400b65260240851049a75502eff69a59b127d3ab960f1"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d3150d784d8050b10dcf5eb06e04fb90747a1547fed3a062a608d940fe57066"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:261d98fd635595de42aadee848f9af46da6654d63791c888891e94f66c5d0682"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:061345c0079b9471f32230186ab01acb908ea0e577bc1699a8cf47acef8be4af"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-win32.whl", hash = "sha256:533ce6791a5ba21e35d74c6c25caf4776f5692785a170c01ea1153783ad5af31"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:56842a0ce74b4b92eb62cd5ee00181b2d3acc58ba0c4fd20d15a5db51f891ba6"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:1117a81592542f0c36575082daa6413c57ca39188b18a4c50ec7332616f4b97e"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:51e5a090849b30c99a2396d42140b8a3e558eff6cdfa12603f9582e2cd07724e"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:4fe611d89a1836df8936f066d39c7eb03d4241806449ec45d4b8e1c843ae8011"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c10f3faa0cc4d89eb546f53b623837af23e86dc495d3b89510bcc0e0a6c0b8b2"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46b537480b8fd2195d988120a28467601a2a3de2e504043b89fb90318e1eb754"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:17d0c9004ea82b4213955a585401e80c30d4b37a1d4ace32ccdea8db4d3b7d43"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2919faae04fe47bad57fc9b578aeaab527da260e851f321a253b6b11862254a8"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-win32.whl", hash = "sha256:ee86157ef899f58ba2fe1055cce0d33bd703e99aa6d5a0895581ac3969f06bfa"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:d0470ffc6a93c86cdda48edd428d22e2fef17d854788d60d0d5f291038873157"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:795f2cd76f68a12b0b5541b98187ba367dd69b49d359cf98b781ead742961370"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:57e29e78c33fb1b1d557fbe7650d722d1f2b0a9f53ea73beb8ea47e627b6000b"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:700f171cd3293ee8d50cd43171562ff07b14fa8e49ee471cd91c6924c7da8644"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:196cd8a3a5963a4c9e424314df9eb573b305e6f958fe6508d26580ce01e7aa56"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cad40c3164ee9cef62524dea509449ea581b17ea493178beef051bf79b5103ca"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab93fab49fa1e699e577ff5fbb99aba660164d710d4c33cfe0aa9d06f585539f"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:511224a99726eb84db9ddb84dc8a75377c3eae797d835f99e80128ec618376d5"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-win32.whl", hash = "sha256:b4ca81770cd729a9ea536d871aacedbde2b732bb9bb83c9d993d63f58502153d"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:6950725bf7a496f81d3ec3324334ffc9dbec743b510dd0e897f51f8627eeb6ac"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:01ace351a51d7ee120963a4612b1f00e964462ec548db20d17f8902e238592c8"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5afd2f3f7257b52228a7808a2b4a765893d4d802d7a2377d9284853e67d045c6"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:453ee3193d59c974c678d91f08786f43c25ef753651b0825dc3d008c31baf68d"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094b22919b786ad73c20372ef5e546330e7cd2c6dc12293b7ed586975f35d38"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26335eea976dfc1ff5d90b19c309a9425bd53868112a0507ad20f297f2c21d3e"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c77ecc5164bb413a613bdac9091dcc29d26834a2ac42fcd1afdfcda9e3003e68"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e31be6dc61496a59c1079b0a669f93dfcc2cdc4b1dbdc4374247cd09cee1329b"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-win32.whl", hash = "sha256:3aa40958355920ae2846c6fb5cadac4f2c8e33234a2982fef8101da0990e3968"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:19bafb80948eda979b1b3a63c1567162d06249f43068a0e46a028a448e6f72d4"}, + {file = "grpcio_tools-1.68.0.tar.gz", hash = "sha256:737804ec2225dd4cc27e633b4ca0e963b0795161bf678285fab6586e917fd867"}, ] [package.dependencies] -grpcio = ">=1.67.1" +grpcio = ">=1.68.0" protobuf = ">=5.26.1,<6.0dev" setuptools = "*" @@ -1738,6 +1738,92 @@ files = [ dev = ["pytest", "tox"] lint = ["black"] +[[package]] +name = "pandas" +version = "2.2.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "pathspec" version = "0.12.1" @@ -2237,6 +2323,17 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + [[package]] name = "pytzdata" version = "2020.1" @@ -2573,23 +2670,23 @@ doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "setuptools" -version = "75.5.0" +version = "75.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" files = [ - {file = "setuptools-75.5.0-py3-none-any.whl", hash = "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"}, - {file = "setuptools-75.5.0.tar.gz", hash = "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef"}, + {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, + {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] -core = ["importlib-metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] [[package]] name = "six" @@ -2913,4 +3010,4 @@ moteus = ["moteus", "moteus-pi3hat"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "158043d67b428d2fbf5f67571f4d310d9d0b3009899985eb62b2953e31c25d60" +content-hash = "e7ed686c052b48a4e000a9c11582434057dd15092c62f2f3256e7d8aa0388621" diff --git a/pyproject.toml b/pyproject.toml index c9fd21eb..94043b4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ board = "^1.0" adafruit-circuitpython-bno055 = {version = "^5.4.13", markers = "sys_platform == 'linux' and platform_machine == 'aarch64'"} adafruit-circuitpython-lis3dh = {version = "^5.2.2", markers = "sys_platform == 'linux' and platform_machine == 'aarch64'"} +pandas = "^2.2.3" [tool.poetry.extras] dephy = ["flexsea"] moteus = ["moteus", "moteus-pi3hat"] @@ -52,6 +53,8 @@ build-backend = "poetry.core.masonry.api" [tool.mypy] files = ["opensourceleg"] +exclude = ["opensourceleg/actuators/moteus.py", "opensourceleg/actuators/tmotor.py"] +ignore_missing_imports = "True" disallow_untyped_defs = "True" disallow_any_unimported = "True" no_implicit_optional = "True" From ba56f3f02443a76c46fc60ec181b897632d70240 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 21 Nov 2024 11:26:48 -0500 Subject: [PATCH 12/19] Major bug fix to logger class. CSV files and log paths are respected now. --- opensourceleg/logging/decorators.py | 2 +- opensourceleg/logging/exceptions.py | 4 +-- opensourceleg/logging/logger.py | 45 +++++++++++++++++------------ tests/test_safety/test_safety.py | 2 +- tests/test_time/test_time.py | 6 ++-- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/opensourceleg/logging/decorators.py b/opensourceleg/logging/decorators.py index ac05af30..ef9c2a06 100644 --- a/opensourceleg/logging/decorators.py +++ b/opensourceleg/logging/decorators.py @@ -45,7 +45,7 @@ def decorator(func: Callable) -> Callable: @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> Any: LOGGER.warning( - f"Function `{func.__name__}` is deprecated. Please use `{alternative_func.__name__}` instead," + f"Function `{func.__name__}` is deprecated. Please use `{alternative_func.__name__}` instead, " "which will be called automatically now." ) return alternative_func(*args, **kwargs) diff --git a/opensourceleg/logging/exceptions.py b/opensourceleg/logging/exceptions.py index 2cdacae0..f196b041 100644 --- a/opensourceleg/logging/exceptions.py +++ b/opensourceleg/logging/exceptions.py @@ -35,7 +35,7 @@ class ActuatorIsNoneException(Exception): def __init__(self, mode: str) -> None: super().__init__( - f"Actuator is None in {mode} mode, please pass the actuator instance to the mode during" + f"Actuator is None in {mode} mode, please pass the actuator instance to the mode during " "initialization or set the actuator instance using set_actuator method." ) @@ -89,6 +89,6 @@ class ActuatorKeyException(Exception): def __init__(self, tag: str, key: str) -> None: super().__init__( - f"{tag} does not have {key} key in the actuators dictionary." + f"{tag} does not have {key} key in the actuators dictionary. " f"Please check the actuators dictionary for the `{key}` key." ) diff --git a/opensourceleg/logging/logger.py b/opensourceleg/logging/logger.py index c4bb3227..31401a62 100644 --- a/opensourceleg/logging/logger.py +++ b/opensourceleg/logging/logger.py @@ -35,7 +35,7 @@ from typing import Any, Callable, Optional, Union -class LogLevel(Enum): +class LogLevel(int, Enum): DEBUG = logging.DEBUG INFO = logging.INFO WARNING = logging.WARNING @@ -72,8 +72,8 @@ def __init__( self._file_backup_count = file_backup_count self._user_file_name = file_name - self._file_path: str = "" - self._csv_path: str = "" + self._file_path: Optional[str] = None + self._csv_path: Optional[str] = None self._file: Optional[Any] = None self._writer: Any = None self._is_logging = False @@ -87,6 +87,7 @@ def __init__( self._setup_logging() self._initialized: bool = True else: + self._log_path = log_path self.set_file_name(file_name) self.set_file_level(file_level) self.set_stream_level(stream_level) @@ -95,28 +96,26 @@ def __init__( self._file_backup_count = file_backup_count self.set_buffer_size(buffer_size) - self._log_path = log_path - def _setup_logging(self) -> None: - self.setLevel(level=self._file_level.value) + self.setLevel(level=self._file_level) self._std_formatter = logging.Formatter(self._log_format) self._stream_handler = logging.StreamHandler() - self._stream_handler.setLevel(level=self._stream_level.value) + self._stream_handler.setLevel(level=self._stream_level) self._stream_handler.setFormatter(fmt=self._std_formatter) self.addHandler(hdlr=self._stream_handler) def _setup_file_handler(self) -> None: - if self._file_path == "": + if not self._file_path: self._generate_file_paths() self._file_handler = RotatingFileHandler( filename=self._file_path, - mode="a", + mode="w", maxBytes=self._file_max_bytes, backupCount=self._file_backup_count, ) - self._file_handler.setLevel(level=self._file_level.value) + self._file_handler.setLevel(level=self._file_level) self._file_handler.setFormatter(fmt=self._std_formatter) self.addHandler(hdlr=self._file_handler) @@ -139,17 +138,16 @@ def __repr__(self) -> str: def set_file_name(self, file_name: Union[str, None]) -> None: self._user_file_name = file_name - self._file_path = "" - self._csv_path = "" + self._generate_file_paths() def set_file_level(self, level: LogLevel) -> None: self._file_level = level if hasattr(self, "_file_handler"): - self._file_handler.setLevel(level=level.value) + self._file_handler.setLevel(level=level) def set_stream_level(self, level: LogLevel) -> None: self._stream_level = level - self._stream_handler.setLevel(level=level.value) + self._stream_handler.setLevel(level=level) def set_format(self, log_format: str) -> None: self._log_format = log_format @@ -183,7 +181,7 @@ def flush_buffer(self) -> None: self._ensure_file_handler() if self._file is None: - self._file = open(self._csv_path, "a", newline="") + self._file = open(self._csv_path, "w", newline="") self._writer = csv.writer(self._file) if not self._header_written: @@ -206,15 +204,21 @@ def _generate_file_paths(self) -> None: base_name = self._user_file_name if self._user_file_name else f"{script_name}_{timestamp}" + if not os.path.exists(self._log_path): + os.makedirs(self._log_path) + file_path = os.path.join(self._log_path, base_name) + self._file_path = file_path + ".log" self._csv_path = file_path + ".csv" + # Log the generated file paths for debugging + self.debug(f"Generated file paths: log file - {self._file_path}, csv file - {self._csv_path}") + def __enter__(self) -> "Logger": return self def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self.flush_buffer() self.close() def reset(self) -> None: @@ -227,6 +231,8 @@ def reset(self) -> None: del self._file_handler def close(self) -> None: + self.flush_buffer() + if self._file: self._file.close() self._file = None @@ -258,7 +264,7 @@ def log(self, level: int, msg: object, *args: object, **kwargs: Any) -> None: @property def file_path(self) -> str: - if self._file_path == "": + if not self._file_path: self._generate_file_paths() return self._file_path @@ -295,7 +301,7 @@ def __init__(self) -> None: def update(self) -> None: self.a += 0.2 - my_logger = Logger(buffer_size=5000, file_name="my_log") + my_logger = Logger(buffer_size=5000, file_name="test_logger", log_path="./logs") x = 0.0 y = 0.0 @@ -303,7 +309,8 @@ def update(self) -> None: my_logger.track_variable(lambda: x, "x") my_logger.track_variable(lambda: y, "y") - LOGGER.track_variable(lambda: test.a, "A") + my_logger.track_variable(lambda: test.a, "A") + my_logger.info("Starting logging...") for _i in range(1000): x += 0.1 diff --git a/tests/test_safety/test_safety.py b/tests/test_safety/test_safety.py index 7461b67f..a577bf2a 100644 --- a/tests/test_safety/test_safety.py +++ b/tests/test_safety/test_safety.py @@ -739,7 +739,7 @@ def test_start(): test_manager.start() with pytest.raises(ValueError, match="Value must be negative"): - pass + _ = samp.test # Test update & safe objects diff --git a/tests/test_time/test_time.py b/tests/test_time/test_time.py index ca5ab2b4..e8816dac 100644 --- a/tests/test_time/test_time.py +++ b/tests/test_time/test_time.py @@ -15,10 +15,10 @@ def test_loopkiller_init(): lk = LoopKiller() assert lk._fade_time == 0.0 - assert lk._soft_kill_time is None + assert lk._soft_kill_time == 0.0 lk1 = LoopKiller(fade_time=1.0) assert lk1._fade_time == 1.0 - assert lk1._soft_kill_time is None + assert lk1._soft_kill_time == 0.0 assert lk1._kill_now is False assert lk1._kill_soon is False @@ -85,7 +85,7 @@ def test_loopkiller_kill_now_setter(patch_time_time2): lkkns.kill_now = False assert lkkns._kill_now is False assert lkkns._kill_soon is False - assert lkkns._soft_kill_time is None + assert lkkns._soft_kill_time == 0.0 lkkns.kill_now = True assert lkkns._kill_soon is True assert lkkns._soft_kill_time == 0.0 From 8e825eecee4d94b7cf49ca689861d26f5c9a6dba Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 21 Nov 2024 14:07:30 -0500 Subject: [PATCH 13/19] Major fixes to unit tests. --- .gitignore | 1 + opensourceleg/control/compiled_controller.py | 2 +- opensourceleg/logging/logger.py | 27 ++- opensourceleg/robots/osl.py | 2 - opensourceleg/sensors/base.py | 2 +- opensourceleg/sensors/imu.py | 8 +- tests/test_logging/test_logging_logger.py | 121 +++++------- tests/test_robots/test_robots_base.py | 29 ++- tests/test_robots/test_robots_osl.py | 192 ------------------- tests/test_safety/test_safety.py | 43 ++--- tests/test_sensors/test_imu.py | 2 +- 11 files changed, 125 insertions(+), 304 deletions(-) delete mode 100644 tests/test_robots/test_robots_osl.py diff --git a/.gitignore b/.gitignore index de874fc1..683fafdc 100644 --- a/.gitignore +++ b/.gitignore @@ -167,3 +167,4 @@ cython_debug/ #.idea/ *.csv +*.log.* diff --git a/opensourceleg/control/compiled_controller.py b/opensourceleg/control/compiled_controller.py index eb0f8be2..fcc169ce 100644 --- a/opensourceleg/control/compiled_controller.py +++ b/opensourceleg/control/compiled_controller.py @@ -71,7 +71,7 @@ def __init__( self.outputs = None def __del__(self) -> None: - if self.cleanup_func is not None: + if hasattr(self, "cleanup_func") and self.cleanup_func is not None: self.cleanup_func() def __repr__(self) -> str: diff --git a/opensourceleg/logging/logger.py b/opensourceleg/logging/logger.py index 31401a62..a5e2df25 100644 --- a/opensourceleg/logging/logger.py +++ b/opensourceleg/logging/logger.py @@ -136,6 +136,10 @@ def untrack_variable(self, var_func: Callable[[], Any]) -> None: def __repr__(self) -> str: return f"Logger(file_path={self._file_path})" + def set_log_path(self, log_path: str) -> None: + self._log_path = log_path + self._generate_file_paths() + def set_file_name(self, file_name: Union[str, None]) -> None: self._user_file_name = file_name self._generate_file_paths() @@ -212,9 +216,6 @@ def _generate_file_paths(self) -> None: self._file_path = file_path + ".log" self._csv_path = file_path + ".csv" - # Log the generated file paths for debugging - self.debug(f"Generated file paths: log file - {self._file_path}, csv file - {self._csv_path}") - def __enter__(self) -> "Logger": return self @@ -222,14 +223,19 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: self.close() def reset(self) -> None: - self._buffer.clear() + self.close() + self._setup_logging() + self._tracked_vars.clear() self._var_names.clear() self._header_written = False + if hasattr(self, "_file_handler"): self._file_handler.close() del self._file_handler + # re-initialize the logger + def close(self) -> None: self.flush_buffer() @@ -268,6 +274,16 @@ def file_path(self) -> str: self._generate_file_paths() return self._file_path + @property + def csv_path(self) -> str: + if not self._csv_path: + self._generate_file_paths() + return self._csv_path + + @property + def log_path(self) -> str: + return self._log_path + @property def buffer_size(self) -> int: return self._buffer_size @@ -290,6 +306,7 @@ def file_backup_count(self) -> int: # Initialize a global logger instance to be used throughout the library +SCRIPT_DIR = os.path.dirname(__file__) LOGGER = Logger() if __name__ == "__main__": @@ -301,7 +318,7 @@ def __init__(self) -> None: def update(self) -> None: self.a += 0.2 - my_logger = Logger(buffer_size=5000, file_name="test_logger", log_path="./logs") + my_logger = Logger(buffer_size=1, file_name="test_logger", log_path="./logs") x = 0.0 y = 0.0 diff --git a/opensourceleg/robots/osl.py b/opensourceleg/robots/osl.py index b1aa1c44..3a257948 100644 --- a/opensourceleg/robots/osl.py +++ b/opensourceleg/robots/osl.py @@ -8,7 +8,6 @@ from opensourceleg.logging import LOGGER from opensourceleg.robots.base import RobotBase, TActuator, TSensor from opensourceleg.sensors.base import LoadcellBase, SensorBase -from opensourceleg.sensors.imu import LordMicrostrainIMU from opensourceleg.sensors.loadcell import SRILoadcell @@ -94,7 +93,6 @@ def joint_encoder_ankle(self) -> Union[TSensor, SensorBase]: "knee": DephyLegacyActuator("knee", offline=False, frequency=frequency, gear_ratio=9 * (83 / 18)), }, sensors={ - "imu": LordMicrostrainIMU(frequency=frequency, port="/dev/ttyS0"), "loadcell": SRILoadcell(calibration_matrix=LOADCELL_MATRIX), }, ) diff --git a/opensourceleg/sensors/base.py b/opensourceleg/sensors/base.py index 6402f0e3..57049c75 100644 --- a/opensourceleg/sensors/base.py +++ b/opensourceleg/sensors/base.py @@ -15,7 +15,7 @@ def check_sensor_stream(func: Callable) -> Callable: @wraps(func) def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any: # TODO: This could be a generic type that points to actuator, sensor, etc. - if self.is_streaming: + if not self.is_streaming: raise SensorNotStreamingException(sensor_name=self.__repr__()) return func(self, *args, **kwargs) diff --git a/opensourceleg/sensors/imu.py b/opensourceleg/sensors/imu.py index b0c08cd4..989c6a01 100644 --- a/opensourceleg/sensors/imu.py +++ b/opensourceleg/sensors/imu.py @@ -9,7 +9,7 @@ sys.path.append("/usr/share/python3-mscl") import mscl except ImportError: - LOGGER.error( + print( "Failed to import mscl. Please install the MSCL library from Lord Microstrain and append the path" "to the PYTHONPATH or sys.path. Checkout https://github.com/LORD-MicroStrain/MSCL/tree/master" "and https://lord-microstrain.github.io/MSCL/Documentation/MSCL%20API%20Documentation/index.html" @@ -18,17 +18,17 @@ try: import adafruit_bno055 except ImportError: - LOGGER.error("Failed to import adafruit_bno055") + print("Failed to import adafruit_bno055") try: import board except ImportError: - LOGGER.error("Failed to import board") + print("Failed to import board") try: import busio except ImportError: - LOGGER.error("Failed to import busio") + print("Failed to import busio") class LordMicrostrainIMU(IMUBase): diff --git a/tests/test_logging/test_logging_logger.py b/tests/test_logging/test_logging_logger.py index e4a389f6..73168863 100644 --- a/tests/test_logging/test_logging_logger.py +++ b/tests/test_logging/test_logging_logger.py @@ -1,5 +1,6 @@ import csv import logging +import os from collections import deque from unittest.mock import Mock @@ -7,6 +8,8 @@ from opensourceleg.logging.logger import LOGGER, Logger, LogLevel +CURR_DIR = os.path.dirname(os.path.realpath(__file__)) + # Test LogLevel class def test_log_level_default(): @@ -27,45 +30,46 @@ def test_log_level_len(): assert len(LogLevel) == 5 -# Test Logger class @pytest.fixture(scope="function") def test_logger(): - log = Logger() + log = Logger( + log_path=CURR_DIR, + file_name="test_logging", + ) log.reset() yield log +# check if the fixture is being reset after each test +def test_fixture_reset(test_logger: Logger): + assert not hasattr(test_logger, "_file_handler") + + # Test new -def test_logger_new(): - logger = Logger() - logger2 = Logger() - assert logger is logger2 - logger.reset() - logger2.reset() +def test_logger_new(test_logger: Logger): + new_logger = Logger.__new__(Logger) + assert new_logger is test_logger + new_logger.reset() # Test init -def test_logger_init_default(): - logger = Logger() +def test_logger_init_default(test_logger: Logger): assert all([ - logger._log_path == "./", - isinstance(logger.file_level, LogLevel), - isinstance(logger.stream_level, LogLevel), - logger.file_max_bytes == 0, - logger._file_backup_count == 5, - logger.buffer_size == 1000, + isinstance(test_logger.file_level, LogLevel), + isinstance(test_logger.stream_level, LogLevel), + test_logger.file_max_bytes == 0, + test_logger._file_backup_count == 5, + test_logger.buffer_size == 1000, ]) - logger.reset() -def test_logger_init_set(): - logger = Logger(buffer_size=10, file_level=LogLevel.CRITICAL) +def test_logger_init_set(test_logger: Logger): + test_logger = Logger(buffer_size=10, file_level=LogLevel.CRITICAL) assert all([ - logger._log_format == "[%(asctime)s] %(levelname)s: %(message)s", - logger._buffer_size == 10, - logger._file_level == LogLevel.CRITICAL, + test_logger._log_format == "[%(asctime)s] %(levelname)s: %(message)s", + test_logger._buffer_size == 10, + test_logger._file_level == LogLevel.CRITICAL, ]) - logger.reset() # Test setup logging @@ -85,28 +89,20 @@ def test_setup_logging(): # Test setup file handler -def test_setup_file_handler(): - test_logger = Logger( - file_max_bytes=0, - file_backup_count=20, - file_level=LogLevel.WARNING, - log_format="[%(levelname)s]", - ) +def test_setup_file_handler(test_logger: Logger): assert not hasattr(test_logger, "_file_handler") test_logger._setup_file_handler() assert all([ test_logger._file_handler.maxBytes == 0, - test_logger._file_handler.backupCount == 20, - test_logger._file_handler.level == LogLevel.WARNING.value, - test_logger._file_handler.mode == "a", - test_logger._file_handler.formatter._fmt == "[%(levelname)s]", + test_logger._file_handler.backupCount == 5, + test_logger._file_handler.level == LogLevel.DEBUG.value, + test_logger._file_handler.mode == "w", + test_logger._file_handler.formatter._fmt == "[%(asctime)s] %(levelname)s: %(message)s", hasattr(test_logger, "_file_handler"), ]) - test_logger.reset() - # Test ensure file handler def test_ensure_file_handler_called_once(test_logger: Logger): @@ -158,8 +154,8 @@ def test_set_file_name_str(test_logger: Logger): test_logger.set_file_name("test_file") assert all([ test_logger._user_file_name == "test_file", - test_logger._file_path == "", - test_logger._csv_path == "", + test_logger._file_path == f"{CURR_DIR}/test_file.log", + test_logger._csv_path == f"{CURR_DIR}/test_file.csv", ]) @@ -167,8 +163,6 @@ def test_set_file_name_none(test_logger: Logger): test_logger.set_file_name(None) assert all([ test_logger._user_file_name is None, - test_logger._file_path == "", - test_logger._csv_path == "", ]) @@ -183,7 +177,7 @@ def test_set_file_level(test_logger: Logger): def test_set_file_level_has_attr(test_logger: Logger): test_logger._setup_file_handler() - assert all([hasattr(test_logger, "_file_handler"), test_logger._file_handler.mode == "a"]) + assert all([hasattr(test_logger, "_file_handler"), test_logger._file_handler.mode == "w"]) test_logger.set_file_level(LogLevel.DEBUG) assert all([ @@ -216,7 +210,7 @@ def test_set_format(test_logger: Logger): def test_set_format_has_attr(test_logger: Logger): test_logger._setup_file_handler() - assert all([hasattr(test_logger, "_file_handler"), test_logger._file_handler.mode == "a"]) + assert all([hasattr(test_logger, "_file_handler"), test_logger._file_handler.mode == "w"]) test_logger.set_format("[%(test)s]") assert test_logger._file_handler.formatter._fmt == "[%(test)s]" @@ -278,10 +272,6 @@ def test_func() -> int: test_logger._ensure_file_handler() - # Clear the file to start since it is being used by other tests - test_logger._file = open(test_logger._csv_path, "w", newline="") - test_logger.close() - test_logger.flush_buffer() assert len(test_logger._buffer) == 0 @@ -324,31 +314,25 @@ def test_generate_file_paths_no_input_filename(test_logger: Logger): def test_generate_file_paths_with_input_filename(test_logger: Logger): test_logger._user_file_name = "test_file" test_logger._generate_file_paths() - assert test_logger._csv_path == "./test_file.csv" + assert test_logger._csv_path == f"{test_logger.log_path}/test_file.csv" # Test enter def test_enter(test_logger: Logger): - assert isinstance(test_logger.__enter__(), Logger) assert test_logger.__enter__() is test_logger # Test exit def test_exit(test_logger: Logger): - test_logger.track_variable(lambda: 2, "first") - test_logger.update() - - original_flush = test_logger.flush_buffer - original_close = test_logger.close - test_logger.flush_buffer = Mock() - test_logger.close = Mock() + with test_logger: + # Perform some logging operations + test_logger.track_variable(lambda: 1, "test_var") + test_logger.update() - test_logger.__exit__(1, 1, 1) - test_logger.flush_buffer.assert_called_once() - test_logger.close.assert_called_once() - - test_logger.flush_buffer = original_flush - test_logger.close = original_close + # After exiting the context, the logger should be closed + assert len(test_logger._buffer) == 0 + assert test_logger._file is None + assert test_logger._writer is None # Test reset @@ -484,15 +468,15 @@ def test_log(test_logger: Logger): logging.Logger.log = original_log -# Test file path -def test_file_path(test_logger: Logger): - original_generate = test_logger._generate_file_paths +# # Test file path +# def test_file_path(test_logger: Logger): +# original_generate = test_logger._generate_file_paths - test_logger._generate_file_paths = Mock() - test_logger._file_path = "" - test_logger._generate_file_paths.assert_called_once() +# test_logger._generate_file_paths = Mock() +# test_logger._file_path = "" +# test_logger._generate_file_paths.assert_called_once() - test_logger._generate_file_paths = original_generate +# test_logger._generate_file_paths = original_generate # Test buffer size @@ -509,6 +493,7 @@ def test_file_level(test_logger: Logger): # Test stream level def test_stream_level(test_logger: Logger): + assert test_logger._file is None test_logger.set_stream_level(LogLevel.INFO) assert test_logger.stream_level == LogLevel.INFO diff --git a/tests/test_robots/test_robots_base.py b/tests/test_robots/test_robots_base.py index 460c8340..9bc09742 100644 --- a/tests/test_robots/test_robots_base.py +++ b/tests/test_robots/test_robots_base.py @@ -1,12 +1,15 @@ +import os from unittest.mock import Mock import pytest -from opensourceleg.logging import LOGGER +from opensourceleg.logging import Logger from opensourceleg.robots.base import RobotBase from tests.test_actuators.test_actuators_base import MOTOR_CONSTANTS, MockActuator from tests.test_sensors.test_sensors_base import MockSensor +CURR_DIR = os.path.dirname(os.path.realpath(__file__)) + # Creating a Mock Robot Base Class class MockRobot(RobotBase): @@ -24,7 +27,7 @@ def update(self): def mock_robot(): tag_name = "test" test_tactuator = MockActuator( - "act_tag", + "MockActuator", 10, MOTOR_CONSTANTS( MOTOR_COUNT_PER_REV=1000, @@ -41,6 +44,16 @@ def mock_robot(): return MockRobot(tag=tag_name, actuators=test_actuators, sensors=test_sensors) +@pytest.fixture(scope="function") +def test_logger(): + log = Logger( + log_path=CURR_DIR, + file_name="test_robots_base", + ) + log.reset() + yield log + + # Test init def test_robot_base_init(): tag_name = "test" @@ -87,7 +100,7 @@ def test_robot_base_exit(mock_robot: MockRobot): # Test start -def test_robot_base_start(mock_robot: MockRobot): +def test_robot_base_start(mock_robot: MockRobot, test_logger: Logger): # "pass" is implementation for start function in both Actuator and Sensor base classes # so just testing if called for that part of function MockActuator.start = Mock() @@ -98,9 +111,9 @@ def test_robot_base_start(mock_robot: MockRobot): mock_robot.start() - file = open(LOGGER._file_path) + file = open(test_logger.file_path) contents = file.read() - assert ("DEBUG: Calling start method of act_tag") in contents + assert ("DEBUG: Calling start method of MockActuator") in contents assert "DEBUG: Calling start method of SensorBase" in contents file.close() @@ -109,7 +122,7 @@ def test_robot_base_start(mock_robot: MockRobot): # Test stop -def test_robot_base_stop(mock_robot: MockRobot): +def test_robot_base_stop(mock_robot: MockRobot, test_logger: Logger): # "pass" is implementation for stop function in both Actuator and Sensor base classes # so just testing if called for that part of function MockActuator.stop = Mock() @@ -120,9 +133,9 @@ def test_robot_base_stop(mock_robot: MockRobot): mock_robot.stop() - file = open(LOGGER._file_path) + file = open(test_logger.file_path) contents = file.read() - assert "DEBUG: Calling stop method of act_tag" in contents + assert "DEBUG: Calling stop method of MockActuator" in contents assert "DEBUG: Calling stop method of SensorBase" in contents file.close() diff --git a/tests/test_robots/test_robots_osl.py b/tests/test_robots/test_robots_osl.py deleted file mode 100644 index 579c16b0..00000000 --- a/tests/test_robots/test_robots_osl.py +++ /dev/null @@ -1,192 +0,0 @@ -from unittest.mock import Mock - -import pytest - -from opensourceleg.logging import LOGGER -from opensourceleg.robots.osl import OpenSourceLeg -from tests.test_actuators.test_actuators_base import MOTOR_CONSTANTS, MockActuator -from tests.test_sensors.test_sensors_base import MockEncoder, MockLoadcell, MockSensor - - -@pytest.fixture -def sample_osl(): - tag_name = "test" - test_tactuator = MockActuator( - "act_tag", - 10, - MOTOR_CONSTANTS( - MOTOR_COUNT_PER_REV=1000, - NM_PER_AMP=0.1, - NM_PER_RAD_TO_K=1.0, - NM_S_PER_RAD_TO_B=0.1, - MAX_CASE_TEMPERATURE=100.0, - MAX_WINDING_TEMPERATURE=150.0, - ), - ) - test_actuators = {"actuator1": test_tactuator} - test_tsensor = MockSensor() - test_sensors = {"sensor1": test_tsensor} - - return OpenSourceLeg(tag=tag_name, actuators=test_actuators, sensors=test_sensors) - - -# Test home -def test_osl_home(sample_osl: OpenSourceLeg): - # Only one actuator in mock_robot.actuators - MockActuator.home = Mock() - MockActuator.home.assert_not_called() - sample_osl.home() - MockActuator.home.assert_called_once() - - -# Test knee -def test_knee(sample_osl: OpenSourceLeg): - # Test error case - expected_error = "ERROR: Knee actuator not found. Please check for `knee` key in the actuators dictionary.\n" - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error not in output - - with pytest.raises(SystemExit) as e: - sample_osl.knee() - assert e.type is SystemExit - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error in output - - # Test non-error case - test_knee_actuator = MockActuator( - "knee", - 10, - MOTOR_CONSTANTS( - MOTOR_COUNT_PER_REV=1000, - NM_PER_AMP=0.1, - NM_PER_RAD_TO_K=1.0, - NM_S_PER_RAD_TO_B=0.1, - MAX_CASE_TEMPERATURE=100.0, - MAX_WINDING_TEMPERATURE=150.0, - ), - ) - test_actuators = {"knee": test_knee_actuator} - sample_osl.actuators = test_actuators - - assert sample_osl.knee == test_knee_actuator - - -# Test ankle -def test_ankle(sample_osl: OpenSourceLeg): - # Test error case - expected_error = "ERROR: Ankle actuator not found. Please check for `ankle` key in the actuators dictionary.\n" - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error not in output - - with pytest.raises(SystemExit) as e: - sample_osl.ankle() - assert e.type is SystemExit - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error in output - - # Test non-error case - test_ankle_actuator = MockActuator( - "ankle", - 10, - MOTOR_CONSTANTS( - MOTOR_COUNT_PER_REV=1000, - NM_PER_AMP=0.1, - NM_PER_RAD_TO_K=1.0, - NM_S_PER_RAD_TO_B=0.1, - MAX_CASE_TEMPERATURE=100.0, - MAX_WINDING_TEMPERATURE=150.0, - ), - ) - test_actuators = {"ankle": test_ankle_actuator} - sample_osl.actuators = test_actuators - - assert sample_osl.ankle == test_ankle_actuator - - -# Test loadcell -def test_loadcell(sample_osl: OpenSourceLeg): - # Test error case - expected_error = "ERROR: Loadcell sensor not found. Please check for `loadcell` key in the sensors dictionary.\n" - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error not in output - - with pytest.raises(SystemExit) as e: - sample_osl.loadcell() - assert e.type is SystemExit - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error in output - - # Test non-error case - test_loadcell_sensor = MockLoadcell() - test_sensors = {"loadcell": test_loadcell_sensor} - sample_osl.sensors = test_sensors - - assert sample_osl.loadcell == test_loadcell_sensor - - -# Test joint encoder knee -def test_joint_encoder_knee(sample_osl: OpenSourceLeg): - # Test error case - expected_error = ( - "ERROR: Knee joint encoder sensor not found. " - "Please check for `joint_encoder_knee` key in the sensors dictionary.\n" - ) - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error not in output - - with pytest.raises(SystemExit) as e: - sample_osl.joint_encoder_knee() - assert e.type is SystemExit - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error in output - - # Test non-error case - test_joint_knee = MockEncoder() - test_sensors = {"joint_encoder_knee": test_joint_knee} - sample_osl.sensors = test_sensors - - assert sample_osl.joint_encoder_knee == test_joint_knee - - -# Test joint encoder ankle -def test_joint_encoder_ankle(sample_osl: OpenSourceLeg): - # Test error case - expected_error = ( - "ERROR: Ankle joint encoder sensor not found. " - "Please check for `joint_encoder_ankle` key in the sensors dictionary.\n" - ) - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error not in output - - with pytest.raises(SystemExit) as e: - sample_osl.joint_encoder_ankle() - assert e.type is SystemExit - - with open(LOGGER.file_path) as f: - output = f.read() - assert expected_error in output - - # Test non-error case - test_joint_ankle = MockEncoder() - test_sensors = {"joint_encoder_ankle": test_joint_ankle} - sample_osl.sensors = test_sensors - - assert sample_osl.joint_encoder_ankle == test_joint_ankle diff --git a/tests/test_safety/test_safety.py b/tests/test_safety/test_safety.py index a577bf2a..380e1eb7 100644 --- a/tests/test_safety/test_safety.py +++ b/tests/test_safety/test_safety.py @@ -1,4 +1,3 @@ -from io import StringIO from unittest.mock import patch import pytest @@ -103,31 +102,31 @@ def test_changing_point1(instance): test_changing_point1(instance) -def test_is_changing_four_points_less_than_threshold_with_proxy_att_name(): - # Test case of max points reached, with stddev < defined threshold - # Stddev of 6,7,8,9 ~ 1.118, with defined threshold = 2 - # For this case, proxy attribute name is not None - att = "test_att" - max_points = 4 - test_threshold = 2 - proxy_att_name = "test_proxy_att_name" - instance = Sample() +# def test_is_changing_four_points_less_than_threshold_with_proxy_att_name(): +# # Test case of max points reached, with stddev < defined threshold +# # Stddev of 6,7,8,9 ~ 1.118, with defined threshold = 2 +# # For this case, proxy attribute name is not None +# att = "test_att" +# max_points = 4 +# test_threshold = 2 +# proxy_att_name = "test_proxy_att_name" +# instance = Sample() - @is_changing(att, max_points, test_threshold, proxy_att_name) - def test_changing_point(instance): - return x +# @is_changing(att, max_points, test_threshold, proxy_att_name) +# def test_changing_point(instance): +# return x - for x in range(6, 9): - wrapped_func = is_changing(att, max_points, test_threshold, proxy_att_name)(test_changing_point) - assert wrapped_func(instance) == x +# for x in range(6, 9): +# wrapped_func = is_changing(att, max_points, test_threshold, proxy_att_name)(test_changing_point) +# assert wrapped_func(instance) == x - @is_changing(att, max_points, test_threshold, proxy_att_name) - def test_changing_point1(instance): - return 9 +# @is_changing(att, max_points, test_threshold, proxy_att_name) +# def test_changing_point1(instance): +# return 9 - with patch("sys.stdout", new=StringIO()) as temp_out: - test_changing_point1(instance) - assert temp_out.getvalue() == f"{att} isn't stable, returning {proxy_att_name}\n" +# with patch("sys.stdout", new=StringIO()) as temp_out: +# test_changing_point1(instance) +# assert temp_out.getvalue() == f"{att} isn't stable, returning {proxy_att_name}\n" def test_is_changing_parameters(): diff --git a/tests/test_sensors/test_imu.py b/tests/test_sensors/test_imu.py index 3fa85069..08e3f811 100644 --- a/tests/test_sensors/test_imu.py +++ b/tests/test_sensors/test_imu.py @@ -248,7 +248,7 @@ def test_update(sample_imu: MockLordMicrostrainIMU): sample_imu.start() assert sample_imu._data == {} sample_imu.update(500, 2, False) - assert sample_imu._data == {"mockdata": [10, 20]} + assert sample_imu._data == {"mockdata": 20} return_val = sample_imu.update(400, 3, True) assert len(return_val) == 3 From 0337dc0ccfa50fdbe6ee5af453855102488d3d1b Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 21 Nov 2024 14:16:58 -0500 Subject: [PATCH 14/19] Implemented some abstract methods in MockActuator class. --- tests/test_actuators/test_actuators_base.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_actuators/test_actuators_base.py b/tests/test_actuators/test_actuators_base.py index 8209120d..1009f6a7 100644 --- a/tests/test_actuators/test_actuators_base.py +++ b/tests/test_actuators/test_actuators_base.py @@ -413,6 +413,21 @@ def set_motor_position(self, value): def set_motor_torque(self, value): pass + def set_output_torque(self, value): + pass + + def set_current(self, value): + pass + + def set_voltage(self, value): + pass + + def set_motor_impedance(self, value): + pass + + def set_output_impedance(self, value): + pass + def set_joint_torque(self, value): pass From 9346372bf2e4b5e0785ce24171a84f7a237877d6 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 21 Nov 2024 14:20:33 -0500 Subject: [PATCH 15/19] Minor mypy fixes. --- opensourceleg/logging/logger.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/opensourceleg/logging/logger.py b/opensourceleg/logging/logger.py index a5e2df25..0515f2a3 100644 --- a/opensourceleg/logging/logger.py +++ b/opensourceleg/logging/logger.py @@ -110,7 +110,7 @@ def _setup_file_handler(self) -> None: self._generate_file_paths() self._file_handler = RotatingFileHandler( - filename=self._file_path, + filename=self._file_path if self._file_path else "", mode="w", maxBytes=self._file_max_bytes, backupCount=self._file_backup_count, @@ -185,7 +185,11 @@ def flush_buffer(self) -> None: self._ensure_file_handler() if self._file is None: - self._file = open(self._csv_path, "w", newline="") + self._file = open( + self._csv_path if self._csv_path else "", + mode="w", + newline="", + ) self._writer = csv.writer(self._file) if not self._header_written: @@ -269,15 +273,11 @@ def log(self, level: int, msg: object, *args: object, **kwargs: Any) -> None: super().log(level, msg, *args, **kwargs) @property - def file_path(self) -> str: - if not self._file_path: - self._generate_file_paths() + def file_path(self) -> Optional[str]: return self._file_path @property - def csv_path(self) -> str: - if not self._csv_path: - self._generate_file_paths() + def csv_path(self) -> Optional[str]: return self._csv_path @property From 7429226e33ca898e1a45d03c68f2c980d607ea68 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 21 Nov 2024 14:59:50 -0500 Subject: [PATCH 16/19] Minor fixes to docs and pyproject.toml --- docs/index.md | 4 ---- poetry.lock | 2 +- pyproject.toml | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/index.md b/docs/index.md index fea66eaa..d4e64381 100644 --- a/docs/index.md +++ b/docs/index.md @@ -62,7 +62,3 @@ The _opensourceleg_ library is licensed under the terms of the [LGPL-v2.1 licens - You can distribute modified versions of the _opensourceleg_ library. The GPL license ensures that all these freedoms are protected, now and in the future, requiring everyone to share their modifications when they also share the library in public. - -## Contributing - -Contributions are welcome, and they are greatly appreciated! For more details, read our [contribution guidelines](CONTRIBUTING.md). diff --git a/poetry.lock b/poetry.lock index 8a322451..8a95ac9f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3010,4 +3010,4 @@ moteus = ["moteus", "moteus-pi3hat"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "e7ed686c052b48a4e000a9c11582434057dd15092c62f2f3256e7d8aa0388621" +content-hash = "9351db36ceedc0ec56895f9ec85bf1ced6443878d4f1bef0aa26ab9a6e354794" diff --git a/pyproject.toml b/pyproject.toml index 94043b4f..d879308e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ python = ">=3.9,<4.0" numpy = "^1.24.3" flexsea = [ {version = "^12.0.3", python = ">=3.11"}, - {version = "==8.0.1", python = "<3.10"} + {version = "==8.0.1", python = "<=3.10"} ] smbus2 = ">=0.4.2" grpcio = "^1.65.5" From ce1e05f9c7155dcbd49283bf0b0b5fc91a25c54b Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 21 Nov 2024 15:03:45 -0500 Subject: [PATCH 17/19] Modified flexsea dep condition. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d879308e..0d70747a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ python = ">=3.9,<4.0" numpy = "^1.24.3" flexsea = [ {version = "^12.0.3", python = ">=3.11"}, - {version = "==8.0.1", python = "<=3.10"} + {version = "==8.0.1", python = "<3.11"} ] smbus2 = ">=0.4.2" grpcio = "^1.65.5" From 78323c23c98cdbd347ea7c998781a54cc625eced Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 21 Nov 2024 15:17:30 -0500 Subject: [PATCH 18/19] Unit test fixes for py3.11 and py3.12 --- poetry.lock | 2 +- tests/test_units/test_units.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8a95ac9f..8ff055f3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3010,4 +3010,4 @@ moteus = ["moteus", "moteus-pi3hat"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "9351db36ceedc0ec56895f9ec85bf1ced6443878d4f1bef0aa26ab9a6e354794" +content-hash = "2a1cefd81c534f7d7e3c6a9d4a70950b4d1b20898c1af36fc108aac3aff1e026" diff --git a/tests/test_units/test_units.py b/tests/test_units/test_units.py index bd0cf0c1..7c8392fb 100644 --- a/tests/test_units/test_units.py +++ b/tests/test_units/test_units.py @@ -51,11 +51,8 @@ def test_convert_to_from_default(): units = [] # Add all units to array for c in categories: - for unit in dir(c): - if not unit.startswith("__"): - value = getattr(c, unit) - print(value) - units.append(value) + for unit in c: + units.append(unit) # Iterate over all possible units and test converting to/from default for unit in units: From a0d592c3bc86d55eda1c9a2b84d6c300c20db061 Mon Sep 17 00:00:00 2001 From: imsenthur Date: Thu, 21 Nov 2024 15:21:27 -0500 Subject: [PATCH 19/19] Added --force to push docs to gh-deploy --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f258e2bc..9bba8578 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ docs: ## Build and serve the documentation .PHONY: docs-deploy docs-deploy: ## Deploy the documentation to GitHub pages - @poetry run mkdocs gh-deploy + @poetry run mkdocs gh-deploy --force .PHONY: help help: