diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 8688759..0000000 --- a/.flake8 +++ /dev/null @@ -1,19 +0,0 @@ -[flake8] - -extend-ignore = - H101 - N - W504 - E203 - W503 - -max-line-length = 88 - -exclude = - .nox/ - -per-file-ignores = - *lib.py:E221,E501 - setup.py:E - __init__.py:E402,F401,F403 - *.pyi:E,W diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbe79c0..bb9f4b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,47 +1,48 @@ on: push: - # Sequence of patterns matched against refs/tags tags: - - '[0-9]+.[0-9]+.[0-9]+' # Exclude pre-releases + - '*' name: Create Release jobs: build: + name: Build wheels runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.12'] + + permissions: + contents: write + id-token: write + steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create release + uses: softprops/action-gh-release@v1 with: - tag_name: ${{ github.ref }} - release_name: lvmnps ${{ github.ref }} - body: '' - draft: false - prerelease: false + name: lvmnps ${{ github.ref_name }} - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install pep517 + python -m pip install --upgrade pip setuptools wheel build - - name: Build package + - name: Build wheels run: | - python -m pep517.build --source --binary --out-dir dist/ . + pyproject-build -w - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_password }} + - name: Build source + run: | + pyproject-build -s + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bd01587..9c00680 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,6 @@ name: Test on: push: - branches: [main] paths-ignore: - 'docs/**' pull_request: @@ -19,52 +18,36 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v3 - - - name: Cache Setup - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - - name: Install poetry - run: | - curl -sSL https://install.python-poetry.org | python3 - + cache: 'pip' - name: Install dependencies run: | - poetry env use python - poetry run pip install -U pip setuptools wheel - poetry --no-ansi install --without helpers + pip install --upgrade wheel pip setuptools + pip install . - - name: Lint with flake8 + - name: Lint with ruff run: | - # stop the build if there are Python syntax errors or undefined names - poetry run pip install flake8 - poetry run flake8 . --count --show-source --statistics + pip install ruff + ruff check src/ tests/ - name: Lint with black run: | - poetry run pip install black - poetry run black --check . - - - name: Lint with isort - run: | - poetry run pip install isort - poetry run isort -c . -vvv + pip install black + black --check src/lvmnps - name: Test with pytest run: | - poetry --no-ansi install --only test - poetry run pytest + pip install pytest pytest-mock pytest-asyncio pytest-cov pytest-httpx + pytest - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf3058..d3acb8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 1.0.0 + +### 🔥 Breaking changes + +* [#36](https://github.com/sdss/lvmnps/pull/36) Complete rewrite, mostly to simplify the code and to take advantage of some additional features in the DLI REST API and to support calling DLI user scripts. + * The main difference in this version is that the code has been significantly simplified by requiring that one instance of the `lvmnps` actor can only control one NPS. This means that all the `switch` flags and parameters have been deprecated. + * The code has been simplified. It still follows the approach of a core, abstract `NPSClient` class with multiple implementations for different switches, but some options that were not used have been removed. + * It's now possible to switch multiple outlets at the same time. This takes advantage of the DLI and NetIO implementations to turn on several outlets as quickly as possible without risking an in-rush overcurrent. + * The DLI client now allows to command user functions (scripts). + + ## 0.4.0 - July 10, 2023 ### 🚀 New diff --git a/codecov.yml b/codecov.yml index 922ddf8..3dd296c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,11 +1,11 @@ coverage: - range: 80...95 + range: 90...100 round: up precision: 1 status: project: default: - target: 85% + target: 90% if_not_found: success if_ci_failed: error informational: false diff --git a/docs/sphinx/actor-schema.rst b/docs/sphinx/actor-schema.rst deleted file mode 100644 index 7aa5caf..0000000 --- a/docs/sphinx/actor-schema.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _actor-schema: - -Actor schema -============ - -In additional to these keywords, the actor also supports the default keywords automatically added by `CLU `__. Actors that subclass from `.lvmnps` may implement additional keywords. - -.. jsonschema:: ../../python/lvmnps/etc/schema.json \ No newline at end of file diff --git a/docs/sphinx/actor.rst b/docs/sphinx/actor.rst deleted file mode 100644 index be656a0..0000000 --- a/docs/sphinx/actor.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _lvmnps: - -Actor ------ - -.. automodule:: lvmnps.actor.actor - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sphinx/api.rst b/docs/sphinx/api.rst new file mode 100644 index 0000000..83f8504 --- /dev/null +++ b/docs/sphinx/api.rst @@ -0,0 +1,45 @@ +.. _lvmnps-api: + +API +=== + +Base client +----------- + +.. autoclass:: lvmnps.nps.core.NPSClient + :members: + :show-inheritance: + +.. autopydantic_model:: lvmnps.nps.core.OutletModel + :model-show-json: false + :exclude-members: model_post_init + + +Implementations +--------------- + +Digital Loggers Inc +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lvmnps.nps.implementations.dli.DLIClient + +.. autopydantic_model:: lvmnps.nps.implementations.dli.DLIOutletModel + :model-show-json: false + :exclude-members: model_post_init + +NetIO +^^^^^ + +.. autoclass:: lvmnps.nps.implementations.netio.NetIOClient + + +Actor +----- + +.. autoclass:: lvmnps.actor.NPSActor + + +Tools +----- + +.. automodule:: lvmnps.tools diff --git a/docs/sphinx/commands.rst b/docs/sphinx/commands.rst index bc8475e..64f6bd0 100644 --- a/docs/sphinx/commands.rst +++ b/docs/sphinx/commands.rst @@ -5,6 +5,6 @@ Commands The ``lvmnps`` actor replies to the follwing commands. -.. click:: lvmnps.actor.commands:parser +.. click:: lvmnps.actor.commands:lvmnps_command_parser :prog: lvmnps :show-nested: diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py index f85ee60..157bfbb 100644 --- a/docs/sphinx/conf.py +++ b/docs/sphinx/conf.py @@ -13,13 +13,7 @@ from pkg_resources import parse_version - -try: - from lvmnps import __version__ -except ModuleNotFoundError: - from sdsstools import get_package_version - - __version__ = get_package_version(__file__, "sdss-lvmnps") or "dev" +from lvmnps import __version__ # Are we building in RTD? @@ -51,6 +45,7 @@ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.autosummary", + "sphinx_autodoc_typehints", "sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx.ext.mathjax", @@ -58,13 +53,17 @@ "sphinx.ext.inheritance_diagram", "myst_parser", "sphinx_copybutton", - "sphinx_click", + "sphinx_click.ext", "sphinx-jsonschema", + "sphinxcontrib.autodoc_pydantic", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] +pygments_style = "lovelace" +pygments_dark_style = "one-dark" + # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # @@ -80,8 +79,8 @@ # General information about the project. project = "lvmnps" -copyright = "{0}, {1}".format("2021", "SDSS LVMI softwareteam in Kyung Hee university") -author = "Mingyeong Yang" +copyright = "{0}, {1}".format("2021-", "Sloan Digital Sky Survey") +author = "Mingyeong Yang and others" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -97,7 +96,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -133,27 +132,24 @@ # Intersphinx mappings intersphinx_mapping = { - "python": ("https://docs.python.org/3.6", None), - "astropy": ("http://docs.astropy.org/en/latest", None), - "numpy": ("http://docs.scipy.org/doc/numpy/", None), - "click": ("https://click.palletsprojects.com/en/7.x/", None), - "aio_pika": ("https://aio-pika.readthedocs.io/en/latest/", None), + "python": ("https://docs.python.org/3.12", None), } autodoc_mock_imports = ["_tkinter", "asynctest", "numpy", "pymodbus"] autodoc_member_order = "groupwise" autodoc_default_options = {"members": None, "show-inheritance": None} -autodoc_typehints = "description" +# autodoc_typehints = "description" -napoleon_use_rtype = False -napoleon_use_ivar = True +# napoleon_use_rtype = False +# napoleon_use_ivar = True + +simplify_optional_unions = True +typehints_use_signature_return = True copybutton_prompt_text = r">>> |\$ " copybutton_prompt_is_regexp = True rst_epilog = f""" -.. |numpy_array| replace:: Numpy array -.. |HDUList| replace:: :class:`~astropy.io.fits.HDUList` .. |npsactor_version| replace:: {__version__} """ @@ -166,8 +162,13 @@ html_theme = "furo" html_logo = "_static/sdssv_logo.png" -html_title = "lvmnps" +html_title = "lvmnps documentation" html_favicon = "./_static/favicon.ico" +html_theme_options = { + "source_repository": "https://github.com/sdss/lvmnps/", + "source_branch": "main", + "source_directory": "docs/sphinx", +} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/sphinx/developer-env.rst b/docs/sphinx/developer-env.rst deleted file mode 100644 index 3beda39..0000000 --- a/docs/sphinx/developer-env.rst +++ /dev/null @@ -1,103 +0,0 @@ -Developer environment -===================== - -LVM Network Power Switch - -Features --------- - -- CLU Actor based interface -- Supports a Dummy PDU -- Supports `iBOOT g2 `__ with python - code from `here `__ -- Supports `Digital Loggers Web - Power `__ with python code - from `here `__ - -Installation ------------- - -Clone this repository. - -:: - - $ git clone https://github.com/sdss/lvmnps - $ cd lvmnps - -Quick Start ------------ - -Prerequisite -~~~~~~~~~~~~ - -If your system already have rabbitmq, and already running with the actors, you don't have to install these below. - -Install `RabbitMQ `__ by using apt-get. - -RabbitMQ is not the dependency of the 'lvmnps' but it is the system-wide configuration for running the software under CLU CLI(command line interface). - -:: - - $ sudo apt-get install -y erlang - $ sudo apt-get install -y rabbitmq-server - $ sudo systemctl enable rabbitmq-server - $ sudo systemctl start rabbitmq-server - - -If your system already have pyenv, you don't have to install these below. -Install `pyenv `__ by using `pyenv -installer `__. -Also, pyenv is the virtual environment for running the python package under your specific python environment. -This is very useful when you want to isolate your python package with others. - -:: - - $ curl https://pyenv.run | bash - -You should add the code below to ``~/.bashrc`` by using your preferred -editor. - -:: - - # pyenv - export PYENV_ROOT="$HOME/.pyenv" - export PATH="$PYENV_ROOT/bin:$PATH" - eval "$(pyenv init -)" - eval "$(pyenv init --path)" - eval "$(pyenv virtualenv-init -)" - -``pyenv`` builds Python from source. So you should install build -dependencies. For more information, check `Common build -problems `__. - -:: - - $ sudo apt-get install -y make build-essential libssl-dev zlib1g-dev \ - libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev \ - libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python-openssl - -Set the python 3.9.1 virtual environment. - -:: - - $ pyenv install 3.9.1 - $ pyenv virtualenv 3.9.1 lvmnps-with-3.9.1 - $ pyenv local lvmnps-with-3.9.1 - -Install `poetry `__ and dependencies. For -more information, check -`sdss/archon `__. - -:: - - $ pip install poetry - $ python create_setup.py - $ pip install -e . - -Test ----- - -:: - - poetry run pytest - poetry run pytest -p no:logging -s -vv diff --git a/docs/sphinx/examples.rst b/docs/sphinx/examples.rst deleted file mode 100644 index 05474e3..0000000 --- a/docs/sphinx/examples.rst +++ /dev/null @@ -1,209 +0,0 @@ -.. _examples: - -Examples -======== - -Starting the Actor ------------------- - -lvmnps actor provides the control system to manage the NPS. -First you have to start the actor by the terminal command line in the python virtual environment that you installed the lvmnps package. :: - - $ lvmnps start - - -If you want to start with debugging mode, you can start like this. -In this case, you can finish the software by ctrl + c on the terminal :: - - $ lvmnps start --debug - - -Also you can check the status of the actor is running by this command :: - - $ lvmnps status - - -After your work is done, you can finish the actor by this command :: - - $ lvmnps stop - - -Finally, you can restart(stop -> start) the actor when the actor is running by this command :: - - $ lvmnps restart - - -Interface with the actor ------------------------- - -If you started the actor by the *lvmnps start* command, you can interface with the actor by the clu CLI(Command Line Interface) :: - - $ clu - - -If you want to ignore the status message from other actors, you can use this command :: - - $ clu -b - - -Then you will enter to the clu CLI. -You can check if the actor is running by the ping-pong commands. :: - - lvmnps ping - 04:57:53.183 lvmnps > - 04:57:53.198 lvmnps : { - "text": "Pong." - } - - - -``help`` command ----------------- - -First you can confirm the existing commands of *lvmnps* by the *help* command :: - - lvmnps help - 09:26:59.497 lvmnps > - 09:26:59.512 lvmnps : { - "help": [ - "Usage: lvmnps [OPTIONS] COMMAND [ARGS]...", - "", - "Options:", - " --help Show this message and exit.", - "", - "Commands:", - " cycle cycle power to an Outlet", - " device return the list of devices connected with switch", - " help Shows the help.", - " off Turn off the Outlet", - " on Turn on the Outlet", - " ping Pings the actor.", - " status print the status of the NPS.", - " switches return the list of switches", - " version Reports the version." - ] - } - - -``reachable`` command ---------------------- - -If you run the switches command via lvmnps, you can get the list of switches :: - - lvmnps reachable switches - -will return this kind of reply.:: - - lvmnps reachable switches - 09:27:25.948 lvmnps > - 09:27:25.960 lvmnps i { - "text": "the list of switches" - } - 09:27:25.973 lvmnps i { - "list": [ - "DLI-NPS-01", - "DLI-NPS-02", - "DLI-NPS-03" - ] - } - 09:27:25.985 lvmnps : { - "text": "done" - } - - -If you run the outlet command via lvmnps reachable command, you can get the list of devices connected with the switch.:: - - lvmnps reachable outlets DLI-NPS-01 - -will return this kind of reply.:: - - lvmnps reachable outlets DLI-NPS-01 - 08:19:36.478 lvmnps > - 08:19:36.491 lvmnps i { - "text": "Individual Control of DLI-NPS-01..." - } - 08:19:37.191 lvmnps i { - "IndividualControl": [ - "DLI-NPS-01.port1", - "-", - "DLI-NPS-01.port3", - "DLI-NPS-01.port4", - "DLI-NPS-01.port5", - "DLI-NPS-01.port6", - "DLI-NPS-01.port7", - "625 nm LED (M625L4)" - ] - } - 08:19:37.204 lvmnps : { - "text": "done" - } - - -``on`` command --------------- - -If you run the on command via lvmnps, you can turn on the power of the device which you want to control.:: - - lvmnps on eight - -will return this kind of reply.:: - - lvmnps on eight - 05:38:07.617 lvmnps > - 05:38:07.633 lvmnps i { - "text": "Turning on port eight..." - } - 05:38:08.706 lvmnps i { - "STATUS": { - "DLI Controller": { - "eight": { - "STATE": 1, - "DESCR": "DLI Controller Port 8", - "SWITCH": "DLI Controller", - "PORT": 8 - } - } - } - } - 05:38:08.719 lvmnps : { - "text": "done" - } - - -``off`` command ---------------- - -If you run the off command via lvmnps, you can turn off the power of the device which you want to control.:: - - lvmnps off eight - -will return this kind of reply.:: - - lvmnps off eight - 05:42:01.403 lvmnps > - 05:42:01.423 lvmnps i { - "text": "Turning off port eight..." - } - 05:42:02.418 lvmnps i { - "STATUS": { - "DLI Controller": { - "eight": { - "STATE": 0, - "DESCR": "DLI Controller Port 8", - "SWITCH": "DLI Controller", - "PORT": 8 - } - } - } - } - 05:42:02.426 lvmnps : { - "text": "done" - } - - -``status`` command ------------------- - -If you run the status command via lvmnps, you can receive the telemetry data of power status of devices :: - - lvmnps status *command* diff --git a/docs/sphinx/exceptions.rst b/docs/sphinx/exceptions.rst deleted file mode 100644 index 3669b83..0000000 --- a/docs/sphinx/exceptions.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. _exceptions: - -Exceptions -========== - -Exceptions ----------- - -.. automodule:: lvmnps.exceptions - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sphinx/index.rst b/docs/sphinx/index.rst index b5cbf9d..26e2e8c 100644 --- a/docs/sphinx/index.rst +++ b/docs/sphinx/index.rst @@ -2,34 +2,34 @@ lvmnps's documentation ====================== +.. warning:: + Version 1.0 introduces breaking changes with respect to previous versions, both in the API and the actor commands. The main difference is that the actor in 1.0 only supports controlling one switch per instance. See the :ref:`actor commands page ` for more deails. + + Contents -------- .. toctree:: :caption: Contents + :maxdepth: 1 introduction - examples + commands .. toctree:: - :caption: API :maxdepth: 3 - actor - switch - commands - actor-schema - exceptions + api .. toctree:: :caption: Development - :maxdepth: 3 + :maxdepth: 1 - developer-env Changelog GitHub Repository Issues + Indices and tables ------------------ diff --git a/docs/sphinx/introduction.rst b/docs/sphinx/introduction.rst index b682044..28b7fc0 100644 --- a/docs/sphinx/introduction.rst +++ b/docs/sphinx/introduction.rst @@ -3,209 +3,66 @@ Introduction ============ -An NPS (Network Power Switch) is a device that can control multiple power switches through a network. By connecting the power of the LVM-I Spectroograph Box and subsystem through this device, the power of each device can be controlled through the network. +``lvmnps`` allows for basic, homogeneous control of a variety of network power supplies (NPS). Currently the supported NPS are -LVM-I selected two models for NPS. -In the LVM-I SCP, we decide to use “web power switch pro” by digital loggers(dli) for NPS. More details can be found through `dli `_ -In the LVM-I TCP, we're using iboot web power switch in dataprobe Co.(iboot). More details can be found through `iboot `_ -We updated *switch* moduel for the nps control library applicable to both models. The powerswitch class is created for the basic modules of dli and iboot and used in the actor command. +- `Digital Loggers Inc devices `__, in particular the `Pro Switch `__ +- `NetIO devices `__. -DLI ---- +The code allows to retrieve the status of the various outlets, set outlet status (on, off, cycle), and execute user functions (only for DLI devices). -The Digital Loggers provides a library made to control the power switch for the above models. Based on this module, the KHU team redefines and asynchronously changes the functions required for the command in SCP. This module plays a role in web crawling from the index web page that provides information about the power switch and controlling the power. For asynchronous web crawling, the asynchronous class provided by httpx was used instead of the default requests library. We also overridden the rest of the functions asynchronously. A new module for controlling power switch is defined as *lvmpower.py*. -.. image:: _static/dlinps.png - :align: center +Installation +------------ -iboot ------ +``lvmscp`` can be installed using ``pip`` as -.. image:: _static/ibootnps.png - :align: center +.. code:: shell + pip install sdss-lvmnps -Start the actor ---------------- +To install ``lvmnps`` for development, first clone the `repository `__ -Start ``lvmnps`` actor. +.. code:: shell -:: + git clone https://github.com/sdss/lvmnps - $ lvmnps start +and then install using `poetry `__ -In another terminal, type ``clu`` and ``lvmnps ping`` for test. - -:: - - $ clu - lvmnps ping - 07:41:22.636 lvmnps > - 07:41:22.645 lvmnps : { - "text": "Pong." - } - -Stop ``lvmnps`` actor. - -:: - - $ lvmnps stop - -Config file structure ---------------------- - -:: - - switches: - name_your_switch_here: # should be a unique name - type: dummy # currently dummy, iboot, dli - num: 8 # number of ports - ports: - 1: - name: "skyw.pwi" # should also be a unique name - desc: "Something that make sense" - should_be_a_unique_name: - type: dummy - ports: - 1: - name: "skye.pwi" - desc: "PlaneWavemount Skye" - -Status return for all commands ------------------------------- - -- if 'name' is not defined then the port name will be 'switch - name'.'port number' eg nps\_dummy\_1.port1 otherwise 'name' from the - config file will be used. -- STATE: 1: ON, 0: OFF, -1: UNKNOWN - - :: - - "STATUS": { - "nps_dummy_1.port1": { - "STATE": -1, - "DESCR": "was 1", - "SWITCH": "nps_dummy_1", - "PORT": 1 - }, - -Run the example lvmnps\_dummy ------------------------------ - -:: - - #> cd lvmnps - #> poetry run lvmnps -vvv -c $(pwd)/python/lvmnps/etc/lvmnps_dummy.yml start - - #> poetry run clu - -- status command without parameter returns all ports of all switches. -- the default is to return only configured ports, otherwise define - 'ouo' false in the config file, see - `lvmnps\_dummy.yml `__ - - :: - - lvmnps statu - - 12:02:08.649 lvmnps > - 12:02:08.660 lvmnps i { - "STATUS": { - "nps\_dummy\_1.port1": { - "STATE": -1, - "DESCR": "was 1", - "SWITCH": "nps\_dummy\_1", - "PORT": 1 - }, - "skye.what.ever": { - "STATE": -1, - "DESCR": "whatever is connected to skye", - "SWITCH": "nps\_dummy\_1", - "PORT": 2 - }, - "skyw.what.ever": { - "STATE": -1, - "DESCR": "Something @ skyw", - "SWITCH": "nps\_dummy\_1", - "PORT": 4 - }, - "skye.pwi": { - "STATE":-1, - "DESCR": "PlaneWavemount Skye", - "SWITCH": "skye.nps", - "PORT": 1 - }, - "skyw.pwi": { - "STATE": -1, - "DESCR": "PlaneWavemount Skyw", - "SWITCH": "nps\_dummy\_3", - "PORT": 1 - } - } - } - -- status command with port name skyw.what.ever - - :: - - lvmnps status what skyw.what.ever - - 12:07:12.349 lvmnps > - 12:07:12.377 lvmnps i { - "STATUS": { - "skyw.what.ever": { - "STATE": -1, - "DESCR": "Something @ skyw", - "SWITCH": "nps\_dummy\_1", - "PORT": 4 - } - -- status command with switch name nps\_dummy\_1 - - :: - - lvmnps status what nps\_dummy\_1 - - 12:07:12.349 lvmnps > - 12:12:21.349 lvmnps i { - "STATUS": { - "nps\_dummy\_1.port1": { - "STATE": -1, - "DESCR": "was 1", - "SWITCH": "nps\_dummy\_1", - "PORT": 1 - }, - "skye.what.ever": { - "STATE": -1, - "DESCR": "whatever is connected to skye", - "SWITCH": "nps\_dummy\_1", - "PORT": 2 - }, - "skyw.what.ever": { - "STATE": -1, - "DESCR": "Something @ skyw", - "SWITCH": "nps\_dummy\_1", - "PORT": 4 - } - } - } - -- status command with switch name nps\_dummy\_1 and port 4 returns - - :: - - lvmnps status what nps\_dummy\_1 4 - - 12:07:12.349 lvmnps > - 12:12:21.349 lvmnps i { - "STATUS": { - "skyw.what.ever": { - "STATE": -1, - "DESCR": "Something @ skyw", - "SWITCH": "nps\_dummy\_1", - "PORT": 4 - } - } - } - -- the commands on and off use the same addressing scheme as status +.. code:: shell + + poetry install + +Configuration files +------------------- + +To run as an actor, a YAML configuration file is required. An example of a valid configuration file is + +.. code:: yaml + + nps: + type: dli + init_parameters: + host: 127.0.0.1 + port: 8088 + user: admin + password: admin + + actor: + name: lvmnps.test + host: localhost + port: 5672 + +The ``actor`` section is common to other CLU actors, and we refer the reader to the `CLU documentation `__. An ``nps`` section is required, defining the type of the power supply (valid types are ``dli`` and ``netio``) and the parameters necessary to initialise the relevant client class. + +Running the actor +----------------- + +The actor can be run by executing + +.. code:: shell + + lvmnps -c CONFIG-FILE start [--debug] + +where ``--debug`` allows to run the actor without detaching the running instance. To stop the actor use ``lvmnps stop``. + +To test the communication with the actor you can install the CLU command line interface, then execute ``clu`` and issue ``lvmnps status``. diff --git a/docs/sphinx/make.bat b/docs/sphinx/make.bat deleted file mode 100644 index 2c0764f..0000000 --- a/docs/sphinx/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt index 7919f0f..b0812c2 100644 --- a/docs/sphinx/requirements.txt +++ b/docs/sphinx/requirements.txt @@ -1,8 +1,7 @@ -sphinx>=4.5.0 +sphinx>=7.0 sphinx-click>=2.6.0 sphinx-jsonschema>=1.16.7 -myst-parser>=0.14.0 +myst-parser>=0.15.0 furo>=2021.6.18-beta.36 -sphinx-autobuild>=2021.3.14 sphinx-copybutton>=0.3.3 -jinja2==3.0.0 +sphinx-autodoc-typehints>=1.23.2 diff --git a/docs/sphinx/switch.rst b/docs/sphinx/switch.rst deleted file mode 100644 index 24fa7e0..0000000 --- a/docs/sphinx/switch.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. _switch: - -Switch -====== - -.. automodule:: lvmnps.switch.powerswitchbase - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: lvmnps.switch.outlet - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: lvmnps.switch.dli.dli - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: lvmnps.switch.dli.powerswitch - :members: - :undoc-members: - :show-inheritance: diff --git a/noxfile.py b/noxfile.py deleted file mode 100644 index a135495..0000000 --- a/noxfile.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: José Sánchez-Gallego (gallegoj@uw.edu) -# @Date: 2021-06-20 -# @Filename: noxfile.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -import contextlib -import os -import tempfile - -import nox - - -@contextlib.contextmanager -def cd(path): - CWD = os.getcwd() - - os.chdir(path) - - try: - yield - finally: - os.chdir(CWD) - - -@nox.session(name="docs-live", reuse_venv=True) -def docs_live(session): - if session.posargs: - docs_dir = session.posargs[0] - else: - docs_dir = "." - - with cd(os.path.join(os.path.dirname(__file__), "docs/sphinx")): - with tempfile.TemporaryDirectory() as destination: - session.run( - "sphinx-autobuild", - # for sphinx-autobuild - "--port=0", - "--open-browser", - # for sphinx - "-b=dirhtml", - "-a", - docs_dir, - destination, - external=True, - ) diff --git a/poetry.lock b/poetry.lock index dc4d9d3..e39bec8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,29 +1,29 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aio-pika" -version = "9.1.4" +version = "9.3.1" description = "Wrapper around the aiormq for asyncio and humans" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "aio_pika-9.1.4-py3-none-any.whl", hash = "sha256:d51738779c8a16f9f64801b71115c7270f761b7869cc19a429165ad715db9e33"}, - {file = "aio_pika-9.1.4.tar.gz", hash = "sha256:fdfd966217e191a601c78b0869dd2d646ef64c11f7a16c88494a17c8b22b001a"}, + {file = "aio_pika-9.3.1-py3-none-any.whl", hash = "sha256:62ea4859572daccfc1d0963785185fa984f41f9fde576f9cc123ec899c257260"}, + {file = "aio_pika-9.3.1.tar.gz", hash = "sha256:f199e5179ff9a362c1494ddd4d26f40834896726fea9b9d91de971b2ad304fc8"}, ] [package.dependencies] -aiormq = ">=6.7.5,<6.8.0" +aiormq = ">=6.7.7,<6.8.0" yarl = "*" [[package]] name = "aiormq" -version = "6.7.6" +version = "6.7.7" description = "Pure python AMQP asynchronous client library" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "aiormq-6.7.6-py3-none-any.whl", hash = "sha256:da53ec26aeecdccda8dab7947f391c9b53943f7dd45c0fc4b515971e7fc320cf"}, - {file = "aiormq-6.7.6.tar.gz", hash = "sha256:ccf495e3da2849be37ba802389839ac7a6f18cf58f2b7d5f2cc4eb21e2dbc564"}, + {file = "aiormq-6.7.7-py3-none-any.whl", hash = "sha256:f5efbfcd7d703f3c05c08d4e74cfaa66ca7199840e2969d75ad41b0810026b0a"}, + {file = "aiormq-6.7.7.tar.gz", hash = "sha256:3b93f612f56989b2757a9a7b299dd94dd3227ce28ba43e81d5fbcded6341dfab"}, ] [package.dependencies] @@ -41,26 +41,37 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + [[package]] name = "anyio" -version = "3.7.1" +version = "4.1.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "anyio-4.1.0-py3-none-any.whl", hash = "sha256:56a415fbc462291813a94528a779597226619c8e78af7de0507333f700011e5f"}, + {file = "anyio-4.1.0.tar.gz", hash = "sha256:5a0bec7085176715be77df87fc66d6c9d70626bd752fcc85f57cdbee5b3760da"}, ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (<0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "appnope" @@ -75,13 +86,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.1.1" +version = "3.1.6" description = "Bash tab completion for argparse" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "argcomplete-3.1.1-py3-none-any.whl", hash = "sha256:35fa893a88deea85ea7b20d241100e64516d6af6d7b0ae2bed1d263d26f70948"}, - {file = "argcomplete-3.1.1.tar.gz", hash = "sha256:6c4c563f14f01440aaffa3eae13441c5db2357b5eec639abe7c0b15334627dff"}, + {file = "argcomplete-3.1.6-py3-none-any.whl", hash = "sha256:71f4683bc9e6b0be85f2b2c1224c47680f210903e23512cfebfe5a41edfd883a"}, + {file = "argcomplete-3.1.6.tar.gz", hash = "sha256:3b1f07d133332547a53c79437527c00be48cca3807b1d4ca5cab1b26313386a6"}, ] [package.extras] @@ -89,20 +100,21 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "asttokens" -version = "2.2.1" +version = "2.4.1" description = "Annotate AST trees with source code positions" optional = false python-versions = "*" files = [ - {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, - {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, ] [package.dependencies] -six = "*" +six = ">=1.12.0" [package.extras] -test = ["astroid", "pytest"] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "attrs" @@ -123,30 +135,44 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] -name = "babel" -version = "2.12.1" -description = "Internationalization utilities" +name = "autodoc-pydantic" +version = "2.0.1" +description = "Seamlessly integrate pydantic models in your Sphinx documentation." optional = false -python-versions = ">=3.7" +python-versions = ">=3.7.1,<4.0.0" files = [ - {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, - {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, + {file = "autodoc_pydantic-2.0.1-py3-none-any.whl", hash = "sha256:d3c302fdb6d37edb5b721f0f540252fa79cea7018bc1a9a85bf70f33a68b0ce4"}, + {file = "autodoc_pydantic-2.0.1.tar.gz", hash = "sha256:7a125a4ff18e4903e27be71e4ddb3269380860eacab4a584d6cc2e212fa96991"}, ] [package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} +pydantic = ">=2.0,<3.0.0" +pydantic-settings = ">=2.0,<3.0.0" +Sphinx = ">=4.0" + +[package.extras] +dev = ["coverage (>=7,<8)", "flake8 (>=3,<4)", "pytest (>=7,<8)", "sphinx-copybutton (>=0.4,<0.5)", "sphinx-rtd-theme (>=1.0,<2.0)", "sphinx-tabs (>=3,<4)", "sphinxcontrib-mermaid (>=0.7,<0.8)", "tox (>=3,<4)"] +docs = ["sphinx-copybutton (>=0.4,<0.5)", "sphinx-rtd-theme (>=1.0,<2.0)", "sphinx-tabs (>=3,<4)", "sphinxcontrib-mermaid (>=0.7,<0.8)"] +erdantic = ["erdantic (>=0.6,<0.7)"] +test = ["coverage (>=7,<8)", "pytest (>=7,<8)"] [[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" +name = "babel" +version = "2.13.1" +description = "Internationalization utilities" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, + {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, + {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, ] +[package.dependencies] +setuptools = {version = "*", markers = "python_version >= \"3.12\""} + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + [[package]] name = "beautifulsoup4" version = "4.12.2" @@ -167,36 +193,29 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.3.0" +version = "23.11.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, + {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, + {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, + {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, + {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, + {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, + {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, + {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, + {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, + {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, + {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, + {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, + {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, + {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, + {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, + {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, + {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, + {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] [package.dependencies] @@ -206,7 +225,7 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -216,108 +235,123 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.2" 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.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "click" -version = "8.1.4" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.4-py3-none-any.whl", hash = "sha256:2739815aaa5d2c986a88f1e9230c55e17f0caad3d958a5e13ad0797c166db9e3"}, - {file = "click-8.1.4.tar.gz", hash = "sha256:b97d0c74955da062a7d4ef92fadb583806a585b2ea81958a81bd72726cbb8e37"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -325,17 +359,21 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "click-default-group" -version = "1.2.2" -description = "Extends click.Group to invoke a command without explicit subcommand name" +version = "1.2.4" +description = "click_default_group" optional = false -python-versions = "*" +python-versions = ">=2.7" files = [ - {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, + {file = "click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f"}, + {file = "click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e"}, ] [package.dependencies] click = "*" +[package.extras] +test = ["pytest"] + [[package]] name = "colorama" version = "0.4.6" @@ -366,71 +404,63 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"] [[package]] name = "coverage" -version = "7.2.7" +version = "7.3.2" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, + {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, + {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, + {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, + {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, + {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, + {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, + {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, + {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, + {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, + {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, + {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, + {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.dependencies] @@ -469,33 +499,15 @@ files = [ [[package]] name = "distlib" -version = "0.3.6" +version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] -[[package]] -name = "doc8" -version = "1.1.1" -description = "Style checker for Sphinx (or other) RST documentation" -optional = false -python-versions = ">=3.8" -files = [ - {file = "doc8-1.1.1-py3-none-any.whl", hash = "sha256:e493aa3f36820197c49f407583521bb76a0fde4fffbcd0e092be946ff95931ac"}, - {file = "doc8-1.1.1.tar.gz", hash = "sha256:d97a93e8f5a2efc4713a0804657dedad83745cca4cd1d88de9186f77f9776004"}, -] - -[package.dependencies] -docutils = ">=0.19,<0.21" -Pygments = "*" -restructuredtext-lint = ">=0.7" -stevedore = "*" -tomli = {version = "*", markers = "python_version < \"3.11\""} - [[package]] name = "docutils" version = "0.20.1" @@ -509,13 +521,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.2" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -523,58 +535,43 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "1.2.0" +version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, - {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, ] [package.extras] -tests = ["asttokens", "littleutils", "pytest", "rich"] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] [[package]] name = "filelock" -version = "3.12.2" +version = "3.13.1" description = "A platform independent file lock." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "flake8" -version = "5.0.4" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "furo" -version = "2023.5.20" +version = "2023.9.10" description = "A clean customisable Sphinx documentation theme." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "furo-2023.5.20-py3-none-any.whl", hash = "sha256:594a8436ddfe0c071f3a9e9a209c314a219d8341f3f1af33fdf7c69544fab9e6"}, - {file = "furo-2023.5.20.tar.gz", hash = "sha256:40e09fa17c6f4b22419d122e933089226dcdb59747b5b6c79363089827dea16f"}, + {file = "furo-2023.9.10-py3-none-any.whl", hash = "sha256:513092538537dc5c596691da06e3c370714ec99bc438680edc1debffb73e5bfc"}, + {file = "furo-2023.9.10.tar.gz", hash = "sha256:5707530a476d2a63b8cad83b4f961f3739a69f4b058bcf38a03a39fa537195b2"}, ] [package.dependencies] @@ -585,54 +582,59 @@ sphinx-basic-ng = "*" [[package]] name = "h11" -version = "0.12.0" +version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, - {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] [[package]] name = "httpcore" -version = "0.13.7" +version = "1.0.2" description = "A minimal low-level HTTP client." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "httpcore-0.13.7-py3-none-any.whl", hash = "sha256:369aa481b014cf046f7067fddd67d00560f2f00426e79569d99cb11245134af0"}, - {file = "httpcore-0.13.7.tar.gz", hash = "sha256:036f960468759e633574d7c121afba48af6419615d36ab8ede979f1ad6276fa3"}, + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, ] [package.dependencies] -anyio = "==3.*" -h11 = ">=0.11,<0.13" -sniffio = "==1.*" +certifi = "*" +h11 = ">=0.13,<0.15" [package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" -version = "0.18.2" +version = "0.25.1" description = "The next generation HTTP client." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "httpx-0.18.2-py3-none-any.whl", hash = "sha256:979afafecb7d22a1d10340bafb403cf2cb75aff214426ff206521fc79d26408c"}, - {file = "httpx-0.18.2.tar.gz", hash = "sha256:9f99c15d33642d38bce8405df088c1c4cfd940284b4290cacbfb02e64f4877c6"}, + {file = "httpx-0.25.1-py3-none-any.whl", hash = "sha256:fec7d6cc5c27c578a391f7e87b9aa7d3d8fbcd034f6399f9f79b45bcc12a866a"}, + {file = "httpx-0.25.1.tar.gz", hash = "sha256:ffd96d5cf901e63863d9f1b4b6807861dbea4d301613415d9e6e57ead15fc5d0"}, ] [package.dependencies] +anyio = "*" certifi = "*" -httpcore = ">=0.13.3,<0.14.0" -rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +httpcore = "*" +idna = "*" sniffio = "*" [package.extras] -brotli = ["brotlicffi (==1.*)"] -http2 = ["h2 (==3.*)"] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] [[package]] name = "idna" @@ -675,24 +677,6 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] -[[package]] -name = "importlib-resources" -version = "6.0.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.0.0-py3-none-any.whl", hash = "sha256:d952faee11004c045f785bb5636e8f885bed30dc3c940d5d42798a2a4541c185"}, - {file = "importlib_resources-6.0.0.tar.gz", hash = "sha256:4cf94875a8368bd89531a756df9a9ebe1f150e0f885030b461237bc7f2d905f2"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -706,13 +690,13 @@ files = [ [[package]] name = "invoke" -version = "1.7.3" +version = "2.2.0" description = "Pythonic task execution" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "invoke-1.7.3-py3-none-any.whl", hash = "sha256:d9694a865764dd3fd91f25f7e9a97fb41666e822bbb00e670091e3f43933574d"}, - {file = "invoke-1.7.3.tar.gz", hash = "sha256:41b428342d466a82135d5ab37119685a989713742be46e42a3a399d685579314"}, + {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, + {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, ] [[package]] @@ -733,24 +717,23 @@ tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < [[package]] name = "ipython" -version = "8.12.2" +version = "8.17.2" description = "IPython: Productive Interactive Computing" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "ipython-8.12.2-py3-none-any.whl", hash = "sha256:ea8801f15dfe4ffb76dea1b09b847430ffd70d827b41735c64a0638a04103bfc"}, - {file = "ipython-8.12.2.tar.gz", hash = "sha256:c7b80eb7f5a855a88efc971fda506ff7a91c280b42cdae26643e0f601ea281ea"}, + {file = "ipython-8.17.2-py3-none-any.whl", hash = "sha256:1e4d1d666a023e3c93585ba0d8e962867f7a111af322efff6b9c58062b3e5444"}, + {file = "ipython-8.17.2.tar.gz", hash = "sha256:126bb57e1895594bb0d91ea3090bbd39384f6fe87c3d57fd558d0670f50339bb"}, ] [package.dependencies] appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" pygments = ">=2.4.0" stack-data = "*" @@ -758,53 +741,36 @@ traitlets = ">=5" typing-extensions = {version = "*", markers = "python_version < \"3.10\""} [package.extras] -all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] -doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] - -[[package]] -name = "isort" -version = "5.12.0" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, -] - -[package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] [[package]] name = "jedi" -version = "0.18.2" +version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, - {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, ] [package.dependencies] -parso = ">=0.8.0,<0.9.0" +parso = ">=0.8.3,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jinja2" @@ -835,20 +801,18 @@ files = [ [[package]] name = "jsonschema" -version = "4.18.0" +version = "4.20.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.18.0-py3-none-any.whl", hash = "sha256:b508dd6142bd03f4c3670534c80af68cd7bbff9ea830b9cf2625d4a3c49ddf60"}, - {file = "jsonschema-4.18.0.tar.gz", hash = "sha256:8caf5b57a990a98e9b39832ef3cb35c176fe331414252b6e1b26fd5866f891a4"}, + {file = "jsonschema-4.20.0-py3-none-any.whl", hash = "sha256:ed6231f0429ecf966f5bc8dfef245998220549cbbcf140f913b7464c52c3b6b3"}, + {file = "jsonschema-4.20.0.tar.gz", hash = "sha256:4f614fd46d8d61258610998997743ec5492a648b33cf478c1ddc23ed4598a5fa"}, ] [package.dependencies] attrs = ">=22.2.0" -importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} jsonschema-specifications = ">=2023.03.6" -pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -858,18 +822,17 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.6.1" +version = "2023.11.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema_specifications-2023.6.1-py3-none-any.whl", hash = "sha256:3d2b82663aff01815f744bb5c7887e2121a63399b49b104a3c96145474d091d7"}, - {file = "jsonschema_specifications-2023.6.1.tar.gz", hash = "sha256:ca1c4dd059a9e7b34101cf5b3ab7ff1d18b139f35950d598d629837ef66e8f28"}, + {file = "jsonschema_specifications-2023.11.1-py3-none-any.whl", hash = "sha256:f596778ab612b3fd29f72ea0d990393d0540a5aab18bf0407a46632eab540779"}, + {file = "jsonschema_specifications-2023.11.1.tar.gz", hash = "sha256:c9b234904ffe02f079bf91b14d79987faa685fd4b39c377a0996954c0090b9ca"}, ] [package.dependencies] -importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} -referencing = ">=0.28.0" +referencing = ">=0.31.0" [[package]] name = "livereload" @@ -888,13 +851,13 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "makefun" -version = "1.15.1" +version = "1.15.2" description = "Small library to dynamically create python functions." optional = false python-versions = "*" files = [ - {file = "makefun-1.15.1-py2.py3-none-any.whl", hash = "sha256:a63cfc7b47a539c76d97bd4fdb833c7d0461e759fd1225f580cb4be6200294d4"}, - {file = "makefun-1.15.1.tar.gz", hash = "sha256:40b0f118b6ded0d8d78c78f1eb679b8b6b2462e3c1b3e05fb1b2da8cd46b48a5"}, + {file = "makefun-1.15.2-py2.py3-none-any.whl", hash = "sha256:1c83abfaefb6c3c7c83ed4a993b4a310af80adf6db15625b184b1f0f7545a041"}, + {file = "makefun-1.15.2.tar.gz", hash = "sha256:16f2a2b34d9ee0c2b578c960a1808c974e2822cf79f6e9b9c455aace10882d45"}, ] [[package]] @@ -994,17 +957,6 @@ files = [ [package.dependencies] traitlets = "*" -[[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 = "mdit-py-plugins" version = "0.4.0" @@ -1177,13 +1129,13 @@ tox-to-nox = ["jinja2", "tox (<4)"] [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -1218,24 +1170,13 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, -] - -[[package]] -name = "pbr" -version = "5.11.1" -description = "Python Build Reasonableness" -optional = false -python-versions = ">=2.6" -files = [ - {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, - {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -1252,52 +1193,30 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -optional = false -python-versions = "*" -files = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] - -[[package]] -name = "pkgutil-resolve-name" -version = "1.3.10" -description = "Resolve a name to an object." -optional = false -python-versions = ">=3.6" -files = [ - {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, - {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, -] - [[package]] name = "platformdirs" -version = "3.8.1" +version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.8.1-py3-none-any.whl", hash = "sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c"}, - {file = "platformdirs-3.8.1.tar.gz", hash = "sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"}, + {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, + {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -1306,13 +1225,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prompt-toolkit" -version = "3.0.39" +version = "3.0.41" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, + {file = "prompt_toolkit-3.0.41-py3-none-any.whl", hash = "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2"}, + {file = "prompt_toolkit-3.0.41.tar.gz", hash = "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0"}, ] [package.dependencies] @@ -1320,25 +1239,27 @@ wcwidth = "*" [[package]] name = "psutil" -version = "5.9.5" +version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, - {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, - {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, - {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, - {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, - {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, - {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, - {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, - {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, - {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, - {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, - {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, - {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, - {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d"}, + {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c"}, + {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28"}, + {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017"}, + {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c"}, + {file = "psutil-5.9.6-cp27-none-win32.whl", hash = "sha256:70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9"}, + {file = "psutil-5.9.6-cp27-none-win_amd64.whl", hash = "sha256:51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac"}, + {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"}, + {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"}, + {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"}, + {file = "psutil-5.9.6-cp36-cp36m-win32.whl", hash = "sha256:3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602"}, + {file = "psutil-5.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa"}, + {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"}, + {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"}, + {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"}, + {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"}, ] [package.extras] @@ -1370,50 +1291,180 @@ files = [ tests = ["pytest"] [[package]] -name = "pycodestyle" -version = "2.9.1" -description = "Python style guide checker" +name = "pydantic" +version = "2.5.2" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, + {file = "pydantic-2.5.2-py3-none-any.whl", hash = "sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0"}, + {file = "pydantic-2.5.2.tar.gz", hash = "sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"}, ] +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.14.5" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + [[package]] -name = "pyflakes" -version = "2.5.0" -description = "passive checker of Python programs" +name = "pydantic-core" +version = "2.14.5" +description = "" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.14.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd"}, + {file = "pydantic_core-2.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66"}, + {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997"}, + {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093"}, + {file = "pydantic_core-2.14.5-cp310-none-win32.whl", hash = "sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720"}, + {file = "pydantic_core-2.14.5-cp310-none-win_amd64.whl", hash = "sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b"}, + {file = "pydantic_core-2.14.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459"}, + {file = "pydantic_core-2.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4"}, + {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada"}, + {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda"}, + {file = "pydantic_core-2.14.5-cp311-none-win32.whl", hash = "sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651"}, + {file = "pydantic_core-2.14.5-cp311-none-win_amd64.whl", hash = "sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077"}, + {file = "pydantic_core-2.14.5-cp311-none-win_arm64.whl", hash = "sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf"}, + {file = "pydantic_core-2.14.5-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093"}, + {file = "pydantic_core-2.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e"}, + {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69"}, + {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d"}, + {file = "pydantic_core-2.14.5-cp312-none-win32.whl", hash = "sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260"}, + {file = "pydantic_core-2.14.5-cp312-none-win_amd64.whl", hash = "sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36"}, + {file = "pydantic_core-2.14.5-cp312-none-win_arm64.whl", hash = "sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325"}, + {file = "pydantic_core-2.14.5-cp37-none-win32.whl", hash = "sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405"}, + {file = "pydantic_core-2.14.5-cp37-none-win_amd64.whl", hash = "sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588"}, + {file = "pydantic_core-2.14.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf"}, + {file = "pydantic_core-2.14.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b"}, + {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec"}, + {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124"}, + {file = "pydantic_core-2.14.5-cp38-none-win32.whl", hash = "sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"}, + {file = "pydantic_core-2.14.5-cp38-none-win_amd64.whl", hash = "sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d"}, + {file = "pydantic_core-2.14.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7"}, + {file = "pydantic_core-2.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955"}, + {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5"}, + {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209"}, + {file = "pydantic_core-2.14.5-cp39-none-win32.whl", hash = "sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6"}, + {file = "pydantic_core-2.14.5-cp39-none-win_amd64.whl", hash = "sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3"}, + {file = "pydantic_core-2.14.5.tar.gz", hash = "sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.1.0" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" files = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, + {file = "pydantic_settings-2.1.0-py3-none-any.whl", hash = "sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a"}, + {file = "pydantic_settings-2.1.0.tar.gz", hash = "sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c"}, ] +[package.dependencies] +pydantic = ">=2.3.0" +python-dotenv = ">=0.21.0" + [[package]] name = "pygments" -version = "2.15.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] @@ -1429,13 +1480,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.21.0" +version = "0.21.1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-asyncio-0.21.0.tar.gz", hash = "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b"}, - {file = "pytest_asyncio-0.21.0-py3-none-any.whl", hash = "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c"}, + {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, + {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, ] [package.dependencies] @@ -1463,15 +1514,33 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-httpx" +version = "0.27.0" +description = "Send responses to httpx." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_httpx-0.27.0-py3-none-any.whl", hash = "sha256:24f6f53d507ab483bea8f89b975a1a111fb613ccab4d86e570be8991776e8bcc"}, + {file = "pytest_httpx-0.27.0.tar.gz", hash = "sha256:a33c4e8df415cc1232b3664869b6a8b8061c4c223335aca0b237cefbc01ba0eb"}, +] + +[package.dependencies] +httpx = "==0.25.*" +pytest = "==7.*" + +[package.extras] +testing = ["pytest-asyncio (==0.21.*)", "pytest-cov (==4.*)"] + [[package]] name = "pytest-mock" -version = "3.11.1" +version = "3.12.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, - {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, + {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, + {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, ] [package.dependencies] @@ -1500,74 +1569,88 @@ termcolor = ">=2.1.0" dev = ["black", "flake8", "pre-commit"] [[package]] -name = "pytz" -version = "2023.3" -description = "World timezone definitions, modern and historical" +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-json-logger" +version = "2.0.7" +description = "A python library adding a json log formatter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, + {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, ] [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "referencing" -version = "0.29.1" +version = "0.31.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.29.1-py3-none-any.whl", hash = "sha256:d3c8f323ee1480095da44d55917cfb8278d73d6b4d5f677e3e40eb21314ac67f"}, - {file = "referencing-0.29.1.tar.gz", hash = "sha256:90cb53782d550ba28d2166ef3f55731f38397def8832baac5d45235f1995e35e"}, + {file = "referencing-0.31.0-py3-none-any.whl", hash = "sha256:381b11e53dd93babb55696c71cf42aef2d36b8a150c49bf0bc301e36d536c882"}, + {file = "referencing-0.31.0.tar.gz", hash = "sha256:cc28f2c88fbe7b961a7817a0abc034c09a1e36358f82fedb4ffdf29a25398863"}, ] [package.dependencies] @@ -1596,164 +1679,166 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] -name = "restructuredtext-lint" -version = "1.4.0" -description = "reStructuredText linter" -optional = false -python-versions = "*" -files = [ - {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, -] - -[package.dependencies] -docutils = ">=0.11,<1.0" - -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" +name = "rich" +version = "13.7.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = "*" +python-versions = ">=3.7.0" files = [ - {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, - {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, ] [package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" [package.extras] -idna2008 = ["idna"] +jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.8.10" +version = "0.13.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.8.10-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:93d06cccae15b3836247319eee7b6f1fdcd6c10dabb4e6d350d27bd0bdca2711"}, - {file = "rpds_py-0.8.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3816a890a6a9e9f1de250afa12ca71c9a7a62f2b715a29af6aaee3aea112c181"}, - {file = "rpds_py-0.8.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7c6304b894546b5a6bdc0fe15761fa53fe87d28527a7142dae8de3c663853e1"}, - {file = "rpds_py-0.8.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad3bfb44c8840fb4be719dc58e229f435e227fbfbe133dc33f34981ff622a8f8"}, - {file = "rpds_py-0.8.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14f1c356712f66653b777ecd8819804781b23dbbac4eade4366b94944c9e78ad"}, - {file = "rpds_py-0.8.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82bb361cae4d0a627006dadd69dc2f36b7ad5dc1367af9d02e296ec565248b5b"}, - {file = "rpds_py-0.8.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2e3c4f2a8e3da47f850d7ea0d7d56720f0f091d66add889056098c4b2fd576c"}, - {file = "rpds_py-0.8.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15a90d0ac11b4499171067ae40a220d1ca3cb685ec0acc356d8f3800e07e4cb8"}, - {file = "rpds_py-0.8.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:70bb9c8004b97b4ef7ae56a2aa56dfaa74734a0987c78e7e85f00004ab9bf2d0"}, - {file = "rpds_py-0.8.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d64f9f88d5203274a002b54442cafc9c7a1abff2a238f3e767b70aadf919b451"}, - {file = "rpds_py-0.8.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ccbbd276642788c4376fbe8d4e6c50f0fb4972ce09ecb051509062915891cbf0"}, - {file = "rpds_py-0.8.10-cp310-none-win32.whl", hash = "sha256:fafc0049add8043ad07ab5382ee80d80ed7e3699847f26c9a5cf4d3714d96a84"}, - {file = "rpds_py-0.8.10-cp310-none-win_amd64.whl", hash = "sha256:915031002c86a5add7c6fd4beb601b2415e8a1c956590a5f91d825858e92fe6e"}, - {file = "rpds_py-0.8.10-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:84eb541a44f7a18f07a6bfc48b95240739e93defe1fdfb4f2a295f37837945d7"}, - {file = "rpds_py-0.8.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f59996d0550894affaad8743e97b9b9c98f638b221fac12909210ec3d9294786"}, - {file = "rpds_py-0.8.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9adb5664b78fcfcd830000416c8cc69853ef43cb084d645b3f1f0296edd9bae"}, - {file = "rpds_py-0.8.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f96f3f98fbff7af29e9edf9a6584f3c1382e7788783d07ba3721790625caa43e"}, - {file = "rpds_py-0.8.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:376b8de737401050bd12810003d207e824380be58810c031f10ec563ff6aef3d"}, - {file = "rpds_py-0.8.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d1c2bc319428d50b3e0fa6b673ab8cc7fa2755a92898db3a594cbc4eeb6d1f7"}, - {file = "rpds_py-0.8.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73a1e48430f418f0ac3dfd87860e4cc0d33ad6c0f589099a298cb53724db1169"}, - {file = "rpds_py-0.8.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134ec8f14ca7dbc6d9ae34dac632cdd60939fe3734b5d287a69683c037c51acb"}, - {file = "rpds_py-0.8.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4b519bac7c09444dd85280fd60f28c6dde4389c88dddf4279ba9b630aca3bbbe"}, - {file = "rpds_py-0.8.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9cd57981d9fab04fc74438d82460f057a2419974d69a96b06a440822d693b3c0"}, - {file = "rpds_py-0.8.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:69d089c026f6a8b9d64a06ff67dc3be196707b699d7f6ca930c25f00cf5e30d8"}, - {file = "rpds_py-0.8.10-cp311-none-win32.whl", hash = "sha256:220bdcad2d2936f674650d304e20ac480a3ce88a40fe56cd084b5780f1d104d9"}, - {file = "rpds_py-0.8.10-cp311-none-win_amd64.whl", hash = "sha256:6c6a0225b8501d881b32ebf3f5807a08ad3685b5eb5f0a6bfffd3a6e039b2055"}, - {file = "rpds_py-0.8.10-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e3d0cd3dff0e7638a7b5390f3a53057c4e347f4ef122ee84ed93fc2fb7ea4aa2"}, - {file = "rpds_py-0.8.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d77dff3a5aa5eedcc3da0ebd10ff8e4969bc9541aa3333a8d41715b429e99f47"}, - {file = "rpds_py-0.8.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41c89a366eae49ad9e65ed443a8f94aee762931a1e3723749d72aeac80f5ef2f"}, - {file = "rpds_py-0.8.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3793c21494bad1373da517001d0849eea322e9a049a0e4789e50d8d1329df8e7"}, - {file = "rpds_py-0.8.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:805a5f3f05d186c5d50de2e26f765ba7896d0cc1ac5b14ffc36fae36df5d2f10"}, - {file = "rpds_py-0.8.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b01b39ad5411563031ea3977bbbc7324d82b088e802339e6296f082f78f6115c"}, - {file = "rpds_py-0.8.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3f1e860be21f3e83011116a65e7310486300e08d9a3028e73e8d13bb6c77292"}, - {file = "rpds_py-0.8.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a13c8e56c46474cd5958d525ce6a9996727a83d9335684e41f5192c83deb6c58"}, - {file = "rpds_py-0.8.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:93d99f957a300d7a4ced41615c45aeb0343bb8f067c42b770b505de67a132346"}, - {file = "rpds_py-0.8.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:148b0b38d719c0760e31ce9285a9872972bdd7774969a4154f40c980e5beaca7"}, - {file = "rpds_py-0.8.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3cc5e5b5514796f45f03a568981971b12a3570f3de2e76114f7dc18d4b60a3c4"}, - {file = "rpds_py-0.8.10-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:e8e24b210a4deb5a7744971f8f77393005bae7f873568e37dfd9effe808be7f7"}, - {file = "rpds_py-0.8.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b41941583adce4242af003d2a8337b066ba6148ca435f295f31ac6d9e4ea2722"}, - {file = "rpds_py-0.8.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c490204e16bca4f835dba8467869fe7295cdeaa096e4c5a7af97f3454a97991"}, - {file = "rpds_py-0.8.10-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee45cd1d84beed6cbebc839fd85c2e70a3a1325c8cfd16b62c96e2ffb565eca"}, - {file = "rpds_py-0.8.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a8ca409f1252e1220bf09c57290b76cae2f14723746215a1e0506472ebd7bdf"}, - {file = "rpds_py-0.8.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96b293c0498c70162effb13100624c5863797d99df75f2f647438bd10cbf73e4"}, - {file = "rpds_py-0.8.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4627520a02fccbd324b33c7a83e5d7906ec746e1083a9ac93c41ac7d15548c7"}, - {file = "rpds_py-0.8.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e39d7ab0c18ac99955b36cd19f43926450baba21e3250f053e0704d6ffd76873"}, - {file = "rpds_py-0.8.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ba9f1d1ebe4b63801977cec7401f2d41e888128ae40b5441270d43140efcad52"}, - {file = "rpds_py-0.8.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:802f42200d8caf7f25bbb2a6464cbd83e69d600151b7e3b49f49a47fa56b0a38"}, - {file = "rpds_py-0.8.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d19db6ba816e7f59fc806c690918da80a7d186f00247048cd833acdab9b4847b"}, - {file = "rpds_py-0.8.10-cp38-none-win32.whl", hash = "sha256:7947e6e2c2ad68b1c12ee797d15e5f8d0db36331200b0346871492784083b0c6"}, - {file = "rpds_py-0.8.10-cp38-none-win_amd64.whl", hash = "sha256:fa326b3505d5784436d9433b7980171ab2375535d93dd63fbcd20af2b5ca1bb6"}, - {file = "rpds_py-0.8.10-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7b38a9ac96eeb6613e7f312cd0014de64c3f07000e8bf0004ad6ec153bac46f8"}, - {file = "rpds_py-0.8.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4d42e83ddbf3445e6514f0aff96dca511421ed0392d9977d3990d9f1ba6753c"}, - {file = "rpds_py-0.8.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b21575031478609db6dbd1f0465e739fe0e7f424a8e7e87610a6c7f68b4eb16"}, - {file = "rpds_py-0.8.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:574868858a7ff6011192c023a5289158ed20e3f3b94b54f97210a773f2f22921"}, - {file = "rpds_py-0.8.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae40f4a70a1f40939d66ecbaf8e7edc144fded190c4a45898a8cfe19d8fc85ea"}, - {file = "rpds_py-0.8.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37f7ee4dc86db7af3bac6d2a2cedbecb8e57ce4ed081f6464510e537589f8b1e"}, - {file = "rpds_py-0.8.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:695f642a3a5dbd4ad2ffbbacf784716ecd87f1b7a460843b9ddf965ccaeafff4"}, - {file = "rpds_py-0.8.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f43ab4cb04bde6109eb2555528a64dfd8a265cc6a9920a67dcbde13ef53a46c8"}, - {file = "rpds_py-0.8.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a11ab0d97be374efd04f640c04fe5c2d3dabc6dfb998954ea946ee3aec97056d"}, - {file = "rpds_py-0.8.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:92cf5b3ee60eef41f41e1a2cabca466846fb22f37fc580ffbcb934d1bcab225a"}, - {file = "rpds_py-0.8.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ceaac0c603bf5ac2f505a78b2dcab78d3e6b706be6596c8364b64cc613d208d2"}, - {file = "rpds_py-0.8.10-cp39-none-win32.whl", hash = "sha256:dd4f16e57c12c0ae17606c53d1b57d8d1c8792efe3f065a37cb3341340599d49"}, - {file = "rpds_py-0.8.10-cp39-none-win_amd64.whl", hash = "sha256:c03a435d26c3999c2a8642cecad5d1c4d10c961817536af52035f6f4ee2f5dd0"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0da53292edafecba5e1d8c1218f99babf2ed0bf1c791d83c0ab5c29b57223068"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d20a8ed227683401cc508e7be58cba90cc97f784ea8b039c8cd01111e6043e0"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97cab733d303252f7c2f7052bf021a3469d764fc2b65e6dbef5af3cbf89d4892"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c398fda6df361a30935ab4c4bccb7f7a3daef2964ca237f607c90e9f3fdf66f"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2eb4b08c45f8f8d8254cdbfacd3fc5d6b415d64487fb30d7380b0d0569837bf1"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7dfb1cbb895810fa2b892b68153c17716c6abaa22c7dc2b2f6dcf3364932a1c"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89c92b74e8bf6f53a6f4995fd52f4bd510c12f103ee62c99e22bc9e05d45583c"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9c0683cb35a9b5881b41bc01d5568ffc667910d9dbc632a1fba4e7d59e98773"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0eeb2731708207d0fe2619afe6c4dc8cb9798f7de052da891de5f19c0006c315"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:7495010b658ec5b52835f21d8c8b1a7e52e194c50f095d4223c0b96c3da704b1"}, - {file = "rpds_py-0.8.10-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c72ebc22e70e04126158c46ba56b85372bc4d54d00d296be060b0db1671638a4"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2cd3045e7f6375dda64ed7db1c5136826facb0159ea982f77d9cf6125025bd34"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:2418cf17d653d24ffb8b75e81f9f60b7ba1b009a23298a433a4720b2a0a17017"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a2edf8173ac0c7a19da21bc68818be1321998528b5e3f748d6ee90c0ba2a1fd"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f29b8c55fd3a2bc48e485e37c4e2df3317f43b5cc6c4b6631c33726f52ffbb3"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a7d20c1cf8d7b3960c5072c265ec47b3f72a0c608a9a6ee0103189b4f28d531"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:521fc8861a86ae54359edf53a15a05fabc10593cea7b3357574132f8427a5e5a"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5c191713e98e7c28800233f039a32a42c1a4f9a001a8a0f2448b07391881036"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:083df0fafe199371206111583c686c985dddaf95ab3ee8e7b24f1fda54515d09"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ed41f3f49507936a6fe7003985ea2574daccfef999775525d79eb67344e23767"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:2614c2732bf45de5c7f9e9e54e18bc78693fa2f635ae58d2895b7965e470378c"}, - {file = "rpds_py-0.8.10-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c60528671d9d467009a6ec284582179f6b88651e83367d0ab54cb739021cd7de"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ee744fca8d1ea822480a2a4e7c5f2e1950745477143668f0b523769426060f29"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a38b9f526d0d6cbdaa37808c400e3d9f9473ac4ff64d33d9163fd05d243dbd9b"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60e0e86e870350e03b3e25f9b1dd2c6cc72d2b5f24e070249418320a6f9097b7"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f53f55a8852f0e49b0fc76f2412045d6ad9d5772251dea8f55ea45021616e7d5"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c493365d3fad241d52f096e4995475a60a80f4eba4d3ff89b713bc65c2ca9615"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:300eb606e6b94a7a26f11c8cc8ee59e295c6649bd927f91e1dbd37a4c89430b6"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a665f6f1a87614d1c3039baf44109094926dedf785e346d8b0a728e9cabd27a"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:927d784648211447201d4c6f1babddb7971abad922b32257ab74de2f2750fad0"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c200b30dd573afa83847bed7e3041aa36a8145221bf0cfdfaa62d974d720805c"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:08166467258fd0240a1256fce272f689f2360227ee41c72aeea103e9e4f63d2b"}, - {file = "rpds_py-0.8.10-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:996cc95830de9bc22b183661d95559ec6b3cd900ad7bc9154c4cbf5be0c9b734"}, - {file = "rpds_py-0.8.10.tar.gz", hash = "sha256:13e643ce8ad502a0263397362fb887594b49cf84bf518d6038c16f235f2bcea4"}, -] - -[[package]] -name = "rstcheck" -version = "3.5.0" -description = "Checks syntax of reStructuredText and code blocks nested within it" + {file = "rpds_py-0.13.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:83feb0f682d75a09ddc11aa37ba5c07dd9b824b22915207f6176ea458474ff75"}, + {file = "rpds_py-0.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa84bbe22ffa108f91631935c28a623001e335d66e393438258501e618fb0dde"}, + {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04f8c76b8d5c70695b4e8f1d0b391d8ef91df00ef488c6c1ffb910176459bc6"}, + {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:032c242a595629aacace44128f9795110513ad27217b091e834edec2fb09e800"}, + {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91276caef95556faeb4b8f09fe4439670d3d6206fee78d47ddb6e6de837f0b4d"}, + {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d22f2cb82e0b40e427a74a93c9a4231335bbc548aed79955dde0b64ea7f88146"}, + {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63c9e2794329ef070844ff9bfc012004aeddc0468dc26970953709723f76c8a5"}, + {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c797ea56f36c6f248656f0223b11307fdf4a1886f3555eba371f34152b07677f"}, + {file = "rpds_py-0.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:82dbcd6463e580bcfb7561cece35046aaabeac5a9ddb775020160b14e6c58a5d"}, + {file = "rpds_py-0.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:736817dbbbd030a69a1faf5413a319976c9c8ba8cdcfa98c022d3b6b2e01eca6"}, + {file = "rpds_py-0.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1f36a1e80ef4ed1996445698fd91e0d3e54738bf597c9995118b92da537d7a28"}, + {file = "rpds_py-0.13.1-cp310-none-win32.whl", hash = "sha256:4f13d3f6585bd07657a603780e99beda96a36c86acaba841f131e81393958336"}, + {file = "rpds_py-0.13.1-cp310-none-win_amd64.whl", hash = "sha256:545e94c84575057d3d5c62634611858dac859702b1519b6ffc58eca7fb1adfcf"}, + {file = "rpds_py-0.13.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bfe72b249264cc1ff2f3629be240d7d2fdc778d9d298087cdec8524c91cd11f"}, + {file = "rpds_py-0.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edc91c50e17f5cd945d821f0f1af830522dba0c10267c3aab186dc3dbaab8def"}, + {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2eca04a365be380ca1f8fa48b334462e19e3382c0bb7386444d8ca43aa01c481"}, + {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e3ac5b602fea378243f993d8b707189f9061e55ebb4e56cb9fdef8166060f28"}, + {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfb5d2ab183c0efe5e7b8917e4eaa2e837aacafad8a69b89aa6bc81550eed857"}, + {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d9793d46d3e6522ae58e9321032827c9c0df1e56cbe5d3de965facb311aed6aa"}, + {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cd935c0220d012a27c20135c140f9cdcbc6249d5954345c81bfb714071b985c"}, + {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37b08df45f02ff1866043b95096cbe91ac99de05936dd09d6611987a82a3306a"}, + {file = "rpds_py-0.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad666a904212aa9a6c77da7dce9d5170008cda76b7776e6731928b3f8a0d40fa"}, + {file = "rpds_py-0.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8a6ad8429340e0a4de89353447c6441329def3632e7b2293a7d6e873217d3c2b"}, + {file = "rpds_py-0.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7c40851b659d958c5245c1236e34f0d065cc53dca8d978b49a032c8e0adfda6e"}, + {file = "rpds_py-0.13.1-cp311-none-win32.whl", hash = "sha256:4145172ab59b6c27695db6d78d040795f635cba732cead19c78cede74800949a"}, + {file = "rpds_py-0.13.1-cp311-none-win_amd64.whl", hash = "sha256:46a07a258bda12270de02b34c4884f200f864bba3dcd6e3a37fef36a168b859d"}, + {file = "rpds_py-0.13.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:ba4432301ad7eeb1b00848cf46fae0e5fecfd18a8cb5fdcf856c67985f79ecc7"}, + {file = "rpds_py-0.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d22e0660de24bd8e9ac82f4230a22a5fe4e397265709289d61d5fb333839ba50"}, + {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76a8374b294e4ccb39ccaf11d39a0537ed107534139c00b4393ca3b542cc66e5"}, + {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7d152ec7bb431040af2500e01436c9aa0d993f243346f0594a15755016bf0be1"}, + {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74a2044b870df7c9360bb3ce7e12f9ddf8e72e49cd3a353a1528cbf166ad2383"}, + {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:960e7e460fda2d0af18c75585bbe0c99f90b8f09963844618a621b804f8c3abe"}, + {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37f79f4f1f06cc96151f4a187528c3fd4a7e1065538a4af9eb68c642365957f7"}, + {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd4ea56c9542ad0091dfdef3e8572ae7a746e1e91eb56c9e08b8d0808b40f1d1"}, + {file = "rpds_py-0.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0290712eb5603a725769b5d857f7cf15cf6ca93dda3128065bbafe6fdb709beb"}, + {file = "rpds_py-0.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0b70c1f800059c92479dc94dda41288fd6607f741f9b1b8f89a21a86428f6383"}, + {file = "rpds_py-0.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3dd5fb7737224e1497c886fb3ca681c15d9c00c76171f53b3c3cc8d16ccfa7fb"}, + {file = "rpds_py-0.13.1-cp312-none-win32.whl", hash = "sha256:74be3b215a5695690a0f1a9f68b1d1c93f8caad52e23242fcb8ba56aaf060281"}, + {file = "rpds_py-0.13.1-cp312-none-win_amd64.whl", hash = "sha256:f47eef55297799956464efc00c74ae55c48a7b68236856d56183fe1ddf866205"}, + {file = "rpds_py-0.13.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:e4a45ba34f904062c63049a760790c6a2fa7a4cc4bd160d8af243b12371aaa05"}, + {file = "rpds_py-0.13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:20147996376be452cd82cd6c17701daba69a849dc143270fa10fe067bb34562a"}, + {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b9535aa22ab023704cfc6533e968f7e420affe802d85e956d8a7b4c0b0b5ea"}, + {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4fa1eeb9bea6d9b64ac91ec51ee94cc4fc744955df5be393e1c923c920db2b0"}, + {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b2415d5a7b7ee96aa3a54d4775c1fec140476a17ee12353806297e900eaeddc"}, + {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:577d40a72550eac1386b77b43836151cb61ff6700adacda2ad4d883ca5a0b6f2"}, + {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af2d1648eb625a460eee07d3e1ea3a4a6e84a1fb3a107f6a8e95ac19f7dcce67"}, + {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b769396eb358d6b55dbf78f3f7ca631ca1b2fe02136faad5af74f0111b4b6b7"}, + {file = "rpds_py-0.13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:249c8e0055ca597707d71c5ad85fd2a1c8fdb99386a8c6c257e1b47b67a9bec1"}, + {file = "rpds_py-0.13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:fe30ef31172bdcf946502a945faad110e8fff88c32c4bec9a593df0280e64d8a"}, + {file = "rpds_py-0.13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2647192facf63be9ed2d7a49ceb07efe01dc6cfb083bd2cc53c418437400cb99"}, + {file = "rpds_py-0.13.1-cp38-none-win32.whl", hash = "sha256:4011d5c854aa804c833331d38a2b6f6f2fe58a90c9f615afdb7aa7cf9d31f721"}, + {file = "rpds_py-0.13.1-cp38-none-win_amd64.whl", hash = "sha256:7cfae77da92a20f56cf89739a557b76e5c6edc094f6ad5c090b9e15fbbfcd1a4"}, + {file = "rpds_py-0.13.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:e9be1f7c5f9673616f875299339984da9447a40e3aea927750c843d6e5e2e029"}, + {file = "rpds_py-0.13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:839676475ac2ccd1532d36af3d10d290a2ca149b702ed464131e450a767550df"}, + {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90031658805c63fe488f8e9e7a88b260ea121ba3ee9cdabcece9c9ddb50da39"}, + {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ba9fbc5d6e36bfeb5292530321cc56c4ef3f98048647fabd8f57543c34174ec"}, + {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08832078767545c5ee12561ce980714e1e4c6619b5b1e9a10248de60cddfa1fd"}, + {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19f5aa7f5078d35ed8e344bcba40f35bc95f9176dddb33fc4f2084e04289fa63"}, + {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80080972e1d000ad0341c7cc58b6855c80bd887675f92871221451d13a975072"}, + {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ee352691c4434eb1c01802e9daa5edcc1007ff15023a320e2693fed6a661b"}, + {file = "rpds_py-0.13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d20da6b4c7aa9ee75ad0730beaba15d65157f5beeaca54a038bb968f92bf3ce3"}, + {file = "rpds_py-0.13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:faa12a9f34671a30ea6bb027f04ec4e1fb8fa3fb3ed030893e729d4d0f3a9791"}, + {file = "rpds_py-0.13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7cf241dbb50ea71c2e628ab2a32b5bfcd36e199152fc44e5c1edb0b773f1583e"}, + {file = "rpds_py-0.13.1-cp39-none-win32.whl", hash = "sha256:dab979662da1c9fbb464e310c0b06cb5f1d174d09a462553af78f0bfb3e01920"}, + {file = "rpds_py-0.13.1-cp39-none-win_amd64.whl", hash = "sha256:a2b3c79586636f1fa69a7bd59c87c15fca80c0d34b5c003d57f2f326e5276575"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5967fa631d0ed9f8511dede08bc943a9727c949d05d1efac4ac82b2938024fb7"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8308a8d49d1354278d5c068c888a58d7158a419b2e4d87c7839ed3641498790c"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0580faeb9def6d0beb7aa666294d5604e569c4e24111ada423cf9936768d95c"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2da81c1492291c1a90987d76a47c7b2d310661bf7c93a9de0511e27b796a8b46"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c9a1dc5e898ce30e2f9c0aa57181cddd4532b22b7780549441d6429d22d3b58"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ae6f423cb7d1c6256b7482025ace2825728f53b7ac58bcd574de6ee9d242c2"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc3179e0815827cf963e634095ae5715ee73a5af61defbc8d6ca79f1bdae1d1d"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0d9f8930092558fd15c9e07198625efb698f7cc00b3dc311c83eeec2540226a8"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d1d388d2f5f5a6065cf83c54dd12112b7389095669ff395e632003ae8999c6b8"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:08b335fb0c45f0a9e2478a9ece6a1bfb00b6f4c4780f9be3cf36479c5d8dd374"}, + {file = "rpds_py-0.13.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d11afdc5992bbd7af60ed5eb519873690d921425299f51d80aa3099ed49f2bcc"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:8c1f6c8df23be165eb0cb78f305483d00c6827a191e3a38394c658d5b9c80bbd"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:528e2afaa56d815d2601b857644aeb395afe7e59212ab0659906dc29ae68d9a6"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df2af1180b8eeececf4f819d22cc0668bfadadfd038b19a90bd2fb2ee419ec6f"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:88956c993a20201744282362e3fd30962a9d86dc4f1dcf2bdb31fab27821b61f"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee70ee5f4144a45a9e6169000b5b525d82673d5dab9f7587eccc92794814e7ac"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5fd099acaee2325f01281a130a39da08d885e4dedf01b84bf156ec2737d78fe"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9656a09653b18b80764647d585750df2dff8928e03a706763ab40ec8c4872acc"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ba239bb37663b2b4cd08e703e79e13321512dccd8e5f0e9451d9e53a6b8509a"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3f55ae773abd96b1de25fc5c3fb356f491bd19116f8f854ba705beffc1ddc3c5"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:f4b15a163448ec79241fb2f1bc5a8ae1a4a304f7a48d948d208a2935b26bf8a5"}, + {file = "rpds_py-0.13.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1a3b2583c86bbfbf417304eeb13400ce7f8725376dc7d3efbf35dc5d7052ad48"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:f1059ca9a51c936c9a8d46fbc2c9a6b4c15ab3f13a97f1ad32f024b39666ba85"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f55601fb58f92e4f4f1d05d80c24cb77505dc42103ddfd63ddfdc51d3da46fa2"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcfd5f91b882eedf8d9601bd21261d6ce0e61a8c66a7152d1f5df08d3f643ab1"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6574f619e8734140d96c59bfa8a6a6e7a3336820ccd1bfd95ffa610673b650a2"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4b9d3f5c48bbe8d9e3758e498b3c34863f2c9b1ac57a4e6310183740e59c980"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdd6f8738e1f1d9df5b1603bb03cb30e442710e5672262b95d0f9fcb4edb0dab"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8c2bf286e5d755a075e5e97ba56b3de08cccdad6b323ab0b21cc98875176b03"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d4b390ee70ca9263b331ccfaf9819ee20e90dfd0201a295e23eb64a005dbef"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:db8d0f0ad92f74feb61c4e4a71f1d573ef37c22ef4dc19cab93e501bfdad8cbd"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2abd669a39be69cdfe145927c7eb53a875b157740bf1e2d49e9619fc6f43362e"}, + {file = "rpds_py-0.13.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c173f529666bab8e3f948b74c6d91afa22ea147e6ebae49a48229d9020a47c4"}, + {file = "rpds_py-0.13.1.tar.gz", hash = "sha256:264f3a5906c62b9df3a00ad35f6da1987d321a053895bd85f9d5c708de5c0fbf"}, +] + +[[package]] +name = "ruff" +version = "0.1.6" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "rstcheck-3.5.0-py3-none-any.whl", hash = "sha256:30c36768c4bd617a85ab93c31facaf410582e53803fde624845eb00c1430070c"}, - {file = "rstcheck-3.5.0.tar.gz", hash = "sha256:d4b035300b7d898403544f38c3a4980171ce85f487d25e188347bbafb6ee58c0"}, + {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:88b8cdf6abf98130991cbc9f6438f35f6e8d41a02622cc5ee130a02a0ed28703"}, + {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c549ed437680b6105a1299d2cd30e4964211606eeb48a0ff7a93ef70b902248"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf5f701062e294f2167e66d11b092bba7af6a057668ed618a9253e1e90cfd76"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05991ee20d4ac4bb78385360c684e4b417edd971030ab12a4fbd075ff535050e"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87455a0c1f739b3c069e2f4c43b66479a54dea0276dd5d4d67b091265f6fd1dc"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:683aa5bdda5a48cb8266fcde8eea2a6af4e5700a392c56ea5fb5f0d4bfdc0240"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:137852105586dcbf80c1717facb6781555c4e99f520c9c827bd414fac67ddfb6"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd98138a98d48a1c36c394fd6b84cd943ac92a08278aa8ac8c0fdefcf7138f35"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0cd909d25f227ac5c36d4e7e681577275fb74ba3b11d288aff7ec47e3ae745"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8fd1c62a47aa88a02707b5dd20c5ff20d035d634aa74826b42a1da77861b5ff"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd89b45d374935829134a082617954120d7a1470a9f0ec0e7f3ead983edc48cc"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:491262006e92f825b145cd1e52948073c56560243b55fb3b4ecb142f6f0e9543"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea284789861b8b5ca9d5443591a92a397ac183d4351882ab52f6296b4fdd5462"}, + {file = "ruff-0.1.6-py3-none-win32.whl", hash = "sha256:1610e14750826dfc207ccbcdd7331b6bd285607d4181df9c1c6ae26646d6848a"}, + {file = "ruff-0.1.6-py3-none-win_amd64.whl", hash = "sha256:4558b3e178145491e9bc3b2ee3c4b42f19d19384eaa5c59d10acf6e8f8b57e33"}, + {file = "ruff-0.1.6-py3-none-win_arm64.whl", hash = "sha256:03910e81df0d8db0e30050725a5802441c2022ea3ae4fe0609b76081731accbc"}, + {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] -[package.dependencies] -docutils = ">=0.7" - [[package]] name = "sdss-clu" -version = "2.1.0" +version = "2.2.2" description = "A new protocol for SDSS actors." optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "sdss_clu-2.1.0-py3-none-any.whl", hash = "sha256:e507f2559b4898be7ef4b95203db95e0cacd35fb2169ca35d7770e1d47d4b997"}, - {file = "sdss_clu-2.1.0.tar.gz", hash = "sha256:4c001db08184c3df57940da45179ec8dbd6f0f47202c4ec4638df9d84ea765b2"}, + {file = "sdss_clu-2.2.2-py3-none-any.whl", hash = "sha256:79912a472c6e4018ddc9df4a4a33f7352b6296d0eb2e753f052c1f775317dd0a"}, + {file = "sdss_clu-2.2.2.tar.gz", hash = "sha256:e8a981dad5d7848c9c4e2b02abd858132f90550facd07775d5b474519c2bc5b5"}, ] [package.dependencies] @@ -1764,30 +1849,48 @@ click-default-group = ">=1.2.2,<2.0.0" jsonschema = ">=4.0.1,<5.0.0" prompt_toolkit = ">=3.0.6,<4.0.0" sdsstools = ">=1.0.0,<2.0.0" -unclick = ">=0.1.0b4,<0.2.0" +unclick = ">=0.1.0b5,<0.2.0" [package.extras] websocket = ["websockets (>=11.0.3,<12.0.0)"] [[package]] name = "sdsstools" -version = "1.1.0" +version = "1.4.0" description = "Small tools for SDSS products" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "sdsstools-1.1.0-py3-none-any.whl", hash = "sha256:d4d57855a9ac779399a68994374dc577deb9f27436019c99f248249070b754a4"}, - {file = "sdsstools-1.1.0.tar.gz", hash = "sha256:083dee962600c479e3a0304a1648713df471725e65f64f880280f33014184feb"}, + {file = "sdsstools-1.4.0-py3-none-any.whl", hash = "sha256:3ebd183fe6a7d67c53fbbcbdac192d3263bfefa57955ab0644612a5baeedda29"}, + {file = "sdsstools-1.4.0.tar.gz", hash = "sha256:cd1920f1d32d1bdf71be68c045d5c288955f947d47fc2cc7ca46debd6d49036a"}, ] [package.dependencies] daemonocle = ">=1.0.2,<2.0.0" -invoke = ">=1.3.0,<2.0.0" +invoke = ">=2.0.0,<3.0.0" packaging = ">=20.4" pygments = ">=2.5.2,<3.0.0" +python-json-logger = ">=2.0.7,<3.0.0" pyyaml = ">=4.0" +rich = ">=13.4.2,<14.0.0" typing-extensions = ">=4.6.3,<5.0.0" +[[package]] +name = "setuptools" +version = "69.0.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -1823,24 +1926,24 @@ files = [ [[package]] name = "soupsieve" -version = "2.4.1" +version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, - {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] [[package]] name = "sphinx" -version = "7.0.1" +version = "7.2.6" description = "Python documentation generator" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "Sphinx-7.0.1.tar.gz", hash = "sha256:61e025f788c5977d9412587e733733a289e2b9fdc2fef8868ddfbfc4ccfe881d"}, - {file = "sphinx-7.0.1-py3-none-any.whl", hash = "sha256:60c5e04756c1709a98845ed27a2eed7a556af3993afb66e77fec48189f742616"}, + {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, + {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, ] [package.dependencies] @@ -1852,7 +1955,7 @@ imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" -Pygments = ">=2.13" +Pygments = ">=2.14" requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" @@ -1860,12 +1963,12 @@ sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +sphinxcontrib-serializinghtml = ">=1.1.9" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] +test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] [[package]] name = "sphinx-autobuild" @@ -1886,6 +1989,25 @@ sphinx = "*" [package.extras] test = ["pytest", "pytest-cov"] +[[package]] +name = "sphinx-autodoc-typehints" +version = "1.25.2" +description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx_autodoc_typehints-1.25.2-py3-none-any.whl", hash = "sha256:5ed05017d23ad4b937eab3bee9fae9ab0dd63f0b42aa360031f1fad47e47f673"}, + {file = "sphinx_autodoc_typehints-1.25.2.tar.gz", hash = "sha256:3cabc2537e17989b2f92e64a399425c4c8bf561ed73f087bc7414a5003616a50"}, +] + +[package.dependencies] +sphinx = ">=7.1.2" + +[package.extras] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)"] +numpy = ["nptyping (>=2.5)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.7.1)"] + [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" @@ -1905,13 +2027,13 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta [[package]] name = "sphinx-click" -version = "4.4.0" +version = "5.1.0" description = "Sphinx extension that automatically documents click applications" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "sphinx-click-4.4.0.tar.gz", hash = "sha256:cc67692bd28f482c7f01531c61b64e9d2f069bfcf3d24cbbb51d4a84a749fa48"}, - {file = "sphinx_click-4.4.0-py3-none-any.whl", hash = "sha256:2821c10a68fc9ee6ce7c92fad26540d8d8c8f45e6d7258f0e4fb7529ae8fab49"}, + {file = "sphinx-click-5.1.0.tar.gz", hash = "sha256:6812c2db62d3fae71a4addbe5a8a0a16c97eb491f3cd63fe34b4ed7e07236f33"}, + {file = "sphinx_click-5.1.0-py3-none-any.whl", hash = "sha256:ae97557a4e9ec646045089326c3b90e026c58a45e083b8f35f17d5d6558d08a0"}, ] [package.dependencies] @@ -1955,45 +2077,54 @@ requests = "*" [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.4" +version = "1.0.7" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, + {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, + {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, ] +[package.dependencies] +Sphinx = ">=5" + [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +version = "1.0.5" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, + {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, + {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, ] +[package.dependencies] +Sphinx = ">=5" + [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.1" +version = "2.0.4" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, + {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, + {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, ] +[package.dependencies] +Sphinx = ">=5" + [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] @@ -2014,43 +2145,49 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +version = "1.0.6" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, + {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, + {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, ] +[package.dependencies] +Sphinx = ">=5" + [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +version = "1.1.9" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, + {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, + {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, ] +[package.dependencies] +Sphinx = ">=5" + [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "stack-data" -version = "0.6.2" +version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" files = [ - {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, - {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, ] [package.dependencies] @@ -2061,20 +2198,6 @@ pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] -[[package]] -name = "stevedore" -version = "5.1.0" -description = "Manage dynamic plugins for Python applications" -optional = false -python-versions = ">=3.8" -files = [ - {file = "stevedore-5.1.0-py3-none-any.whl", hash = "sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d"}, - {file = "stevedore-5.1.0.tar.gz", hash = "sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c"}, -] - -[package.dependencies] -pbr = ">=2.0.0,<2.1.0 || >2.1.0" - [[package]] name = "termcolor" version = "2.3.0" @@ -2102,59 +2225,59 @@ files = [ [[package]] name = "tornado" -version = "6.3.2" +version = "6.3.3" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">= 3.8" files = [ - {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"}, - {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"}, - {file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"}, - {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4"}, - {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe"}, - {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d"}, - {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0"}, - {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411"}, - {file = "tornado-6.3.2-cp38-abi3-win32.whl", hash = "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2"}, - {file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"}, - {file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"}, + {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, + {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, + {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, + {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, + {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, ] [[package]] name = "traitlets" -version = "5.9.0" +version = "5.13.0" description = "Traitlets Python configuration system" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, - {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, + {file = "traitlets-5.13.0-py3-none-any.whl", hash = "sha256:baf991e61542da48fe8aef8b779a9ea0aa38d8a54166ee250d5af5ecf4486619"}, + {file = "traitlets-5.13.0.tar.gz", hash = "sha256:9b232b9430c8f57288c1024b34a8f0251ddcc47268927367a0dd3eeaca40deb5"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.6.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "unclick" -version = "0.1.0b4" +version = "0.1.0b5" description = "A reverse parser for the click CLI library" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "unclick-0.1.0b4-py3-none-any.whl", hash = "sha256:f9cc891e7dcdee16c7cf07c7f744b2071d1b58402e75281a26b8ba3594e14464"}, - {file = "unclick-0.1.0b4.tar.gz", hash = "sha256:a45cf639a42df6ff5739d16fe7f400d86f42dc39d2dfd9072b827e93c0610e47"}, + {file = "unclick-0.1.0b5-py3-none-any.whl", hash = "sha256:63cfa6e2108c4c8d6c56fa3323b8fd266d890c80f93b5037705a34d9812db02f"}, + {file = "unclick-0.1.0b5.tar.gz", hash = "sha256:5671f680800554db52298e91b8a7dbd366df435847328459dc6ac22a6189c6c2"}, ] [package.dependencies] @@ -2163,133 +2286,148 @@ makefun = ">=1.15.1,<2.0.0" [[package]] name = "urllib3" -version = "2.0.3" +version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, - {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.23.1" +version = "20.24.7" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, - {file = "virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, + {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, + {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, ] [package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.12,<4" -platformdirs = ">=3.5.1,<4" +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "wcwidth" -version = "0.2.6" +version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, - {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, + {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, + {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, ] [[package]] name = "yarl" -version = "1.9.2" +version = "1.9.3" description = "Yet another URL library" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, - {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, - {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, - {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, - {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, - {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, - {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, - {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, - {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, - {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, - {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, - {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, - {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32435d134414e01d937cd9d6cc56e8413a8d4741dea36af5840c7750f04d16ab"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a5211de242754b5e612557bca701f39f8b1a9408dff73c6db623f22d20f470e"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:525cd69eff44833b01f8ef39aa33a9cc53a99ff7f9d76a6ef6a9fb758f54d0ff"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc94441bcf9cb8c59f51f23193316afefbf3ff858460cb47b5758bf66a14d130"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e36021db54b8a0475805acc1d6c4bca5d9f52c3825ad29ae2d398a9d530ddb88"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0f17d1df951336a02afc8270c03c0c6e60d1f9996fcbd43a4ce6be81de0bd9d"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f3faeb8100a43adf3e7925d556801d14b5816a0ac9e75e22948e787feec642"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aed37db837ecb5962469fad448aaae0f0ee94ffce2062cf2eb9aed13328b5196"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:721ee3fc292f0d069a04016ef2c3a25595d48c5b8ddc6029be46f6158d129c92"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b8bc5b87a65a4e64bc83385c05145ea901b613d0d3a434d434b55511b6ab0067"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:dd952b9c64f3b21aedd09b8fe958e4931864dba69926d8a90c90d36ac4e28c9a"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:c405d482c320a88ab53dcbd98d6d6f32ada074f2d965d6e9bf2d823158fa97de"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9df9a0d4c5624790a0dea2e02e3b1b3c69aed14bcb8650e19606d9df3719e87d"}, + {file = "yarl-1.9.3-cp310-cp310-win32.whl", hash = "sha256:d34c4f80956227f2686ddea5b3585e109c2733e2d4ef12eb1b8b4e84f09a2ab6"}, + {file = "yarl-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:cf7a4e8de7f1092829caef66fd90eaf3710bc5efd322a816d5677b7664893c93"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d61a0ca95503867d4d627517bcfdc28a8468c3f1b0b06c626f30dd759d3999fd"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73cc83f918b69110813a7d95024266072d987b903a623ecae673d1e71579d566"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d81657b23e0edb84b37167e98aefb04ae16cbc5352770057893bd222cdc6e45f"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a1a8443091c7fbc17b84a0d9f38de34b8423b459fb853e6c8cdfab0eacf613"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe34befb8c765b8ce562f0200afda3578f8abb159c76de3ab354c80b72244c41"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c757f64afe53a422e45e3e399e1e3cf82b7a2f244796ce80d8ca53e16a49b9f"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a57b41a0920b9a220125081c1e191b88a4cdec13bf9d0649e382a822705c65"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:632c7aeb99df718765adf58eacb9acb9cbc555e075da849c1378ef4d18bf536a"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b0b8c06afcf2bac5a50b37f64efbde978b7f9dc88842ce9729c020dc71fae4ce"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1d93461e2cf76c4796355494f15ffcb50a3c198cc2d601ad8d6a96219a10c363"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4003f380dac50328c85e85416aca6985536812c082387255c35292cb4b41707e"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4d6d74a97e898c1c2df80339aa423234ad9ea2052f66366cef1e80448798c13d"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b61e64b06c3640feab73fa4ff9cb64bd8182de52e5dc13038e01cfe674ebc321"}, + {file = "yarl-1.9.3-cp311-cp311-win32.whl", hash = "sha256:29beac86f33d6c7ab1d79bd0213aa7aed2d2f555386856bb3056d5fdd9dab279"}, + {file = "yarl-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:f7271d6bd8838c49ba8ae647fc06469137e1c161a7ef97d778b72904d9b68696"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:dd318e6b75ca80bff0b22b302f83a8ee41c62b8ac662ddb49f67ec97e799885d"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c4b1efb11a8acd13246ffb0bee888dd0e8eb057f8bf30112e3e21e421eb82d4a"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f034386e5550b5dc8ded90b5e2ff7db21f0f5c7de37b6efc5dac046eb19c10"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd49a908cb6d387fc26acee8b7d9fcc9bbf8e1aca890c0b2fdfd706057546080"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa4643635f26052401750bd54db911b6342eb1a9ac3e74f0f8b58a25d61dfe41"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e741bd48e6a417bdfbae02e088f60018286d6c141639359fb8df017a3b69415a"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c86d0d0919952d05df880a1889a4f0aeb6868e98961c090e335671dea5c0361"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d5434b34100b504aabae75f0622ebb85defffe7b64ad8f52b8b30ec6ef6e4b9"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79e1df60f7c2b148722fb6cafebffe1acd95fd8b5fd77795f56247edaf326752"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:44e91a669c43f03964f672c5a234ae0d7a4d49c9b85d1baa93dec28afa28ffbd"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3cfa4dbe17b2e6fca1414e9c3bcc216f6930cb18ea7646e7d0d52792ac196808"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:88d2c3cc4b2f46d1ba73d81c51ec0e486f59cc51165ea4f789677f91a303a9a7"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cccdc02e46d2bd7cb5f38f8cc3d9db0d24951abd082b2f242c9e9f59c0ab2af3"}, + {file = "yarl-1.9.3-cp312-cp312-win32.whl", hash = "sha256:96758e56dceb8a70f8a5cff1e452daaeff07d1cc9f11e9b0c951330f0a2396a7"}, + {file = "yarl-1.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:c4472fe53ebf541113e533971bd8c32728debc4c6d8cc177f2bff31d011ec17e"}, + {file = "yarl-1.9.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:126638ab961633f0940a06e1c9d59919003ef212a15869708dcb7305f91a6732"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c99ddaddb2fbe04953b84d1651149a0d85214780e4d0ee824e610ab549d98d92"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dab30b21bd6fb17c3f4684868c7e6a9e8468078db00f599fb1c14e324b10fca"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:828235a2a169160ee73a2fcfb8a000709edf09d7511fccf203465c3d5acc59e4"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc391e3941045fd0987c77484b2799adffd08e4b6735c4ee5f054366a2e1551d"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51382c72dd5377861b573bd55dcf680df54cea84147c8648b15ac507fbef984d"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:28a108cb92ce6cf867690a962372996ca332d8cda0210c5ad487fe996e76b8bb"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8f18a7832ff85dfcd77871fe677b169b1bc60c021978c90c3bb14f727596e0ae"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:7eaf13af79950142ab2bbb8362f8d8d935be9aaf8df1df89c86c3231e4ff238a"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:66a6dbf6ca7d2db03cc61cafe1ee6be838ce0fbc97781881a22a58a7c5efef42"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a0a4f3aaa18580038cfa52a7183c8ffbbe7d727fe581300817efc1e96d1b0e9"}, + {file = "yarl-1.9.3-cp37-cp37m-win32.whl", hash = "sha256:946db4511b2d815979d733ac6a961f47e20a29c297be0d55b6d4b77ee4b298f6"}, + {file = "yarl-1.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2dad8166d41ebd1f76ce107cf6a31e39801aee3844a54a90af23278b072f1ccf"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bb72d2a94481e7dc7a0c522673db288f31849800d6ce2435317376a345728225"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a172c3d5447b7da1680a1a2d6ecdf6f87a319d21d52729f45ec938a7006d5d8"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2dc72e891672343b99db6d497024bf8b985537ad6c393359dc5227ef653b2f17"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8d51817cf4b8d545963ec65ff06c1b92e5765aa98831678d0e2240b6e9fd281"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53ec65f7eee8655bebb1f6f1607760d123c3c115a324b443df4f916383482a67"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cfd77e8e5cafba3fb584e0f4b935a59216f352b73d4987be3af51f43a862c403"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e73db54c967eb75037c178a54445c5a4e7461b5203b27c45ef656a81787c0c1b"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09c19e5f4404574fcfb736efecf75844ffe8610606f3fccc35a1515b8b6712c4"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6280353940f7e5e2efaaabd686193e61351e966cc02f401761c4d87f48c89ea4"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c25ec06e4241e162f5d1f57c370f4078797ade95c9208bd0c60f484834f09c96"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7217234b10c64b52cc39a8d82550342ae2e45be34f5bff02b890b8c452eb48d7"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4ce77d289f8d40905c054b63f29851ecbfd026ef4ba5c371a158cfe6f623663e"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f74b015c99a5eac5ae589de27a1201418a5d9d460e89ccb3366015c6153e60a"}, + {file = "yarl-1.9.3-cp38-cp38-win32.whl", hash = "sha256:8a2538806be846ea25e90c28786136932ec385c7ff3bc1148e45125984783dc6"}, + {file = "yarl-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:6465d36381af057d0fab4e0f24ef0e80ba61f03fe43e6eeccbe0056e74aadc70"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2f3c8822bc8fb4a347a192dd6a28a25d7f0ea3262e826d7d4ef9cc99cd06d07e"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7831566595fe88ba17ea80e4b61c0eb599f84c85acaa14bf04dd90319a45b90"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff34cb09a332832d1cf38acd0f604c068665192c6107a439a92abfd8acf90fe2"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe8080b4f25dfc44a86bedd14bc4f9d469dfc6456e6f3c5d9077e81a5fedfba7"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8535e111a064f3bdd94c0ed443105934d6f005adad68dd13ce50a488a0ad1bf3"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d155a092bf0ebf4a9f6f3b7a650dc5d9a5bbb585ef83a52ed36ba46f55cc39d"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:778df71c8d0c8c9f1b378624b26431ca80041660d7be7c3f724b2c7a6e65d0d6"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f9cafaf031c34d95c1528c16b2fa07b710e6056b3c4e2e34e9317072da5d1a"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ca6b66f69e30f6e180d52f14d91ac854b8119553b524e0e28d5291a724f0f423"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0e7e83f31e23c5d00ff618045ddc5e916f9e613d33c5a5823bc0b0a0feb522f"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:af52725c7c39b0ee655befbbab5b9a1b209e01bb39128dce0db226a10014aacc"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0ab5baaea8450f4a3e241ef17e3d129b2143e38a685036b075976b9c415ea3eb"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d350388ba1129bc867c6af1cd17da2b197dff0d2801036d2d7d83c2d771a682"}, + {file = "yarl-1.9.3-cp39-cp39-win32.whl", hash = "sha256:e2a16ef5fa2382af83bef4a18c1b3bcb4284c4732906aa69422cf09df9c59f1f"}, + {file = "yarl-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:d92d897cb4b4bf915fbeb5e604c7911021a8456f0964f3b8ebbe7f9188b9eabb"}, + {file = "yarl-1.9.3-py3-none-any.whl", hash = "sha256:271d63396460b6607b588555ea27a1a02b717ca2e3f2cf53bdde4013d7790929"}, + {file = "yarl-1.9.3.tar.gz", hash = "sha256:4a14907b597ec55740f63e52d7fee0e9ee09d5b9d57a4f399a7423268e457b57"}, ] [package.dependencies] @@ -2298,20 +2436,20 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.16.0" +version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.16.0-py3-none-any.whl", hash = "sha256:5dadc3ad0a1f825fe42ce1bce0f2fc5a13af2e6b2d386af5b0ff295bc0a287d3"}, - {file = "zipp-3.16.0.tar.gz", hash = "sha256:1876cb065531855bbe83b6c489dcf69ecc28f1068d8e95959fe8bbc77774c941"}, + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" -python-versions = ">=3.8,<4.0" -content-hash = "f60bcac209b85f2ee22e88f99e7c5f682a71022418956e55c0378e715289f854" +python-versions = ">=3.9,<4.0" +content-hash = "427d3b8d11258e830cbadc22b7ea10b7c084d15326a096fe8fd9626ca232f5fb" diff --git a/pyproject.toml b/pyproject.toml index 2e2f0c5..82b0b69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sdss-lvmnps" -version = "0.4.1a0" +version = "1.0.0a0" description = "A library and actor to communicate with an SDSS-V LVM network power switch" authors = ["Florian Briegel ", "José Sánchez-Gallego ", "Changgon Kim ", "Mingyeong Yang "] license = "BSD-3-Clause" @@ -13,77 +13,78 @@ classifiers = [ "Intended Audience :: Science/Research", "Natural Language :: English", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Documentation :: Sphinx", "Topic :: Software Development :: Libraries :: Python Modules" ] packages = [ - { include = "lvmnps", from = "python" } + { include = "lvmnps", from = "src" } ] -include = ["python/lvmnps/etc/*"] +include = ["src/lvmnps/actor/schema.json"] [tool.poetry.scripts] lvmnps = "lvmnps.__main__:main" [tool.poetry.dependencies] -python = ">=3.8,<4.0" +python = ">=3.10,<4.0" sdsstools = "^1.0.0" click-default-group = "^1.2.2" sdss-clu = "^2.0.0" -httpx = "^0.18.1" -requests = ">2.20" +httpx = ">=0.18.1" +pydantic = "^2.5.2" -[tool.poetry.group.helpers.dependencies] +[tool.poetry.group.dev.dependencies] ipython = ">=7.11.0" -flake8 = ">=3.7.9" -doc8 = ">=0.8.0" -isort = ">=4.3.21" ipdb = ">=0.12.3" -rstcheck = ">=3.3.1" black = ">=21.7b0" - -[tool.poetry.group.test] -optional = true - -[tool.poetry.group.test.dependencies] pytest = ">=5.2.2" pytest-asyncio = ">=0.10.0" pytest-cov = ">=2.8.1" pytest-mock = ">=1.13.0" pytest-sugar = ">=0.9.2" coverage = {version = ">=5.0", extras = ["toml"]} - -[tool.poetry.group.docs] -optional = true - -[tool.poetry.group.docs.dependencies] -Sphinx = ">=4.1.2" +Sphinx = ">=7.0.0" sphinx-jsonschema = ">=1.16.7" myst-parser = ">=0.14.0" -furo = ">=2021.6.18-beta.36" +furo = ">=2021.6.18" nox = ">=2021.6.12" sphinx-autobuild = ">=2021.3.14" sphinx-copybutton = ">=0.3.3" sphinx-click = ">=3.0.1" - -[tool.isort] -line_length = 79 -sections = ["FUTURE", "STDLIB", "THIRDPARTY", "SDSS", "FIRSTPARTY", "LOCALFOLDER"] -default_section = "THIRDPARTY" -known_first_party = "lvmnps" -known_sdss = ["sdsstools", "clu"] -balanced_wrapping = true -include_trailing_comma = false -lines_after_imports = 2 -use_parentheses = true +ruff = ">=0.1.0" +sphinx-autodoc-typehints = ">=1.25.2" +invoke = ">=2.2.0" +autodoc-pydantic = "^2.0.1" +pytest-httpx = "^0.27.0" [tool.black] line-length = 88 -target-version = ['py311'] +target-version = ['py312'] fast = true +[tool.ruff] +line-length = 88 +target-version = 'py312' +select = ["E", "F", "I"] +unfixable = ["F841"] +exclude = ["typings/"] + +[tool.ruff.per-file-ignores] +"__init__.py" = ["F401", "F403", "E402"] + +[tool.ruff.isort] +known-first-party = ["lvmnps"] +lines-after-imports = 2 +section-order = ["future", "standard-library", "typing", "third-party", "sdss", "first-party", "local-folder"] + +[tool.ruff.isort.sections] +typing = ["typing"] +sdss = ["sdsstools", "clu"] + [tool.pytest.ini_options] addopts = "--cov lvmnps --cov-report xml --cov-report html --cov-report term" asyncio_mode = "auto" @@ -92,13 +93,15 @@ asyncio_mode = "auto" branch = true disable_warnings = ["include-ignored"] omit = [ - "python/lvmnps/__main__.py", + "src/lvmnps/__main__.py", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", - "if TYPE_CHECKING" + "if TYPE_CHECKING", + "raise NotImplementedError()", + "pass" ] [build-system] diff --git a/python/lvmnps/actor/actor.py b/python/lvmnps/actor/actor.py deleted file mode 100644 index 3da9604..0000000 --- a/python/lvmnps/actor/actor.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: Mingyeong YANG (mingyeong@khu.ac.kr), Florian Briegel (briegel@mpia.de) -# @Date: 2021-03-22 -# @Filename: lvmnps/actor/actor.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -import asyncio -import os -from typing import ClassVar, Dict - -import click - -from clu import Command -from clu.actor import AMQPActor - -from lvmnps.actor.commands import parser as nps_command_parser -from lvmnps.switch.factory import powerSwitchFactory - - -__all__ = ["NPSActor"] - - -class NPSActor(AMQPActor): - """LVM network power switches base actor. - - Subclassed from the `.AMQPActor` class. - - """ - - parser: ClassVar[click.Group] = nps_command_parser - BASE_CONFIG: ClassVar[str | Dict | None] = None - - def __init__(self, *args, **kwargs): - if "schema" not in kwargs: - kwargs["schema"] = os.path.join( - os.path.dirname(__file__), - "../etc/schema.json", - ) - - super().__init__(*args, **kwargs) - - self.connect_timeout = 3 - - async def start(self): - """Start the actor and connect the power switches.""" - - await super().start() - - if "timeouts" in self.config and "switch_connect" in self.config["timeouts"]: - self.connect_timeout = self.config["timeouts"]["switch_connect"] - - assert len(self.parser_args) == 1 - - switches = list(self.parser_args[0].values()) - - # self.parser_args[0] is the list of switch instances - - tasks = [ - asyncio.wait_for(switch.start(), timeout=self.connect_timeout) - for switch in switches - ] - results = await asyncio.gather(*tasks, return_exceptions=True) - - valid_switches = [] - for ii, result in enumerate(results): - switch_name = switches[ii].name - if isinstance(result, Exception): - self.log.error( - f"Unexpected exception of type {type(result)} while initialising " - f"switch {switch_name}: {str(result)}" - ) - else: - valid_switches.append(switches[ii]) - - self.parser_args[0] = {switch.name: switch for switch in valid_switches} - - all_names = [ - outlet.name.lower() - for switch in valid_switches - for outlet in switch.outlets - ] - - if len(all_names) != len(list(set(all_names))): - self.log.warning("Repeated outlet names. This may lead to errors.") - - self.log.debug("Start done") - - async def stop(self): - """Stop the actor and disconnect the power switches.""" - - for switch in self.parser_args[0].values(): - try: - self.log.debug(f"Stop {switch.name} ...") - await asyncio.wait_for(switch.stop(), timeout=self.connect_timeout) - except Exception as ex: - self.log.error(f"Unexpected exception of {type(ex)}: {ex}") - - return await super().stop() - - @classmethod - def from_config(cls, config, *args, simulate: bool = False, **kwargs): - """Creates an actor from a configuration file.""" - - if config is None: - if cls.BASE_CONFIG is None: - raise RuntimeError("The class does not have a base configuration.") - config = cls.BASE_CONFIG - - instance = super(NPSActor, cls).from_config(config, *args, **kwargs) - - assert isinstance(instance, NPSActor) - assert isinstance(instance.config, dict) - - switches = {} - - if "switches" in instance.config: - if simulate: - instance.log.warn("In simulation mode !!!") - for key, swconfig in instance.config["switches"].items(): - if "name" in swconfig: - name = swconfig["name"] - else: - name = key - - instance.log.info(f"Instance {name}: {swconfig}") - try: - switches[name] = powerSwitchFactory( - name, swconfig, instance.log, simulate=simulate - ) - except Exception as ex: - instance.log.error(f"Power switch factory error {type(ex)}: {ex}") - - instance.parser_args = [switches] - - return instance - - -NPSCommand = Command[NPSActor] diff --git a/python/lvmnps/actor/commands/__init__.py b/python/lvmnps/actor/commands/__init__.py deleted file mode 100644 index ada461d..0000000 --- a/python/lvmnps/actor/commands/__init__.py +++ /dev/null @@ -1,50 +0,0 @@ -import glob -import importlib -import os - -import click - -from clu.command import Command -from clu.parsers.click import CluGroup, get_command_model, help_, ping, version - - -@click.group(cls=CluGroup) -def parser(*args): - pass - - -parser.add_command(ping) -parser.add_command(version) -parser.add_command(help_) -parser.add_command(get_command_model) - - -@parser.command(name="__commands") -@click.pass_context -def __commands(ctx, command: Command, *args): - # Returns all commands. - - # we have to use the help key for the command list, - # don't want to change the standard model. - command.finish(help=[k for k in ctx.command.commands.keys() if k[:2] != "__"]) - - -parser.add_command(__commands) - - -# Autoimport all modules in this directory so that they are added to the parser. - -exclusions = ["__init__.py"] - -cwd = os.getcwd() -os.chdir(os.path.dirname(os.path.realpath(__file__))) - -files = [ - file_ for file_ in glob.glob("**/*.py", recursive=True) if file_ not in exclusions -] - -for file_ in files: - modname = file_[0:-3].replace("/", ".") - mod = importlib.import_module("lvmnps.actor.commands." + modname) - -os.chdir(cwd) diff --git a/python/lvmnps/actor/commands/onoff.py b/python/lvmnps/actor/commands/onoff.py deleted file mode 100644 index e6456e7..0000000 --- a/python/lvmnps/actor/commands/onoff.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de) -# @Date: 2021-08-12 -# @Filename: onoff.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -import asyncio -from typing import TYPE_CHECKING - -import click - -from lvmnps.actor.commands import parser - - -if TYPE_CHECKING: - from lvmnps.actor.actor import NPSCommand - from lvmnps.switch.powerswitchbase import PowerSwitchBase - - -async def switch_control( - command: str, - switch: PowerSwitchBase, - on: bool, - name: str, - portnum: int | None, -): - """The function for parsing the actor command to the switch library.""" - - status = {} - if command == "on" or command == "off": - await switch.setState(on, name, portnum) - # status |= await switch.statusAsDict(name, portnum) works only with python 3.9 - status[switch.name] = dict( - list(status.items()) - + list((await switch.statusAsDict(name, portnum)).items()) # noqa: W503 - ) - - return status - - -@parser.command() -@click.argument("OUTLET", type=str) -@click.argument("PORTNUM", type=int, required=False) -@click.option( - "--switch", - type=str, - help="Address this switch specifically. Otherwise the first switch " - "with an outlet that matches NAME will be commanded.", -) -@click.option("--off-after", type=float, help="Turn off after X seconds.") -async def on( - command: NPSCommand, - switches: dict[str, PowerSwitchBase], - outlet: str, - portnum: int | None = None, - switch: str | None = None, - off_after: float | None = None, -): - """ - Turn on the outlet. - - \b - :param OUTLET: Outlet or switch name. - :param PORTNUM: Portnumber if switch name is provided. - """ - - if portnum: - command.info(text=f"Turning on {outlet} port {portnum} ...") - else: - command.info(text=f"Turning on outlet {outlet} ...") - - the_switch: PowerSwitchBase | None = None - current_status: dict | None = None - for sw in switches.values(): - if switch and sw.name != switch: - continue - - current_status = await sw.statusAsDict(outlet, portnum) - if current_status: - the_switch = sw - break - - if current_status is None or the_switch is None: - return command.fail(f"Could not find a match for {outlet}:{portnum}.") - - outletname_list = list(current_status.keys()) - outletname = outletname_list[0] - - if current_status[outletname]["state"] != 1: - current_status = await switch_control("on", the_switch, True, outlet, portnum) - elif current_status[outletname]["state"] == 1: - return command.finish(text=f"The outlet {outletname} is already ON") - else: - return command.fail(text=f"The outlet {outletname} returns wrong value") - - command.info(status=current_status) - - if off_after is not None: - command.info(f"The switch will be turned off after {off_after} seconds.") - await asyncio.sleep(off_after - 1) - current_status = await switch_control("off", the_switch, False, outlet, portnum) - command.info(status=current_status) - - return command.finish() - - -@parser.command() -@click.argument("OUTLET", type=str) -@click.argument("PORTNUM", type=int, required=False) -@click.option( - "--switch", - type=str, - help="Address this switch specifically. Otherwise the first switch " - "with an outlet that matches NAME will be commanded.", -) -async def off( - command: NPSCommand, - switches: dict[str, PowerSwitchBase], - outlet: str, - portnum: int | None = None, - switch: str | None = None, -): - """ - Turn off the outlet. - - \b - :param OUTLET: Outlet or switch name. - :param PORTNUM: Portnumber if switch name is provided. - """ - - if portnum: - command.info(text=f"Turning off {outlet} port {portnum} ...") - else: - command.info(text=f"Turning off outlet {outlet} ...") - - the_switch: PowerSwitchBase | None = None - current_status: dict | None = None - for sw in switches.values(): - if switch and sw.name != switch: - continue - - current_status = await sw.statusAsDict(outlet, portnum) - if current_status: - the_switch = sw - break - - if current_status is None or the_switch is None: - return command.fail(f"Could not find a match for {outlet}:{portnum}.") - - outletname_list = list(current_status.keys()) - outletname = outletname_list[0] - - if current_status[outletname]["state"] != 0: - current_status = await switch_control("off", the_switch, False, outlet, portnum) - elif current_status[outletname]["state"] == 0: - return command.finish(text=f"The outlet {outletname} is already OFF") - else: - return command.fail(text=f"The outlet {outletname} returns wrong value") - - command.info(status=current_status) - - return command.finish() diff --git a/python/lvmnps/actor/commands/outlets.py b/python/lvmnps/actor/commands/outlets.py deleted file mode 100644 index f675e5c..0000000 --- a/python/lvmnps/actor/commands/outlets.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: José Sánchez-Gallego (gallegoj@uw.edu) -# @Date: 2022-05-22 -# @Filename: outlets.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import click - -from lvmnps.actor.commands import parser - - -if TYPE_CHECKING: - from lvmnps.actor.actor import NPSCommand - from lvmnps.switch.powerswitchbase import PowerSwitchBase - - -@parser.command() -@click.argument("SWITCHNAME", type=str, required=False) -async def outlets( - command: NPSCommand, - switches: dict[str, PowerSwitchBase], - switchname: str | None = None, -): - """Returns the list of names for each outlet on a specific power switch.""" - - if switchname: - if switchname not in switches: - return command.fail(f"Unknown switch {switchname}.") - switch_instances = [switches[switchname]] - else: - switch_instances = list(switches.values()) - - outlets = [] - for switch in switch_instances: - for o in switch.outlets: - if o.inuse or not switch.onlyusedones: - outlets.append(o.name) - - return command.finish(outlets=outlets) diff --git a/python/lvmnps/actor/commands/status.py b/python/lvmnps/actor/commands/status.py deleted file mode 100644 index de7af0f..0000000 --- a/python/lvmnps/actor/commands/status.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de) -# @Date: 2021-08-12 -# @Filename: status.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import click - -from lvmnps.actor.commands import parser - - -if TYPE_CHECKING: - from lvmnps.actor.actor import NPSCommand - from lvmnps.switch.powerswitchbase import PowerSwitchBase - - -@parser.command() -@click.argument("SWITCHNAME", type=str, required=False) -@click.argument("PORTNUM", type=int, required=False) -@click.option( - "-o", - "--outlet", - type=str, - help="Print only the information for this outlet.", -) -async def status( - command: NPSCommand, - switches: dict[str, PowerSwitchBase], - switchname: str | None = None, - portnum: int | None = None, - outlet: str | None = None, -): - """ - Returns the dictionary of a specific outlet. - - \b - :param SWITCHNAME: Switch name. - :param PORTNUM: Portnumber. - """ - if switchname and switchname not in switches: - return command.fail(f"Unknown switch {switchname}.") - - status = {} - if switchname is None: - for switch in switches.values(): - if not await switch.isReachable(): - continue - current_status = await switch.statusAsDict(outlet, portnum) - if current_status: - status[switch.name] = current_status - else: - switch = switches[switchname] - if await switch.isReachable(): - current_status = await switch.statusAsDict(outlet, portnum) - if current_status: - status[switch.name] = current_status - - if status == {}: - return command.fail("Unable to find matching outlets.") - - return command.finish(message={"status": status}) diff --git a/python/lvmnps/actor/commands/switches.py b/python/lvmnps/actor/commands/switches.py deleted file mode 100644 index 85d36cb..0000000 --- a/python/lvmnps/actor/commands/switches.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: José Sánchez-Gallego (gallegoj@uw.edu) -# @Date: 2022-05-22 -# @Filename: switches.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from . import parser - - -if TYPE_CHECKING: - from lvmnps.actor.actor import NPSCommand - from lvmnps.switch.powerswitchbase import PowerSwitchBase - - -@parser.command() -async def switches(command: NPSCommand, switches: dict[str, PowerSwitchBase]): - """Lists the available switches.""" - - return command.finish(switches=list(switches.keys())) diff --git a/python/lvmnps/etc/lvmnps.yml b/python/lvmnps/etc/lvmnps.yml deleted file mode 100644 index e2f7873..0000000 --- a/python/lvmnps/etc/lvmnps.yml +++ /dev/null @@ -1,42 +0,0 @@ -# A dictionary of controller name to NPS controller connection parameters. -switches: - DLI-01: - type: dli - name: DLI1 - hostname: 10.7.45.22 - user: 'admin' - password: 'rLXR3KxUqiCPGvA' - onoff_timeout: 3 - ouo: False # handle also unconfigured ports - ports: - number_of_ports: 8 - DLI-02: - type: dli - name: DLI2 - hostname: 10.7.45.29 - user: 'admin' - password: 'VCrht9wfx2CQN9b' - onoff_timeout: 3 - ouo: False # handle also unconfigured ports - ports: - number_of_ports: 8 - DLI-03: - type: dli - name: DLI3 - hostname: 10.7.45.31 - user: 'admin' - password: 'JNC_zbf5tdc4deb*npx' - onoff_timeout: 3 - ouo: False # handle also unconfigured ports - ports: - number_of_ports: 8 - -timeouts: - switch_connect: 3 - -# Actor configuration for the AMQPActor class -actor: - name: lvmnps - host: localhost - port: 5672 - log_dir: '/data/logs/lvmnps' diff --git a/python/lvmnps/etc/lvmnps_dummy.yml b/python/lvmnps/etc/lvmnps_dummy.yml deleted file mode 100644 index 5c9b25d..0000000 --- a/python/lvmnps/etc/lvmnps_dummy.yml +++ /dev/null @@ -1,60 +0,0 @@ -# A dictionary of controller name to NPS controller connection parameters. -switches: - cab: - type: dummy - num: 4 - ports: - 1: - name: 'cab.mocon' - desc: 'Mocon motor controller' - sci: - type: dummy - num: 4 - ports: - 1: - name: 'sci.pwi' - desc: 'Planewave mount' - 2: - name: 'sci.tpc' - desc: 'Planewave tiny pc' - skye: - type: dummy - num: 4 - ports: - 1: - name: 'skye.pwi' - desc: 'Planewave mount' - 2: - name: 'skye.tpc' - desc: 'Planewave tiny pc' - skyw: - type: dummy - num: 4 - ports: - 1: - name: 'skyw.pwi' - desc: 'Planewave mount' - 2: - name: 'skyw.tpc' - desc: 'Planewave tiny pc' - spec: - type: dummy - num: 4 - ports: - 1: - name: 'spec.pwi' - desc: 'Planewave mount' - 2: - name: 'spec.tpc' - desc: 'Planewave tiny pc' - - -timeouts: - switch_connect: 1 - -# Actor configuration for the AMQPActor class -actor: - name: lvmnps - host: localhost - port: 5672 - log_dir: '~/tmp/log' diff --git a/python/lvmnps/etc/lvmnps_netio.yml b/python/lvmnps/etc/lvmnps_netio.yml deleted file mode 100644 index b5a578e..0000000 --- a/python/lvmnps/etc/lvmnps_netio.yml +++ /dev/null @@ -1,24 +0,0 @@ -# A dictionary of controller name to NPS controller connection parameters. -switches: - skye.nps: - type: netio -# hostname: '192.168.1.17' - hostname: 'localhost:8080' - username: 'netio' - password: 'netio' - ouo: False - ports: - number_of_ports: 4 - 1: - name: 'skye.pwi' - desc: 'PlaneWavemount Skye' - -timeouts: - switch_connect: 1 - -# Actor configuration for the AMQPActor class -actor: - name: lvmnps - host: localhost - port: 5672 - log_dir: '~/tmp/log' diff --git a/python/lvmnps/etc/lvmnps_pwi.yml b/python/lvmnps/etc/lvmnps_pwi.yml deleted file mode 100644 index 19ae8d6..0000000 --- a/python/lvmnps/etc/lvmnps_pwi.yml +++ /dev/null @@ -1,32 +0,0 @@ -# A dictionary of controller name to NPS controller connection parameters. -switches: - skye.nps: - type: iboot - hostname: '192.168.178.52' - username: 'admin' - password: 'admin' - ports: - num: 1 - 1: - name: 'skye.pwi' - desc: 'PlaneWavemount Skye' - skyw.nps: - type: iboot - hostname: '192.168.178.53' - username: 'admin' - password: 'admin' - ports: - num: 1 - 1: - name: 'skyw.pwi' - desc: 'PlaneWavemount Skye' - -timeouts: - switch_connect: 1 - -# Actor configuration for the AMQPActor class -actor: - name: lvmnps - host: localhost - port: 5672 - log_dir: '~/tmp/log' diff --git a/python/lvmnps/etc/lvmnps_telescope.yml b/python/lvmnps/etc/lvmnps_telescope.yml deleted file mode 100644 index 4390e1a..0000000 --- a/python/lvmnps/etc/lvmnps_telescope.yml +++ /dev/null @@ -1,79 +0,0 @@ -# A dictionary of controller name to NPS controller connection parameters. -switches: - cab: - type: netio - hostname: '10.8.38.110:8080' - username: 'netio' - password: 'netio' - num: 4 - ports: - 1: - name: 'cab.mocon' - desc: 'Mocon motor controller' - - sci: - type: netio - hostname: '10.8.38.106:8080' - username: 'netio' - password: 'netio' - num: 4 - ports: - 1: - name: 'sci.pwi' - desc: 'Planewave mount' - 2: - name: 'sci.tpc' - desc: 'Planewave tiny pc' - - skye: - type: netio - hostname: '10.8.38.107:8080' - username: 'netio' - password: 'netio' - num: 4 - ports: - 1: - name: 'skye.pwi' - desc: 'Planewave mount' - 2: - name: 'skye.tpc' - desc: 'Planewave tiny pc' - - skyw: - type: netio - hostname: '10.8.38.108:8080' - username: 'netio' - password: 'netio' - num: 4 - ports: - 1: - name: 'skyw.pwi' - desc: 'Planewave mount' - 2: - name: 'skyw.tpc' - desc: 'Planewave tiny pc' - - spec: - type: netio - hostname: '10.8.38.109:8080' - username: 'netio' - password: 'netio' - num: 4 - ports: - 1: - name: 'spec.pwi' - desc: 'Planewave mount' - 2: - name: 'spec.tpc' - desc: 'Planewave tiny pc' - - -timeouts: - switch_connect: 1 - -# Actor configuration for the AMQPActor class -actor: - name: lvmnps - host: localhost - port: 5672 - log_dir: '~/tmp/log' diff --git a/python/lvmnps/etc/schema.json b/python/lvmnps/etc/schema.json deleted file mode 100644 index 0c250ec..0000000 --- a/python/lvmnps/etc/schema.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "definitions": {}, - "type": "object", - "properties": { - "text": { "type": "string" }, - "status": { - "type": "object", - "switch": { - "type": "object", - "outlet": { - "type": "object", - "state": { "type": "integer" }, - "descr": { "type": "string" }, - "switch": { "type": "string" }, - "port": { "type": "integer" } - } - } - }, - "switches": { "type": "array", "items": { "type": "string" } }, - "outlets": { "type": "array", "items": { "type": "string" } }, - "error": { "type": "string" } - }, - "additionalProperties": false -} diff --git a/python/lvmnps/exceptions.py b/python/lvmnps/exceptions.py deleted file mode 100644 index 0d92c7a..0000000 --- a/python/lvmnps/exceptions.py +++ /dev/null @@ -1,31 +0,0 @@ -# !usr/bin/env python -# -*- coding: utf-8 -*- -# -# Licensed under a 3-clause BSD license. -# -# @Author: Mingyeong Yang (mingyeong@khu.ac.kr), Changgon Kim (changgonkim@khu.ac.kr) -# @Date: 2021-08-24 -# @Update: 2021-10-09 -# @Filename: lvmnps/switch/dli/dli.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import absolute_import, division, print_function - - -class NpsActorError(Exception): - """A custom core NpsActor exception""" - - def __init__(self, message=None): - message = "There has been an error" if not message else message - - super(NpsActorError, self).__init__(message) - - -class NpsActorWarning(Warning): - """Base warning for NpsActor.""" - - -class PowerException(Exception): - """An error Exception class for powerswitch factory.""" - - pass diff --git a/python/lvmnps/switch/dli/dli.py b/python/lvmnps/switch/dli/dli.py deleted file mode 100644 index 229b372..0000000 --- a/python/lvmnps/switch/dli/dli.py +++ /dev/null @@ -1,215 +0,0 @@ -# !usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: Mingyeong Yang (mingyeong@khu.ac.kr), Changgon Kim (changgonkim@khu.ac.kr) -# @Date: 2021-08-24 -# @Update: 2021-10-09 -# @Filename: lvmnps/switch/dli/dli.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -import asyncio -import logging -from typing import TYPE_CHECKING - -import httpx - - -if TYPE_CHECKING: - from sdsstools.logger import SDSSLogger - - from ..outlet import Outlet - - -class DLI(object): - """Powerswitch class to manage the DLI power switch. - - Parameters - ---------- - hostname - The hostname from the configuration (the IP address for connection). - user - The username from the configuration (the id for login). - password - The password from the configuration (the password for login). - name - The name of the DLI Controller. - log - The logger for logging. - onoff_timeout - The timeout, in seconds, before failing an on/off command. - - """ - - def __init__( - self, - hostname: str, - user: str, - password: str, - name: str | None = None, - log: SDSSLogger | None = None, - onoff_timeout=3, - ): - self.user = user - self.hostname = hostname - self.name = name or hostname - - self.log = log or logging.getLogger(f"{self.__class__.__name__}_{self.name}") - - self.onoff_timeout = onoff_timeout - - self.client: httpx.AsyncClient - self.add_client(password) - - self.lock = asyncio.Lock() - - def add_client(self, password: str): - """Add the `httpx.AsyncClient` to the DLI object.""" - - try: - auth = httpx.DigestAuth(self.user, password) - self.client = httpx.AsyncClient( - auth=auth, - base_url=f"http://{self.hostname}/restapi", - headers={}, - ) - except Exception as ex: - self.log.error(f"{type(ex)}: couldn't access client {self.hostname}: {ex}") - - async def verify(self, outlets: list[Outlet]): - """Verifies if we can reach the switch by the "get" method. - - Also compares the outlet lists with the configuration, and returns true - if it's identical. - - Parameters - ---------- - outlets - The list of `.Outlet` instance to check. - - """ - - result = False - - async with self.lock: - async with self.client as client: - r = await client.get("relay/outlets/") - if r.status_code != 200: - raise RuntimeError(f"GET returned code {r.status_code}.") - else: - result = self.compare(r.json(), outlets) - - return result - - def compare(self, json: dict, outlets: list[Outlet]): - """Compares the names of the outlets with the response JSON object. - - Parameters - ---------- - json - The json list from the restful API. The current status of the power - switch is contained here. - outlets - List of `.Outlet` objects to compare. - - """ - - same = True - - for outlet in outlets: - portnum = outlet.portnum - if json[portnum - 1]["name"] != outlet.name: - same = False - break - - return same - - async def on(self, outlet: int = 0): - """Turn on the power to the outlet. - - Set the value of the outlet state by using a PUT request. Note that the - outlets in the RESTful API are zero-indexed. - - Parameters - ---------- - outlet - The number indicating the outlet (1-indexed). - - """ - - outlet = outlet - 1 - - async with self.lock: - async with self.client as client: - r = await asyncio.wait_for( - client.put( - f"relay/outlets/{outlet}/state/", - data={"value": True}, - headers={"X-CSRF": "x"}, - ), - self.onoff_timeout, - ) - if r.status_code != 204: - raise RuntimeError(f"PUT returned code {r.status_code}.") - - async def off(self, outlet=0): - """Turn off the power to the outlet. - - Set the value of the outlet state by using a PUT request. Note that the - outlets in the RESTful API are zero-indexed. - - Parameters - ---------- - outlet - The number indicating the outlet (1-indexed). - - """ - - outlet = outlet - 1 - - async with self.lock: - async with self.client as client: - r = await asyncio.wait_for( - client.put( - f"relay/outlets/{outlet}/state/", - data={"value": False}, - headers={"X-CSRF": "x"}, - ), - self.onoff_timeout, - ) - if r.status_code != 204: - raise RuntimeError(f"PUT returned code {r.status_code}.") - - async def get_outlets_response(self): - """Returns the raw response to a ``relay/outlets`` GET request..""" - - async with self.lock: - async with self.client as client: - r = await client.get("relay/outlets/") - if r.status_code != 200: - raise RuntimeError(f"GET returned code {r.status_code}.") - - return r.json() - - async def status(self): - """Returns the status as a dictionary. - - Receives the data from the switch by the GET method as a JSON. Note that - this method returns the status of all the outlets (ports 1-8). - - """ - - async with self.lock: - async with self.client as client: - r = await client.get("relay/outlets/") - if r.status_code != 200: - raise RuntimeError(f"GET returned code {r.status_code}.") - - outlets_dict = {} - - data = r.json() - for n in range(0, 8): - outlets_dict[n + 1] = data[n]["state"] - - return outlets_dict diff --git a/python/lvmnps/switch/dli/powerswitch.py b/python/lvmnps/switch/dli/powerswitch.py deleted file mode 100644 index 380e6a7..0000000 --- a/python/lvmnps/switch/dli/powerswitch.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de), -# Mingyeong Yang (mingyeong@khu.ac.kr), -# Changgon Kim (changgonkim@khu.ac.kr) -# @Date: 2021-06-24 -# @Update: 2021-10-09 -# @Filename: lvmnps/switch/dli/powerswitch.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from lvmnps.switch.dli.dli import DLI -from lvmnps.switch.powerswitchbase import PowerSwitchBase - - -if TYPE_CHECKING: - from sdsstools.logger import SDSSLogger - - from ..outlet import Outlet - - -__all__ = ["DLIPowerSwitch"] - - -class DLIPowerSwitch(PowerSwitchBase): - """A DLI power switch. - - Parameters - ---------- - name - A name identifying the power switch. - config - The configuration defined on the .yaml file under ``/etc/lvmnps_dli.yml``. - log - The logger for logging. - - """ - - def __init__(self, name: str, config: dict, log: SDSSLogger | None = None): - super().__init__(name, config, log) - - hostname = self.config_get("hostname") - user = self.config_get("user") - password = self.config_get("password") - - onoff_timeout = self.config_get("onoff_timeout", 3) - - if hostname is None or user is None or password is None: - raise ValueError( - "Hostname or credentials are missing. " - "Cannot create new DLI instance." - ) - - self.name = name - - self.dli = DLI( - hostname, - user, - password, - log=self.log, - name=self.name, - onoff_timeout=onoff_timeout, - ) - - self.reachable = False - - async def start(self): - """Adds the client controlling the DLI Power Switch. - - Checks if the Power switch is reachable. - If the Power switch is reachable, updates the data of Outlet objects. - - """ - - # Instead of sending several requests, which makes it a bit slower, - # get all the information form the outlets and manually update states. - - outlet_data = await self.dli.get_outlets_response() - - for i in range(len(outlet_data)): - outlet = self.collectOutletsByNameAndPort(portnum=i + 1) - if len(outlet) == 0 or len(outlet) > 1: - continue - - outlet[0].setState(outlet_data[i]["state"]) - - if self.onlyusedones is False: - if outlet[0].inuse: - continue - - if outlet_data[i]["name"] == "": - continue - - outlet[0].name = outlet_data[i]["name"] - outlet[0].description = outlet_data[i]["name"] - outlet[0].inuse = True - - try: - inuse = [outlet for outlet in self.outlets if outlet.inuse] - self.reachable = await self.dli.verify(inuse) - except Exception as ex: - raise RuntimeError(f"Unexpected exception is {type(ex)}: {ex}") - - async def stop(self): - """Closes the connection to the client.""" - - pass - - async def isReachable(self): - """Check if the power switch is reachable.""" - - return self.reachable - - async def update(self, outlets: list[Outlet] | None = None): - """Updates the data based on the received status dictionary from the DLI class. - - Parameters - ---------- - outlets - List of `.Outlet` objects. If `None`, updates the status of all outlets. - - """ - - outlets = outlets or self.outlets - - try: - if self.reachable: - # set the status to the real state - status = await self.dli.status() - for o in outlets: - o.setState(status[o.portnum]) - else: - for o in outlets: - o.setState(-1) - except Exception as ex: - for o in outlets: - o.setState(-1) - raise RuntimeError(f"Unexpected exception for {type(ex)}: {ex}") - - async def switch(self, state: bool, outlets: list[Outlet]): - """Controls the switch (turning on or off). - - Parameters - ---------- - state - The state to which to switch the outlet(s). - outlets - List of outlets to command. - - """ - - state = bool(state) - - try: - if self.reachable: - for o in outlets: - await (self.dli.on(o.portnum) if state else self.dli.off(o.portnum)) - await self.update(outlets) - except Exception as ex: - raise RuntimeError(f"Unexpected exception to {type(ex)}: {ex}") diff --git a/python/lvmnps/switch/dummy/__init__.py b/python/lvmnps/switch/dummy/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/lvmnps/switch/dummy/powerswitch.py b/python/lvmnps/switch/dummy/powerswitch.py deleted file mode 100644 index 739de12..0000000 --- a/python/lvmnps/switch/dummy/powerswitch.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de) -# @Date: 2021-06-24 -# @Filename: lvmnps/switch/dummy/powerswitch.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -import asyncio - -from sdsstools.logger import SDSSLogger - -from lvmnps.switch.powerswitchbase import PowerSwitchBase - - -__all__ = ["PowerSwitch"] - - -class PowerSwitch(PowerSwitchBase): - """Powerswitch class to manage the Digital Loggers Web power switch""" - - def __init__(self, name: str, config: dict, log: SDSSLogger): - super().__init__(name, config, log) - self.delay = self.config_get("delay", 0.0) - for o in self.outlets: - o.setState(0) - - async def start(self): - if not await self.isReachable(): - self.log.warning(f"{self.name} not reachable on start up") - await self.update(self.outlets) - - async def stop(self): - self.log.debug( - "For a moment, nothing happened. Then, after a second or so, " - "nothing continued to happen ..." - ) - - async def isReachable(self): - return True - - async def update(self, outlets): - pass - - async def switch(self, state, outlets): - for o in outlets: - self.log.debug(f"{self.name} set") - await asyncio.sleep(self.delay) - self.log.debug(f"{self.name} {outlets}") - o.setState(state) diff --git a/python/lvmnps/switch/factory.py b/python/lvmnps/switch/factory.py deleted file mode 100644 index 6cf0d1e..0000000 --- a/python/lvmnps/switch/factory.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de) -# @Date: 2021-06-22 -# @Update: 2021-10-09 -# @Filename: lvmnps/switch/factory.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from typing import TYPE_CHECKING, Type - -from sdsstools.logger import SDSSLogger - -from lvmnps.exceptions import PowerException - -from .dli.powerswitch import DLIPowerSwitch -from .dummy.powerswitch import PowerSwitch as DummyPowerSwitch -from .iboot.powerswitch import PowerSwitch as IBootPowerSwitch -from .netio.powerswitch import PowerSwitch as NetIOPowerSwitch - - -if TYPE_CHECKING: - from lvmnps.switch.powerswitchbase import PowerSwitchBase - - -# from .iboot.powerswitch import PowerSwitch as IBootPowerSwitch - - -def powerSwitchFactory( - name: str, config: dict, log: SDSSLogger, simulate: bool = False -): - """Power switch factory method which helps the user to select the `.PowerSwitch` - class based on the configuration file that is selected. - - Parameters - ---------- - name - the name of the Dli Controller - config - The configuration dictionary from the configuration file .yml - log - The logger for logging - simulate - on True overwrite type from config with dummy - - """ - - def throwError(n, c): - """The method to throw the Exception.""" - raise PowerException(f"Power switch {n} with type {c['type']} not defined") - - factorymap: dict[str, Type[PowerSwitchBase]] = { - "dli": DLIPowerSwitch, - "netio": NetIOPowerSwitch, - "iboot": IBootPowerSwitch, - "dummy": DummyPowerSwitch, - } - - log.info(f"{simulate:-}") - return factorymap.get( - config["type"] if not simulate else "dummy", lambda n, c, _: throwError(n, c) - )(name, config, log) diff --git a/python/lvmnps/switch/iboot/iboot.py b/python/lvmnps/switch/iboot/iboot.py deleted file mode 100644 index f775804..0000000 --- a/python/lvmnps/switch/iboot/iboot.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2013, Luke Fitzgerald -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those -of the authors and should not be interpreted as representing official policies, -either expressed or implied, of the FreeBSD Project.""" - -import asyncio -import logging -import socket -import struct - - -HELLO_STR = "hello-000" - -HEADER_STRUCT = struct.Struct(" List[OUTPUT]: - """Return list of all outputs in format of self.OUTPUT""" - - @abstractmethod - def _set_outputs(self, actions: Dict[int, ACTION]) -> None: - """Set multiple outputs.""" - - def get_outputs(self) -> List[OUTPUT]: - """Returns list of available sockets and their state""" - return self._get_outputs() - - def get_outputs_filtered(self, ids): - """ """ - outputs = self.get_outputs() - for i in ids: - try: - yield next(filter(lambda output: output.ID == i, outputs)) - except StopIteration: - raise UnknownOutputId("Invalid output ID") - - def get_output(self, id: int) -> OUTPUT: - """Get state of single socket by its id""" - outputs = self.get_outputs() - try: - return next(filter(lambda output: output.ID == id, outputs)) - except StopIteration: - raise UnknownOutputId("Invalid output ID") - - def set_outputs(self, actions: Dict[int, ACTION]) -> None: - """ - Set state of multiple outputs at once - >>> n.set_outputs({1: n.ACTION.ON, 2:n.ACTION.OFF}) - """ - # TODO verify if socket id's are in range - if self._write_access: - self._set_outputs(actions) - else: - raise AuthError("cannot write, without write access") - - def set_output(self, id: int, action: ACTION) -> None: - self.set_outputs({id: action}) - - def __repr__(self): - return f"" - - -class JsonDevice(Device): - def __init__( - self, url, auth_r=None, auth_rw=None, verify=None, skip_init=False, timeout=2 - ): - self._url = url - self._verify = verify - self.timeout = timeout - - # read-write can do read, so we don't need read-only permission - if auth_rw: - self._user = auth_rw[0] - self._pass = auth_rw[1] - self._write_access = True - elif auth_r: - self._user = auth_r[0] - self._pass = auth_r[1] - else: - raise AuthError("No auth provided.") - - if not skip_init: - self.init() - - def init(self): - # request information about the Device - r_json = self._get() - - self.NumOutputs = r_json["Agent"]["NumOutputs"] - self.DeviceName = r_json["Agent"]["DeviceName"] - self.SerialNumber = r_json["Agent"]["SerialNumber"] - - def get_info(self): - r_json = self._get() - r_json.pop("Outputs") - return r_json - - @staticmethod - def _parse_response(response: requests.Response) -> dict: - """ - Parse JSON response according to - https://www.netio-products.com/files/NETIO-M2M-API-Protocol-JSON.pdf - """ - - if response.status_code == 400: - raise CommunicationError("Control command syntax error") - - if response.status_code == 401: - raise AuthError("Invalid Username or Password") - - if response.status_code == 403: - raise AuthError("Insufficient permissions to write") - - if not response.ok: - raise CommunicationError("Communication with device failed") - - try: - rj = response.json() - except ValueError: - raise CommunicationError("Response does not contain valid json") - - return rj - - def _post(self, body: dict) -> dict: - try: - response = requests.post( - self._url, - data=json.dumps(body), - auth=requests.auth.HTTPBasicAuth(self._user, self._pass), - verify=self._verify, - timeout=self.timeout, - ) - except requests.exceptions.SSLError: - raise AuthError("Invalid certificate") - - return self._parse_response(response) - - def _get(self) -> dict: - try: - response = requests.get( - self._url, - auth=requests.auth.HTTPBasicAuth(self._user, self._pass), - verify=self._verify, - timeout=self.timeout, - ) - except requests.exceptions.SSLError: - raise AuthError("Invalid certificate") - - return self._parse_response(response) - - def _get_outputs(self) -> List[Device.OUTPUT]: - """ - Send empty GET request to the device. - Parse out the output states according to specification. - """ - - r_json = self._get() - - outputs = list() - - for output in r_json.get("Outputs"): - state = self.OUTPUT( - ID=output.get("ID"), - Name=output.get("Name"), - State=output.get("State"), - Action=self.ACTION(output.get("Action")), - Delay=output.get("Delay"), - Current=output.get("Current"), - PowerFactor=output.get("PowerFactor"), - Load=output.get("Load"), - Energy=output.get("Energy"), - ) - outputs.append(state) - return outputs - - def _set_outputs(self, actions: dict) -> dict: - outputs = [] - for id, action in actions.items(): - outputs.append({"ID": id, "Action": action}) - - body = {"Outputs": outputs} - - return self._post(body) - - # TODO verify response action diff --git a/python/lvmnps/switch/netio/Netio/__init__.py b/python/lvmnps/switch/netio/Netio/__init__.py deleted file mode 100644 index e89b5cb..0000000 --- a/python/lvmnps/switch/netio/Netio/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .Device import JsonDevice as Netio # Default device diff --git a/python/lvmnps/switch/netio/Netio/__main__.py b/python/lvmnps/switch/netio/Netio/__main__.py deleted file mode 100644 index 2f05ddc..0000000 --- a/python/lvmnps/switch/netio/Netio/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .cli import main - - -if __name__ == "__main__": - main() diff --git a/python/lvmnps/switch/netio/Netio/cli.py b/python/lvmnps/switch/netio/Netio/cli.py deleted file mode 100644 index e6a2ece..0000000 --- a/python/lvmnps/switch/netio/Netio/cli.py +++ /dev/null @@ -1,396 +0,0 @@ -#!/usr/bin/env python3 -""" -Netio Command line interface -""" - -import argparse -import configparser -import os -import sys -import traceback -from typing import List -from urllib.parse import urlparse, urlunparse - -import pkg_resources -import requests - -from . import Netio -from .exceptions import NetioException - - -def str2action(s: str) -> Netio.ACTION: - """Parse Device.ACTION, either by name or by integer representation""" - try: - return Netio.ACTION[s.upper()] - except KeyError or AttributeError: - try: - return Netio.ACTION(int(s)) - except ValueError: - raise argparse.ArgumentTypeError( - f"{s!r} is not a valid " f"{Netio.ACTION.__name__}" - ) - - -# all Device.ACTION choices, including INT -ACTION_CHOICES = [e.value for e in Netio.ACTION] + [e.name for e in Netio.ACTION] - -EPILOG = """ -Report bugs to: averner@netio.eu -project repository and documentation: https://github.com/netioproducts/PyNetio -released under MIT license by NETIO Products a.s. -""" - - -def get_arg(arg, config, name, env_name, section, default): - """ - argument is looked up in this order: - 1. argument itself - 2. specified section - 3. DEFAULT section - 4. param default - """ - if arg == default: - if env_name and env_name in os.environ: - return os.environ[env_name] - if section and config.has_option(section, name): - return config[section][name] - return config["DEFAULT"].get(name, default) - return arg - - -def get_ids(id_strs: List[str], num_outputs: int) -> List[int]: - """ - Generate a list of integer IDs from list of strings. - The list can either contain one string "ALL" or an individual IDs - - >> get_ids(["ALL"], 4) - [1, 2, 3, 4] - """ - all_ids = range(1, num_outputs + 1) - all_outputs_mode = False - individual_outputs_mode = False - result = [] - - for id_str in id_strs: - if id_str.lower() == "all": - if individual_outputs_mode: - raise NetioException("Expecting either individual outputs IDs or 'ALL'") - all_outputs_mode = True - result = list(all_ids) - elif id_str.isdecimal() and int(id_str) in all_ids: - if all_outputs_mode: - raise NetioException("Expecting either individual outputs IDs or 'ALL'") - individual_outputs_mode = True - result.append(int(id_str)) - else: - raise NetioException( - f"Invalid output ID '{id_str}', " - f"valid range is {1}-{num_outputs + 1} or 'ALL'" - ) - - return result - - -def get_output_actions(ids_and_actions, num_outputs): - """ - Parse out pairs of ID + ACTION from iterable `ids_and_actions` - parse 'all' keyword if present. - input can't have combination of all and individual IDs - - return dictionary containing ID: ACTION pairs - """ - max_id = num_outputs + 1 - all_ids = range(1, max_id) - all_outputs_mode = False - individual_outputs_mode = False - result = {} - - if len(ids_and_actions) % 2 != 0: - raise NetioException( - "Expecting ID ACTION pairs but got an " - f"odd number of arguments ({len(ids_and_actions)})" - ) - pairs = zip(ids_and_actions[::2], ids_and_actions[1::2]) - - for pair in list(pairs): - id_str, action_str = pair - - if id_str.lower() == "all": - if individual_outputs_mode: - raise NetioException("Expecting either individual outputs IDs or 'ALL'") - all_outputs_mode = True - action = str2action(action_str) - result = dict(zip(all_ids, [action] * num_outputs)) - elif id_str.isdecimal() and int(id_str) in all_ids: - if all_outputs_mode: - raise NetioException("Expecting either individual outputs IDs or 'ALL'") - individual_outputs_mode = True - id = int(id_str) - if id in result: - raise NetioException( - "Multiple actions given for id " - "'{}' but expecting only one".format(id) - ) - action = str2action(action_str) - result[id] = action - else: - raise NetioException( - f"Invalid output ID '{id_str}', valid range is {1}-{max_id} or 'ALL'" - ) - - return result - - -def load_config(args): - """Load configuration file and other other configs""" - - config = configparser.ConfigParser( - {"user": "", "password": "", "no_cert_warning": ""} - ) - if not args.conf: - args.conf = os.environ.get("NETIO_CONFIG") - - if args.conf: - try: - with open(args.conf) as fp: - config.read_file(fp, args.conf) - except (TypeError, OSError, FileNotFoundError, configparser.Error) as e: - raise NetioException(f"Failed reading config ({e.__class__.__name__})") - - # resolve the device alias - if config.has_option(args.device, "url"): - args.device = config[args.device]["url"] - - u = urlparse(args.device) - - args.cert = get_arg(args.cert, config, "cert", None, u.netloc, True) - args.user = get_arg(args.user, config, "user", "NETIO_USER", u.netloc, None) - args.password = get_arg( - args.password, config, "password", "NETIO_PASSWORD", u.netloc, None - ) - args.no_cert_warning = get_arg( - args.no_cert_warning, config, "no_cert_warning", None, u.netloc, False - ) - - # resolve the path of cert relative to configuration path - basedir = ( - os.path.dirname(args.conf) if args.conf else os.path.dirname(os.path.curdir) - ) - args.cert = ( - args.cert if isinstance(args.cert, bool) else os.path.join(basedir, args.cert) - ) - - return args - - -def parse_args(): - parser = argparse.ArgumentParser(epilog=EPILOG) # prog='netio') - - parser.add_argument( - "device", metavar="DEVICE", action="store", help="Netio device URL" - ) - - parser.add_argument( - "-u", - "--user", - action="store", - dest="user", - metavar="U", - help="M2M API username", - ) - parser.add_argument( - "-p", - "--password", - action="store", - dest="password", - metavar="P", - help="M2M API password", - ) - - parser.add_argument( - "-C", - "--cert", - action="store_false", - dest="cert", - default=True, - help="HTTPS Certificate", - ) - parser.add_argument( - "-c", - "--config", - action="store", - dest="conf", - metavar="CFG", - help="Configuration file", - ) - parser.add_argument( - "-v", "--verbose", action="count", default=0, help="increase verbosity" - ) - parser.add_argument( - "--no-cert-warning", - action="store_true", - help="Disable warnings about certificate's subjectAltName versus commonName", - ) - - try: - version = pkg_resources.require("Netio")[0].version - except pkg_resources.DistributionNotFound: - version = "Unknown" - - parser.add_argument( - "--version", action="version", version=f"%(prog)s (version {version})" - ) - - command_parser = parser.add_subparsers(metavar="COMMAND", help="device command") - command_parser.required = True - - # GET command subparser - get_parser = command_parser.add_parser( - "get", help="GET output state", aliases=["GET", "G", "g"] - ) - get_parser.add_argument( - "id", - metavar="ID", - nargs="*", - default=["ALL"], - help="Output ID. All if not specified", - ) - get_parser.set_defaults(func=command_get) - get_parser.add_argument( - "-d", "--delimiter", action="store", dest="delim", default="\t", help="" - ) - get_parser.add_argument( - "--no-header", action="store_true", help="don't print column description" - ) - get_parser.add_argument( - "--action-int", action="store_true", help="print action as integer" - ) - - # SET command subparser - set_parser = command_parser.add_parser( - "set", help="SET output state", aliases=["SET", "S", "s"] - ) - set_parser.set_defaults(func=command_set) - # We are using a forged meta variable to get ID and action pairs into the - # help. The actual result is still a list of individual parameters and - # parsing the pairs is done later by get_output_actions. - set_parser.add_argument( - "id_and_action", - metavar="ID ACTION", - nargs="+", - help="output ID and action pairs" - " (valid actions: " - f"{[a.name for a in Netio.ACTION]})", - ) - - # INFO command subparser - info_parser = command_parser.add_parser( - "info", help="show device info", aliases=["INFO", "I", "i"] - ) - info_parser.set_defaults(func=command_info) - - return parser.parse_args() - - -def print_traceback(args, file=sys.stderr): - """ - Print traceback if requested by argument '--verbose'. A traceback is also - considered as requested if arguments have not been parsed yet (args are - None). - """ - if not args or (hasattr(args, "verbose") and args.verbose): - traceback.print_exc(file=file) - - -def main(): - """Main entry point of the app""" - args = None - - try: - args = parse_args() - args = load_config(args) - - if args.no_cert_warning: - requests.packages.urllib3.disable_warnings() - - u = urlparse(args.device) - u = u._replace(path="/netio.json") if not u.path else u # no path specified - - # try to run the specified command, on fail print nice fail message - device = Netio( - urlunparse(u), - auth_rw=(args.user, args.password), - verify=args.cert, - skip_init=True, - ) - args.func(device, args) - except NetioException as e: - print(e.args[0], file=sys.stderr) - print_traceback(args) - sys.exit(1) - except Exception as e: - print("Internal error: ", e, file=sys.stderr) - print_traceback(args) - sys.exit(1) - - -def command_set(device: Netio, args: argparse.Namespace) -> None: - """Set the output specified in args.id to args.action""" - - device.init() - - actions = get_output_actions(args.id_and_action, device.NumOutputs) - device.set_outputs(actions) - - -def command_get(device: Netio, args: argparse.Namespace) -> None: - """Print the state of the output and exit""" - - # init because we need to know NumOutputs so we can generate id list for "ALL" - # This initialization could be skipped, but that would require different handling - # for 'all' parameter - device.init() - - ids = get_ids(args.id, device.NumOutputs) # returns single or range - outputs = list(device.get_outputs_filtered(ids)) - - if not args.no_header: - print( - "id", - "State", - "Action", - "Delay", - "Current", - "PFactor", - "Load", - "Energy", - "Name", - sep=args.delim, - ) - for o in outputs: - action = o.Action.name if not args.action_int else o.Action.value - print( - o.ID, - o.State, - action, - o.Delay, - o.Current, - o.PowerFactor, - o.Load, - o.Energy, - o.Name, - sep=args.delim, - ) - - -def command_info(device: Netio, args: argparse.Namespace) -> None: - """Print out all data from device info""" - for key, data in device.get_info().items(): - print(key) - for subkey, value in data.items(): - pad = 18 - len(subkey) - print(" ", subkey, " " * pad, value) - - -if __name__ == "__main__": - main() diff --git a/python/lvmnps/switch/netio/Netio/exceptions.py b/python/lvmnps/switch/netio/Netio/exceptions.py deleted file mode 100644 index b1e4919..0000000 --- a/python/lvmnps/switch/netio/Netio/exceptions.py +++ /dev/null @@ -1,14 +0,0 @@ -class NetioException(Exception): - """Base exception for Device""" - - -class CommunicationError(NetioException): - """Communication with Device failed""" - - -class AuthError(NetioException): - """Authentication missing, or invalid""" - - -class UnknownOutputId(NetioException): - """Unknown output ID""" diff --git a/python/lvmnps/switch/netio/__init__.py b/python/lvmnps/switch/netio/__init__.py deleted file mode 100644 index 483a3a7..0000000 --- a/python/lvmnps/switch/netio/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de) -# @Date: 2022-07-11 -# @Filename: lvmnps/switch/netio/__init__.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) diff --git a/python/lvmnps/switch/netio/powerswitch.py b/python/lvmnps/switch/netio/powerswitch.py deleted file mode 100644 index 0091799..0000000 --- a/python/lvmnps/switch/netio/powerswitch.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de) -# @Date: 2022-07-11 -# @Filename: lvmnps/switch/netio/powerswitch.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -import asyncio - -from sdsstools.logger import SDSSLogger - -from lvmnps.switch.netio.Netio import Netio -from lvmnps.switch.powerswitchbase import PowerSwitchBase - - -__all__ = ["PowerSwitch"] - - -class PowerSwitch(PowerSwitchBase): - """Powerswitch class to manage the Netio Web power switch""" - - def __init__(self, name: str, config: dict, log: SDSSLogger): - super().__init__(name, config, log) - - for o in self.outlets: - o.setState(0) - - hostname = self.config_get("hostname") - username = self.config_get("username", "netio") - password = self.config_get("password", "netio") - - self.con_url = f"http://{hostname}/netio.json" - self.con_args = {"auth_rw": (username, password), "verify": True} - self.portsnum = int(self.config_get("ports.num", "4")) - - self.netio = Netio(self.con_url, **self.con_args, skip_init=True) - - async def start(self): - await self.update(self.outlets) - - async def stop(self): - self.log.debug( - "For a moment, nothing happened. Then, after a second or so, " - "nothing continued to happen ..." - ) - - async def isReachable(self): - loop = asyncio.get_event_loop() - try: - await loop.run_in_executor(None, self.netio.init) - return True - except Exception as ex: - self.log.error(f"{self.name}: {ex}") - return False - - async def update(self, outlets): - loop = asyncio.get_event_loop() - try: - relays = await loop.run_in_executor(None, self.netio.get_outputs) - for o in outlets: - o.setState( - relays[o.portnum - 1].State if o.portnum <= len(relays) else -1 - ) - - except Exception as ex: - self.log.error(f"{self.name}: {ex}") - for o in outlets: - o.setState(-1) - - async def switch(self, state, outlets): - loop = asyncio.get_event_loop() - try: - await loop.run_in_executor( - None, - self.netio.set_outputs, - { - o.portnum: Netio.ACTION.ON if state else Netio.ACTION.OFF - for o in outlets - }, - ) - self.log.debug(f"{self.name} {outlets}") - for o in outlets: - o.setState(state) - - except Exception as ex: - self.log.error(f"{self.name}: {ex}") - for o in outlets: - o.setState(-1) diff --git a/python/lvmnps/switch/outlet.py b/python/lvmnps/switch/outlet.py deleted file mode 100644 index 8d9a25e..0000000 --- a/python/lvmnps/switch/outlet.py +++ /dev/null @@ -1,96 +0,0 @@ -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de) -# @Date: 2021-06-22 -# @Filename: lvmnps/switch/outlet.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from .powerswitchbase import PowerSwitchBase - - -class Outlet(object): - """Outlet class to manage the power switch. - - Parameters - ---------- - switch - The parent `.PowerSwitchBase` instance to which this outlet is associated with. - name - The name of the outlet. - portnum - The number of the port. - description - The description about the outlet. - state - The state of the outlet (on: 1, off: 0). - - """ - - def __init__( - self, - switch: PowerSwitchBase, - name: str, - portnum: int, - description: str | None = None, - state: int = 0, - ): - self.switch = switch - self.name = name if name else f"{self.switch.name}.port{portnum}" - self.portnum = portnum - - default_description = f"{self.switch.name} Port {portnum}" - self.description = description if description else default_description - - self.inuse = bool(name) or bool(description) - self.state = state - - def __str__(self): - return f"#{self.portnum}:{self.name}={self.state}" - - def __repr__(self): - return self.__str__() - - @staticmethod - def parse(value): - """Parse the input data for ON/OFF.""" - - if isinstance(value, str): - value = value.lower() - - if value in ["off", "0", 0, False]: - return 0 - if value in ["on", "1", 1, True]: - return 1 - - return -1 - - def setState(self, value): - """Class method: Set the state of the outlet inside the class.""" - self.state = Outlet.parse(value) - - def isOn(self): - """Return the state of the outlet.""" - return self.state == 1 - - def isOff(self): - """Return the state of the outlet.""" - return self.state == 0 - - def isValid(self): - """Return the validity of the outlet.""" - return self.state == -1 - - def toDict(self): - """Return the dictionary describing the status of the outlet.""" - return { - "state": self.state, - "descr": self.description, - "switch": self.switch.name, - "port": self.portnum, - } diff --git a/python/lvmnps/switch/powerswitchbase.py b/python/lvmnps/switch/powerswitchbase.py deleted file mode 100644 index 30cfca9..0000000 --- a/python/lvmnps/switch/powerswitchbase.py +++ /dev/null @@ -1,269 +0,0 @@ -# -*- coding: utf-8 -*- -# -# @Author: Florian Briegel (briegel@mpia.de) -# @Date: 2021-06-24 -# @Filename: lvmnps/switch/powerswitchbase.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from abc import abstractmethod - -from sdsstools.logger import SDSSLogger - -from lvmnps.switch.outlet import Outlet - - -__all__ = ["PowerSwitchBase"] - - -class PowerSwitchBase(object): - """PowerSwitchBase class for multiple power switches from different manufacturers. - - The Powerswitch classes will inherit from the `.PowerSwitchBase` class. - - Parameters - ---------- - name - A name identifying the power switch. - config - The configuration defined on the .yaml file under ``/etc/lvmnps.yml``. - log - The logger for logging. - - """ - - def __init__(self, name: str, config: dict, log: SDSSLogger | None = None): - self.name = name - self.log = log or SDSSLogger(f"powerswitchbase.{name}") - self.config = config - - numports = self.config_get("ports.number_of_ports", 8) - if numports is None: - raise ValueError(f"{name}: unknown number of ports.") - self.numports: int = numports - - self.outlets = [ - Outlet( - self, - self.config_get(f"ports.{portnum}.name"), - portnum, - self.config_get(f"ports.{portnum}.desc"), - -1, - ) - for portnum in range(1, self.numports + 1) - ] - - self.onlyusedones = self.config_get("ouo", True) - self.log.debug(f"Only used ones: {self.onlyusedones}") - - def config_get(self, key, default=None): - """Read the configuration and extract the data as a structure that we want. - - Notice: DOESN'T work for keys with dots !!! - - Parameters - ---------- - key - The tree structure as a string to extract the data. - For example, if the configuration structure is :: - - ports: - 1: - desc: "Hg-Ar spectral callibration lamp" - - You can input the key as - ``ports.1.desc`` to take the information "Hg-Ar spectral callibration lamp". - - """ - - def g(config, key, d=None): - """Internal function for parsing the key from the configuration. - - Parameters - ---------- - config - config from the class member, which is saved from the class instance - key - The tree structure as a string to extract the data. - For example, if the configuration structure is :: - - ports: - num:1 - 1: - desc: "Hg-Ar spectral callibration lamp" - - You can input the key as "ports.1.desc" to take the information - "Hg-Ar spectral callibration lamp" - - """ - - k = key.split(".", maxsplit=1) - c = config.get( - k[0] if not k[0].isnumeric() else int(k[0]) - ) # keys can be numeric - - return ( - d - if c is None - else c - if len(k) < 2 - else g(c, k[1], d) - if type(c) is dict - else d - ) - - return g(self.config, key, default) - - def findOutletByName(self, name: str): - """Find the outlet by the name, comparing with the name from the Outlet object. - - Parameters - ---------- - name - The string to compare with the name in Outlet instance. - """ - for o in self.outlets: - if o.name.lower() == name.lower(): - return o - - def collectOutletsByNameAndPort( - self, - name: str | None = None, - portnum: int | None = None, - ): - """Collects the outlet by the name and ports, - comparing with the name and ports from the Outlet object. - - Parameters - ---------- - name - The string to compare with the name in Outlet instance. - portnum - The integer for indicating each Outlet instances. If zero or `None`, - identifies the outlet only by name. - - Returns - ------- - outlets - A list of `.Outlet` that match the name and port number. If ``name=None``, - the outlet matching the port number is returned. If both ``name`` and - ``portnum`` are `None`, a list with all the outlets connected to this - switch is returned. - - """ - - if not name or name == self.name: - if portnum: - if portnum > self.numports: - return [] - return [self.outlets[portnum - 1]] - else: - outlets = [] - - for o in self.outlets: - if o.inuse or not self.onlyusedones: - outlets.append(o) - - return outlets - else: - o = self.findOutletByName(name) - if o: - return [o] - - return [] - - async def setState( - self, - state: bool | int, - name: str | None = None, - portnum: int | None = None, - ): - """Set the state of the Outlet instance to On/Off. (On = 1, Off = 0). - - Note that dependending on the values passed to ``name`` and ``portnum``, - multiple outlets may be commanded. - - Parameters - ---------- - state - The boolean value (True, False) to set the state inside the Outlet object. - name - The string to compare with the name in Outlet instance. - portnum - The integer for indicating each Outlet instances. - - """ - - state_int = Outlet.parse(state) - if state_int == -1: - raise ValueError(f"{self.name}: cannot parse state {state!r}.") - - return await self.switch( - state_int, - self.collectOutletsByNameAndPort(name, portnum), - ) - - async def statusAsDict(self, name: str | None = None, portnum: int | None = None): - """Get the status of the `.Outlets` as a dictionary. - - Parameters - ---------- - name - The string to compare with the name in Outlet instance. - ``name`` can be a switch or an outlet name. - portnum - The integer for indicating each Outlet instances. - - """ - - outlets = self.collectOutletsByNameAndPort(name, portnum) - - await self.update(outlets) - - status = {} - for o in outlets: - status[f"{o.name}"] = o.toDict() - - return status - - @abstractmethod - async def start(self): - """Starts the switch instance, potentially connecting to the device server.""" - pass - - @abstractmethod - async def stop(self): - """Stops the connection to the switch server.""" - pass - - @abstractmethod - async def isReachable(self): - """Verify we can reach the switch. Returns `True` if ok.""" - pass - - @abstractmethod - async def update(self, outlets: list[Outlet] | None): - """Retrieves the status of a list of outlets and updates the internal mapping. - - Parameters - ---------- - outlets - A list of `.Outlets` to update. If `None`, all outlets are updated. - - """ - pass - - @abstractmethod - async def switch(self, state: int, outlets: list[Outlet]): - """Changes the state of an outlet. - - Parameters - ---------- - state - The final state for the outlets. 0: off, 1: on. - outlets - A list of `.Outlets` which status will be updated. - - """ - pass diff --git a/python/lvmnps/__init__.py b/src/lvmnps/__init__.py similarity index 69% rename from python/lvmnps/__init__.py rename to src/lvmnps/__init__.py index d032a79..b4e30ad 100644 --- a/python/lvmnps/__init__.py +++ b/src/lvmnps/__init__.py @@ -9,12 +9,13 @@ from sdsstools import get_config, get_logger, get_package_version -# pip package name NAME = "sdss-lvmnps" -# Loads config. config name is the package name. -config = get_config("lvmnps") -log = get_logger(NAME) -# package name should be pip package name +log = get_logger(NAME, use_rich_handler=True) + __version__ = get_package_version(path=__file__, package_name=NAME) + + +from .actor import NPSActor, NPSCommand +from .nps import DLIClient, NPSClient diff --git a/python/lvmnps/__main__.py b/src/lvmnps/__main__.py similarity index 53% rename from python/lvmnps/__main__.py rename to src/lvmnps/__main__.py index 956eed4..6e523db 100644 --- a/python/lvmnps/__main__.py +++ b/src/lvmnps/__main__.py @@ -1,5 +1,6 @@ import asyncio import functools +import logging import os import click @@ -29,38 +30,20 @@ def wrapper(*args, **kwargs): "--config", "config_file", type=click.Path(exists=True, dir_okay=False), + required=True, help="Path to the user configuration file.", ) -@click.option( - "-r", - "--rmq_url", - "rmq_url", - default=None, - type=str, - help="rabbitmq url, eg: amqp://guest:guest@localhost:5672/", -) @click.option( "-v", "--verbose", - count=True, - help="Debug mode. Use additional v for more details.", -) -@click.option( - "-s", - "--simulate", - count=True, - help="Simulation mode. Overwrite configured nps device with a dummy device", + is_flag=True, + help="Debug mode.", ) @click.pass_context -def lvmnps(ctx, config_file, rmq_url, verbose, simulate): - """Nps Actor.""" +def lvmnps(ctx: click.Context, config_file: str, verbose: bool = False): + """Network Power Supply actor.""" - ctx.obj = { - "verbose": verbose, - "config_file": config_file, - "rmq_url": rmq_url, - "simulate": simulate, - } + ctx.obj = {"verbose": verbose, "config_file": config_file} @lvmnps.group(cls=DaemonGroup, prog="nps_actor", workdir=os.getcwd()) @@ -69,20 +52,17 @@ def lvmnps(ctx, config_file, rmq_url, verbose, simulate): async def actor(ctx): """Runs the actor.""" - default_config_file = os.path.join(os.path.dirname(__file__), "etc/lvmnps.yml") - config_file = ctx.obj["config_file"] or default_config_file + config_file = ctx.obj["config_file"] + + lvmnps = NPSActor.from_config(config_file, verbose=ctx.obj["verbose"]) - lvmnps = NPSActor.from_config( - config_file, - url=ctx.obj["rmq_url"], - verbose=ctx.obj["verbose"], - simulate=ctx.obj["simulate"], - ) + if lvmnps.log.fh: + lvmnps.log.fh.setLevel(logging.NOTSET) if ctx.obj["verbose"]: - if lvmnps.log.fh: - lvmnps.log.fh.setLevel(0) - lvmnps.log.sh.setLevel(0) + lvmnps.log.sh.setLevel(logging.NOTSET) + else: + lvmnps.log.sh.setLevel(logging.INFO) await lvmnps.start() await lvmnps.run_forever() diff --git a/python/lvmnps/actor/__init__.py b/src/lvmnps/actor/__init__.py similarity index 100% rename from python/lvmnps/actor/__init__.py rename to src/lvmnps/actor/__init__.py diff --git a/src/lvmnps/actor/actor.py b/src/lvmnps/actor/actor.py new file mode 100644 index 0000000..2bdb0db --- /dev/null +++ b/src/lvmnps/actor/actor.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: Mingyeong YANG (mingyeong@khu.ac.kr), Florian Briegel (briegel@mpia.de) +# @Date: 2021-03-22 +# @Filename: lvmnps/actor/actor.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import pathlib +from os import PathLike + +from typing import TYPE_CHECKING + +from clu import Command +from clu.actor import AMQPActor +from sdsstools.configuration import Configuration + +from lvmnps import __version__ +from lvmnps import log as nps_log +from lvmnps.actor.commands import lvmnps_command_parser +from lvmnps.nps.core import NPSClient +from lvmnps.nps.implementations import VALID_NPS_TYPES + + +if TYPE_CHECKING: + from sdsstools.logger import SDSSLogger + + +__all__ = ["NPSActor"] + + +AnyPath = str | PathLike[str] + + +def get_nps_from_config(config: Configuration) -> NPSClient: + """Returns an `.NPSClient` instance from the configuration parameters.""" + + if "nps" not in config: + raise ValueError("nps section does not exist in the configuration.") + + nps_type = config["nps.type"] + if nps_type is None: + raise ValueError("nps.type not defined.") + + if nps_type not in VALID_NPS_TYPES: + raise ValueError(f"Invalid NPS {nps_type}. Valid types are {VALID_NPS_TYPES}.") + + init_parameters = config.get("nps.init_parameters", {}) + + if nps_type == "dli": + from lvmnps.nps.implementations.dli import DLIClient + + return DLIClient(**init_parameters) + + raise RuntimeError("Failed creating NPS client from configuration.") + + +class NPSActor(AMQPActor): + """LVM network power switches base actor.""" + + parser = lvmnps_command_parser + + def __init__( + self, + *args, + schema: AnyPath | None = None, + log: SDSSLogger | None = None, + **kwargs, + ): + cwd = pathlib.Path(__file__).parent + + schema = schema or cwd / "schema.json" + log = log or nps_log + + kwargs["version"] = __version__ + + super().__init__(*args, log=log, schema=schema, **kwargs) + + if not isinstance(self.config, Configuration): + self.config = Configuration(self.config) + + self.nps = get_nps_from_config(self.config) + + async def start(self, **kwargs): # pragma: no cover + """Starts the actor.""" + + await self.nps.setup() + + return await super().start(**kwargs) + + async def stop(self): + "Stops the actor." + + await self.nps.stop() + + return await super().stop() + + +NPSCommand = Command[NPSActor] diff --git a/src/lvmnps/actor/commands/__init__.py b/src/lvmnps/actor/commands/__init__.py new file mode 100644 index 0000000..dfaeb90 --- /dev/null +++ b/src/lvmnps/actor/commands/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: __init__.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +from clu.parsers.click import command_parser as lvmnps_command_parser + +from .onoff import cycle, off, on +from .refresh import refresh +from .scripts import scripts +from .status import status diff --git a/src/lvmnps/actor/commands/onoff.py b/src/lvmnps/actor/commands/onoff.py new file mode 100644 index 0000000..ea2881e --- /dev/null +++ b/src/lvmnps/actor/commands/onoff.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-23 +# @Filename: onoff.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import click + +from . import lvmnps_command_parser + + +if TYPE_CHECKING: + from src.lvmnps.actor.actor import NPSCommand + + +__all__ = ["on", "off", "cycle"] + + +class OutletNameParamType(click.ParamType): + name = "outlet_name" + + def convert(self, value, param, ctx): + if isinstance(value, int): + return value + + try: + return int(value, 10) + except ValueError: + return value + + +@lvmnps_command_parser.command() +@click.argument("OUTLETS", type=OutletNameParamType(), nargs=-1) +async def on(command: NPSCommand, outlets: tuple[str | int, ...]): + """Turns an outlet or outlets on. + + OUTLETS can be a single outlet name or outlet ID, or a list of them that will + be switched on as quickly as possible. An argument that can be casted into + an integer will be considered an outlet ID. + + """ + + nps = command.actor.nps + + outlet_data = await nps.set_state(outlets, on=True) + + command.finish(outlets=[outlet.model_dump() for outlet in outlet_data]) + + +@lvmnps_command_parser.command() +@click.argument("OUTLETS", type=OutletNameParamType(), nargs=-1) +async def off(command: NPSCommand, outlets: tuple[str | int, ...]): + """Turns an outlet or outlets off. + + OUTLETS can be a single outlet name or outlet ID, or a list of them that will + be switched on as quickly as possible. An argument that can be casted into + an integer will be considered an outlet ID. + + """ + + nps = command.actor.nps + + outlet_data = await nps.set_state(outlets, on=False) + + command.finish(outlets=[outlet.model_dump() for outlet in outlet_data]) + + +@lvmnps_command_parser.command() +@click.argument("OUTLETS", type=OutletNameParamType(), nargs=-1) +@click.option( + "--delay", + type=float, + default=3, + help="Delay between off an on in seconds", +) +async def cycle(command: NPSCommand, outlets: tuple[str | int, ...], delay: float = 3): + """Cycles an outlet or outlets. + + OUTLETS can be a single outlet name or outlet ID, or a list of them that will + be switched on as quickly as possible. An argument that can be casted into + an integer will be considered an outlet ID. + + """ + + nps = command.actor.nps + + outlet_data = await nps.cycle(outlets, delay=delay) + + command.finish(outlets=[outlet.model_dump() for outlet in outlet_data]) diff --git a/src/lvmnps/actor/commands/refresh.py b/src/lvmnps/actor/commands/refresh.py new file mode 100644 index 0000000..8dcd63f --- /dev/null +++ b/src/lvmnps/actor/commands/refresh.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-23 +# @Filename: refresh.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import click + +from . import lvmnps_command_parser + + +if TYPE_CHECKING: + from src.lvmnps.actor.actor import NPSCommand + + +__all__ = ["refresh"] + + +@lvmnps_command_parser.command() +@click.option( + "-q", + "--quiet", + is_flag=True, + help="Does not output status after refreshing", +) +async def refresh(command: NPSCommand, quiet: bool = False): + """Refreshes the internal list of outlets.""" + + nps = command.actor.nps + + await nps.refresh() + + if not quiet: + await command.child_command("status") + + command.finish() diff --git a/src/lvmnps/actor/commands/scripts.py b/src/lvmnps/actor/commands/scripts.py new file mode 100644 index 0000000..002f009 --- /dev/null +++ b/src/lvmnps/actor/commands/scripts.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-23 +# @Filename: scripts.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import click + +from . import lvmnps_command_parser + + +if TYPE_CHECKING: + from src.lvmnps.actor.actor import NPSCommand + + +__all__ = ["scripts"] + + +def validate_nps(command: NPSCommand): + """Checks that the NPS implements scripting.""" + + nps = command.actor.nps + + if nps.implementations.get("scripting", False) is False: + command.fail("Scripting not allowed for this NPS.") + return False + + return True + + +@lvmnps_command_parser.group() +def scripts(): + """Handles user scripts.""" + + pass + + +@scripts.command() +@click.argument("SCRIPT", type=str, nargs=-1) +async def run(command: NPSCommand, script: tuple[str, ...]): + """Runs a user script. + + The first argument is expected to be the name of the script. Additional + argument will be considered arguments to pass to the script function. + + """ + + if not validate_nps(command): + return + + if len(script) == 0: + return command.fail("Not enough parameters.") + + nps = command.actor.nps + + script_name = script[0] + script_args = script[1:] + + try: + thread_id = await nps.run_script(script_name, *script_args) + except Exception as err: + return command.fail(f"Failed executing script {script_name!r}: {err}") + + return command.finish( + script={ + "name": script_name, + "args": list(script_args), + "running": True, + "thread_id": thread_id, + } + ) + + +@scripts.command() +@click.argument("THREAD", type=int, required=False) +async def stop(command: NPSCommand, thread: int | None): + """Runs a user script. + + If the thread number is not provided, stops all running scripts. + + """ + + if not validate_nps(command): + return + + nps = command.actor.nps + + try: + await nps.stop_script(thread_num=thread) + except Exception: + return command.fail("Failed stopping scripts.") + + return command.finish() + + +@scripts.command(name="list") +async def list_scripts(command: NPSCommand): + """Lists running scripts.""" + + if not validate_nps(command): + return + + nps = command.actor.nps + + threads = await nps.list_running_scripts() + + scripts = [ + {"name": threads[id_], "args": [], "running": True, "thread_id": id_} + for id_ in threads + ] + + return command.finish(scripts=scripts) diff --git a/src/lvmnps/actor/commands/status.py b/src/lvmnps/actor/commands/status.py new file mode 100644 index 0000000..6ec3800 --- /dev/null +++ b/src/lvmnps/actor/commands/status.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: status.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from . import lvmnps_command_parser + + +if TYPE_CHECKING: + from src.lvmnps.actor.actor import NPSCommand + + +__all__ = ["status"] + + +@lvmnps_command_parser.command() +async def status(command: NPSCommand): + """Outputs the status of the network power switch.""" + + nps = command.actor.nps + + command.info(nps_type=nps.nps_type) + command.info(outlet_names=list(nps.outlets)) + + outlets = [outlet.model_dump() for outlet in nps.outlets.values()] + command.info(outlets=outlets) + + command.finish() diff --git a/src/lvmnps/actor/schema.json b/src/lvmnps/actor/schema.json new file mode 100644 index 0000000..c331ee1 --- /dev/null +++ b/src/lvmnps/actor/schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "definitions": { + "script": { + "type": "object", + "description": "Information about a user script", + "properties": { + "name": { "type": "string" }, + "args": { "type": "array", "items": { "type": "string" } }, + "running": { "type": "boolean" }, + "thread_id": { "type": "integer" } + }, + "required": ["name", "args", "running", "thread_id"], + "additionalProperties": false + } + }, + "type": "object", + "properties": { + "nps_type": { "type": "string", "description": "Type of NPS switch" }, + "outlet_names": { + "type": "array", + "items": { "type": "string" }, + "description": "List of outlet names" + }, + "outlets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Original name of the outlet" + }, + "normalised_name": { + "type": "string", + "description": "Normalised outlet name" + }, + "id": { + "type": "integer", + "description": "Numerical outlet identifier" + }, + "state": { + "type": "boolean", + "description": "Outlet state (true=ON)" + } + }, + "required": ["name", "normalised_name", "id", "state"], + "additionalProperties": true, + "description": "Properties of each outlet" + } + }, + "script": { "$ref": "#/definitions/script" }, + "scripts": { "type": "array", "items": { "$ref": "#/definitions/script" } } + }, + "additionalProperties": false +} diff --git a/src/lvmnps/exceptions.py b/src/lvmnps/exceptions.py new file mode 100644 index 0000000..9201e2a --- /dev/null +++ b/src/lvmnps/exceptions.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: exceptions.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + + +class NPSException(Exception): + """Base NPS exception.""" + + +class VerificationError(NPSException): + """Failed to connect to the power supply.""" + + +class ResponseError(NPSException): + """Invalid response from the power supply API.""" + + +class NPSWarning(UserWarning): + """Base NPS warning.""" diff --git a/src/lvmnps/nps/__init__.py b/src/lvmnps/nps/__init__.py new file mode 100644 index 0000000..267aec1 --- /dev/null +++ b/src/lvmnps/nps/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: __init__.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +from .core import NPSClient +from .implementations import * diff --git a/src/lvmnps/nps/core.py b/src/lvmnps/nps/core.py new file mode 100644 index 0000000..d407074 --- /dev/null +++ b/src/lvmnps/nps/core.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: core.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import abc +import asyncio + +from typing import Any, Sequence, TypedDict + +import httpx +from pydantic import BaseModel, ConfigDict + +from lvmnps import log +from lvmnps.exceptions import VerificationError +from lvmnps.tools import get_outlet_by_id, get_outlet_by_name, normalise_outlet_name + + +__all__ = ["NPSClient", "OutletModel", "OutletArgType"] + + +class OutletModel(BaseModel): + """A model for an outlet status.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + id: int + name: str + normalised_name: str = "" + state: bool = False + + _client: NPSClient | None = None + + def model_post_init(self, __context: Any) -> None: + self.normalised_name = normalise_outlet_name(self.name) + + return super().model_post_init(__context) + + def set_client(self, nps: NPSClient): + """Sets the NPS client.""" + + self._client = nps + + async def on(self): + """Sets the state of the outlet to "on".""" + + if not self._client: + raise RuntimeError("NPS client not set.") + + await self._client.set_state(self, on=True) + + async def off(self): + """Sets the state of the outlet to "off".""" + + if not self._client: + raise RuntimeError("NPS client not set.") + + await self._client.set_state(self, on=False) + + +OutletArgType = OutletModel | int | str | Sequence[str | int | OutletModel] + + +class ImplementationsDict(TypedDict): + """Dictionary of NPS implementations.""" + + scripting: bool + + +class NPSClient(abc.ABC): + """Base NPS client.""" + + nps_type: str + implementations: ImplementationsDict = {"scripting": False} + + def __init__(self): + self.outlets: dict[str, OutletModel] = {} + + # Time after switching an outlet on during which switching outlets on is + # delayed to prevent simultaneous inrush currents on power-on time. + self.delay: float = 1 + + async def setup(self): + """Sets up the power supply, setting any required configuration options.""" + + pass + + async def stop(self): + """Performs any necessary operations to gracefully disconnect from the NPS.""" + + pass + + @abc.abstractmethod + async def verify(self): + """Checks that the NPS is connected and responding.""" + + pass + + @abc.abstractmethod + async def refresh(self): + """Refreshes the list of outlets.""" + + pass + + def _validate_response(self, response: httpx.Response, expected_code: int = 200): + """Validates an HTTP response.""" + + if response.status_code != expected_code: + raise VerificationError( + f"Request returned response with status code {response.status_code}." + ) + + def get(self, outlet: int | str): + """Retrieves an outlet by ID or name.""" + + if isinstance(outlet, int): + return get_outlet_by_id(self.outlets, outlet) + elif isinstance(outlet, str): + return get_outlet_by_name(self.outlets, outlet) + else: + raise TypeError("Invalid outlet type. Only int and str are allowed.") + + async def set_state( + self, + outlets: OutletArgType, + on: bool = False, + ) -> list[OutletModel]: + """Sets the state of an outlet or list of outlets. + + Parameters + ---------- + outlets + An outlet or list of outlets whose state will be set. An outlet + can be specified by its name, number, or model instance. If a list + of outlet is provided the behaviour will depend on the client + implementation. Outlets may be switched concurrently or sequentially, + with a delay to avoid in-rush currents. + on + Whether to turn the outlet on (if ``True``) or off. + + """ + + _outlets: list[OutletModel] = [] + + if isinstance(outlets, str) or not isinstance(outlets, Sequence): + outlets = [outlets] + + for outlet in outlets: + if isinstance(outlet, str): + _outlets.append(get_outlet_by_name(self.outlets, outlet)) + elif isinstance(outlet, int): + _outlets.append(get_outlet_by_id(self.outlets, outlet)) + else: + _outlets.append(outlet) + + names = [outlet.name for outlet in _outlets] + log.debug(f"Setting outlets {names} to state on={on}.") + await self._set_state_internal(_outlets, on=on) + + await self.refresh() + + # Gets the updated outlets we modified. + switched_outlets: list[OutletModel] = [] + for _outlet in _outlets: + switched_outlets.append( + get_outlet_by_name( + self.outlets, + _outlet.normalised_name, + ) + ) + + return switched_outlets + + @abc.abstractmethod + async def _set_state_internal(self, outlets: list[OutletModel], on: bool = False): + """Internal method for setting the outlet state. + + This method is intended to be overridden by each specific implementation. + All implementations should handle switching single outlets or multiple ones, + and do it in a way that is safe and efficient given the hardware specifications. + + """ + + async def cycle( + self, + outlets: OutletArgType, + delay: float = 3, + ) -> list[OutletModel]: + """Turns off the selected outlets and turns them on again after a delay. + + Parameters + ---------- + outlets + An outlet or list of outlets whose state will be set. An outlet + can be specified by its name, number, or model instance. + delay + Number of seconds the code will wait before turning on the + outlets again. + + """ + + await self.set_state(outlets, on=False) + await asyncio.sleep(delay) + switched_outlets = await self.set_state(outlets, on=True) + + return switched_outlets + + async def run_script(self, name: str, *args, **kwargs) -> int: + """Runs a user script.""" + + raise NotImplementedError() + + async def stop_script(self, thread_num: int | None = None): + """Stops a running script.""" + + raise NotImplementedError() + + async def list_running_scripts(self) -> dict[int, str]: + """Returns a list of running scripts.""" + + raise NotImplementedError() diff --git a/src/lvmnps/nps/implementations/__init__.py b/src/lvmnps/nps/implementations/__init__.py new file mode 100644 index 0000000..9d7ab17 --- /dev/null +++ b/src/lvmnps/nps/implementations/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: __init__.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + + +__all__ = [ + "DLIClient", + "DLIOutletModel", + "NetIOClient", + "NetIOOutLetModel", + "VALID_NPS_TYPES", +] + + +VALID_NPS_TYPES: list[str] = ["dli"] + + +from .dli import DLIClient, DLIOutletModel +from .netio import NetIOClient, NetIOOutLetModel diff --git a/src/lvmnps/nps/implementations/dli.py b/src/lvmnps/nps/implementations/dli.py new file mode 100644 index 0000000..1e2655c --- /dev/null +++ b/src/lvmnps/nps/implementations/dli.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: dli.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import warnings + +import httpx +from pydantic import ConfigDict, SecretStr +from pydantic.dataclasses import dataclass + +from lvmnps import log +from lvmnps.exceptions import NPSWarning, ResponseError, VerificationError +from lvmnps.nps.core import NPSClient, OutletModel +from lvmnps.tools import APIClient + + +__all__ = ["DLIClient", "DLIOutletModel"] + + +class DLIOutletModel(OutletModel): + """A model for a DLI outlet status.""" + + index: int = 0 + physical_state: bool = False + transient_state: bool = False + critical: bool = False + locked: bool = False + cycle_delay: float | None = None + + +@dataclass(config=ConfigDict(extra="forbid")) +class DLIClient(NPSClient): + """An NPS client for a Digital Loggers switch.""" + + host: str + port: int = 80 + user: str = "admin" + password: SecretStr = SecretStr("admin") + api_route: str = "restapi/" + + def __post_init__(self): + super().__init__() + + self.base_url = f"http://{self.host}:{self.port}/{self.api_route}" + self.api_client = APIClient( + self.base_url, + self.user, + self.password, + auth_method="digest", + ) + + self.outlets: dict[str, DLIOutletModel] = {} + + self.nps_type = "dli" + self.implementations = {"scripting": True} + + async def setup(self): + """Sets up the power supply, setting any required configuration options.""" + + log.info("Setting up DLI switch.") + + try: + await self.verify() + except VerificationError as err: + warnings.warn( + "Cannot setup DLI. Power switch " + f"verification failed with error: {err}", + NPSWarning, + ) + return + + async with self.api_client as client: + # Change in-rush delay to 1 second. + log.debug("Setting sequence delay to 1 second.") + response = await client.put( + url="/relay/sequence_delay/", + data={"value": 1}, + headers={"X-CSRF": "x"}, + ) + self._validate_response(response, 204) + + await self.refresh() + + log.info("Set up complete.") + + async def verify(self): + """Checks that the NPS is connected and responding.""" + + async with self.api_client as client: + try: + response = await client.get(url="/", headers={"Range": "dli-depth=1"}) + except httpx.ConnectError as err: + raise VerificationError(f"Failed to connect to DLI: {err}") + + # 206 because we have asked for the API to only do depth=1 + self._validate_response(response, 206) + + async def refresh(self): + """Refreshes the list of outlets.""" + + log.debug("Refreshing list of outlets.") + + url = "/relay/outlets/" + async with self.api_client as client: + response = await client.get(url=url) + + self._validate_response(response) + + data = response.json() + log.debug(f"Found {len(data)} outlets.") + + self.outlets = {} + + for outlet_id in range(1, len(data) + 1): + outlet_data = data[outlet_id - 1] + outlet_data["id"] = outlet_id + outlet_data["index"] = outlet_id - 1 + + outlet = DLIOutletModel(**outlet_data) + outlet.set_client(self) + self.outlets[outlet.normalised_name] = outlet + + async def _set_state_internal( + self, + outlets: list[DLIOutletModel], + on: bool = False, + ): + """Sets the state of a list of outlets.""" + + outlet_indices = [outlet.index for outlet in outlets] + + # Use a matrix URI to set all the states at once. + outlet_path = "=" + ",".join(map(str, outlet_indices)) + + async with self.api_client as client: + response = await client.put( + url=f"/relay/outlets/{outlet_path}/state/", + data={"value": on}, + headers={"X-CSRF": "x"}, + ) + self._validate_response(response, 207) + + return + + async def list_scripts(self): + "Retrieves the list of user scripts." "" + + async with self.api_client as client: + response = await client.get(url="/script/user_functions/") + self._validate_response(response, 200) + + return list(response.json()) + + async def run_script(self, name: str, *args, check_exists: bool = True) -> int: + """Runs a user script. + + Parameters + ---------- + name + The script name. + args + Arguments with which to call the script function. + check_exists + If ``True``, checks that the script exists in the DLI before + executing it. + + Returns + ------- + thread_num + The thread identifier for the running script. + + """ + + if check_exists: + scripts = await self.list_scripts() + if name not in scripts: + raise ValueError(f"Unknown user function {name!r}.") + + data = {"user_function": name} + if len(args) > 0: + args_comma = ", ".join(map(str, args)) + data["source"] = f"{name}({args_comma})" + + async with self.api_client as client: + response = await client.post( + url="/script/start/", + json=[data], + headers={"X-CSRF": "x"}, + ) + + if response.status_code == 409: + raise ResponseError("Invalid function name or argument") + + self._validate_response(response, 200) + + thread_num = int(response.json()) + + log.info(f"Script {name!r} is running as thread {thread_num}") + + return thread_num + + async def stop_script(self, thread_num: int | None = None): + """Stops a running script. + + Parameters + ---------- + thread_num + The thread to stop. If not specified, stops all threads. + + """ + + if thread_num is None: + log.info("Stopping all script threads.") + else: + log.info(f"Stopping script thread {thread_num}.") + + async with self.api_client as client: + response = await client.post( + url="/script/stop/", + json=["all" if thread_num is None else str(thread_num)], + headers={"X-CSRF": "x"}, + ) + + self._validate_response(response, 200) + + async def list_running_scripts(self) -> dict[int, str]: + """Returns a mapping of running thread number to script name.""" + + threads: dict[int, str] = {} + + async with self.api_client as client: + response = await client.get(url="/script/threads/") + self._validate_response(response, 200) + + for thread, description in response.json().items(): + script_name = description["label"].split(" ")[0] + threads[int(thread)] = script_name + + return threads diff --git a/src/lvmnps/nps/implementations/netio.py b/src/lvmnps/nps/implementations/netio.py new file mode 100644 index 0000000..de82b2f --- /dev/null +++ b/src/lvmnps/nps/implementations/netio.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-23 +# @Filename: netio.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import warnings + +import httpx +from pydantic import ConfigDict, SecretStr +from pydantic.dataclasses import dataclass + +from lvmnps import log +from lvmnps.exceptions import NPSWarning, VerificationError +from lvmnps.nps.core import NPSClient, OutletModel +from lvmnps.tools import APIClient + + +class NetIOOutLetModel(OutletModel): + """Outlet model for NetIO switches.""" + + pass + + +@dataclass(config=ConfigDict(extra="forbid")) +class NetIOClient(NPSClient): + """An NPS client for a NetIO switch. + + This implementation uses the JSON API, see + https://www.netio-products.com/files/NETIO-M2M-API-Protocol-JSON.pdf + + """ + + host: str + port: int = 80 + user: str = "admin" + password: SecretStr = SecretStr("admin") + + def __post_init__(self): + super().__init__() + + self.base_url = f"http://{self.host}:{self.port}" + self.api_client = APIClient( + self.base_url, + self.user, + self.password, + auth_method="basic", + ) + + self.outlets: dict[str, NetIOOutLetModel] = {} + + self.nps_type = "netio" + self.implementations = {"scripting": False} + + async def setup(self): + """Sets up the power supply, setting any required configuration options.""" + + log.info("Setting up NetIO switch.") + + try: + await self.verify() + except VerificationError as err: + warnings.warn( + "Cannot setup NetIO. Power switch " + f"verification failed with error: {err}", + NPSWarning, + ) + return + + await self.refresh() + + log.info("Set up complete.") + + async def verify(self): + """Checks that the NPS is connected and responding.""" + + async with self.api_client as client: + try: + response = await client.get(url="/netio.json") + except httpx.ConnectError as err: + raise VerificationError(f"Failed to connect to NetIO: {err}") + + self._validate_response(response, 200) + + async def refresh(self): + """Refreshes the list of outlets.""" + + log.debug("Refreshing list of outlets.") + + async with self.api_client as client: + response = await client.get(url="/netio.json") + + self._validate_response(response) + + data = response.json() + outlet_json = data["Outputs"] + + log.debug(f"Found {len(outlet_json)} outlets.") + + self.outlets = {} + + for data in outlet_json: + outlet = NetIOOutLetModel( + id=data["ID"], + name=data["Name"], + state=data["State"], + ) + outlet.set_client(self) + self.outlets[outlet.normalised_name] = outlet + + async def _set_state_internal( + self, + outlets: list[NetIOOutLetModel], + on: bool = False, + ): + """Sets the state of a list of outlets.""" + + outputs: list[dict[str, str | int]] = [] + + for outlet in outlets: + outputs.append({"ID": outlet.id, "Action": int(on)}) + + async with self.api_client as client: + response = await client.post( + url="/netio.json", + json={"Outputs": outputs}, + timeout=5, + ) + + self._validate_response(response) + + return diff --git a/src/lvmnps/tools.py b/src/lvmnps/tools.py new file mode 100644 index 0000000..57e3281 --- /dev/null +++ b/src/lvmnps/tools.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: tools.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import asyncio + +from typing import TYPE_CHECKING + +import httpx +from pydantic import SecretStr +from pydantic.dataclasses import dataclass +from typing_extensions import Literal + +from lvmnps import log + + +if TYPE_CHECKING: + from lvmnps.nps.core import OutletModel + + +__all__ = [ + "APIClient", + "normalise_outlet_name", + "get_outlet_by_name", + "get_outlet_by_id", +] + + +@dataclass +class APIClient: + """A wrapper around ``httpx.AsyncClient`` to yield a new client.""" + + base_url: str + user: str + password: SecretStr + + auth_method: Literal["digest", "basic"] = "digest" + + def __post_init__(self): + self.client: httpx.AsyncClient | None = None + self.lock = asyncio.Lock() + + async def __aenter__(self): + """Yields a new client.""" + + await self.lock.acquire() + + log.debug(f"Creating async client to {self.base_url!r} with digest.") + + if self.auth_method == "digest": + auth = httpx.DigestAuth(self.user, self.password.get_secret_value()) + elif self.auth_method == "basic": + auth = (self.user, self.password.get_secret_value()) + else: + raise ValueError(f"Invalud authentication method {self.auth_method!r}.") + + self.client = httpx.AsyncClient( + auth=auth, + base_url=self.base_url, + headers={}, + ) + + return self.client + + async def __aexit__(self, exc_type, exc, tb): + """Closes the client.""" + + self.lock.release() + + if self.client and not self.client.is_closed: + log.debug("Closing async client.") + await self.client.aclose() + + +def normalise_outlet_name(name: str): + """Returns a normalised name for an outlet.""" + + return name.lower().replace(" ", "_") + + +def get_outlet_by_name(outlet_data: dict[str, OutletModel], name: str): + """Gets an outlet from a list of outlets. + + Parameters + ---------- + outlet_data + The mapping of outlet name to outlet model data. + name + The name of the outlet to retrieve. + + Returns + ------- + outlet + The outlet matching the input name. + + Raises + ------ + ValueError + If the outlet cannot be found. + + """ + + normalised_name = normalise_outlet_name(name) + + if normalised_name in outlet_data: + return outlet_data[normalised_name] + + raise ValueError(f"Cannot find outlet with name {name!r}.") + + +def get_outlet_by_id(outlet_data: dict[str, OutletModel], id: int): + """Gets an outlet by id. + + Parameters + ---------- + outlet_data + The mapping of outlet name to outlet model data. + id + The id of the outlet to retrieve. + + Returns + ------- + outlet + The outlet matching the id. + + Raises + ------ + ValueError + If the outlet cannot be found. + + """ + + for outlet in outlet_data.values(): + if outlet.id == id: + return outlet + + raise ValueError(f"Cannot find outlet with id {id!r}.") diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..bec4fe7 --- /dev/null +++ b/tasks.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: tasks.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import pathlib +import tempfile + +from invoke.context import Context +from invoke.tasks import task + + +@task() +def docs_live(context: Context): + """Uses sphinx-autobuild to build a temporary copy of the docs.""" + + docs_dir = pathlib.Path(__file__).parent / "docs/sphinx" + + with context.cd(docs_dir): + with tempfile.TemporaryDirectory() as destination: + context.run( + "sphinx-autobuild --port=0 --open-browser -b=dirhtml " + f"-a {docs_dir} {destination} --watch ../../src/lvmnps", + pty=True, + ) diff --git a/python/lvmnps/switch/dli/__init__.py b/tests/__init__.py similarity index 100% rename from python/lvmnps/switch/dli/__init__.py rename to tests/__init__.py diff --git a/tests/config.yaml b/tests/config.yaml new file mode 100644 index 0000000..3883331 --- /dev/null +++ b/tests/config.yaml @@ -0,0 +1,13 @@ +--- +nps: + type: dli + init_parameters: + host: 127.0.0.1 + port: 8088 + user: admin + password: admin + +actor: + name: lvmnps.test + host: localhost + port: 5672 diff --git a/tests/conftest.py b/tests/conftest.py index c8d6ec9..421a63f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,48 +1,186 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: conftest.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) from __future__ import annotations -import os +import pathlib + +from typing import TYPE_CHECKING import pytest +from pytest_httpx import HTTPXMock +from pytest_mock import MockerFixture -import clu.testing +from clu.testing import setup_test_actor from sdsstools import read_yaml_file -from sdsstools.logger import get_logger from lvmnps.actor.actor import NPSActor -from lvmnps.switch.factory import powerSwitchFactory +from lvmnps.nps import DLIClient +from lvmnps.nps.core import NPSClient, OutletModel +from lvmnps.nps.implementations.dli import DLIOutletModel +from lvmnps.nps.implementations.netio import NetIOClient + + +if TYPE_CHECKING: + from sdsstools import Configuration + + +class NPSTestClient(NPSClient): + """Test NPS client.""" + + nps_type = "test" + + async def setup(self): + await self.refresh() + + async def refresh(self): + if "test_1" in self.outlets: + return + + self.outlets = {"test_1": OutletModel(id=1, name="test_1", state=False)} + self.outlets["test_1"].set_client(self) + + async def verify(self): + pass + + async def _set_state_internal(self, outlets: list[OutletModel], on: bool = False): + for outlet in outlets: + outlet.state = on + + +@pytest.fixture +async def nps_test_client(): + _client = NPSTestClient() + await _client.setup() + + yield _client + + +dli_default_outlets = [ + { + "critical": False, + "transient_state": False, + "state": False, + "physical_state": False, + "name": "Argon", + "locked": False, + "cycle_delay": None, + }, + { + "critical": False, + "transient_state": True, + "state": True, + "physical_state": True, + "name": "Neon", + "locked": False, + "cycle_delay": None, + }, +] + + +@pytest.fixture +def lvmnps_config(): + yield read_yaml_file(pathlib.Path(__file__).parent / "./config.yaml") -@pytest.fixture() -def test_config(): - yield read_yaml_file(os.path.join(os.path.dirname(__file__), "test_switch.yml")) +@pytest.fixture +async def dli_client(httpx_mock: HTTPXMock, lvmnps_config: Configuration): + init_parameters = lvmnps_config["nps.init_parameters"] + + client = DLIClient(**init_parameters) + + _base_url = f"http://{init_parameters['host']}:{init_parameters['port']}/restapi" + + httpx_mock.add_response( + method="GET", + url=f"{_base_url}/", + status_code=206, + ) + + httpx_mock.add_response( + method="PUT", + url=f"{_base_url}/relay/sequence_delay/", + status_code=204, + ) + + httpx_mock.add_response( + method="GET", + url=f"{_base_url}/relay/outlets/", + json=dli_default_outlets, + status_code=200, + ) + + yield client + + +netio_default_outlets = { + "Agent": { + "Model": "4PS", + "DeviceName": "PowerPDU SkyW", + "MAC": "24:A4:2C:39:9D:27", + "SerialNumber": "24A42C399D27", + "JSONVer": "2.3", + "Time": "1970-01-02T04:24:59+01:00", + "Uptime": 55499, + "Version": "3.0.1", + "OemID": 100, + "VendorID": 0, + "NumOutputs": 4, + "NumInputs": 0, + }, + "Outputs": [ + {"ID": 1, "Name": "PW Mount", "State": 1, "Action": 6, "Delay": 5000}, + {"ID": 2, "Name": "PW MiniPC", "State": 1, "Action": 6, "Delay": 5000}, + {"ID": 3, "Name": "Power output 3", "State": 0, "Action": 6, "Delay": 5000}, + {"ID": 4, "Name": "Power output 4", "State": 0, "Action": 6, "Delay": 5000}, + ], +} @pytest.fixture -def switches(test_config): - assert "switches" in test_config +async def netio_client(httpx_mock: HTTPXMock): + client = NetIOClient(host="127.0.0.1") - switches = [] - for name, conf in test_config["switches"].items(): - try: - switches.append(powerSwitchFactory(name, conf, get_logger("test"))) - except Exception as ex: - print(f"Error in power switch factory {type(ex)}: {ex}") + _base_url = "http://127.0.0.1:80" + + httpx_mock.add_response( + method="GET", + url=f"{_base_url}/netio.json", + json=netio_default_outlets, + status_code=200, + ) + + httpx_mock.add_response( + method="GET", + url=f"{_base_url}/netio.json", + json=netio_default_outlets, + status_code=200, + ) + + yield client + + +@pytest.fixture +async def nps_actor(mocker: MockerFixture, lvmnps_config: Configuration): + actor = NPSActor.from_config(lvmnps_config) - return switches + actor.nps = mocker.MagicMock(spec=DLIClient) + actor.nps.nps_type = "dli" + actor.nps.outlets = {"outlet_1": DLIOutletModel(id=1, name="outlet_1")} + async def _set_state_mock(outlets: list[int | str], on: bool = False): + for outlet in actor.nps.outlets.values(): + outlet.state = on -@pytest.fixture() -async def actor(switches, test_config: dict): - _actor = NPSActor.from_config(test_config) - _actor = await clu.testing.setup_test_actor(_actor) # type: ignore + return list(actor.nps.outlets.values()) - _actor.parser_args = [{switch.name: switch for switch in switches}] - await _actor.start() + actor.nps.set_state = mocker.MagicMock(side_effect=_set_state_mock) - yield _actor + await setup_test_actor(actor) # type: ignore - _actor.mock_replies.clear() - await _actor.stop() + yield actor diff --git a/tests/test_actor.py b/tests/test_actor.py index 8503b5f..7438ef3 100644 --- a/tests/test_actor.py +++ b/tests/test_actor.py @@ -1,62 +1,162 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-23 +# @Filename: test_actor.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) from __future__ import annotations +from typing import TYPE_CHECKING + import pytest +from pytest_mock import MockerFixture + +from lvmnps.actor.actor import NPSActor + + +if TYPE_CHECKING: + from sdsstools import Configuration + + +async def test_actor(nps_actor: NPSActor): + assert isinstance(nps_actor, NPSActor) + + await nps_actor.stop() + + +@pytest.mark.parametrize("nps_type", [None, "bad_name"]) +async def test_actor_invalid_type(nps_type: str | None, lvmnps_config: Configuration): + lvmnps_config["nps"]["type"] = nps_type + + with pytest.raises(ValueError): + NPSActor.from_config(lvmnps_config) + + +async def test_actor_config_missing(lvmnps_config: Configuration): + del lvmnps_config["nps"] + + with pytest.raises(ValueError): + NPSActor.from_config(lvmnps_config) + + +async def test_command_status(nps_actor: NPSActor, mocker: MockerFixture): + cmd = await nps_actor.invoke_mock_command("status") + await cmd + + assert cmd.status.did_succeed + assert len(cmd.replies) == 5 + assert cmd.replies[-2].body == { + "outlets": [ + { + "critical": False, + "cycle_delay": None, + "id": 1, + "index": 0, + "locked": False, + "name": "outlet_1", + "normalised_name": "outlet_1", + "physical_state": False, + "state": False, + "transient_state": False, + } + ] + } + + +async def test_command_refresh(nps_actor: NPSActor, mocker: MockerFixture): + cmd = await nps_actor.invoke_mock_command("refresh") + await cmd + + assert cmd.status.did_succeed + + +@pytest.mark.parametrize("on", [True, False]) +@pytest.mark.parametrize("outlet", [1, "outlet_1"]) +async def test_command_onoff(nps_actor: NPSActor, outlet: str | int, on: bool): + cmd = await nps_actor.invoke_mock_command(f"on {outlet}" if on else f"off {outlet}") + await cmd + + assert cmd.status.did_succeed + assert nps_actor.nps.outlets["outlet_1"].state is on + + +async def test_command_cycle(nps_actor: NPSActor): + cmd = await nps_actor.invoke_mock_command("cycle 1") + await cmd + + assert cmd.status.did_succeed + + +async def test_command_script_list(nps_actor: NPSActor): + cmd = await nps_actor.invoke_mock_command("scripts list") + await cmd + + assert cmd.status.did_succeed + + +async def test_command_script_list_invalid(nps_actor: NPSActor): + nps_actor.nps.implementations = {"scripting": False} + + cmd = await nps_actor.invoke_mock_command("scripts list") + await cmd + + assert cmd.status.did_fail + + +async def test_command_script_run(nps_actor: NPSActor): + cmd = await nps_actor.invoke_mock_command("scripts run user_function1") + await cmd + + assert cmd.status.did_succeed -from lvmnps.actor.actor import AMQPActor, NPSActor +async def test_command_script_run_not_enough_args(nps_actor: NPSActor): + cmd = await nps_actor.invoke_mock_command("scripts run") + await cmd -async def test_actor(actor: NPSActor): - assert actor + assert cmd.status.did_fail -async def test_ping(actor: NPSActor): - command = await actor.invoke_mock_command("ping") - await command +async def test_command_script_run_invalid(nps_actor: NPSActor): + nps_actor.nps.implementations = {"scripting": False} - assert command.status.did_succeed - assert len(command.replies) == 2 - assert command.replies[1].message["text"] == "Pong." + cmd = await nps_actor.invoke_mock_command("scripts run user_function1") + await cmd + assert cmd.status.did_fail -async def test_actor_no_config(): - with pytest.raises(RuntimeError): - NPSActor.from_config(None) +async def test_command_script_run_fails(nps_actor: NPSActor, mocker: MockerFixture): + nps_actor.nps.run_script = mocker.AsyncMock(side_effect=RuntimeError) -async def test_actor_start(switches, test_config: dict, mocker): - actor = NPSActor.from_config(test_config) - mocker.patch.object(AMQPActor, "start") + cmd = await nps_actor.invoke_mock_command("scripts run user_function1") + await cmd - actor.parser_args = [{switch.name: switch for switch in switches}] + assert cmd.status.did_fail - for switch in switches: - mocker.patch.object(switch, "start") - await actor.start() +async def test_command_script_stop(nps_actor: NPSActor): + cmd = await nps_actor.invoke_mock_command("scripts stop") + await cmd - assert len(actor.parser_args[0].keys()) == len(switches) + assert cmd.status.did_succeed - await actor.stop() +async def test_command_script_stop_invalid(nps_actor: NPSActor): + nps_actor.nps.implementations = {"scripting": False} -async def test_actor_start_one_fails(switches, test_config: dict, mocker): - actor = NPSActor.from_config(test_config) - mocker.patch.object(AMQPActor, "start") + cmd = await nps_actor.invoke_mock_command("scripts stop") + await cmd - actor.parser_args = [{switch.name: switch for switch in switches}] + assert cmd.status.did_fail - for ii, switch in enumerate(switches): - mocker.patch.object( - switch, - "start", - side_effect=None if ii != 1 else RuntimeError, - ) - await actor.start() +async def test_command_script_stop_fails(nps_actor: NPSActor, mocker: MockerFixture): + nps_actor.nps.stop_script = mocker.AsyncMock(side_effect=RuntimeError) - assert len(actor.parser_args[0].keys()) == len(switches) - 1 + cmd = await nps_actor.invoke_mock_command("scripts stop") + await cmd - await actor.stop() + assert cmd.status.did_fail diff --git a/tests/test_async.py b/tests/test_async.py deleted file mode 100644 index c71caa1..0000000 --- a/tests/test_async.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import annotations - -import asyncio -from typing import Any - -from lvmnps.actor.actor import NPSActor - - -async def test_async_onoff(switches, actor: NPSActor): - # status check of nps_dummy_1 port1 - assert actor - - assert switches[3].name == "slow" - assert switches[3].outlets[0].name == "slow" - assert switches[3].outlets[0].state == 0 - - assert switches[4].name == "fast" - assert switches[4].outlets[0].name == "fast" - assert switches[4].outlets[0].state == 0 - - task = [] - task.append(actor.invoke_mock_command("on slow 1")) - task.append(actor.invoke_mock_command("on fast 1")) - - await asyncio.gather(*task) - - status_task = [] - status_task.append(actor.invoke_mock_command("status slow 1")) - status_task.append(actor.invoke_mock_command("status fast 1")) - - status_before: Any = await asyncio.gather(*status_task) - assert status_before[0].replies[-1].message["status"]["slow"]["slow"]["state"] == 0 - assert status_before[1].replies[-1].message["status"]["fast"]["fast"]["state"] == 1 - - await asyncio.sleep(2) - - status_task = [] - status_task.append(actor.invoke_mock_command("status slow 1")) - status_task.append(actor.invoke_mock_command("status fast 1")) - - status_after: Any = await asyncio.gather(*status_task) - assert status_after[0].replies[-1].message["status"]["slow"]["slow"]["state"] == 1 - assert status_after[1].replies[-1].message["status"]["fast"]["fast"]["state"] == 1 diff --git a/tests/test_dli.py b/tests/test_dli.py index 5302ab0..9316420 100644 --- a/tests/test_dli.py +++ b/tests/test_dli.py @@ -1,131 +1,184 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-23 +# @Filename: test_dli.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) from __future__ import annotations -import os +import re -import pytest - -from sdsstools import read_yaml_file +from typing import TYPE_CHECKING -from lvmnps.switch.dli.powerswitch import DLIPowerSwitch +import httpx +import pytest +from pytest_httpx import HTTPXMock +from lvmnps.exceptions import NPSWarning, ResponseError, VerificationError -@pytest.fixture -async def dli_switches(mocker): - root = os.path.dirname(__file__) - config = read_yaml_file(os.path.join(root, "test_dli_switch.yml")) +from .conftest import dli_default_outlets - switches = [] - for switch_name in config["switches"]: - switch = DLIPowerSwitch(switch_name, config["switches"][switch_name]) - # Fake the client reply to a get(/relay/outlets) - get_mock = mocker.MagicMock(status_code=200) +if TYPE_CHECKING: + from lvmnps.nps import DLIClient - # Build a fake reply with two outlets defined and six empty ones - get_mock.json.return_value = [ - {"state": False, "name": "Outlet 1"}, - {"state": True, "name": "Outlet 2"}, - ] - get_mock.json.return_value += 6 * [{"state": False, "name": ""}] - # Patch the client to use the mocked get method. - mocker.patch.object( - switch.dli.client, - "get", - return_value=get_mock, - ) +async def test_dli(dli_client: DLIClient): + await dli_client.setup() - # Also mock PUT - mocker.patch.object( - switch.dli.client, - "put", - return_value=mocker.MagicMock(status_code=204), - ) + assert len(dli_client.outlets) == 2 - await switch.start() - switches.append(switch) +async def test_dli_verification_fails(dli_client: DLIClient, httpx_mock: HTTPXMock): + httpx_mock.reset(False) + httpx_mock.add_response(url=re.compile(r"http://.+?/restapi/"), status_code=500) - yield switches + with pytest.warns(NPSWarning): + await dli_client.setup() - for switch in switches: - await switch.stop() + assert len(dli_client.outlets) == 0 -async def test_dli_power_switch(dli_switches: list[DLIPowerSwitch]): - switch = dli_switches[0] +async def test_dli_verification_connection_error( + dli_client: DLIClient, + httpx_mock: HTTPXMock, +): + httpx_mock.reset(False) + httpx_mock.add_exception(httpx.ConnectError("Connection failed")) - assert switch.name == "DLI-01" - assert len(switch.outlets) == 8 - assert switch.outlets[1].inuse is False + with pytest.raises(VerificationError): + await dli_client.verify() -async def test_dli_power_switch_handle_undefined(dli_switches: list[DLIPowerSwitch]): - switch = dli_switches[0] - switch.onlyusedones = False +async def test_dli_set_state(dli_client: DLIClient, httpx_mock: HTTPXMock): + await dli_client.setup() - await switch.start() + httpx_mock.add_response( + method="PUT", + url=re.compile(r"http://.+?/restapi/relay/outlets/=0/state/"), + status_code=207, + ) - assert switch.name == "DLI-01" - assert len(switch.outlets) == 8 + response_json = dli_default_outlets.copy() + response_json[0]["state"] = True - assert switch.outlets[1].name == "Outlet 2" - assert switch.outlets[1].inuse is True - assert (await switch.isReachable()) is True + httpx_mock.add_response( + method="GET", + url=re.compile(r"http://.+?/restapi/relay/outlets/"), + status_code=200, + json=response_json, + ) + await dli_client.set_state("argon", on=True) -async def test_dli_on(dli_switches: list[DLIPowerSwitch]): - switch = dli_switches[0] + assert dli_client.outlets["argon"].state is True - assert switch.outlets[0].state == 0 - await switch.switch(True, [switch.outlets[0]]) +async def test_dli_list_scripts(dli_client: DLIClient, httpx_mock: HTTPXMock): + await dli_client.setup() + httpx_mock.add_response( + method="GET", + url=re.compile(r"http://.+?/restapi/script/user_functions/"), + status_code=200, + json={"user_function1": {}, "user_function2": {}}, + ) -async def test_dli_off(dli_switches: list[DLIPowerSwitch]): - switch = dli_switches[0] + user_functions = await dli_client.list_scripts() + assert len(user_functions) == 2 - await switch.switch(False, [switch.outlets[0]]) +async def test_dli_list_running_scripts(dli_client: DLIClient, httpx_mock: HTTPXMock): + await dli_client.setup() -async def test_dli_verify_fails(dli_switches: list[DLIPowerSwitch], mocker): - switch = dli_switches[0] - mocker.patch.object(switch.dli, "verify", side_effect=ValueError) + httpx_mock.add_response( + method="GET", + url=re.compile(r"http://.+?/restapi/script/threads/"), + status_code=200, + json={"1": {"label": "user_function1 (blah)"}}, + ) - with pytest.raises(RuntimeError): - await switch.start() + running_threads = await dli_client.list_running_scripts() + assert running_threads == {1: "user_function1"} -def test_dli_missing_credentials(): - with pytest.raises(ValueError): - DLIPowerSwitch("test", {}) +@pytest.mark.parametrize("check_exists", [True, False]) +@pytest.mark.parametrize("function_args", [[1, 2], []]) +async def test_dli_run_script( + dli_client: DLIClient, + httpx_mock: HTTPXMock, + check_exists: bool, + function_args: list, +): + await dli_client.setup() + if check_exists: + httpx_mock.add_response( + method="GET", + url=re.compile(r"http://.+?/restapi/script/user_functions/"), + status_code=200, + json={"user_function1": {}, "user_function2": {}}, + ) -async def test_dli_switch_fails(dli_switches: list[DLIPowerSwitch], mocker): - switch = dli_switches[0] - mocker.patch.object(switch.dli, "on", side_effect=ValueError) + httpx_mock.add_response( + method="POST", + url=re.compile(r"http://.+?/restapi/script/start/"), + status_code=200, + json="1", + ) + + await dli_client.run_script( + "user_function1", + *function_args, + check_exists=check_exists, + ) + + +async def test_dli_run_script_does_not_exist( + dli_client: DLIClient, + httpx_mock: HTTPXMock, +): + await dli_client.setup() + + httpx_mock.add_response( + method="GET", + url=re.compile(r"http://.+?/restapi/script/user_functions/"), + status_code=200, + json={"user_function3": {}}, + ) - with pytest.raises(RuntimeError): - await switch.switch(True, [switch.outlets[0]]) + with pytest.raises(ValueError): + await dli_client.run_script("user_function1") -async def test_dli_update_fails(dli_switches: list[DLIPowerSwitch], mocker): - switch = dli_switches[0] - mocker.patch.object(switch.dli, "status", side_effect=ValueError) +async def test_dli_run_script_invalid(dli_client: DLIClient, httpx_mock: HTTPXMock): + await dli_client.setup() - with pytest.raises(RuntimeError): - await switch.update([switch.outlets[0]]) + httpx_mock.add_response( + method="POST", + url=re.compile(r"http://.+?/restapi/script/start/"), + status_code=409, + json="1", + ) - assert switch.outlets[0].state == -1 + with pytest.raises(ResponseError): + await dli_client.run_script("user_function1", check_exists=False) -async def test_dli_update_unreachable(dli_switches: list[DLIPowerSwitch], mocker): - switch = dli_switches[0] - switch.reachable = False +@pytest.mark.parametrize("thread_num", [None, 1]) +async def test_dli_stop_script( + dli_client: DLIClient, + httpx_mock: HTTPXMock, + thread_num: int | None, +): + await dli_client.setup() - await switch.update([switch.outlets[0]]) + httpx_mock.add_response( + method="POST", + url=re.compile(r"http://.+?/restapi/script/stop/"), + status_code=200, + ) - assert switch.outlets[0].state == -1 + await dli_client.stop_script(thread_num) diff --git a/tests/test_dli_switch.yml b/tests/test_dli_switch.yml deleted file mode 100644 index 2bc1606..0000000 --- a/tests/test_dli_switch.yml +++ /dev/null @@ -1,15 +0,0 @@ -# A dictionary of controller name to NPS controller connection parameters. -switches: - DLI-01: - type: dli - name: DLI-01 - hostname: 10.7.45.22 - user: 'admin' - password: 'rLXR3KxUqiCPGvA' - onoff_timeout: 3 - ouo: True - ports: - number_of_ports: 8 - 1: - name: 'Outlet 1' - desc: '' diff --git a/tests/test_netio.py b/tests/test_netio.py new file mode 100644 index 0000000..41ee56a --- /dev/null +++ b/tests/test_netio.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: test_nps.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import re + +from typing import TYPE_CHECKING + +import httpx +import pytest +from pytest_httpx import HTTPXMock + +from lvmnps.exceptions import NPSWarning, VerificationError + +from .conftest import netio_default_outlets + + +if TYPE_CHECKING: + from lvmnps.nps import NetIOClient + + +async def test_netio(netio_client: NetIOClient): + await netio_client.setup() + + assert len(netio_client.outlets) == 4 + + +async def test_netio_verification_fails( + netio_client: NetIOClient, + httpx_mock: HTTPXMock, +): + httpx_mock.reset(False) + httpx_mock.add_response(url=re.compile(r"http://.+?/netio.json"), status_code=500) + + with pytest.warns(NPSWarning): + await netio_client.setup() + + assert len(netio_client.outlets) == 0 + + +async def test_netio_verification_connection_error( + netio_client: NetIOClient, + httpx_mock: HTTPXMock, +): + httpx_mock.reset(False) + httpx_mock.add_exception(httpx.ConnectError("Connection failed")) + + with pytest.raises(VerificationError): + await netio_client.verify() + + +async def test_netio_set_state(netio_client: NetIOClient, httpx_mock: HTTPXMock): + await netio_client.setup() + + httpx_mock.add_response( + method="POST", + url=re.compile(r"http://.+?/netio.json"), + status_code=200, + ) + + response_json = netio_default_outlets.copy() + response_json["Outputs"][2]["State"] = True + + httpx_mock.add_response( + method="GET", + url=re.compile(r"http://.+?/netio.json"), + status_code=200, + json=response_json, + ) + + await netio_client.set_state(3, on=True) + + assert netio_client.get(3).state is True diff --git a/tests/test_nps.py b/tests/test_nps.py new file mode 100644 index 0000000..0a5b1ec --- /dev/null +++ b/tests/test_nps.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-22 +# @Filename: test_nps.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import pytest + +from lvmnps.nps.core import NPSClient, OutletModel + + +async def test_npsclient(nps_test_client: NPSClient): + await nps_test_client.stop() + + assert nps_test_client is not None + assert len(nps_test_client.outlets) == 1 + + +@pytest.mark.parametrize("outlets", [1, "test_1", "Test_1", [1], ["test_1"]]) +async def test_set_state(nps_test_client: NPSClient, outlets): + assert nps_test_client is not None + + await nps_test_client.set_state(outlets, on=True) + + outlet = nps_test_client.outlets["test_1"] + assert outlet.state is True + + +async def test_set_state_outlet_model(nps_test_client: NPSClient): + assert nps_test_client is not None + + outlet = nps_test_client.outlets["test_1"] + await nps_test_client.set_state([outlet], on=True) + + assert outlet.state is True + + +@pytest.mark.parametrize("client", [True, False]) +async def test_outlet_on_off(nps_test_client: NPSClient, client: bool): + assert nps_test_client is not None + + outlet = nps_test_client.outlets["test_1"] + + if client: + await outlet.on() + assert outlet.state is True + + await outlet.off() + assert outlet.state is False + + else: + outlet._client = None + + with pytest.raises(RuntimeError): + await outlet.off() + + with pytest.raises(RuntimeError): + await outlet.on() + + +async def test_outlet_cycle(nps_test_client: NPSClient): + assert nps_test_client is not None + + outlet = nps_test_client.outlets["test_1"] + await nps_test_client.cycle(outlet, delay=0.1) + + assert outlet.state is True + + +@pytest.mark.parametrize("outlet", [1, "test_1"]) +async def test_client_get(nps_test_client: NPSClient, outlet: str | int): + assert isinstance(nps_test_client.get(outlet), OutletModel) + + +async def test_client_get_invalid_type(nps_test_client: NPSClient): + with pytest.raises(TypeError): + nps_test_client.get(None) # type: ignore diff --git a/tests/test_onoff.py b/tests/test_onoff.py deleted file mode 100644 index d8e8235..0000000 --- a/tests/test_onoff.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import annotations - -import asyncio - -from lvmnps.actor.actor import NPSActor - - -async def test_onoff(switches, actor: NPSActor): - assert switches[0].name == "nps_dummy_1" - assert switches[0].outlets[0].name == "port1" - assert switches[0].outlets[0].state == 0 - - # switch on nps_dummy_1 port1 - command = await actor.invoke_mock_command("on nps_dummy_1 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 4 - assert command.replies[-2].message["status"]["nps_dummy_1"]["port1"]["state"] == 1 - - assert switches[0].outlets[0].state == 1 - - # switch off nps_dummy_1 port1 - command = await actor.invoke_mock_command("off nps_dummy_1 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 4 - assert command.replies[-2].message["status"]["nps_dummy_1"]["port1"]["state"] == 0 - - assert switches[0].outlets[0].state == 0 - - # switch skye.nps port 1 - assert switches[1].name == "skye.nps" - assert switches[1].outlets[0].name == "skye.pwi" - switches[1].outlets[0].state = 0 - assert switches[1].outlets[0].state == 0 - - # switch on skye.nps port1 - command = await actor.invoke_mock_command("on skye.nps 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 4 - assert command.replies[-2].message["status"]["skye.nps"]["skye.pwi"]["state"] == 1 - assert switches[1].outlets[0].state == 1 - - # switch off skye.nps port1 - command = await actor.invoke_mock_command("off skye.nps 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 4 - assert command.replies[-2].message["status"]["skye.nps"]["skye.pwi"]["state"] == 0 - assert switches[1].outlets[0].state == 0 - - -async def test_status_already_on(switches, actor: NPSActor): - assert actor - - assert switches[0].name == "nps_dummy_1" - assert switches[0].outlets[0].name == "port1" - switches[0].outlets[0].state = 0 - assert switches[0].outlets[0].state == 0 - - # switch on nps_dummy_1 port1 - command = await actor.invoke_mock_command("on nps_dummy_1 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 4 - assert command.replies[-2].message["status"]["nps_dummy_1"]["port1"]["state"] == 1 - - # switch on nps_dummy_1 port1 - command = await actor.invoke_mock_command("on nps_dummy_1 1") - await command - assert command.status.did_succeed - - -async def test_status_already_off(switches, actor: NPSActor): - assert actor - assert switches[0].name == "nps_dummy_1" - assert switches[0].outlets[0].name == "port1" - switches[0].outlets[0].state = 0 - assert switches[0].outlets[0].state == 0 - - # switch on nps_dummy_1 port1 - command = await actor.invoke_mock_command("on nps_dummy_1 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 4 - assert command.replies[-2].message["status"]["nps_dummy_1"]["port1"]["state"] == 1 - - # switch off nps_dummy_1 port1 - command = await actor.invoke_mock_command("off nps_dummy_1 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 4 - assert command.replies[-2].message["status"]["nps_dummy_1"]["port1"]["state"] == 0 - - # switch off nps_dummy_1 port1 - command = await actor.invoke_mock_command("off nps_dummy_1 1") - await command - assert command.status.did_succeed - - -async def test_on_succeed(switches, actor: NPSActor): - assert actor - assert switches[0].name == "nps_dummy_1" - assert switches[0].outlets[0].name == "port1" - switches[0].outlets[0].state = -1 - assert switches[0].outlets[0].state == -1 - - # switch on nps_dummy_1 port1 - command = await actor.invoke_mock_command("on nps_dummy_1 1") - await command - assert command.status.did_succeed - - -async def test_off_succeed(switches, actor: NPSActor): - assert actor - assert switches[0].name == "nps_dummy_1" - assert switches[0].outlets[0].name == "port1" - switches[0].outlets[0].state = -1 - assert switches[0].outlets[0].state == -1 - - # switch on nps_dummy_1 port1 - command = await actor.invoke_mock_command("off nps_dummy_1 1") - await command - assert command.status.did_succeed - - -async def test_status_off_after(switches, actor: NPSActor): - assert actor - assert switches[0].name == "nps_dummy_1" - assert switches[0].outlets[0].name == "port1" - switches[0].outlets[0].state = 0 - assert switches[0].outlets[0].state == 0 - - # switch on nps_dummy_1 port1 - status = [] - - status.append( - asyncio.create_task(actor.invoke_mock_command("on --off-after 3 nps_dummy_1 1")) - ) - status.append(asyncio.create_task(say_after(0.2, actor))) - - status_result = list(await asyncio.gather(*status)) - - status = status_result[1].replies[-1].message["status"] - assert status["nps_dummy_1"]["port1"]["state"] == 1 # noqa: W503 - - await asyncio.sleep(2) - - command = await actor.invoke_mock_command("status nps_dummy_1 1") - await command - assert command.status.did_succeed - assert command.replies[-1].message["status"]["nps_dummy_1"]["port1"]["state"] == 0 - assert switches[0].outlets[0].state == 0 - - -async def say_after(delay, actor_mock): - await asyncio.sleep(delay) - command = await actor_mock.invoke_mock_command("status nps_dummy_1 1") - await command - assert command.status.did_succeed - return command diff --git a/tests/test_outlets.py b/tests/test_outlets.py deleted file mode 100644 index 6c9222d..0000000 --- a/tests/test_outlets.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: José Sánchez-Gallego (gallegoj@uw.edu) -# @Date: 2022-05-22 -# @Filename: test_outlets.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from lvmnps.actor import NPSActor - - -async def test_command_outlets(actor: NPSActor): - cmd = await (await actor.invoke_mock_command("outlets")) - assert cmd.status.did_succeed - - assert len(cmd.replies) == 2 - assert cmd.replies.get("outlets") == [ - "port1", - "skye.what.ever", - "skyw.what.ever", - "skye.pwi", - "skyw.pwi", - "slow", - "fast", - ] - - -async def test_command_outlets_switchname(actor: NPSActor): - cmd = await (await actor.invoke_mock_command("outlets nps_dummy_1")) - assert cmd.status.did_succeed - - assert len(cmd.replies) == 2 - assert cmd.replies.get("outlets") == ["port1", "skye.what.ever", "skyw.what.ever"] - - -async def test_command_outlets_switchname_unknown(actor: NPSActor): - cmd = await (await actor.invoke_mock_command("outlets blah")) - assert cmd.status.did_fail diff --git a/tests/test_status.py b/tests/test_status.py deleted file mode 100644 index 9fae272..0000000 --- a/tests/test_status.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import annotations - -import pytest - -from lvmnps.actor.actor import NPSActor - - -async def test_status(switches, actor: NPSActor): - # status check of nps_dummy_1 port1 - assert actor - command = await actor.invoke_mock_command("status nps_dummy_1 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 2 - assert command.replies[-1].message["status"]["nps_dummy_1"]["port1"]["state"] == 0 - - assert switches[0].name == "nps_dummy_1" - assert switches[0].outlets[0].name == "port1" - assert switches[0].outlets[0].state == 0 - - # status check of nps_dummy_1 port1 - assert actor - command = await actor.invoke_mock_command("status nps_dummy_1 1") - await command - assert command.status.did_succeed - assert len(command.replies) == 2 - assert command.replies[-1].message["status"]["nps_dummy_1"]["port1"]["state"] == 0 - - # switch status - command = await actor.invoke_mock_command("status nps_dummy_1") - await command - assert command.status.did_succeed - assert len(command.replies) == 2 - - status = command.replies[-1].message["status"] - assert status["nps_dummy_1"]["port1"]["state"] == 0 - assert status["nps_dummy_1"]["skye.what.ever"]["state"] == 0 - assert status["nps_dummy_1"]["skyw.what.ever"]["state"] == 0 - - # status of all available switches - command = await actor.invoke_mock_command("status") - await command - assert command.status.did_succeed - status = command.replies[-1].message["status"] - - assert status["nps_dummy_1"]["port1"]["state"] == 0 - assert status["nps_dummy_1"]["skye.what.ever"]["state"] == 0 - assert status["nps_dummy_1"]["skyw.what.ever"]["state"] == 0 - - -async def test_status_bad_switchname(actor: NPSActor): - command = await actor.invoke_mock_command("status BLAH") - await command - - assert command.status.did_fail - assert command.replies.get("error") == "Unknown switch BLAH." - - -@pytest.mark.parametrize("switchname", ["", "nps_dummy_1"]) -async def test_status_not_reachable_error(actor: NPSActor, switchname, mocker): - for switch in actor.parser_args[0].values(): - mocker.patch.object(switch, "isReachable", return_value=False) - - command = await actor.invoke_mock_command(f"status {switchname}") - await command - - assert command.status.did_fail - assert command.replies.get("error") == "Unable to find matching outlets." diff --git a/tests/test_switch.yml b/tests/test_switch.yml deleted file mode 100644 index a1f9cbc..0000000 --- a/tests/test_switch.yml +++ /dev/null @@ -1,51 +0,0 @@ -# A dictionary of controller name to NPS controller connection parameters. -switches: - nps_dummy_1: - type: dummy - num: 8 - ports: - 1: - name: 'port1' - desc: 'was 1' - 2: - name: 'skye.what.ever' - desc: 'whatever is connected to skye' - 4: - name: 'skyw.what.ever' - desc: 'Something @ skyw' - skye.nps: - type: dummy - ports: - 1: - name: 'skye.pwi' - desc: 'PlaneWavemount Skye' - nps_dummy_3: - type: dummy - ports: - num: 2 - 1: - name: 'skyw.pwi' - desc: 'PlaneWavemount Skyw' - slow: - type: dummy - delay: 2.0 - ports: - num: 1 - 1: - name: 'slow' - fast: - type: dummy - ports: - num: 1 - 1: - name: 'fast' - -timeouts: - switch_connect: 1 - -# Actor configuration for the AMQPActor class -actor: - name: lvmnps - host: localhost - port: 5672 - log_dir: '~/data/logs/lvmnps' diff --git a/tests/test_switches.py b/tests/test_switches.py deleted file mode 100644 index 8599d93..0000000 --- a/tests/test_switches.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# @Author: José Sánchez-Gallego (gallegoj@uw.edu) -# @Date: 2022-05-22 -# @Filename: test_switches.py -# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) - -from __future__ import annotations - -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from lvmnps.actor import NPSActor - - -async def test_command_switches(actor: NPSActor): - cmd = await (await actor.invoke_mock_command("switches")) - assert cmd.status.did_succeed - - assert len(cmd.replies) == 2 - assert cmd.replies.get("switches") == [ - "nps_dummy_1", - "skye.nps", - "nps_dummy_3", - "slow", - "fast", - ] diff --git a/tests/test_tools.py b/tests/test_tools.py new file mode 100644 index 0000000..b26f171 --- /dev/null +++ b/tests/test_tools.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# @Author: José Sánchez-Gallego (gallegoj@uw.edu) +# @Date: 2023-11-23 +# @Filename: test_tools.py +# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) + +from __future__ import annotations + +import pytest + +from lvmnps.nps.core import OutletModel +from lvmnps.tools import get_outlet_by_id, get_outlet_by_name, normalise_outlet_name + + +@pytest.fixture +def outlets(): + _outlets: list[OutletModel] = [] + + for id_ in range(1, 6): + _outlets.append(OutletModel(id=id_, name=f"Outlet {id_}")) + + yield {_outlet.normalised_name: _outlet for _outlet in _outlets} + + +@pytest.mark.parametrize( + ["input", "expected"], + [ + ["Outlet1", "outlet1"], + ["oUtlEt 1", "outlet_1"], + ["outlet_1", "outlet_1"], + ], +) +def test_nomalise_name(input: str, expected: str): + assert normalise_outlet_name(input) == expected + + +@pytest.mark.parametrize("input", ["outlet 1", "Outlet 1", "outlet_1"]) +def test_get_outlet_by_name(outlets: dict[str, OutletModel], input: str): + outlet = get_outlet_by_name(outlets, input) + + assert outlet.normalised_name == "outlet_1" + assert outlet.id == 1 + + +def test_get_outlet_by_name_not_found(outlets: dict[str, OutletModel]): + with pytest.raises(ValueError): + get_outlet_by_name(outlets, "outlet 7") + + +@pytest.mark.parametrize("input", [1, 3, 5]) +def test_get_outlet_by_id(outlets: dict[str, OutletModel], input: int): + outlet = get_outlet_by_id(outlets, input) + + assert outlet.id == input + + +def test_get_outlet_by_id_not_found(outlets: dict[str, OutletModel]): + with pytest.raises(ValueError): + get_outlet_by_id(outlets, 7)