diff --git a/.github/workflows/build-docs-mypy.yaml b/.github/workflows/build-docs-mypy.yaml
deleted file mode 100644
index 6803c53..0000000
--- a/.github/workflows/build-docs-mypy.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Doc builder and mypy
-
-on: push
-
-jobs:
- build-docs:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Build docs
- env:
- NO_TTY: true
- run: |
- cd python/remotivelabs-broker
- ./docker-build.sh
- - name: Setup Python
- uses: actions/setup-python@v5
- with:
- python-version: 3.8
- architecture: x64
- - name: Install mypy
- run: |
- pip install mypy
- pip install python/remotivelabs-broker
- pip install python/remotivelabs-broker[default]
- - name: Run mypy
- run: |
- mypy python/remotivelabs-broker/remotivelabs/
- mypy . --exclude python/remotivelabs-broker/remotivelabs/
diff --git a/.github/workflows/build-remotivelabs-broker-ci.yaml b/.github/workflows/build-remotivelabs-broker-ci.yaml
new file mode 100644
index 0000000..43b4c46
--- /dev/null
+++ b/.github/workflows/build-remotivelabs-broker-ci.yaml
@@ -0,0 +1,58 @@
+name: Build remotivelabs-broker package and documentation
+
+on: push
+
+jobs:
+ build:
+ name: Build remotivelabs-broker
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.10"
+
+ - name: Build package and docs
+ env:
+ NO_TTY: true
+ run: ./python/remotivelabs-broker/docker-build.sh
+ - id: build_output
+ run: echo "whl_path=$(ls python/remotivelabs-broker/dist/*.whl)" >> $GITHUB_OUTPUT
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: remotivelabs-broker-whl
+ path: ${{ steps.build_output.outputs.whl_path }}
+ if-no-files-found: error
+ retention-days: 1
+ overwrite: true
+
+ test:
+ name: Install remotivelabs-broker
+ needs: build
+
+ strategy:
+ matrix:
+ python-version: ["3.8", "3.9", "3.10"]
+ os: [ubuntu-22.04, ubuntu-24.04, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - uses: actions/download-artifact@v4
+ with:
+ name: remotivelabs-broker-whl
+
+ - id: download_artifact
+ run: echo "library_path=$(ls *.whl)" >> $GITHUB_OUTPUT
+ shell: bash
+
+ - name: Install remotivelabs-broker
+ run: pip install ${{ steps.download_artifact.outputs.library_path }}
+ shell: bash
diff --git a/.github/workflows/code-qa.yaml b/.github/workflows/code-qa.yaml
deleted file mode 100644
index b1bc38e..0000000
--- a/.github/workflows/code-qa.yaml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Code-QA
-
-on: push
-
-jobs:
- ruff-lint:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: chartboost/ruff-action@v1
-
- ruff-format:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: chartboost/ruff-action@v1
- with:
- src: "."
- args: format --check --diff
-
- pylint:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Setup Python
- uses: actions/setup-python@v5
- with:
- python-version: 3.8
- architecture: x64
- - name: Install pylint
- run: |
- pip install pylint
- pip install python/remotivelabs-broker
- pip install python/remotivelabs-broker[default]
- - name: Run pylint
- run: |
- cd python/remotivelabs-broker
- pylint **/*.py
-
- rust-cargo:
- name: Rust cargo tasks
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: dtolnay/rust-toolchain@stable
- - name: Build and run tests
- run: |
- pushd rust/remotivelabs-broker
- cargo test
- popd
- - name: Check format
- run: |
- pushd rust/remotivelabs-broker
- cargo fmt --check
- popd
diff --git a/.github/workflows/python-code-qa-ci.yaml b/.github/workflows/python-code-qa-ci.yaml
new file mode 100644
index 0000000..7535fd8
--- /dev/null
+++ b/.github/workflows/python-code-qa-ci.yaml
@@ -0,0 +1,41 @@
+name: Python Code QA CI
+
+on: push
+
+jobs:
+ python-ci:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ["3.8", "3.9", "3.10"]
+ defaults:
+ run:
+ working-directory: ./python/remotivelabs-broker
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ - uses: abatilo/actions-poetry@v2
+ - run: poetry self add 'poethepoet[poetry_plugin]'
+
+ - name: Generate stubs (and build package)
+ env:
+ NO_TTY: true
+ run: ./docker-build.sh
+
+ - run: poetry install
+
+ - name: run pytest
+ run: poetry poe test
+
+ - name: run ruff
+ run: poetry poe lint
+
+ - name: run pylint
+ run: poetry poe pylint
+
+ - name: run mypy
+ run: poetry poe mypy
diff --git a/.github/workflows/rust-code-qa-ci.yaml b/.github/workflows/rust-code-qa-ci.yaml
new file mode 100644
index 0000000..c64d686
--- /dev/null
+++ b/.github/workflows/rust-code-qa-ci.yaml
@@ -0,0 +1,24 @@
+name: Rust Code QA CI
+
+on: push
+
+jobs:
+ rust-ci:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: dtolnay/rust-toolchain@stable
+
+ - name: Build and run tests
+ run: |
+ pushd rust/remotivelabs-broker
+ cargo test
+ popd
+
+ - name: Check format
+ run: |
+ pushd rust/remotivelabs-broker
+ cargo fmt --check
+ popd
diff --git a/.python-version b/.python-version
index cc1923a..c8cfe39 100644
--- a/.python-version
+++ b/.python-version
@@ -1 +1 @@
-3.8
+3.10
diff --git a/README.md b/README.md
index 9fd8bdb..b73d0c4 100644
--- a/README.md
+++ b/README.md
@@ -1,49 +1,31 @@
# RemotiveLabs RemotiveBroker GRPC APIs
-This repo contains all public API from [RemotiveLabs](https://remotivelabs.com/) for integrating
-with RemotiveBroker using your favourite programming language.
+This repo contains all public API from [RemotiveLabs](https://remotivelabs.com/) for integrating with RemotiveBroker using your favourite
+programming language.
-We currently have pre-built GAPIC (Generated API Client) for python and grpc-web
-but will add more moving forward.
-
-For other languages visit https://grpc.io/ to read up how to generate code for your
-programming language based on our protocol buffer definition files
+We currently have pre-built GAPIC (Generated API Client) for a few languages, but will add more moving forward.
+For other languages visit https://grpc.io/ to read up how to generate code for your programming language based on our protocol buffer
+definition files.
## Protocol buffer definitions
Use these to generate GAPIC for your programming language.
-- [Documentation and source code](protos/)
-
-## Python RemotiveBroker API
-
-Pre-built Python GAPIC + some extra helper functions
-
-- [Documentation and source code](python/remotivelabs-broker/)
-- [Code samples](https://github.com/remotivelabs/remotivelabs-samples/tree/main/python)
-
-
-## gRPC-web RemotiveBroker API
+[Protobuf files](./protos/README.md)
-Pre-built gRPC-web RemotiveBroker GAPIC. gRPC-web is designed to run in browser and
-not in nodejs. This is Javascipt+Typescript.
+## Language specific APIs
-- [Documentation and source code](grpc-web/grpc-web-stubs)
+- [Python](./python/remotivelabs-broker/)
+- [gRPC-web](./grpc-web/README.md)
+- [Rust](./rust/README.md)
## RemotiveBroker configuration schemas
[JSON schemas](schemas) of configuration files used by RemotiveBroker.
-## Coming soon
-
-Pre-built GAPIC for the following languages are coming shortly
-
-* Java and Kotlin
-* NodeJS
-
## Questions
-Please use our discussion forum/community for any questions you might have
-https://github.com/remotivelabs/remotivelabs-community/discussions
+Please use our discussion forum/community for any questions you might have:
+https://github.com/remotivelabs/remotivelabs-community/discussions
diff --git a/grpc-web/README.md b/grpc-web/README.md
index 57af5e4..2b23f74 100644
--- a/grpc-web/README.md
+++ b/grpc-web/README.md
@@ -1,26 +1,57 @@
-# RemotiveLabs WEB-GRPC
+# RemotiveBroker WEB-GRPC API
-Web-grpc is javascript to be used in a browser context, not intended
-to bu used with nodejs, read more here https://github.com/grpc/grpc-web
+`remotivelabs-grpc-web-stubs` - Javascript SDK for interacting with the RemotiveBroker API in a browser context. Not intended to be used
+with NodeJS.
-## Grpc-web-stubs
+Published to npm on [https://www.npmjs.com/package/remotivelabs-grpc-web-stubs](https://www.npmjs.com/package/remotivelabs-grpc-web-stubs).
-[![npm version](https://img.shields.io/npm/v/remotivelabs-grpc-web-stubs.svg)](https://www.npmjs.com/package/remotivelabs-grpc-web-stubs)
+## Getting started
-Pre-built GRPC stubs generated from protobuf files
+```bash
+cd grpc-web/grpc-web-stubs
+# install dependencies
+yarn install
+
+# TODO
```
-npm install --save remotivelabs-grpc-web-stubs
-```
-or
-```
-yarn add remotivelabs-grpc-web-stubs
+
+## Building
+
+```bash
+cd grpc-web/grpc-web-stubs
+
+# Build docker image
+docker build -t remotivelabs/grpc-web-generator .
+
+# Generate typescript
+./generate-ts.sh
+
+# Generate javascript
+./generate-js.sh
```
-Read more: [grpc-web-stubs/](grpc-web-stubs/)
+## Versioning
-## Grpc-web-client
+Versioning is done using `npm version`, see [Publishing](#publishing).
-RemotiveBroker high-level client is coming soon. This API will be based on our
-RemotiveBrokerApp that is using the grpc-web-stubs but adds layer on top to make
-it simpler to use.
\ No newline at end of file
+Follow [Semantic versioning](https://semver.org/). Beta versions should be suffixed with `-beta*`, example `0.2.0-beta1`.
+
+## Publishing
+
+Published to npm on [https://www.npmjs.com/package/remotivelabs-grpc-web-stubs](https://www.npmjs.com/package/remotivelabs-grpc-web-stubs).
+
+```bash
+# generate stubs
+./generate-ts.sh
+
+# update version
+npm version x.y.z
+
+# commit version
+git add .
+git commit -m "release: Prepare a release for version x.y.z"
+
+# publish
+npm publish
+```
diff --git a/grpc-web/grpc-web-stubs/README.md b/grpc-web/grpc-web-stubs/README.md
index 7b5d22d..bed3cce 100644
--- a/grpc-web/grpc-web-stubs/README.md
+++ b/grpc-web/grpc-web-stubs/README.md
@@ -5,44 +5,35 @@
These are generated grpc-web stubs to be used in a browser context, not intended
to bu used with nodejs, read more here https://github.com/grpc/grpc-web
-## Usage
+## Installation
-### Installation
-```
-npm install --save remotivelabs-grpc-web-stubs
+```bash
+yarn install --save remotivelabs-grpc-web-stubs
```
+
or
-```
+
+```bash
yarn add remotivelabs-grpc-web-stubs
```
-### Import
-```
+## Usage
+
+```javascript
import {SystemServiceClient} from 'remotivelabs-grpc-web-stubs'
const client = new SystemServiceClient(brokerUrl)
```
-## Development
-
-### Build grpc-web-generator
+## Examples
-This image is on dockerhub so you do not have to build it
-```sh
-docker build -t remotivelabs/grpc-web-generator .
-```
-
-### Generate stubs
+See [examples](./examples/README.md)
-From this directory run the following commands and update `src/index.ts` to explicitly export types.
+## License
-```sh
-sh ./generate-ts.sh
-```
+`remotivelabs-grpc-web-stubs` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
-## Release Instructions
+## Documentation
-1. Commit changes.
-2. Generate stubs `sh ./generate-ts.sh`.
-3. Update version with `npm version x.y.z` and commit it with `release: Prepare a release for version x.y.z`.
-4. Publish with `npm publish`.
+TODO: We dont have any documentation
+See [https://docs.remotivelabs.com/apis/python/remotivelabs/broker](https://docs.remotivelabs.com/apis/python/remotivelabs/broker).
diff --git a/grpc-web/grpc-web-stubs/examples/README.md b/grpc-web/grpc-web-stubs/examples/README.md
new file mode 100644
index 0000000..4f2254e
--- /dev/null
+++ b/grpc-web/grpc-web-stubs/examples/README.md
@@ -0,0 +1 @@
+# `remotivelabs-grpc-web-stubs` examples
diff --git a/python/README.md b/python/README.md
index 98da2f7..ca3c963 100644
--- a/python/README.md
+++ b/python/README.md
@@ -1,60 +1,69 @@
-# RemotiveLabs Python libraries
+# RemotiveBroker Python API
-## Usage
+`remotivelabs-broker` - Python SDK for interacting with the RemotiveBroker API.
-Python RemotiveBroker API
+Published to PyPI on [https://pypi.org/project/remotivelabs-broker/](https://pypi.org/project/remotivelabs-broker/).
-https://pypi.org/project/remotivelabs-broker/
+## Getting started
-Published on [PyPi](https://pypi.org/) with the prefix `remotivelabs-` and with the user [remotivelabs](https://pypi.org/user/remotivelabs/).
-All packages use [Hatch](https://hatch.pypa.io/) for packaging and publishing.
+Prerequisites:
+```bash
+# Install poetry (and optionally poe plugin to simplify poetry command execution)
+pipx install poetry
+poetry self add 'poethepoet[poetry_plugin]'
+```
+Build and run:
-## Development
+```bash
+cd python/remotivelabs-broker
-Install hatch on your development computer:
+# Install dependencies in a virtualenv
+poetry install
- pip install hatch
+# Build the library (output in dist/)
+poetry build
-Any project related information is stored in `pyproject.toml`.
+# Check (test, lint, check types)
+poetry poe check
-Hatch supplies a basic set of tools for development.
-All operations should be done in an virtual environment created by hatch.
+# run standard test suite
+poetry run pytest
-Go to the directory of the module which you are going to be working on.
-For example:
+# run tests against running broker
+poetry run pytest -m server --broker
+```
- cd python/remotivelabs-broker
+If you need to (re)generate protobuf stubs, see [Building](#building).
-Create a local environment.
+## Building
- hatch env create
+Building the complete package, including protobuf stubs and documentation, is done using a docker container:
-Start a shell in the environment.
+```bash
+./python/remotivelabs-broker/docker-build.sh
+```
- hatch shell
+## Versioning
-While in this virtual environment the library is available as a python module without needing to be installed.
+Update the package version by editing the following `pyproject.toml` file.
-Build the library:
+Follow [Semantic versioning](https://semver.org/). Beta versions should be suffixed with `-beta*`, example `0.2.0-beta1`.
- hatch build
+## Publishing
-Update the package version by editing the file `__about__.py`.
-This version stamp will be used in Pypi.
-Follow [Semantic versioning](https://semver.org/).
+Published to PyPI on [https://pypi.org/project/remotivelabs-broker/](https://pypi.org/project/remotivelabs-broker/):
-Testing, with coverage:
+All RemotiveLabs libraries:
+- share the same `remotivelabs` [namespace package](https://peps.python.org/pep-0420/).
+- use the `remotivelabs-` prefix in the library name.
+- are published with the [remotivelabs](https://pypi.org/user/remotivelabs/) user.
- hatch run cov
-
-Without coverage:
-
- hatch run no-cov
-
-While in the hatch virtual environment.
-Run the Python debugger:
-
- python -m pdb ...
+```bash
+# Get token from Johan Rask
+poetry config pypi-token.pypi
+# find username and password in less secret location
+poetry publish
+```
diff --git a/python/remotivelabs-broker/.pylintrc b/python/remotivelabs-broker/.pylintrc
deleted file mode 100644
index 1d2ae19..0000000
--- a/python/remotivelabs-broker/.pylintrc
+++ /dev/null
@@ -1,640 +0,0 @@
-[MAIN]
-
-# Analyse import fallback blocks. This can be used to support both Python 2 and
-# 3 compatible code, which means that the block might have code that exists
-# only in one or another interpreter, leading to false positives when analysed.
-analyse-fallback-blocks=no
-
-# Clear in-memory caches upon conclusion of linting. Useful if running pylint
-# in a server-like mode.
-clear-cache-post-run=no
-
-# Load and enable all available extensions. Use --list-extensions to see a list
-# all available extensions.
-#enable-all-extensions=
-
-# In error mode, messages with a category besides ERROR or FATAL are
-# suppressed, and no reports are done by default. Error mode is compatible with
-# disabling specific errors.
-#errors-only=
-
-# Always return a 0 (non-error) status code, even if lint errors are found.
-# This is primarily useful in continuous integration scripts.
-#exit-zero=
-
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code.
-extension-pkg-allow-list=
-
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
-# for backward compatibility.)
-extension-pkg-whitelist=
-
-# Return non-zero exit code if any of these messages/categories are detected,
-# even if score is above --fail-under value. Syntax same as enable. Messages
-# specified are enabled, while categories only check already-enabled messages.
-fail-on=
-
-# Specify a score threshold under which the program will exit with error.
-fail-under=10
-
-# Interpret the stdin as a python script, whose filename needs to be passed as
-# the module_or_package argument.
-#from-stdin=
-
-# Files or directories to be skipped. They should be base names, not paths.
-ignore=CVS
-
-# Add files or directories matching the regular expressions patterns to the
-# ignore-list. The regex matches against paths and can be in Posix or Windows
-# format. Because '\\' represents the directory delimiter on Windows systems,
-# it can't be used as an escape character.
-ignore-paths=
-
-# Files or directories matching the regular expression patterns are skipped.
-# The regex matches against base names, not paths. The default value ignores
-# Emacs file locks
-ignore-patterns=\..*
-
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis). It
-# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=
-
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
-# number of processors available to use, and will cap the count on Windows to
-# avoid hangs.
-jobs=1
-
-# Control the amount of potential inferred values when inferring a single
-# object. This can help the performance when dealing with large functions or
-# complex, nested conditions.
-limit-inference-results=100
-
-# List of plugins (as comma separated values of python module names) to load,
-# usually to register additional checkers.
-load-plugins=
-
-# Pickle collected data for later comparisons.
-persistent=yes
-
-# Minimum Python version to use for version dependent checks. Will default to
-# the version used to run pylint.
-py-version=3.8
-
-# Discover python modules and packages in the file system subtree.
-recursive=no
-
-# Add paths to the list of the source roots. Supports globbing patterns. The
-# source root is an absolute path or a path relative to the current working
-# directory used to determine a package namespace for modules located under the
-# source root.
-source-roots=
-
-# When enabled, pylint would attempt to guess common misconfiguration and emit
-# user-friendly hints instead of false-positive error messages.
-suggestion-mode=yes
-
-# Allow loading of arbitrary C extensions. Extensions are imported into the
-# active Python interpreter and may run arbitrary code.
-unsafe-load-any-extension=no
-
-# In verbose mode, extra non-checker-related info will be displayed.
-#verbose=
-
-
-[BASIC]
-
-# Naming style matching correct argument names.
-argument-naming-style=snake_case
-
-# Regular expression matching correct argument names. Overrides argument-
-# naming-style. If left empty, argument names will be checked with the set
-# naming style.
-#argument-rgx=
-
-# Naming style matching correct attribute names.
-attr-naming-style=snake_case
-
-# Regular expression matching correct attribute names. Overrides attr-naming-
-# style. If left empty, attribute names will be checked with the set naming
-# style.
-#attr-rgx=
-
-# Bad variable names which should always be refused, separated by a comma.
-bad-names=foo,
- bar,
- baz,
- toto,
- tutu,
- tata
-
-# Bad variable names regexes, separated by a comma. If names match any regex,
-# they will always be refused
-bad-names-rgxs=
-
-# Naming style matching correct class attribute names.
-class-attribute-naming-style=any
-
-# Regular expression matching correct class attribute names. Overrides class-
-# attribute-naming-style. If left empty, class attribute names will be checked
-# with the set naming style.
-#class-attribute-rgx=
-
-# Naming style matching correct class constant names.
-class-const-naming-style=UPPER_CASE
-
-# Regular expression matching correct class constant names. Overrides class-
-# const-naming-style. If left empty, class constant names will be checked with
-# the set naming style.
-#class-const-rgx=
-
-# Naming style matching correct class names.
-class-naming-style=PascalCase
-
-# Regular expression matching correct class names. Overrides class-naming-
-# style. If left empty, class names will be checked with the set naming style.
-#class-rgx=
-
-# Naming style matching correct constant names.
-const-naming-style=UPPER_CASE
-
-# Regular expression matching correct constant names. Overrides const-naming-
-# style. If left empty, constant names will be checked with the set naming
-# style.
-#const-rgx=
-
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=-1
-
-# Naming style matching correct function names.
-function-naming-style=snake_case
-
-# Regular expression matching correct function names. Overrides function-
-# naming-style. If left empty, function names will be checked with the set
-# naming style.
-#function-rgx=
-
-# Good variable names which should always be accepted, separated by a comma.
-good-names=i,
- j,
- k,
- ex,
- Run,
- _
-
-# Good variable names regexes, separated by a comma. If names match any regex,
-# they will always be accepted
-good-names-rgxs=
-
-# Include a hint for the correct naming format with invalid-name.
-include-naming-hint=no
-
-# Naming style matching correct inline iteration names.
-inlinevar-naming-style=any
-
-# Regular expression matching correct inline iteration names. Overrides
-# inlinevar-naming-style. If left empty, inline iteration names will be checked
-# with the set naming style.
-#inlinevar-rgx=
-
-# Naming style matching correct method names.
-method-naming-style=snake_case
-
-# Regular expression matching correct method names. Overrides method-naming-
-# style. If left empty, method names will be checked with the set naming style.
-#method-rgx=
-
-# Naming style matching correct module names.
-module-naming-style=snake_case
-
-# Regular expression matching correct module names. Overrides module-naming-
-# style. If left empty, module names will be checked with the set naming style.
-#module-rgx=
-
-# Colon-delimited sets of names that determine each other's naming style when
-# the name regexes allow several styles.
-name-group=
-
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=^_
-
-# List of decorators that produce properties, such as abc.abstractproperty. Add
-# to this list to register other decorators that produce valid properties.
-# These decorators are taken in consideration only for invalid-name.
-property-classes=abc.abstractproperty
-
-# Regular expression matching correct type alias names. If left empty, type
-# alias names will be checked with the set naming style.
-#typealias-rgx=
-
-# Regular expression matching correct type variable names. If left empty, type
-# variable names will be checked with the set naming style.
-#typevar-rgx=
-
-# Naming style matching correct variable names.
-variable-naming-style=snake_case
-
-# Regular expression matching correct variable names. Overrides variable-
-# naming-style. If left empty, variable names will be checked with the set
-# naming style.
-#variable-rgx=
-
-
-[CLASSES]
-
-# Warn about protected attribute access inside special methods
-check-protected-access-in-special-methods=no
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,
- __new__,
- setUp,
- asyncSetUp,
- __post_init__
-
-# List of member names, which should be excluded from the protected access
-# warning.
-exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
-
-
-[DESIGN]
-
-# List of regular expressions of class ancestor names to ignore when counting
-# public methods (see R0903)
-exclude-too-few-public-methods=
-
-# List of qualified class names to ignore when counting class parents (see
-# R0901)
-ignored-parents=
-
-# Maximum number of arguments for function / method.
-max-args=7
-
-# Maximum number of attributes for a class (see R0902).
-max-attributes=10
-
-# Maximum number of boolean expressions in an if statement (see R0916).
-max-bool-expr=5
-
-# Maximum number of branch for function / method body.
-max-branches=12
-
-# Maximum number of locals for function / method body.
-max-locals=15
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
-
-# Maximum number of return / yield for function / method body.
-max-returns=6
-
-# Maximum number of statements in function / method body.
-max-statements=50
-
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=2
-
-
-[EXCEPTIONS]
-
-# Exceptions that will emit a warning when caught.
-overgeneral-exceptions=builtins.BaseException,builtins.Exception
-
-
-[FORMAT]
-
-# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
-expected-line-ending-format=
-
-# Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )??$
-
-# Number of spaces of indent required inside a hanging or continued line.
-indent-after-paren=4
-
-# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
-# tab).
-indent-string=' '
-
-# Maximum number of characters on a single line.
-max-line-length=140
-
-# Maximum number of lines in a module.
-max-module-lines=1000
-
-# Allow the body of a class to be on the same line as the declaration if body
-# contains single statement.
-single-line-class-stmt=no
-
-# Allow the body of an if to be on the same line as the test if there is no
-# else.
-single-line-if-stmt=no
-
-
-[IMPORTS]
-
-# List of modules that can be imported at any level, not just the top level
-# one.
-allow-any-import-level=
-
-# Allow explicit reexports by alias from a package __init__.
-allow-reexport-from-package=no
-
-# Allow wildcard imports from modules that define __all__.
-allow-wildcard-with-all=no
-
-# Deprecated modules which should not be used, separated by a comma.
-deprecated-modules=
-
-# Output a graph (.gv or any supported image format) of external dependencies
-# to the given file (report RP0402 must not be disabled).
-ext-import-graph=
-
-# Output a graph (.gv or any supported image format) of all (i.e. internal and
-# external) dependencies to the given file (report RP0402 must not be
-# disabled).
-import-graph=
-
-# Output a graph (.gv or any supported image format) of internal dependencies
-# to the given file (report RP0402 must not be disabled).
-int-import-graph=
-
-# Force import order to recognize a module as part of the standard
-# compatibility libraries.
-known-standard-library=
-
-# Force import order to recognize a module as part of a third party library.
-known-third-party=enchant
-
-# Couples of modules and preferred modules, separated by a comma.
-preferred-modules=
-
-
-[LOGGING]
-
-# The type of string formatting that logging methods do. `old` means using %
-# formatting, `new` is for `{}` formatting.
-logging-format-style=old
-
-# Logging modules to check that the string format arguments are in logging
-# function parameter format.
-logging-modules=logging
-
-
-[MESSAGES CONTROL]
-
-# Only show warnings with the listed confidence levels. Leave empty to show
-# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
-# UNDEFINED.
-confidence=HIGH,
- CONTROL_FLOW,
- INFERENCE,
- INFERENCE_FAILURE,
- UNDEFINED
-
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifiers separated by comma (,) or put this
-# option multiple times (only on the command line, not in the configuration
-# file where it should appear only once). You can also use "--disable=all" to
-# disable everything first and then re-enable specific checks. For example, if
-# you want to run only the similarities checker, you can use "--disable=all
-# --enable=similarities". If you want to run only the classes checker, but have
-# no Warning level messages displayed, use "--disable=all --enable=classes
-# --disable=W".
-disable=raw-checker-failed,
- bad-inline-option,
- locally-disabled,
- file-ignored,
- suppressed-message,
- useless-suppression,
- deprecated-pragma,
- use-symbolic-message-instead,
- use-implicit-booleaness-not-comparison-to-string,
- use-implicit-booleaness-not-comparison-to-zero,
- missing-function-docstring,
- missing-class-docstring,
- missing-module-docstring,
- logging-fstring-interpolation,
- broad-exception-raised,
- too-few-public-methods
-
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time (only on the command line, not in the configuration file where
-# it should appear only once). See also the "--disable" option for examples.
-enable=
-
-
-[METHOD_ARGS]
-
-# List of qualified names (i.e., library.method) which require a timeout
-# parameter e.g. 'requests.api.get,requests.api.post'
-timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,
- XXX,
- TODO
-
-# Regular expression of note tags to take in consideration.
-notes-rgx=
-
-
-[REFACTORING]
-
-# Maximum number of nested blocks for function / method body
-max-nested-blocks=5
-
-# Complete name of functions that never returns. When checking for
-# inconsistent-return-statements if a never returning function is called then
-# it will be considered as an explicit return statement and no message will be
-# printed.
-never-returning-functions=sys.exit,argparse.parse_error
-
-
-[REPORTS]
-
-# Python expression which should return a score less than or equal to 10. You
-# have access to the variables 'fatal', 'error', 'warning', 'refactor',
-# 'convention', and 'info' which contain the number of messages in each
-# category, as well as 'statement' which is the total number of statements
-# analyzed. This score is used by the global evaluation report (RP0004).
-evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
-
-# Template used to display messages. This is a python new-style format string
-# used to format the message information. See doc for all details.
-msg-template=
-
-# Set the output format. Available formats are: text, parseable, colorized,
-# json2 (improved json format), json (old json format) and msvs (visual
-# studio). You can also give a reporter class, e.g.
-# mypackage.mymodule.MyReporterClass.
-#output-format=
-
-# Tells whether to display a full report or only the messages.
-reports=no
-
-# Activate the evaluation score.
-score=yes
-
-
-[SIMILARITIES]
-
-# Comments are removed from the similarity computation
-ignore-comments=yes
-
-# Docstrings are removed from the similarity computation
-ignore-docstrings=yes
-
-# Imports are removed from the similarity computation
-ignore-imports=yes
-
-# Signatures are removed from the similarity computation
-ignore-signatures=yes
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-
-[SPELLING]
-
-# Limits count of emitted suggestions for spelling mistakes.
-max-spelling-suggestions=4
-
-# Spelling dictionary name. No available dictionaries : You need to install
-# both the python package and the system dependency for enchant to work.
-spelling-dict=
-
-# List of comma separated words that should be considered directives if they
-# appear at the beginning of a comment and should not be checked.
-spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
-
-# List of comma separated words that should not be checked.
-spelling-ignore-words=
-
-# A path to a file that contains the private dictionary; one word per line.
-spelling-private-dict-file=
-
-# Tells whether to store unknown words to the private dictionary (see the
-# --spelling-private-dict-file option) instead of raising a message.
-spelling-store-unknown-words=no
-
-
-[STRING]
-
-# This flag controls whether inconsistent-quotes generates a warning when the
-# character used as a quote delimiter is used inconsistently within a module.
-check-quote-consistency=no
-
-# This flag controls whether the implicit-str-concat should generate a warning
-# on implicit string concatenation in sequences defined over several lines.
-check-str-concat-over-line-jumps=no
-
-
-[TYPECHECK]
-
-# List of decorators that produce context managers, such as
-# contextlib.contextmanager. Add to this list to register other decorators that
-# produce valid context managers.
-contextmanager-decorators=contextlib.contextmanager
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E1101 when accessed. Python regular
-# expressions are accepted.
-generated-members=
-
-# Tells whether to warn about missing members when the owner of the attribute
-# is inferred to be None.
-ignore-none=yes
-
-# This flag controls whether pylint should warn about no-member and similar
-# checks whenever an opaque object is returned when inferring. The inference
-# can return multiple potential results while evaluating a Python object, but
-# some branches might not be evaluated, which results in partial inference. In
-# that case, it might be useful to still emit no-member and other checks for
-# the rest of the inferred objects.
-ignore-on-opaque-inference=yes
-
-# List of symbolic message names to ignore for Mixin members.
-ignored-checks-for-mixins=no-member,
- not-async-context-manager,
- not-context-manager,
- attribute-defined-outside-init
-
-# List of class names for which member attributes should not be checked (useful
-# for classes with dynamically set attributes). This supports the use of
-# qualified names.
-ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
-
-# Show a hint with possible names when a member name was not found. The aspect
-# of finding the hint is based on edit distance.
-missing-member-hint=yes
-
-# The minimum edit distance a name should have in order to be considered a
-# similar match for a missing member name.
-missing-member-hint-distance=1
-
-# The total number of similar names that should be taken in consideration when
-# showing a hint for a missing member.
-missing-member-max-choices=1
-
-# Regex pattern to define which classes are considered mixins.
-mixin-class-rgx=.*[Mm]ixin
-
-# List of decorators that change the signature of a decorated function.
-signature-mutators=
-
-
-[VARIABLES]
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid defining new builtins when possible.
-additional-builtins=
-
-# Tells whether unused global variables should be treated as a violation.
-allow-global-unused-variables=yes
-
-# List of names allowed to shadow builtins
-allowed-redefined-builtins=
-
-# List of strings which can identify a callback function by name. A callback
-# name must start or end with one of those strings.
-callbacks=cb_,
- _cb
-
-# A regular expression matching the name of dummy variables (i.e. expected to
-# not be used).
-dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
-
-# Argument names that match this expression will be ignored.
-ignored-argument-names=_.*|^ignored_|^unused_
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# List of qualified module names which can have objects that can redefine
-# builtins.
-redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
diff --git a/python/remotivelabs-broker/.ruff.toml b/python/remotivelabs-broker/.ruff.toml
deleted file mode 100644
index 8b1d4fc..0000000
--- a/python/remotivelabs-broker/.ruff.toml
+++ /dev/null
@@ -1,76 +0,0 @@
-# Exclude a variety of commonly ignored directories.
-exclude = [
- ".bzr",
- ".direnv",
- ".eggs",
- ".git",
- ".git-rewrite",
- ".hg",
- ".ipynb_checkpoints",
- ".mypy_cache",
- ".nox",
- ".pants.d",
- ".pyenv",
- ".pytest_cache",
- ".pytype",
- ".ruff_cache",
- ".svn",
- ".tox",
- ".venv",
- ".vscode",
- "__pypackages__",
- "_build",
- "buck-out",
- "build",
- "dist",
- "node_modules",
- "site-packages",
- "venv",
- 'deps',
- 'binaries',
- '__pycache__'
-]
-
-line-length = 140
-indent-width = 4
-
-# Assume Python 3.8
-target-version = "py38"
-
-[lint]
-select = ["C901", "E", "W", "F", "RET505", "I001", "B034", "EXE001", "N806", "UP032", "FA100"]
-ignore = []
-
-# Allow fix for all enabled rules (when `--fix`) is provided.
-fixable = ["ALL"]
-unfixable = []
-
-# Allow unused variables when underscore-prefixed.
-dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
-
-[format]
-# Like Black, use double quotes for strings.
-quote-style = "double"
-
-# Like Black, indent with spaces, rather than tabs.
-indent-style = "space"
-
-# Like Black, respect magic trailing commas.
-skip-magic-trailing-comma = false
-
-# Like Black, automatically detect the appropriate line ending.
-line-ending = "auto"
-
-# Enable auto-formatting of code examples in docstrings. Markdown,
-# reStructuredText code/literal blocks and doctests are all supported.
-#
-# This is currently disabled by default, but it is planned for this
-# to be opt-out in the future.
-docstring-code-format = false
-
-# Set the line length limit used when formatting code snippets in
-# docstrings.
-#
-# This only has an effect when the `docstring-code-format` setting is
-# enabled.
-docstring-code-line-length = "dynamic"
diff --git a/python/remotivelabs-broker/README.md b/python/remotivelabs-broker/README.md
index a1c561a..a58cd72 100644
--- a/python/remotivelabs-broker/README.md
+++ b/python/remotivelabs-broker/README.md
@@ -1,62 +1,27 @@
-# RemotiveLabs Broker
+# RemotiveLabs Broker API
[![PyPI - Version](https://img.shields.io/pypi/v/remotivelabs-broker.svg)](https://pypi.org/project/remotivelabs-broker)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/remotivelabs-broker.svg)](https://pypi.org/project/remotivelabs-broker)
-- [Link to **Samples**](https://github.com/remotivelabs/remotivelabs-samples/tree/main/python).
-- [Link to **Documentation**](https://docs.remotivelabs.com/apis/python/remotivelabs/broker).
-
------
-
-**Table of Contents**
-
-- [Installation](#installation)
-- [Examples](#examples)
-- [Build and Publish](#build-and-publish)
-- [License](#license)
+`remotivelabs-broker` is a simple library for interfacing with the [RemotiveBroker](https://docs.remotivelabs.com/docs/remotive-broker).
## Installation
+
Install into your Python environment with _PIP_:
- pip install remotivelabs-broker
+```bash
+pip install remotivelabs-broker
+```
## Examples
-Examples using this library are found in the [Remotive Labs samples repository](https://github.com/remotivelabs/remotivelabs-samples).
-
-
-### Develop and test locally
-
-Put version in `__about__.py`. Beta versions should be suffixed with `b*`, example `0.2.0b1`
-
-For initial build and generation of proto stubs, documentation and distribution package, run `./docker-build.sh`
-
-
-Once stubs are generated you can use hatch to build distribution package
- hatch build
+See [examples](./examples/README.md)
-above command will output
-
- [wheel]
- dist/remotivelabs_broker-0.2.0b12-py3-none-any.whl
-
-You can now install this library version in another terminal by doing
+## License
- pip3 install [your path]/dist/remotivelabs_broker-0.2.0b12-py3-none-any.whl
+`remotivelabs-broker` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
-### Build and publish
-Make sure to put version in `__about__.py`. Beta versions should be suffixed with `b*`, example `0.2.0b1`
+## Documentation
-```
-./docker-build.sh
-hatch publish
-```
-find username and password in less secret location
-or use
-```
-hatch publish --user __token__
-```
-## License
-
-`remotivelabs-broker` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
+See [https://docs.remotivelabs.com/apis/python/remotivelabs/broker](https://docs.remotivelabs.com/apis/python/remotivelabs/broker).
diff --git a/python/remotivelabs-broker/docker-build.sh b/python/remotivelabs-broker/docker-build.sh
index a4fbb18..3776ca3 100755
--- a/python/remotivelabs-broker/docker-build.sh
+++ b/python/remotivelabs-broker/docker-build.sh
@@ -1,21 +1,16 @@
#!/bin/bash
-
+#
+# Generates code from proto files, builds package and generates documentation
+#
set -e
-# Generates code from proto files, builds package and generates documentation
+SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd)
+TAG=remotivelabs/python-api-build-image
-docker build -t remotivelabs/python-api-build-image -f docker/Dockerfile .
+docker build -t "${TAG}" -f "${SCRIPT_DIR}/docker/Dockerfile" "${SCRIPT_DIR}"
+# set args to allow build to run in non-tty shells
ARGS="-it"
+[ "${NO_TTY}" == "true" ] && ARGS="-i"
-if [ "${NO_TTY}" == "true" ]; then
- ARGS="-i"
-fi
-
-
-docker run \
- -u $(id -u):$(id -g) \
- -v $(pwd)/../../:/app \
- -e "protofile=*.proto" \
- -w /app \
- ${ARGS} remotivelabs/python-api-build-image
+docker run -v "${SCRIPT_DIR}/../../:/app" "${ARGS}" "${TAG}"
diff --git a/python/remotivelabs-broker/docker/Dockerfile b/python/remotivelabs-broker/docker/Dockerfile
index 6abb261..3aaf57a 100644
--- a/python/remotivelabs-broker/docker/Dockerfile
+++ b/python/remotivelabs-broker/docker/Dockerfile
@@ -1,28 +1,42 @@
-# Make sure we are on same or lower version of protoc than we
-# This Dockerfile ensures that we are using libprotoc 3.19.2
-# but this is is determined by the version of grpcio-tools
+FROM python:3.10-slim
-FROM ubuntu:jammy
+ENV protofile=*.proto \
+ # python config
+ PYTHONFAULTHANDLER=1 \
+ PYTHONUNBUFFERED=1 \
+ PYTHONHASHSEED=random \
+ # pip config
+ PIP_NO_CACHE_DIR=off \
+ PIP_DISABLE_PIP_VERSION_CHECK=on \
+ PIP_DEFAULT_TIMEOUT=100 \
+ # poetry config
+ POETRY_VERSION=1.8 \
+ POETRY_VIRTUALENVS_IN_PROJECT=false
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ wget \
+ unzip && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
-ARG URL_PROTOC_DOC=https://github.com/pseudomuto/protoc-gen-doc/releases/download/v1.5.1/protoc-gen-doc_1.5.1_linux_amd64.tar.gz
-RUN apt-get update && apt-get install -y python3.11 python3-pip wget unzip
-RUN pip install hatch json-schema-for-humans
-RUN pip3 install grpcio-tools==1.44.0
-RUN pip3 install hatch
-RUN pip3 install pdoc
-RUN pip3 install mypy-protobuf==3.3.0
-COPY docker/download_protoc.sh .
-RUN rm /bin/sh && ln -s /bin/bash /bin/sh
-RUN sh download_protoc.sh
+# See https://python-poetry.org/docs/#ci-recommendations
+RUN pip install "poetry==$POETRY_VERSION"
+RUN poetry self add 'poethepoet[poetry_plugin]'
+# install the protobuf compiler
+COPY --chmod=0755 docker/download_protoc.sh /tmp/download_protoc.sh
+RUN /tmp/download_protoc.sh
-RUN wget ${URL_PROTOC_DOC} -O /tmp/protoc.tar.gz && \
- tar zxfv /tmp/protoc.tar.gz protoc-gen-doc && \
- mv protoc-gen-doc /usr/bin -v
+# ... and the documentation plugin for protobuf compiler
+COPY --chmod=0755 docker/download_protoc_gen_doc.sh /tmp/download_protoc_gen_doc.sh
+RUN /tmp/download_protoc_gen_doc.sh
+
+# Set up build script and entrypoint. It is possible to customize arguments to it using CMD or from command line
+COPY --chmod=755 docker/entrypoint.sh /usr/local/bin/entrypoint.sh
+COPY --chmod=755 docker/build.sh /usr/local/bin/build.sh
VOLUME /app
-ENV protofile=*.proto
-VOLUME /ouput
+WORKDIR /app
-CMD ["./python/remotivelabs-broker/docker/build-all-in-docker.sh"]
+ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
diff --git a/python/remotivelabs-broker/docker/build-all-in-docker.sh b/python/remotivelabs-broker/docker/build-all-in-docker.sh
deleted file mode 100755
index d843cb1..0000000
--- a/python/remotivelabs-broker/docker/build-all-in-docker.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/bin/bash
-
-set -e
-
-HOME=/app/python/remotivelabs-broker
-STUBS_OUTPUT=$HOME/remotivelabs/broker/generated/sync
-mkdir -p "${STUBS_OUTPUT}"
-
-#1.1 Generate stubs
-python3 -m grpc_tools.protoc \
- -I /app/protos \
- --python_out=$STUBS_OUTPUT \
- --grpc_python_out=$STUBS_OUTPUT \
- --mypy_out=$STUBS_OUTPUT \
- --mypy_grpc_out=$STUBS_OUTPUT \
- /app/protos/*.proto
-
-cd $HOME
-#1.2 Fix those imports
-python3 misc/fix_import_statements.py
-
-rm -rf dist
-
-# Build distribution wheel
-hatch build
-
-# Install wheel in order for pdoc to run properly so docs can be built
-dist_wheel=$(ls -AU dist/*.whl | head -1)
-echo "dist = $dist_wheel"
-pip3 install $dist_wheel
-
-
-# Build python documentation
-
-mkdir -p dist/doc/
-DOCS=$(realpath dist/doc)
-
-
-function generate_python_docs() {
- PY_DOCS=$DOCS/python
- mkdir $PY_DOCS
-
- pdoc \
- --favicon https://releases.remotivelabs.com/favicon.ico \
- --logo https://releases.remotivelabs.com/remotive-labs-logo-neg.png \
- --no-show-source \
- -t misc/theme \
- -o $PY_DOCS \
- ./remotivelabs
-}
-
-function generate_proto_docs() {
- PROTO_DOCS=$DOCS/protos
- mkdir $PROTO_DOCS
- PROTO_FILES="common.proto network_api.proto system_api.proto traffic_api.proto"
- protoc --doc_out=$PROTO_DOCS --doc_opt=html,index.html $PROTO_FILES
-}
-
-function generate_json() {
- JSON_DOCS=$DOCS/json
- mkdir $JSON_DOCS
- # Generate
- generate-schema-doc metadb.json $JSON_DOCS
- generate-schema-doc interfaces_schema.json $JSON_DOCS
- generate-schema-doc scripted_db.json $JSON_DOCS
-}
-
-generate_python_docs
-(cd /app/protos && generate_proto_docs)
-(cd /app/schemas/ && generate_json)
diff --git a/python/remotivelabs-broker/docker/build.sh b/python/remotivelabs-broker/docker/build.sh
new file mode 100755
index 0000000..3454ba5
--- /dev/null
+++ b/python/remotivelabs-broker/docker/build.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+#
+# Build the remotivelabs-broker python library together with its documentation.
+#
+# We use grpc-tools to generate the python stubs for the protobuf definition in protos/ dir,
+# and then package the python library using hatch. Documentation is generated for python, protobuf
+# and json-schema.
+#
+set -e
+
+PROJECT_DIR=/app/python/remotivelabs-broker
+
+PROTO_IN=/app/protos
+PROTO_STUBS_OUT=$PROJECT_DIR/remotivelabs/broker/generated/sync
+
+SCHEMA_IN=/app/schemas
+
+BUILD_DIR=$PROJECT_DIR/dist
+
+DOCS_DIR=$PROJECT_DIR/dist/doc
+PY_DOCS=$DOCS_DIR/python
+PROTO_DOCS=$DOCS_DIR/protos
+JSON_DOCS=$DOCS_DIR/json
+
+function setup_build_env() {
+ cd $PROJECT_DIR
+ rm -rf $BUILD_DIR
+
+ # Make sure we dont mess up any .venv already present in the mounted volume
+ export POETRY_VIRTUALENVS_IN_PROJECT=false
+ poetry install
+}
+
+function generate_protobuf_files() {
+ mkdir -p "${PROTO_STUBS_OUT}"
+
+ poetry run python -m grpc_tools.protoc \
+ --proto_path=$PROTO_IN \
+ --python_out=$PROTO_STUBS_OUT \
+ --grpc_python_out=$PROTO_STUBS_OUT \
+ --pyi_out=$PROTO_STUBS_OUT \
+ $PROTO_IN/*.proto
+
+ # Note: protobuf compiler does not support generating relative or custom absolute imports for python. Use a script to do this manually...
+ poetry run python $PROJECT_DIR/misc/fix_import_statements.py
+}
+
+function build_python_library() {
+ poetry build
+}
+
+function generate_python_docs() {
+ mkdir -p $PY_DOCS
+
+ poetry run pdoc \
+ --favicon https://releases.remotivelabs.com/favicon.ico \
+ --logo https://releases.remotivelabs.com/remotive-labs-logo-neg.png \
+ --no-show-source \
+ -t misc/theme \
+ -o $PY_DOCS \
+ $PROJECT_DIR/remotivelabs
+}
+
+# TODO: Move away from python lib
+# TODO: Investigate the possibility to use grpcio-tools proto compiler instead of this custom one.
+# See e.g. protoc-docs-plugin on pypi
+function generate_proto_docs() {
+ mkdir -p $PROTO_DOCS
+
+ PROTO_FILES="$PROTO_IN/common.proto $PROTO_IN/network_api.proto $PROTO_IN/system_api.proto $PROTO_IN/traffic_api.proto"
+ protoc -I $PROTO_IN --doc_out=$PROTO_DOCS --doc_opt=html,index.html $PROTO_FILES
+}
+
+# TODO: Move away from python lib
+function generate_json() {
+ mkdir -p $JSON_DOCS
+
+ poetry run generate-schema-doc $SCHEMA_IN/metadb.json $JSON_DOCS
+ poetry run generate-schema-doc $SCHEMA_IN/interfaces_schema.json $JSON_DOCS
+ poetry run generate-schema-doc $SCHEMA_IN/scripted_db.json $JSON_DOCS
+}
+
+echo -e "\n######################"
+echo "Installing python dependencies in ${PROJECT_DIR}"
+echo "######################"
+setup_build_env
+
+echo -e "\n######################"
+echo "Generating protobuf files to ${PROTO_STUBS_OUT}"
+echo "######################"
+generate_protobuf_files
+
+echo -e "\n######################"
+echo "Building python library in ${BUILD_DIR}"
+echo "######################"
+build_python_library
+
+echo -e "\n######################"
+echo "Generating pydocs in ${PY_DOCS}"
+echo "######################"
+generate_python_docs
+
+echo -e "\n######################"
+echo "Generating protobuf docs to ${PROTO_DOCS}"
+echo "######################"
+generate_proto_docs
+
+echo -e "\n######################"
+echo "Generating json schema docs to ${JSON_DOCS}"
+echo "######################"
+generate_json
diff --git a/python/remotivelabs-broker/docker/download_protoc.sh b/python/remotivelabs-broker/docker/download_protoc.sh
index cc3cc33..376830f 100644
--- a/python/remotivelabs-broker/docker/download_protoc.sh
+++ b/python/remotivelabs-broker/docker/download_protoc.sh
@@ -1,14 +1,41 @@
-#!/bin/sh
-
+#!/bin/bash
#
# Downloads correct protoc compiler for correct platform during docker build
#
+PROTOC_VERSION="28.2"
+BASE_URL="https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}"
+
+ARCH=$(uname -m)
+OS=$(uname -s)
+
+case "${OS}-${ARCH}" in
+ "Linux-x86_64")
+ URL="${BASE_URL}-linux-x86_64.zip"
+ ;;
+ "Linux-aarch64")
+ URL="${BASE_URL}-linux-aarch_64.zip"
+ ;;
+ "Darwin-arm64")
+ URL="${BASE_URL}-osx-aarch_64.zip"
+ ;;
+ *)
+ echo "Unsupported platform: ${OS}-${ARCH}"
+ exit 1
+ ;;
+esac
+
+# Create a temporary directory and set up cleanup
+TMP_DIR=$(mktemp -d)
+cleanup() {
+ rm -rf "$TMP_DIR"
+}
+trap cleanup EXIT
-mkdir /apps
-mkdir /app
-export ARCH=$(uname -p)
+echo "Downloading protoc compiler for ${OS}-${ARCH} from: $URL"
+wget "$URL" -O "$TMP_DIR/proto-compiler.zip"
-if [ "$ARCH" = "aarch64" ] ; then export ARCH=aarch_64 ; else export ARCH=x86_64 ; fi
+unzip "$TMP_DIR/proto-compiler.zip" -d "$TMP_DIR"
+mv "$TMP_DIR/bin/protoc" /usr/bin/protoc
+chmod +x /usr/bin/protoc
-(wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.2/protoc-3.19.2-linux-$ARCH.zip -O /tmp/proto-compiler.zip &&
-cd tmp && unzip proto-compiler.zip && mv /tmp/bin/protoc /bin/protoc && chmod 777 /bin/protoc)
\ No newline at end of file
+echo "protoc compiler installed successfully!"
diff --git a/python/remotivelabs-broker/docker/download_protoc_gen_doc.sh b/python/remotivelabs-broker/docker/download_protoc_gen_doc.sh
new file mode 100644
index 0000000..7d3e3ab
--- /dev/null
+++ b/python/remotivelabs-broker/docker/download_protoc_gen_doc.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+#
+# Downloads the correct documentation generator plugin for the Google Protocol Buffers compiler
+#
+PROTOC_GEN_DOC_VERSION="1.5.1"
+BASE_URL="https://github.com/pseudomuto/protoc-gen-doc/releases/download/v${PROTOC_GEN_DOC_VERSION}/protoc-gen-doc_${PROTOC_GEN_DOC_VERSION}"
+
+ARCH=$(uname -m)
+OS=$(uname -s)
+
+case "${OS}-${ARCH}" in
+ "Linux-x86_64")
+ URL="${BASE_URL}_linux_amd64.tar.gz"
+ ;;
+ "Linux-aarch64")
+ URL="${BASE_URL}_linux_arm64.tar.gz"
+ ;;
+ "Darwin-arm64")
+ URL="${BASE_URL}_darwin_arm64.tar.gz"
+ ;;
+ *)
+ echo "Unsupported platform: ${OS}-${ARCH}"
+ exit 1
+ ;;
+esac
+
+# Create a temporary directory and set up cleanup
+TMP_DIR=$(mktemp -d)
+cleanup() {
+ rm -rf "$TMP_DIR"
+}
+trap cleanup EXIT
+
+echo "Downloading protoc-gen-doc for ${OS}-${ARCH} from: $URL"
+wget "$URL" -O "$TMP_DIR/protoc-gen-doc.tar.gz"
+
+tar -zxf "$TMP_DIR/protoc-gen-doc.tar.gz" -C "$TMP_DIR"
+mv "$TMP_DIR/protoc-gen-doc" /usr/bin/protoc-gen-doc
+chmod +x /usr/bin/protoc-gen-doc
+
+echo "protoc compiler documentation plugin installed successfully!"
diff --git a/python/remotivelabs-broker/docker/entrypoint.sh b/python/remotivelabs-broker/docker/entrypoint.sh
new file mode 100755
index 0000000..276f64e
--- /dev/null
+++ b/python/remotivelabs-broker/docker/entrypoint.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# Docker entrypoint
+#
+# Creates a dummy user (hostuser) with the same UID/GID as the host user and executes the build script (build.sh) with that user.
+# This has several benefits, including:
+# - hostuser has its own HOME directory, which it has full permissions to.
+# - hostuser will have full access to the mounted host directory.
+# - hostuser will produce output files with the correct permissions for host to access them later.
+# - you may pass on environment variables to hostuser by appending them to /home/hostuser/.bashrc
+#
+set -e
+
+create_group_if_not_exists() {
+ local gid=$1
+ if ! getent group "$gid" >/dev/null 2>&1; then
+ addgroup --gid "$gid" hostgroup --quiet
+ fi
+}
+
+create_user_if_not_exists() {
+ local uid=$1
+ local gid=$2
+ if ! getent passwd "$uid" >/dev/null 2>&1; then
+ adduser --uid "$uid" --gid "$gid" --disabled-password --gecos "" hostuser --quiet
+ fi
+}
+
+# We know that working dir is set to the mounted host volume. Get the UID/GID of the owner so that we can run as that user in the container.
+HOST_UID=$(stat -c "%u" "$PWD")
+HOST_GID=$(stat -c "%g" "$PWD")
+
+CURRENT_UID=$(id -u)
+CURRENT_GID=$(id -g)
+
+create_group_if_not_exists "$HOST_GID"
+create_user_if_not_exists "$HOST_UID" "$HOST_GID"
+
+if [ "$CURRENT_UID" -eq "$HOST_UID" ] && [ "$CURRENT_GID" -eq "$HOST_GID" ]; then
+ exec /usr/local/bin/build.sh "$@"
+else
+ user=$(getent passwd "$HOST_UID" | cut -d: -f1)
+ exec su - "$user" -c "/usr/local/bin/build.sh $@"
+fi
diff --git a/python/remotivelabs-broker/misc/fix_import_statements.py b/python/remotivelabs-broker/misc/fix_import_statements.py
index dd55b7a..2eee14f 100644
--- a/python/remotivelabs-broker/misc/fix_import_statements.py
+++ b/python/remotivelabs-broker/misc/fix_import_statements.py
@@ -7,6 +7,11 @@
#
# This script goes through all the python files in the folder and does the replacement based on the regex pattern
# `regex_string` defined below.
+#
+# See also:
+# - https://github.com/grpc/grpc/issues/9575
+# - https://github.com/protocolbuffers/protobuf/issues/1491
+# - https://github.com/protocolbuffers/protobuf/issues/5374
import glob
import re
diff --git a/python/remotivelabs-broker/poetry.lock b/python/remotivelabs-broker/poetry.lock
new file mode 100644
index 0000000..7800bed
--- /dev/null
+++ b/python/remotivelabs-broker/poetry.lock
@@ -0,0 +1,1172 @@
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+
+[[package]]
+name = "astroid"
+version = "3.2.4"
+description = "An abstract syntax tree for Python with inference support."
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"},
+ {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""}
+
+[[package]]
+name = "astunparse"
+version = "1.6.3"
+description = "An AST unparser for Python"
+optional = false
+python-versions = "*"
+files = [
+ {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"},
+ {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"},
+]
+
+[package.dependencies]
+six = ">=1.6.1,<2.0"
+wheel = ">=0.23.0,<1.0"
+
+[[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 = "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 = "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 = "dataclasses-json"
+version = "0.5.9"
+description = "Easily serialize dataclasses to and from JSON"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "dataclasses-json-0.5.9.tar.gz", hash = "sha256:e9ac87b73edc0141aafbce02b44e93553c3123ad574958f0fe52a534b6707e8e"},
+ {file = "dataclasses_json-0.5.9-py3-none-any.whl", hash = "sha256:1280542631df1c375b7bc92e5b86d39e06c44760d7e3571a537b3b8acabf2f0c"},
+]
+
+[package.dependencies]
+marshmallow = ">=3.3.0,<4.0.0"
+marshmallow-enum = ">=1.5.1,<2.0.0"
+typing-inspect = ">=0.4.0"
+
+[package.extras]
+dev = ["flake8", "hypothesis", "ipython", "mypy (>=0.710)", "portray", "pytest (>=7.2.0)", "setuptools", "simplejson", "twine", "types-dataclasses", "wheel"]
+
+[[package]]
+name = "dill"
+version = "0.3.9"
+description = "serialize all of Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"},
+ {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"},
+]
+
+[package.extras]
+graph = ["objgraph (>=1.7.2)"]
+profile = ["gprof2dot (>=2022.7.29)"]
+
+[[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 = "grpc-interceptor"
+version = "0.15.4"
+description = "Simplifies gRPC interceptors"
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+ {file = "grpc-interceptor-0.15.4.tar.gz", hash = "sha256:1f45c0bcb58b6f332f37c637632247c9b02bc6af0fdceb7ba7ce8d2ebbfb0926"},
+ {file = "grpc_interceptor-0.15.4-py3-none-any.whl", hash = "sha256:0035f33228693ed3767ee49d937bac424318db173fef4d2d0170b3215f254d9d"},
+]
+
+[package.dependencies]
+grpcio = ">=1.49.1,<2.0.0"
+
+[package.extras]
+testing = ["protobuf (>=4.21.9)"]
+
+[[package]]
+name = "grpc-stubs"
+version = "1.53.0.5"
+description = "Mypy stubs for gRPC"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "grpc-stubs-1.53.0.5.tar.gz", hash = "sha256:3e1b642775cbc3e0c6332cfcedfccb022176db87e518757bef3a1241397be406"},
+ {file = "grpc_stubs-1.53.0.5-py3-none-any.whl", hash = "sha256:04183fb65a1b166a1febb9627e3d9647d3926ccc2dfe049fe7b6af243428dbe1"},
+]
+
+[package.dependencies]
+grpcio = "*"
+
+[[package]]
+name = "grpcio"
+version = "1.66.2"
+description = "HTTP/2-based RPC framework"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "grpcio-1.66.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:fe96281713168a3270878255983d2cb1a97e034325c8c2c25169a69289d3ecfa"},
+ {file = "grpcio-1.66.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:73fc8f8b9b5c4a03e802b3cd0c18b2b06b410d3c1dcbef989fdeb943bd44aff7"},
+ {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:03b0b307ba26fae695e067b94cbb014e27390f8bc5ac7a3a39b7723fed085604"},
+ {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d69ce1f324dc2d71e40c9261d3fdbe7d4c9d60f332069ff9b2a4d8a257c7b2b"},
+ {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bc2ceadc2529ab0b227b1310d249d95d9001cd106aa4d31e8871ad3c428d73"},
+ {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ac475e8da31484efa25abb774674d837b343afb78bb3bcdef10f81a93e3d6bf"},
+ {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0be4e0490c28da5377283861bed2941d1d20ec017ca397a5df4394d1c31a9b50"},
+ {file = "grpcio-1.66.2-cp310-cp310-win32.whl", hash = "sha256:4e504572433f4e72b12394977679161d495c4c9581ba34a88d843eaf0f2fbd39"},
+ {file = "grpcio-1.66.2-cp310-cp310-win_amd64.whl", hash = "sha256:2018b053aa15782db2541ca01a7edb56a0bf18c77efed975392583725974b249"},
+ {file = "grpcio-1.66.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:2335c58560a9e92ac58ff2bc5649952f9b37d0735608242973c7a8b94a6437d8"},
+ {file = "grpcio-1.66.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a3d462826f4868b442a6b8fdbe8b87b45eb4f5b5308168c156b21eca43f61c"},
+ {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a9539f01cb04950fd4b5ab458e64a15f84c2acc273670072abe49a3f29bbad54"},
+ {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce89f5876662f146d4c1f695dda29d4433a5d01c8681fbd2539afff535da14d4"},
+ {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25a14af966438cddf498b2e338f88d1c9706f3493b1d73b93f695c99c5f0e2a"},
+ {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6001e575b8bbd89eee11960bb640b6da6ae110cf08113a075f1e2051cc596cae"},
+ {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ea1d062c9230278793820146c95d038dc0f468cbdd172eec3363e42ff1c7d01"},
+ {file = "grpcio-1.66.2-cp311-cp311-win32.whl", hash = "sha256:38b68498ff579a3b1ee8f93a05eb48dc2595795f2f62716e797dc24774c1aaa8"},
+ {file = "grpcio-1.66.2-cp311-cp311-win_amd64.whl", hash = "sha256:6851de821249340bdb100df5eacfecfc4e6075fa85c6df7ee0eb213170ec8e5d"},
+ {file = "grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf"},
+ {file = "grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8"},
+ {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6"},
+ {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7"},
+ {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd"},
+ {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee"},
+ {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c"},
+ {file = "grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453"},
+ {file = "grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679"},
+ {file = "grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d"},
+ {file = "grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34"},
+ {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed"},
+ {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7"},
+ {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46"},
+ {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a"},
+ {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b"},
+ {file = "grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75"},
+ {file = "grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf"},
+ {file = "grpcio-1.66.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:a18e20d8321c6400185b4263e27982488cb5cdd62da69147087a76a24ef4e7e3"},
+ {file = "grpcio-1.66.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:02697eb4a5cbe5a9639f57323b4c37bcb3ab2d48cec5da3dc2f13334d72790dd"},
+ {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:99a641995a6bc4287a6315989ee591ff58507aa1cbe4c2e70d88411c4dcc0839"},
+ {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ed71e81782966ffead60268bbda31ea3f725ebf8aa73634d5dda44f2cf3fb9c"},
+ {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbd27c24a4cc5e195a7f56cfd9312e366d5d61b86e36d46bbe538457ea6eb8dd"},
+ {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9a9724a156c8ec6a379869b23ba3323b7ea3600851c91489b871e375f710bc8"},
+ {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d8d4732cc5052e92cea2f78b233c2e2a52998ac40cd651f40e398893ad0d06ec"},
+ {file = "grpcio-1.66.2-cp38-cp38-win32.whl", hash = "sha256:7b2c86457145ce14c38e5bf6bdc19ef88e66c5fee2c3d83285c5aef026ba93b3"},
+ {file = "grpcio-1.66.2-cp38-cp38-win_amd64.whl", hash = "sha256:e88264caad6d8d00e7913996030bac8ad5f26b7411495848cc218bd3a9040b6c"},
+ {file = "grpcio-1.66.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:c400ba5675b67025c8a9f48aa846f12a39cf0c44df5cd060e23fda5b30e9359d"},
+ {file = "grpcio-1.66.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:66a0cd8ba6512b401d7ed46bb03f4ee455839957f28b8d61e7708056a806ba6a"},
+ {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:06de8ec0bd71be123eec15b0e0d457474931c2c407869b6c349bd9bed4adbac3"},
+ {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb57870449dfcfac428afbb5a877829fcb0d6db9d9baa1148705739e9083880e"},
+ {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b672abf90a964bfde2d0ecbce30f2329a47498ba75ce6f4da35a2f4532b7acbc"},
+ {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad2efdbe90c73b0434cbe64ed372e12414ad03c06262279b104a029d1889d13e"},
+ {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c3a99c519f4638e700e9e3f83952e27e2ea10873eecd7935823dab0c1c9250e"},
+ {file = "grpcio-1.66.2-cp39-cp39-win32.whl", hash = "sha256:78fa51ebc2d9242c0fc5db0feecc57a9943303b46664ad89921f5079e2e4ada7"},
+ {file = "grpcio-1.66.2-cp39-cp39-win_amd64.whl", hash = "sha256:728bdf36a186e7f51da73be7f8d09457a03061be848718d0edf000e709418987"},
+ {file = "grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231"},
+]
+
+[package.extras]
+protobuf = ["grpcio-tools (>=1.66.2)"]
+
+[[package]]
+name = "grpcio-tools"
+version = "1.66.2"
+description = "Protobuf code generator for gRPC"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "grpcio_tools-1.66.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:40b7ad804ff78490408177cfe87427d5a67224f82a2bdfabe9d8d6ac6239733b"},
+ {file = "grpcio_tools-1.66.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a886fa2ff9e897b35489557d1c61cbc0e4efc42c4dc0d120a9516f294fefb107"},
+ {file = "grpcio_tools-1.66.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:1d5e22b2c7f5b453462c85aa66f99961d5c7b275d1c60b84fe847c06c73c9400"},
+ {file = "grpcio_tools-1.66.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a425b2600ad4fcf887107ee975a9b7c20478c2959c58b12af7f36577d7a7f7b3"},
+ {file = "grpcio_tools-1.66.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef979af76b0cd3f5235d3ec30e86a4f0acc0eab179e796ddbb481aa351a1e6ca"},
+ {file = "grpcio_tools-1.66.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:99638043e1a78b8617f31b676f1ecf248d75a45b318776af3acc48a85c8e10a2"},
+ {file = "grpcio_tools-1.66.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a465850c7e5c4ab588c7b7275d47781e9c0ee397a8faf4977262592f95e1831"},
+ {file = "grpcio_tools-1.66.2-cp310-cp310-win32.whl", hash = "sha256:48997b704d2fcf59d922228c7a79fcd35d52ca8b2202e5cfe193962643b8354f"},
+ {file = "grpcio_tools-1.66.2-cp310-cp310-win_amd64.whl", hash = "sha256:ab4eda584ba2e647e9bb5098f5e4e8d370a333761bf33924e9a7c14f069c8b08"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:007750b4db62018e441f8401fa567aa11174ae0173826cbbe54982fdf2383067"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18554bc91640b2f1ce18aa5c6bebd51500ca0b43b5df4e700e6f76522e2b0e94"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:3fe2fc2e4a16d745cae01e1348b401378e58ced920ff759a6b4b85a7ad507896"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0933420362621d8792fea9350f0c82c514da5f93888d1476c37d9e3722d260b0"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3aef5abd34bea8ea98448cd58a938992238c4717df93d12f84fa5f56efb11d0"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7afd9eb9be413a731cff7ad638081795a7ed0fec4b23af5cec2099fbd9d742f9"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fd1fa95188ae7d5460a8c4a2abcb2777fdf9c3b80d592a2e8434c52a6eb48e8d"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-win32.whl", hash = "sha256:80c233215cf0f08353b7aac4e86cdedf4d545ed368a7491ccc9996e5a317dce4"},
+ {file = "grpcio_tools-1.66.2-cp311-cp311-win_amd64.whl", hash = "sha256:2a9a376b300aa2b4da8e6c4f6f746e824d3f24eefeac2753ffffe2b9f37d156d"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:d8ca76fc40a7d35ddf1229afd04408e2ff94caf4385068c8b147e064e951e0ba"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6cc3da6994d575c425c74ce33e34b86a975ea7e78bb9c3525e8439a3af3c508f"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:89e437ced43275e7427cc82a837f5cd43ebe18a1080b0e50a47627895b44b0e6"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d95f030e708266d7fd6d3e5d56e30a9bbbe230604856b1fe93edd892e4389aab"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b3cf9ae67f8bb431ab3ff60db75c3586dc5aa993be4b15bd7cad651362563cd"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b4896a0853fc402273e908c0a0710d25242f1ae907efb9d22ba6d82d4ba00ad8"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d31aad10f90fccb0073bc03b4d1b67690ef4f0cd9af96e82944b9cc655d12b6f"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-win32.whl", hash = "sha256:d8f976f35683e49467d0bf2b90c170ac5443cd162d48d8d868801fd0d87a5fa8"},
+ {file = "grpcio_tools-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:b2c19e5a888a6ee48ba699581a90c04806b2a93f574f37449c359ec17a793669"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:7e8c9aa91a9e51199048202e3c54491e0a89fb3ac47dde36ff2964fbcee143a3"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0eaedd3c77824c3762b728c485f91097a58116fa135f3bbc24703621476cd866"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a14007902fb6565c21815da4177105ec905ef37f0550190c4d1bbeb2928c6560"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8f098bb92d192230f3b23df514b139f3549e2a4390d1f0f0d8ff89de458c54"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68642829368f4f83929e0df571dbbc99f1f1553555d8f98d0582da9f6743d9e"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:5fd20110d2c7706dfdd95457807acb8c050253be2e272b9f5fb977e87ea44d86"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:4b16244be4cff92408eb82901b883a70f3dd902fb7c7f66e2a368271be84cde4"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-win32.whl", hash = "sha256:d872ba3bbe9e15b43eeb9310dad5edbf490bb3ab0072a46b3a12fed0234eec23"},
+ {file = "grpcio_tools-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:a2810921218471aab5c8cd20204d3b1886aa8e13b495e882158bb398982cf18e"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:538eb263b9969e866619775df341307ece0b09afce091ede8141c5bb4d7d8933"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9a68c71bb1358f0994fc7d0f0d70a0d419d57507faa25c982145be401f6aca48"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:1bc41d5b36d414bb0940aa50e30d624903a2538f9387ae730953675adcbe1498"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43dcd3ee13418545ea10416f46296ddbc7fb355cf136ddebd3b3f881a383168"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc16f9e6baafed315846e79a746513863e6ecbb89e9c98d872834e44f9e87a5"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3655c96eef8aac2a610bbf4cb9c7839fcff09f07a609b74408b3b0a136e1ef57"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:86d971fc64e63642058ac01ce2e484a8340d60a95ead0dc6697ef2aa18a7b936"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-win32.whl", hash = "sha256:c14db004b28ee2adefc6d36107d7fdf770f7509bd1f1ecd195eecb88cdbe5d96"},
+ {file = "grpcio_tools-1.66.2-cp38-cp38-win_amd64.whl", hash = "sha256:c65f12474634195ff5ed91b304412b80008c067d28226c26b4e451ea9da16b24"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:75c6a25a5cf729c4606c388013cf7c59dda99cf3718c24fe4fd52b06c19955d0"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a5146e780ed87348d84b11fc3843741e676b2a84d493363bf0b4ae31c56841b"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c42ba1b24e701544bf08a43bb2d63d56dedd0fd33a5b499c9cf85e15aa154b13"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5daf9807260e172ffcc5dd582c01f60bac820f99f0151a507c8a537f9e6dceb8"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a15a4d0f4eba3773dabe07113b42e018a8fa9a28441483ada111991d5c1468b6"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:cc4f65cd189832676dca16046a4b6247d0bc1fc20648d16ac7fb0b075d1658f4"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ba63dbcbb8ade67e5a04dd3a6c5860efb454bda6d5e8558b17c9a7251339ce36"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-win32.whl", hash = "sha256:c4df0f547f4193dfa6689949b374974f08d81f129174738f0410ba8d45dc63be"},
+ {file = "grpcio_tools-1.66.2-cp39-cp39-win_amd64.whl", hash = "sha256:0cad9ffe5df7801201773b91f14923cf3e20ca764e418ae7f8cb75f6045a0aa1"},
+ {file = "grpcio_tools-1.66.2.tar.gz", hash = "sha256:4a36e07913d26ba5ccfd2685ba63ca97f26b08c249d2cc9e74dda37efa49d7e4"},
+]
+
+[package.dependencies]
+grpcio = ">=1.66.2"
+protobuf = ">=5.26.1,<6.0dev"
+setuptools = "*"
+
+[[package]]
+name = "htmlmin"
+version = "0.1.12"
+description = "An HTML Minifier"
+optional = false
+python-versions = "*"
+files = [
+ {file = "htmlmin-0.1.12.tar.gz", hash = "sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"},
+]
+
+[[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 = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
+[[package]]
+name = "isort"
+version = "5.13.2"
+description = "A Python utility / library to sort Python imports."
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
+ {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
+]
+
+[package.extras]
+colors = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "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 = "json-schema-for-humans"
+version = "1.0.2"
+description = "Generate static HTML documentation from JSON schemas"
+optional = false
+python-versions = "<4.0,>=3.8"
+files = [
+ {file = "json_schema_for_humans-1.0.2-py3-none-any.whl", hash = "sha256:d6ecb023b4f802b10b01abca1295a37e363d9f060e54c21aa2cddea44731c6e1"},
+ {file = "json_schema_for_humans-1.0.2.tar.gz", hash = "sha256:8bd807a2bac31650226e451ad3b9583c27ce916375d6938ac9d0251eb6549ad5"},
+]
+
+[package.dependencies]
+click = ">=8.0.1,<9.0.0"
+dataclasses-json = ">=0.5.6,<0.6.0"
+htmlmin = ">=0.1.12,<0.2.0"
+Jinja2 = ">3"
+markdown2 = ">=2.4.1,<3.0.0"
+MarkupSafe = ">=2.0,<3.0"
+Pygments = ">=2.10.0,<3.0.0"
+pytz = "*"
+PyYAML = ">=5.4.1,<7"
+requests = ">=2.31.0,<3.0.0"
+
+[[package]]
+name = "markdown2"
+version = "2.5.1"
+description = "A fast and complete Python implementation of Markdown"
+optional = false
+python-versions = "<4,>=3.8"
+files = [
+ {file = "markdown2-2.5.1-py2.py3-none-any.whl", hash = "sha256:190ae60a4bd0425c60c863bede18a9f3d45b1cbf3fbc9f40b4fac336ff2c520b"},
+ {file = "markdown2-2.5.1.tar.gz", hash = "sha256:12fc04ea5a87f7bb4b65acf5bf3af1183b20838cc7d543b74c92ec7eea4bbc74"},
+]
+
+[package.extras]
+all = ["latex2mathml", "pygments (>=2.7.3)", "wavedrom"]
+code-syntax-highlighting = ["pygments (>=2.7.3)"]
+latex = ["latex2mathml"]
+wavedrom = ["wavedrom"]
+
+[[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 = "marshmallow"
+version = "3.22.0"
+description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "marshmallow-3.22.0-py3-none-any.whl", hash = "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9"},
+ {file = "marshmallow-3.22.0.tar.gz", hash = "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e"},
+]
+
+[package.dependencies]
+packaging = ">=17.0"
+
+[package.extras]
+dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"]
+docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.13)", "sphinx (==8.0.2)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"]
+tests = ["pytest", "pytz", "simplejson"]
+
+[[package]]
+name = "marshmallow-enum"
+version = "1.5.1"
+description = "Enum field for Marshmallow"
+optional = false
+python-versions = "*"
+files = [
+ {file = "marshmallow-enum-1.5.1.tar.gz", hash = "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58"},
+ {file = "marshmallow_enum-1.5.1-py2.py3-none-any.whl", hash = "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"},
+]
+
+[package.dependencies]
+marshmallow = ">=2.0.0"
+
+[[package]]
+name = "mccabe"
+version = "0.7.0"
+description = "McCabe checker, plugin for flake8"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
+ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
+]
+
+[[package]]
+name = "mypy"
+version = "1.11.2"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"},
+ {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"},
+ {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"},
+ {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"},
+ {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"},
+ {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"},
+ {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"},
+ {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"},
+ {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"},
+ {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"},
+ {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"},
+ {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"},
+ {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"},
+ {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"},
+ {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"},
+ {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"},
+ {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"},
+ {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"},
+ {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"},
+ {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"},
+ {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"},
+ {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"},
+ {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"},
+ {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"},
+ {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"},
+ {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"},
+ {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"},
+]
+
+[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)"]
+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 = "packaging"
+version = "24.1"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
+]
+
+[[package]]
+name = "pdoc"
+version = "12.3.1"
+description = "API Documentation for Python Projects"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pdoc-12.3.1-py3-none-any.whl", hash = "sha256:c3f24f31286e634de9c76fa6e67bd5c0c5e74360b41dc91e6b82499831eb52d8"},
+ {file = "pdoc-12.3.1.tar.gz", hash = "sha256:453236f225feddb8a9071428f1982a78d74b9b3da4bc4433aedb64dbd0cc87ab"},
+]
+
+[package.dependencies]
+astunparse = {version = "*", markers = "python_version < \"3.9\""}
+Jinja2 = ">=2.11.0"
+MarkupSafe = "*"
+pygments = ">=2.12.0"
+
+[package.extras]
+dev = ["black", "hypothesis", "mypy", "pytest", "pytest-cov", "pytest-timeout", "ruff", "tox", "types-pygments"]
+
+[[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 = "protobuf"
+version = "5.27.2"
+description = ""
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "protobuf-5.27.2-cp310-abi3-win32.whl", hash = "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38"},
+ {file = "protobuf-5.27.2-cp310-abi3-win_amd64.whl", hash = "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505"},
+ {file = "protobuf-5.27.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5"},
+ {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b"},
+ {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e"},
+ {file = "protobuf-5.27.2-cp38-cp38-win32.whl", hash = "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863"},
+ {file = "protobuf-5.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6"},
+ {file = "protobuf-5.27.2-cp39-cp39-win32.whl", hash = "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca"},
+ {file = "protobuf-5.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce"},
+ {file = "protobuf-5.27.2-py3-none-any.whl", hash = "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470"},
+ {file = "protobuf-5.27.2.tar.gz", hash = "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714"},
+]
+
+[[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 = "pylint"
+version = "3.2.7"
+description = "python code static checker"
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"},
+ {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"},
+]
+
+[package.dependencies]
+astroid = ">=3.2.4,<=3.3.0-dev0"
+colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
+dill = [
+ {version = ">=0.2", markers = "python_version < \"3.11\""},
+ {version = ">=0.3.7", markers = "python_version >= \"3.12\""},
+ {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
+]
+isort = ">=4.2.5,<5.13.0 || >5.13.0,<6"
+mccabe = ">=0.6,<0.8"
+platformdirs = ">=2.2.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+tomlkit = ">=0.10.1"
+typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+spelling = ["pyenchant (>=3.2,<4.0)"]
+testutils = ["gitpython (>3)"]
+
+[[package]]
+name = "pytest"
+version = "8.3.3"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
+ {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=1.5,<2"
+tomli = {version = ">=1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-cov"
+version = "5.0.0"
+description = "Pytest plugin for measuring coverage."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
+ {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
+]
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
+
+[[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 = "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 = "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 = "ruff"
+version = "0.6.9"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"},
+ {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"},
+ {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"},
+ {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"},
+ {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"},
+ {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"},
+ {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"},
+ {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"},
+ {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"},
+ {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"},
+ {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"},
+]
+
+[[package]]
+name = "setuptools"
+version = "75.1.0"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"},
+ {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"},
+]
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"]
+core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.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", "packaging (>=23.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.11.*)", "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 = "tomli"
+version = "2.0.2"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
+ {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
+]
+
+[[package]]
+name = "tomlkit"
+version = "0.13.2"
+description = "Style preserving TOML library"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"},
+ {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
+]
+
+[[package]]
+name = "types-protobuf"
+version = "5.28.0.20240924"
+description = "Typing stubs for protobuf"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "types-protobuf-5.28.0.20240924.tar.gz", hash = "sha256:d181af8a256e5a91ce8d5adb53496e880efd9144c7d54483e3653332b60296f0"},
+ {file = "types_protobuf-5.28.0.20240924-py3-none-any.whl", hash = "sha256:5cecf612ccdefb7dc95f7a51fb502902f20fc2e6681cd500184aaa1b3931d6a7"},
+]
+
+[[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 = "typing-inspect"
+version = "0.9.0"
+description = "Runtime inspection utilities for typing module."
+optional = false
+python-versions = "*"
+files = [
+ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"},
+ {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=0.3.0"
+typing-extensions = ">=3.7.4"
+
+[[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 = "wheel"
+version = "0.44.0"
+description = "A built-package format for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "wheel-0.44.0-py3-none-any.whl", hash = "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f"},
+ {file = "wheel-0.44.0.tar.gz", hash = "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49"},
+]
+
+[package.extras]
+test = ["pytest (>=6.0.0)", "setuptools (>=65)"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.8"
+content-hash = "6faffcadb09720da42bda62ed78feb40645ddcb0f3ca31774389bd759897a292"
diff --git a/python/remotivelabs-broker/poetry.toml b/python/remotivelabs-broker/poetry.toml
new file mode 100644
index 0000000..24a1ce5
--- /dev/null
+++ b/python/remotivelabs-broker/poetry.toml
@@ -0,0 +1,2 @@
+virtualenvs.in-project = true
+virtualenvs.prefer-active-python = true
diff --git a/python/remotivelabs-broker/pyproject.toml b/python/remotivelabs-broker/pyproject.toml
index d48275b..8a1dc38 100644
--- a/python/remotivelabs-broker/pyproject.toml
+++ b/python/remotivelabs-broker/pyproject.toml
@@ -1,22 +1,14 @@
-[build-system]
-requires = ["hatchling"]
-build-backend = "hatchling.build"
-
-[project]
+[tool.poetry]
name = "remotivelabs-broker"
description = 'RemotiveLabs Broker gRPC API'
+version = "0.3.0-beta1"
readme = "README.md"
-requires-python = ">=3.7"
license = "Apache-2.0"
-keywords = [
- "automotive",
- "autotech",
- "networking",
- "CAN",
-]
-authors = [
- { name = "Support", email = "support@remotivelabs.com" },
-]
+homepage = "https://remotivelabs.com/"
+repository = "https://github.com/remotivelabs/remotivelabs-apis"
+documentation = "https://docs.remotivelabs.com/apis/python/remotivelabs/broker"
+authors = ["Support "]
+keywords = ["automotive", "autotech", "networking", "CAN"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
@@ -28,75 +20,125 @@ classifiers = [
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: Implementation :: CPython",
- "Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Communications",
"Topic :: Internet",
"Topic :: Scientific/Engineering :: Information Analysis",
]
-dependencies = [
- "grpc-interceptor~=0.14",
- "grpcio~=1.44",
- "grpc-stubs~=1.53.0.5",
- "requests~=2.21",
- "protobuf>=3.19.0,<=3.20.1",
- "mypy-protobuf~=3.3.0",
- "types-protobuf~=4.24.0.20240106",
- "grpc-interceptor"
-]
-dynamic = ["version"]
+packages = [{ include = "remotivelabs" }]
-[project.urls]
-Homepage = "https://remotivelabs.com/"
-Documentation = "https://docs.remotivelabs.com/apis/python/remotivelabs/broker"
+[tool.poetry.urls]
Issues = "https://github.com/remotivelabs/remotivelabs-apis/issues"
-Source = "https://github.com/remotivelabs/remotivelabs-apis/tree/main/python/remotivelabs-broker"
-
-[project.optional-dependencies]
-default = [
- "pytest-cov~=3.0",
- "pytest-pep8~=1.0",
- "pytest~=7.1",
- "pdoc~=12.2",
-]
-[tool.hatch.version]
-path = "remotivelabs/broker/__about__.py"
+[tool.poetry.dependencies]
+python = "^3.8"
+requests = "^2.32"
+grpcio = "1.66.2"
+# Make sure to use the protobuf version specified in grpcio-tools:
+# https://github.com/grpc/grpc/blob/v1.66.2/tools/distrib/python/grpcio_tools/grpc_version.py
+protobuf = "5.27.2"
+grpc-interceptor = "^0.15" # can be removed when we drop support for remotivelabs.broker.sync
-[tool.hatch.envs.default]
-python = "python3"
+[tool.poetry.group.dev.dependencies]
+grpcio-tools = "1.66.2"
+types-protobuf = "^5.27.0"
+grpc-stubs = "^1.53" # can be removed when we drop support for remotivelabs.broker.sync
-[tool.hatch.envs.default.scripts]
-cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=remotivelabs --cov=tests"
-no-cov = "cov --no-cov"
-generate_stubs = "sh build_proto.sh"
-generate_doc = "sh misc/build_doc.sh"
+[tool.poetry.group.test.dependencies]
+pytest = "^8.3"
+pytest-cov = "^5.0"
-[tool.pyright]
-include = ["remotivelabs"]
+[tool.poetry.group.lint.dependencies]
+ruff = "^0.6.9"
+pylint = "^3.2.7"
+mypy = "^1.11.2"
-[[tool.hatch.envs.test.matrix]]
-python = ["38", "39", "310", "311"]
+[tool.poetry.group.docs.dependencies]
+pdoc = "^12.2"
+json-schema-for-humans = "^1.0"
-[tool.coverage.run]
-branch = true
-parallel = true
-omit = [
- "remotivelabs/broker/__about__.py",
-]
+[tool.poe.tasks]
+test = "pytest --cov=remotivelabs.broker"
+test-server = "pytest -m server --cov=remotivelabs.broker"
+pylint = [{ cmd = "pylint ." }]
+lint = [{ cmd = "ruff check ." }, { cmd = "ruff format --check --diff ." }]
+format = [{ cmd = "ruff format ." }, { cmd = "ruff check --fix ." }]
+mypy = [{ cmd = "mypy -p remotivelabs.broker -p misc -p tests" }]
+check = ["lint", "mypy", "pylint"]
[tool.pytest.ini_options]
+addopts = "-v -m 'not server'"
markers = [
- "server: Run test towards a live server.",
+ "server: marks tests that require a live server (deselect with '-m \"not server\"')",
+]
+
+[tool.ruff]
+line-length = 140
+
+[tool.ruff.lint]
+select = [
+ "ARG",
+ "B034",
+ "C901",
+ "E",
+ "EXE",
+ "F",
+ "FA",
+ "I",
+ "N",
+ "UP032",
+ "RET",
+ "W",
]
-[tool.coverage.report]
-exclude_lines = [
- "no cov",
- "if __name__ == .__main__.:",
- "if TYPE_CHECKING:",
+[tool.pylint]
+recursive = true
+ignore-paths = ['^.venv/.*$']
+
+[tool.pylint.format]
+max-line-length = 140
+max-module-lines = 1000
+
+[tool.pylint.messages_control]
+disable = [
+ 'wrong-import-order',
+ 'missing-module-docstring',
+ 'missing-class-docstring',
+ 'missing-function-docstring',
+ 'duplicate-code',
+ 'logging-fstring-interpolation',
+ 'too-few-public-methods',
+ 'no-member',
]
-[tool.hatch.build.targets.wheel]
-packages = ["remotivelabs"]
+[tool.mypy]
+python_version = "3.8"
+namespace_packages = true
+explicit_package_bases = true
+mypy_path = "."
+packages = ["remotivelabs.broker", "misc", "tests"]
+
+# See https://mypy.readthedocs.io/en/stable/config_file.html
+disallow_untyped_calls = false
+check_untyped_defs = true
+warn_return_any = true
+warn_unused_ignores = true
+hide_error_codes = false
+
+[[tool.mypy.overrides]]
+# A field in diagnostics proto file is accidentally named same as the type (bytes bytes), so we need to ignore the type warning.
+module = "remotivelabs.broker.generated.sync.diagnostics_api_pb2"
+disable_error_code = ["valid-type"]
+
+[[tool.mypy.overrides]]
+# Also, it seems experimental features are missing in type definitions
+module = "remotivelabs.broker.generated.*"
+disable_error_code = ["attr-defined"]
+
+[[tool.mypy.overrides]]
+# We ignore stuff in sync, they are deprecated
+module = "remotivelabs.broker.sync.*"
+ignore_errors = true
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/python/remotivelabs-broker/remotivelabs/broker/__about__.py b/python/remotivelabs-broker/remotivelabs/broker/__about__.py
index f3062b2..09e2860 100644
--- a/python/remotivelabs-broker/remotivelabs/broker/__about__.py
+++ b/python/remotivelabs-broker/remotivelabs/broker/__about__.py
@@ -1,5 +1,9 @@
# SPDX-FileCopyrightText: 2022-present remotiveLabs
#
# SPDX-License-Identifier: Apache-2.0
+from importlib.metadata import PackageNotFoundError, version
-__version__ = "0.1.26"
+try:
+ __version__ = version("remotivelabs-broker")
+except PackageNotFoundError:
+ __version__ = "unknown"
diff --git a/python/remotivelabs-broker/remotivelabs/broker/__init__.py b/python/remotivelabs-broker/remotivelabs/broker/__init__.py
index 984e275..8fbc345 100644
--- a/python/remotivelabs-broker/remotivelabs/broker/__init__.py
+++ b/python/remotivelabs-broker/remotivelabs/broker/__init__.py
@@ -1,37 +1,51 @@
"""
-remotiveLabs Python API for remotiveBroker.
-See `version` below.
+RemotiveLabs Python API for RemotiveBroker.
+
+This API uses protobuffer and gRPC stubs directly, available in the submodules:
+- `remotivelabs.broker.common_pb2`.
+- `remotivelabs.broker.common_pb2_grpc`.
+- `remotivelabs.broker.diagnostics_api_pb2`.
+- `remotivelabs.broker.diagnostics_api_pb2_grpc`.
+- `remotivelabs.broker.functional_api_pb2`.
+- `remotivelabs.broker.functional_api_pb2_grpc`.
+- `remotivelabs.broker.network_api_pb2`.
+- `remotivelabs.broker.network_api_pb2_grpc`.
+- `remotivelabs.broker.system_api_pb2`.
+- `remotivelabs.broker.system_api_pb2_grpc`.
+- `remotivelabs.broker.traffic_api_pb2`.
+- `remotivelabs.broker.traffic_api_pb2_grpc`.
In addition to return codes, this package uses logging to convey operational
-status. Logging is done to the name space "com.remotivelabs.broker".
+status. Logging is done to the namespace "remotivelabs.broker".
-As a user, enable basic logging in your application with:
```python
-logging.basicConfig()
+# Disable logging for this package:
+logging.getLogger("remotivelabs.broker").propagate = False
```
-
-Disable logging for this package:
-
-```python
-logging.getLogger("com.remotivelabs.broker").propagate = False
-```
-
-Use sub module: `remotivelabs.broker.sync`.
"""
# SPDX-FileCopyrightText: 2022-present remotiveLabs
#
# SPDX-License-Identifier: Apache-2.0
-
-import logging
-
from .__about__ import __version__
-
-log: logging.Logger = logging.getLogger("com.remotivelabs.broker")
-"""Package logging interface"""
-
-log.addHandler(logging.NullHandler())
+from ._log import configure_logging
+from .generated.sync import (
+ common_pb2, # noqa: F401
+ common_pb2_grpc, # noqa: F401
+ diagnostics_api_pb2, # noqa: F401
+ diagnostics_api_pb2_grpc, # noqa: F401
+ functional_api_pb2, # noqa: F401
+ functional_api_pb2_grpc, # noqa: F401
+ network_api_pb2, # noqa: F401
+ network_api_pb2_grpc, # noqa: F401
+ system_api_pb2, # noqa: F401
+ system_api_pb2_grpc, # noqa: F401
+ traffic_api_pb2, # noqa: F401
+ traffic_api_pb2_grpc, # noqa: F401
+)
version: str = __version__
"""Library version"""
+
+configure_logging()
diff --git a/python/remotivelabs-broker/remotivelabs/broker/_log.py b/python/remotivelabs-broker/remotivelabs/broker/_log.py
new file mode 100644
index 0000000..b20e558
--- /dev/null
+++ b/python/remotivelabs-broker/remotivelabs/broker/_log.py
@@ -0,0 +1,11 @@
+"""
+Configure library logger
+
+See https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
+"""
+
+import logging
+
+
+def configure_logging() -> None:
+ logging.getLogger("remotivelabs.broker").addHandler(logging.NullHandler())
diff --git a/python/remotivelabs-broker/remotivelabs/broker/sync/__init__.py b/python/remotivelabs-broker/remotivelabs/broker/sync/__init__.py
index 6ab3495..e22ed8d 100644
--- a/python/remotivelabs-broker/remotivelabs/broker/sync/__init__.py
+++ b/python/remotivelabs-broker/remotivelabs/broker/sync/__init__.py
@@ -3,26 +3,13 @@
Create a connection with the method `remotivelabs.broker.sync.create_channel`.
-This API uses protobuffer and gRPC stubs directly. Which are availble in the submodules:
-- `remotivelabs.broker.sync.common_pb2`.
-- `remotivelabs.broker.sync.common_pb2_grpc`.
-- `remotivelabs.broker.sync.diagnostics_api_pb2`.
-- `remotivelabs.broker.sync.diagnostics_api_pb2_grpc`.
-- `remotivelabs.broker.sync.functional_api_pb2`.
-- `remotivelabs.broker.sync.functional_api_pb2_grpc`.
-- `remotivelabs.broker.sync.network_api_pb2`.
-- `remotivelabs.broker.sync.network_api_pb2_grpc`.
-- `remotivelabs.broker.sync.system_api_pb2`.
-- `remotivelabs.broker.sync.system_api_pb2_grpc`.
-- `remotivelabs.broker.sync.traffic_api_pb2`.
-- `remotivelabs.broker.sync.traffic_api_pb2_grpc`.
-
For an example on how to use these we recommend looking at the samples for this library.
Which is available at the repository remotiveLabs samples:
Link: .
"""
+# Prefer import from root module. We keep this import for backwards compatibility.
from ..generated.sync import (
common_pb2, # noqa: F401
common_pb2_grpc, # noqa: F401
@@ -37,7 +24,13 @@
traffic_api_pb2, # noqa: F401
traffic_api_pb2_grpc, # noqa: F401
)
-from .client import BrokerException, Client, SignalIdentifier, SignalsInFrame, SignalValue
+from .client import (
+ BrokerException,
+ Client,
+ SignalIdentifier,
+ SignalsInFrame,
+ SignalValue,
+)
from .helper import (
act_on_scripted_signal,
act_on_signal,
diff --git a/python/remotivelabs-broker/remotivelabs/broker/sync/client.py b/python/remotivelabs-broker/remotivelabs/broker/sync/client.py
index 8232c02..3d7664f 100644
--- a/python/remotivelabs-broker/remotivelabs/broker/sync/client.py
+++ b/python/remotivelabs-broker/remotivelabs/broker/sync/client.py
@@ -8,8 +8,12 @@
import grpc
-from ..generated.sync import network_api_pb2 as network_api
-from ..generated.sync import network_api_pb2_grpc, system_api_pb2_grpc, traffic_api_pb2_grpc
+from .. import network_api_pb2 as network_api
+from .. import (
+ network_api_pb2_grpc,
+ system_api_pb2_grpc,
+ traffic_api_pb2_grpc,
+)
from . import helper as br
from .signalcreator import SignalCreator
@@ -112,9 +116,8 @@ def __iter__(self):
def __next__(self):
try:
result = self.signals[self.index]
- # pylint: disable=raise-missing-from
- except IndexError:
- raise StopIteration
+ except IndexError as ex:
+ raise StopIteration from ex
self.index += 1
return result
@@ -132,10 +135,11 @@ def parse(signal_id: str) -> SignalIdentifier:
return SignalIdentifier(s[1], s[0])
-class BrokerException(Exception):
+class BrokerException(Exception): # noqa: N818
pass
+# pylint: disable=too-many-instance-attributes
class Client:
def __init__(self, client_id: str = "broker_client"):
self._signal_creator: SignalCreator
@@ -195,7 +199,7 @@ def to_protobuf_signal(s: SignalIdentifier):
lambda sub: (wait_for_subscription_queue.put((self.client_id, sub))),
),
).start()
- # Wait for subscription
+
client_id, subscription = wait_for_subscription_queue.get()
return subscription
@@ -209,7 +213,6 @@ def _on_signals(self, signals_in_frame: network_api.Signals, callback):
self.on_signals(SignalsInFrame(list(map(SignalValue, signals_in_frame)))) # type: ignore[call-overload]
def list_signal_names(self) -> List[SignalIdentifier]:
- # Lists available signals
configuration = self._system_stub.GetConfiguration(br.common_pb2.Empty())
signal_names: List[SignalIdentifier] = []
diff --git a/python/remotivelabs-broker/remotivelabs/broker/sync/helper.py b/python/remotivelabs-broker/remotivelabs/broker/sync/helper.py
index 87a4d1c..e1e779a 100644
--- a/python/remotivelabs-broker/remotivelabs/broker/sync/helper.py
+++ b/python/remotivelabs-broker/remotivelabs/broker/sync/helper.py
@@ -2,6 +2,7 @@
import hashlib
import itertools
+import logging
import ntpath
import os
import posixpath
@@ -12,8 +13,19 @@
import grpc
from grpc_interceptor import ClientCallDetails, ClientInterceptor
-from .. import log
-from ..generated.sync import common_pb2, network_api_pb2, network_api_pb2_grpc, system_api_pb2, system_api_pb2_grpc
+from .. import (
+ common_pb2,
+ network_api_pb2,
+ network_api_pb2_grpc,
+ system_api_pb2,
+ system_api_pb2_grpc,
+)
+
+_logger = logging.getLogger(__name__)
+
+
+# pylint: disable=protected-access
+# pylint: disable=too-many-arguments
class HeaderInterceptor(ClientInterceptor):
@@ -95,9 +107,8 @@ def publish_signals(client_id, stub, signals_with_payload, frequency: int = 0) -
try:
stub.PublishSignals(publisher_info)
- # pylint: disable=protected-access
- except grpc._channel._Rendezvous as err: # type:ignore[attr-defined]
- log.error(err)
+ except grpc._channel._Rendezvous: # type:ignore[attr-defined]
+ _logger.exception("A Rendezvous error occurred")
def printer(signals: Sequence[common_pb2.SignalId]) -> None:
@@ -108,7 +119,7 @@ def printer(signals: Sequence[common_pb2.SignalId]) -> None:
"""
for signal in signals:
- log.info(f"{signal} {signal.namespace.name}")
+ _logger.info(f"{signal} {signal.namespace.name}")
def get_sha256(path: str) -> str:
@@ -118,11 +129,9 @@ def get_sha256(path: str) -> str:
:param path: Path to file
:rtype int:
"""
-
with open(path, "rb") as f:
b = f.read() # read entire file as bytes
- readable_hash = hashlib.sha256(b).hexdigest()
- return readable_hash
+ return hashlib.sha256(b).hexdigest()
def generate_data(file, dest_path, chunk_size, sha256) -> Generator[system_api_pb2.FileUploadRequest, None, None]:
@@ -147,13 +156,13 @@ def upload_file(system_stub: system_api_pb2_grpc.SystemServiceStub, path: str, d
"""
sha256 = get_sha256(path)
- log.debug(f"SHA256 for file {path}: {sha256}")
+ _logger.debug(f"SHA256 for file {path}: {sha256}")
with open(path, "rb") as file:
# make sure path is unix style (necessary for windows, and does no harm om
# linux)
upload_iterator = generate_data(file, dest_path.replace(ntpath.sep, posixpath.sep), 1000000, sha256)
response = system_stub.UploadFile(upload_iterator, compression=grpc.Compression.Gzip)
- log.debug(f"Uploaded {path} with response {response}")
+ _logger.debug(f"Uploaded {path} with response {response}")
def download_file(system_stub: system_api_pb2_grpc.SystemServiceStub, path: str, dest_path: str) -> None:
@@ -198,7 +207,7 @@ def reload_configuration(
request = common_pb2.Empty()
response = system_stub.ReloadConfiguration(request, timeout=60000)
- log.debug(f"Reload configuration with response {response}")
+ _logger.debug(f"Reload configuration with response {response}")
def check_license(
@@ -231,8 +240,7 @@ def act_on_signal(
:param fun: Callback for receiving signals update
:param on_subscribed: Callback for successful subscription
"""
-
- log.debug("Subscription started")
+ _logger.debug("Subscription started")
sub_info = network_api_pb2.SubscriberConfig(
clientId=client_id,
@@ -243,25 +251,24 @@ def act_on_signal(
subscripton = network_stub.SubscribeToSignals(sub_info, timeout=None)
if on_subscribed:
on_subscribed(subscripton)
- log.debug("Waiting for signal...")
+
+ _logger.debug("Waiting for signal...")
for subs_counter in subscripton:
fun(subs_counter.signal)
+ except grpc._channel._Rendezvous: # type:ignore[attr-defined]
+ _logger.exception("A Rendezvous error occurred")
except grpc.RpcError as e:
# Only try to cancel if cancel was not already attempted
- # pylint: disable=no-member
if e.code() != grpc.StatusCode.CANCELLED:
try:
subscripton.cancel()
- print("A gRPC error occurred:")
- print(e)
+ _logger.exception("A gRPC error occurred")
except grpc.RpcError:
pass
- # pylint: disable=protected-access, bad-except-order
- except grpc._channel._Rendezvous as err: # type:ignore[attr-defined]
- log.error(err)
+
# reload, alternatively non-existing signal
- log.debug("Subscription terminated")
+ _logger.debug("Subscription terminated")
def act_on_scripted_signal(
@@ -283,7 +290,7 @@ def act_on_scripted_signal(
:param on_subscribed: Callback for successful subscription
"""
- log.debug("Subscription with mapping code started...")
+ _logger.debug("Subscription with mapping code started...")
sub_info = network_api_pb2.SubscriberWithScriptConfig(
clientId=client_id,
@@ -294,20 +301,20 @@ def act_on_scripted_signal(
subscription = network_stub.SubscribeToSignalWithScript(sub_info, timeout=None)
if on_subscribed:
on_subscribed(subscription)
- log.debug("Waiting for signal...")
+
+ _logger.debug("Waiting for signal...")
for subs_counter in subscription:
fun(subs_counter.signal)
- except grpc.RpcError as e:
+ except grpc._channel._Rendezvous: # type:ignore[attr-defined]
+ _logger.exception("A Rendezvous error occurred")
+
+ except grpc.RpcError:
+ _logger.exception("A gRPC error occurred")
try:
subscription.cancel()
- print("A gRPC error occurred:")
- print(e)
except grpc.RpcError:
pass
- # pylint: disable=protected-access, bad-except-order
- except grpc._channel._Rendezvous as err: # type:ignore[attr-defined]
- log.error(err)
# reload, alternatively non-existing signal
- log.debug("Subscription terminated")
+ _logger.debug("Subscription terminated")
diff --git a/python/remotivelabs-broker/remotivelabs/broker/sync/signalcreator.py b/python/remotivelabs-broker/remotivelabs/broker/sync/signalcreator.py
index ddf4b6c..e6b9d7b 100644
--- a/python/remotivelabs-broker/remotivelabs/broker/sync/signalcreator.py
+++ b/python/remotivelabs-broker/remotivelabs/broker/sync/signalcreator.py
@@ -3,20 +3,24 @@
import logging
from typing import Any, Dict, List, Optional, Sequence, TypeVar
-from ..generated.sync import common_pb2, network_api_pb2, system_api_pb2_grpc
+from .. import common_pb2, network_api_pb2, system_api_pb2_grpc
-T = TypeVar("T")
+_logger = logging.getLogger(__name__)
-_logger = logging.getLogger("remotivelabs.SignalCreator")
_MSG_DUPLICATE = "Warning duplicated (namespace.signal): {}, to avoid" + 'ambiguity set "short_names": false in your interfaces.json on {}'
+T = TypeVar("T")
+
+
+# pylint: disable=invalid-name
+# pylint: disable=broad-exception-raised
+
-# pylint: disable=C0103
class MetaGetter:
def __init__(self, proto_message):
self.meta = proto_message
- def _getDefault(self, field: T, default: Optional[T]) -> T:
+ def _getDefault(self, field: T, default: Optional[T]) -> T: # noqa: N802
if field is not None:
return field
@@ -25,51 +29,51 @@ def _getDefault(self, field: T, default: Optional[T]) -> T:
raise Exception("Failed to retrieve meta data field")
- def getDescription(self, default: Optional[str] = None) -> str:
+ def getDescription(self, default: Optional[str] = None) -> str: # noqa: N802
"""Get protobuffer MetaData field description"""
return self._getDefault(self.meta.description, default)
- def getUnit(self, default: Optional[str] = None) -> str:
+ def getUnit(self, default: Optional[str] = None) -> str: # noqa: N802
"""Get protobuffer MetaData field unit"""
return self._getDefault(self.meta.unit, default)
- def getMax(self, default: Optional[float] = None) -> float:
+ def getMax(self, default: Optional[float] = None) -> float: # noqa: N802
"""Get protobuffer MetaData field max"""
return self._getDefault(self.meta.max, default)
- def getMin(self, default: Optional[float] = None) -> float:
+ def getMin(self, default: Optional[float] = None) -> float: # noqa: N802
"""Get protobuffer MetaData field min"""
return self._getDefault(self.meta.min, default)
- def getSize(self, default: Optional[int] = None) -> int:
+ def getSize(self, default: Optional[int] = None) -> int: # noqa: N802
"""Get protobuffer MetaData field size"""
return self._getDefault(self.meta.size, default)
- def getIsRaw(self, default: Optional[bool] = None) -> bool:
+ def getIsRaw(self, default: Optional[bool] = None) -> bool: # noqa: N802
"""Get protobuffer MetaData field isRaw"""
return self._getDefault(self.meta.isRaw, default)
- def getFactor(self, default: Optional[float] = None) -> float:
+ def getFactor(self, default: Optional[float] = None) -> float: # noqa: N802
"""Get protobuffer MetaData field factor"""
return self._getDefault(self.meta.factor, default)
- def getOffset(self, default: Optional[float] = None) -> float:
+ def getOffset(self, default: Optional[float] = None) -> float: # noqa: N802
"""Get protobuffer MetaData field offset"""
return self._getDefault(self.meta.offset, default)
- def getSenders(self, default: Optional[Sequence[str]] = None) -> Sequence[str]:
+ def getSenders(self, default: Optional[Sequence[str]] = None) -> Sequence[str]: # noqa: N802
"""Get protobuffer MetaData field sender"""
return self._getDefault(self.meta.sender, default)
- def getReceivers(self, default: Optional[Sequence[str]] = None) -> Sequence[str]:
+ def getReceivers(self, default: Optional[Sequence[str]] = None) -> Sequence[str]: # noqa: N802
"""Get protobuffer MetaData field receiver"""
return self._getDefault(self.meta.receiver, default)
- def getCycleTime(self, default: Optional[float] = None) -> float:
+ def getCycleTime(self, default: Optional[float] = None) -> float: # noqa: N802
"""Get protobuffer MetaData field cycleTime"""
return self._getDefault(self.meta.cycleTime, default)
- def getStartValue(self, default: Optional[float] = None) -> float:
+ def getStartValue(self, default: Optional[float] = None) -> float: # noqa: N802
"""Get protobuffer MetaData field startValue"""
return self._getDefault(self.meta.startValue, default)
@@ -79,7 +83,11 @@ class SignalCreator:
Class for prepearing and writing signals via gRPC.
"""
- def __init__(self, system_stub: system_api_pb2_grpc.SystemServiceStub, namespaces: List[str] | None = None):
+ def __init__(
+ self,
+ system_stub: system_api_pb2_grpc.SystemServiceStub,
+ namespaces: List[str] | None = None,
+ ):
self._sinfos: Dict[Any, Any] = {}
self._virtual: List[Any] = []
self._networks: Dict[Any, Any] = {}
@@ -210,8 +218,3 @@ def signal_with_payload(self, name: str, namespace_name: str, value_pair, allow_
params = {"id": signal, key: value}
return network_api_pb2.Signal(**params)
-
- # Above is simlar as this, but parameterised.
- # return network_api_pb2.Signal(
- # id=signal, value_dict.get_key=value_dict["integer"]
- # )
diff --git a/python/remotivelabs-broker/tests/__init__.py b/python/remotivelabs-broker/tests/__init__.py
deleted file mode 100644
index a48296a..0000000
--- a/python/remotivelabs-broker/tests/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# SPDX-FileCopyrightText: 2022-present remotiveLabs
-#
-# SPDX-License-Identifier: Apache-2.0
diff --git a/python/remotivelabs-broker/tests/test_live.py b/python/remotivelabs-broker/tests/test_live.py
index a8ed9c0..cf84b54 100644
--- a/python/remotivelabs-broker/tests/test_live.py
+++ b/python/remotivelabs-broker/tests/test_live.py
@@ -18,14 +18,12 @@ def __init__(self):
# Setup broker with predefined settings
-@pytest.fixture
@pytest.fixture(name="broker_connection")
def fixture_broker_connection():
return Connection()
# Setup broker configured for testing
-@pytest.fixture
@pytest.fixture(name="broker_configured")
def fixture_broker_configured(broker_connection):
br.upload_folder(broker_connection.system_stub, "tests/configuration_udp")
diff --git a/rust/README.md b/rust/README.md
new file mode 100644
index 0000000..6638287
--- /dev/null
+++ b/rust/README.md
@@ -0,0 +1,28 @@
+# RemotiveBroker Rust API
+
+`remotivelabs-broker` - Rust library with gRPC-bindings and utility functions.
+
+## Getting started
+
+```bash
+cd rust/remotivelabs-broker
+
+# build
+cargo build
+```
+
+## Building
+
+```bash
+cd rust/remotivelabs-broker
+
+cargo build
+```
+
+## Versioning
+
+Follow [Semantic versioning](https://semver.org/). Beta versions should be suffixed with `-beta*`, example `0.2.0-beta1`.
+
+## Publishing
+
+This library is not published.
diff --git a/rust/remotivelabs-broker/Cargo.lock b/rust/remotivelabs-broker/Cargo.lock
index e1e83e0..8e10e40 100644
--- a/rust/remotivelabs-broker/Cargo.lock
+++ b/rust/remotivelabs-broker/Cargo.lock
@@ -885,7 +885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
-name = "remotive-broker"
+name = "remotivelabs-broker"
version = "0.1.0"
dependencies = [
"clap",
diff --git a/rust/remotivelabs-broker/Cargo.toml b/rust/remotivelabs-broker/Cargo.toml
index b8bc305..fa2f2f6 100644
--- a/rust/remotivelabs-broker/Cargo.toml
+++ b/rust/remotivelabs-broker/Cargo.toml
@@ -1,7 +1,7 @@
[package]
-name = "remotive-broker"
+name = "remotivelabs-broker"
version = "0.1.0"
-authors = [ "RemotiveLabs support ", "Lind, Niclas "]
+authors = ["RemotiveLabs support "]
edition = "2021"
[dependencies]
@@ -9,7 +9,13 @@ futures = "0.3.19"
path-slash = "0.1.4"
prost = "0.9.0"
sha2 = "0.10.1"
-tokio = { version = "1.14.0", features = ["rt-multi-thread", "time", "fs", "macros", "net"] }
+tokio = { version = "1.14.0", features = [
+ "rt-multi-thread",
+ "time",
+ "fs",
+ "macros",
+ "net",
+] }
tonic = { version = "0.6.2", features = ["tls", "tls-roots"] }
walkdir = "2.3.2"
diff --git a/rust/remotivelabs-broker/README.md b/rust/remotivelabs-broker/README.md
index 99a5f21..ddb3fdf 100644
--- a/rust/remotivelabs-broker/README.md
+++ b/rust/remotivelabs-broker/README.md
@@ -1,9 +1,24 @@
-# RemotiveLabs Broker
+# RemotiveBroker Rust API
+
Rust library with gRPC-bindings and utility functions.
-gRPC bindings are generated from [this source](../../proto/) when compiling the project.
+## Usage
+
+```rust
+use remotive_broker::{
+ Connection,
+};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let mut con = Connection::new("http://localhost:50051".to_string(), None).await?;
+
+ println!("Checking license...");
+ con.check_license().await?;
-This project is contributed by _Niclas Lind_ (@niclaslind).
+ Ok(())
+}
+```
## Examples
diff --git a/rust/remotivelabs-broker/build.rs b/rust/remotivelabs-broker/build.rs
index e41aff8..38ba352 100644
--- a/rust/remotivelabs-broker/build.rs
+++ b/rust/remotivelabs-broker/build.rs
@@ -1,7 +1,7 @@
fn main() -> Result<(), Box> {
tonic_build::configure()
.build_server(false)
- .out_dir("src/remotive_api")
+ .out_dir("src/generated")
.compile(
&[
"common.proto",
diff --git a/rust/remotivelabs-broker/examples/README.md b/rust/remotivelabs-broker/examples/README.md
new file mode 100644
index 0000000..d090dd0
--- /dev/null
+++ b/rust/remotivelabs-broker/examples/README.md
@@ -0,0 +1 @@
+# `remotivelabs-broker` examples
diff --git a/rust/remotivelabs-broker/examples/pub_client.rs b/rust/remotivelabs-broker/examples/pub_client.rs
index 154783f..ee82aa5 100644
--- a/rust/remotivelabs-broker/examples/pub_client.rs
+++ b/rust/remotivelabs-broker/examples/pub_client.rs
@@ -1,7 +1,7 @@
use std::io::Write;
-use remotive_broker::{
- remotive_api::base::{
+use remotivelabs_broker::{
+ generated::base::{
signal::Payload::Integer, ClientId, NameSpace, PublisherConfig, Signal, SignalId, Signals,
},
Connection,
diff --git a/rust/remotivelabs-broker/examples/sub_client.rs b/rust/remotivelabs-broker/examples/sub_client.rs
index 9c238a4..a76403a 100644
--- a/rust/remotivelabs-broker/examples/sub_client.rs
+++ b/rust/remotivelabs-broker/examples/sub_client.rs
@@ -1,5 +1,5 @@
-use remotive_broker::{
- remotive_api::base::{ClientId, NameSpace, SignalId, SignalIds, SubscriberConfig},
+use remotivelabs_broker::{
+ generated::base::{ClientId, NameSpace, SignalId, SignalIds, SubscriberConfig},
Connection,
};
use std::{thread, time};
diff --git a/rust/remotivelabs-broker/examples/subscribe.rs b/rust/remotivelabs-broker/examples/subscribe.rs
index 4decddd..633097a 100644
--- a/rust/remotivelabs-broker/examples/subscribe.rs
+++ b/rust/remotivelabs-broker/examples/subscribe.rs
@@ -2,8 +2,8 @@ use clap::Parser;
use std::error::Error;
use std::fmt;
-use remotive_broker::{
- remotive_api::base::{ClientId, NameSpace, SignalId, SignalIds, SubscriberConfig},
+use remotivelabs_broker::{
+ generated::base::{ClientId, NameSpace, SignalId, SignalIds, SubscriberConfig},
Connection,
};
diff --git a/rust/remotivelabs-broker/src/remotive_api/.gitignore b/rust/remotivelabs-broker/src/generated/.gitignore
similarity index 100%
rename from rust/remotivelabs-broker/src/remotive_api/.gitignore
rename to rust/remotivelabs-broker/src/generated/.gitignore
diff --git a/rust/remotivelabs-broker/src/remotive_api/mod.rs b/rust/remotivelabs-broker/src/generated/mod.rs
similarity index 100%
rename from rust/remotivelabs-broker/src/remotive_api/mod.rs
rename to rust/remotivelabs-broker/src/generated/mod.rs
diff --git a/rust/remotivelabs-broker/src/lib.rs b/rust/remotivelabs-broker/src/lib.rs
index a3b48dd..17c0eaf 100644
--- a/rust/remotivelabs-broker/src/lib.rs
+++ b/rust/remotivelabs-broker/src/lib.rs
@@ -3,12 +3,12 @@
use std::{error::Error, fs, path::Path};
use futures::{stream, Stream};
-use path_slash::PathExt;
-use remotive_api::base::{
+use generated::base::{
file_upload_request::Data, functional_service_client::FunctionalServiceClient,
network_service_client::NetworkServiceClient, system_service_client::SystemServiceClient,
traffic_service_client::TrafficServiceClient, FileDescription, FileUploadRequest,
};
+use path_slash::PathExt;
use sha2::{Digest, Sha256};
use tonic::{
metadata::AsciiMetadataValue,
@@ -18,9 +18,9 @@ use tonic::{
};
use walkdir::WalkDir;
-use crate::remotive_api::base::{Empty, LicenseStatus};
+use crate::generated::base::{Empty, LicenseStatus};
-pub mod remotive_api;
+pub mod generated;
#[derive(Clone)]
pub struct XApiIntercept {