From 981ef42c556f45735e4adbf5eb8586ee0097d0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Tue, 30 Dec 2025 23:36:46 +0100 Subject: [PATCH] docs: change docs to markdown, modernize sphinx and rtd config --- .readthedocs.yaml | 27 ++- docs/Makefile | 20 -- docs/_ext/edit_on_github.py | 35 ---- docs/conf.py | 21 +- docs/development/architecture.md | 7 + docs/development/architecture.rst | 11 -- docs/development/contributing.md | 12 ++ docs/development/contributing.rst | 18 -- docs/development/packaging.md | 14 ++ docs/development/packaging.rst | 23 --- docs/development/setup.md | 133 +++++++++++++ docs/development/setup.rst | 145 -------------- docs/index.md | 25 +++ docs/index.rst | 37 ---- docs/requires.txt | 5 - docs/usage/configuration.md | 171 ++++++++++++++++ docs/usage/configuration.rst | 188 ------------------ docs/usage/installation.md | 58 ++++++ docs/usage/installation.rst | 60 ------ pyproject.toml | 2 +- uv.lock | 310 ++++++++++++++++++++++++++++-- 21 files changed, 737 insertions(+), 585 deletions(-) delete mode 100644 docs/Makefile delete mode 100644 docs/_ext/edit_on_github.py create mode 100644 docs/development/architecture.md delete mode 100644 docs/development/architecture.rst create mode 100644 docs/development/contributing.md delete mode 100644 docs/development/contributing.rst create mode 100644 docs/development/packaging.md delete mode 100644 docs/development/packaging.rst create mode 100644 docs/development/setup.md delete mode 100644 docs/development/setup.rst create mode 100644 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/requires.txt create mode 100644 docs/usage/configuration.md delete mode 100644 docs/usage/configuration.rst create mode 100644 docs/usage/installation.md delete mode 100644 docs/usage/installation.rst diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 81437eb7..a6afdca0 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,25 +1,20 @@ -# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required version: 2 -# Set the version of Python and other tools you might need build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.9" + python: "3.13" + jobs: + pre_create_environment: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + create_environment: + - uv venv "${READTHEDOCS_VIRTUALENV_PATH}" + install: + - UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" uv sync --frozen --group docs -# Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py - -# If using Sphinx, optionally build your docs in additional formats such as PDF -formats: - - pdf - -# Optionally declare the Python requirements required to build your docs -python: - install: - - requirements: docs/requires.txt diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cbb..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_ext/edit_on_github.py b/docs/_ext/edit_on_github.py deleted file mode 100644 index 411b243b..00000000 --- a/docs/_ext/edit_on_github.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Loosely based on gist.github.com/MantasVaitkunas/7c16de233812adcb7028 -This version is taken from https://github.com/theislab/scCODA/blob/master/docs/source/_ext/edit_on_github.py -""" - -import warnings - -__licence__ = "BSD (3 clause)" - - -def get_github_repo(app): - return app.config.github_repo, "/docs/" - - -def html_page_context(app, pagename, templatename, context, doctree): - if templatename != "page.html": - return - - if not app.config.github_repo: - warnings.warn("`github_repo `not specified") - return - - repo, conf_py_path = get_github_repo(app) - - # For sphinx_rtd_theme. - context["display_github"] = True - context["github_user"] = "SFTtech" - context["github_version"] = "master" - context["github_repo"] = repo - context["conf_py_path"] = conf_py_path - - -def setup(app): - app.add_config_value("github_repo", "", True) - app.connect("html-page-context", html_page_context) diff --git a/docs/conf.py b/docs/conf.py index d64c204a..285d91be 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ author = "Michael Loipführer, Jonas Jelten, Michael Enßlin" copyright = f"{datetime.datetime.now():%Y}, {author}" # pylint: disable=redefined-builtin -version = abrechnung.__version__.replace(".dirty", "") +version = abrechnung.__version__ release = version # -- General configuration --------------------------------------------------- @@ -42,9 +42,14 @@ "sphinx.ext.mathjax", "sphinx.ext.napoleon", "sphinx.ext.autosummary", - "sphinx_autodoc_typehints", + "sphinx_copybutton", + "myst_parser", "sphinxcontrib.openapi", - *[p.stem for p in (HERE / "_ext").glob("*.py")], +] +myst_enable_extensions = [ + "colon_fence", + "html_image", + "attrs_inline", ] # Generate the API documentation when building @@ -79,11 +84,11 @@ html_theme_options = dict(navigation_depth=1, titles_only=True) github_repo = "abrechnung" html_context = dict( - display_github=True, # Integrate GitHub - github_user="SFTtech", # Username - github_repo="abrechnung", # Repo name - github_version="master", # Version - conf_py_path="/docs/", # Path in the checkout to the docs root + display_github=True, + github_user="SFTtech", + github_repo="abrechnung", + github_version="master", + conf_py_path="/docs/", ) # Add any paths that contain custom static files (such as style sheets) here, diff --git a/docs/development/architecture.md b/docs/development/architecture.md new file mode 100644 index 00000000..fcc93cb6 --- /dev/null +++ b/docs/development/architecture.md @@ -0,0 +1,7 @@ +# Architecture + +TODO + +- PostgreSQL DB, exposed as websocket and http REST API +- various python services (e.g. mail delivery, database cleanup) +- web-client diff --git a/docs/development/architecture.rst b/docs/development/architecture.rst deleted file mode 100644 index a64b0c66..00000000 --- a/docs/development/architecture.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. _abrechnung-dev-architecture: - -****************** -Architecture -****************** - -TODO - -* PostgreSQL DB, exposed as websocket and http REST API -* various python services (e.g. mail delivery, database cleanup) -* web-client diff --git a/docs/development/contributing.md b/docs/development/contributing.md new file mode 100644 index 00000000..fb121a14 --- /dev/null +++ b/docs/development/contributing.md @@ -0,0 +1,12 @@ +# Contributing + +## As Users + +In case you encounter bugs or have ideas for new and useful features don't hesitate to open an +[issue](https://github.com/SFTtech/abrechnung/issues>) and start a discussion. + +## As Developers + +Get started by setting up a local [development environment](./setup.md) and reading through the +[architecture overview](./architecture.rst) and then start hacking. +[Pull requests](https://github.com/SFTtech/abrechnung/pulls) are always welcome! diff --git a/docs/development/contributing.rst b/docs/development/contributing.rst deleted file mode 100644 index 6df1a56b..00000000 --- a/docs/development/contributing.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _abrechnung-dev-contributing: - -****************** -Contributing -****************** - -As Users --------- - -In case you encounter bugs or have ideas for new and useful features don't hesitate to open an -`issue `_ and start a discussion. - -As Developers -------------- - -Get started by setting up a local :ref:`development environment ` and reading through the -:ref:`abrechnung-dev-architecture` overview and then start hacking. -`Pull requests `_ are always welcome! diff --git a/docs/development/packaging.md b/docs/development/packaging.md new file mode 100644 index 00000000..01024f92 --- /dev/null +++ b/docs/development/packaging.md @@ -0,0 +1,14 @@ +# Packaging + +## Debian and Ubuntu + +Debian and Ubuntu packages are built by the CI on every push to master and are attached to a new release on every tag +to master. + +To build the packages locally run + +```shell +uv run ./tools/build_debian_packages.py +``` + +Docker is a requirement as the packages are built inside a matching docker container for each distribution. diff --git a/docs/development/packaging.rst b/docs/development/packaging.rst deleted file mode 100644 index f09da5ea..00000000 --- a/docs/development/packaging.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. _abrechnung-dev-packaging: - -****************** -Packaging -****************** - -.. highlight:: shell - -The main ``abrechnung`` python package is automatically built and uploaded to -`Test Pypi `_ and -`Pypi `_ on every tag on master by the CI pipeline. - -Debian and Ubuntu ------------------ - -Debian and Ubuntu packages are built by the CI on every push to master and are attached to a new release on every tag -to master. - -To build the packages locally run:: - - ./tools/build_debian_packages.py - -Docker is a requirement as the packages are built inside a matching docker container for each distribution. diff --git a/docs/development/setup.md b/docs/development/setup.md new file mode 100644 index 00000000..9dc0f462 --- /dev/null +++ b/docs/development/setup.md @@ -0,0 +1,133 @@ +# Setup + +```{contents} Table of Contents +:depth: 2 +``` + +## Installation + +Fork and clone the repository + +```shell +git clone https://github.com/SFTtech/abrechnung.git +cd abrechnung +``` + +Then install the package in local development mode as well as all required dependencies. + +Setup a virtual environment and install the packages using uv + +```shell +uv sync +``` + +Additionally you probably will want to activate the git pre-commit hooks (for formatting and linting) by running + +```shell +uv run pre-commit install +``` + +## Database Setup + +Have a running **PostgreSQL** database server. +Create the database (in a psql prompt): + +```sql +create role someuser with login password 'somepassword'; +create database somedatabasename owner someuser; +``` + +- exit the `psql` prompt +- Copy `config/abrechnung.yaml` to the project root folder and adjust it, e.g. enter someuser, somepassword and somedatabasename +- Populate database by applying all migrations: + +```shell +uv run abrechnung -c abrechnung.yaml db migrate +``` + +- Launch `abrechnung -c abrechnung.yaml api` +- Launch `abrechnung -c abrechnung.yaml mailer` to get mail delivery (working mail server in config file required!) + +It is always possible wipe and rebuild the database with + +```shell +uv run abrechnung -c abrechnung.yaml db rebuild +``` + +## Database migrations + +In case a new features requires changes to the database schema create a new migration file with + +```shell +uv run sftkit create-migration +``` + +In case you did not install the abrechnung in development mode it might be necessary to add the project root folder +to your `PYTHONPATH`. + +## Running tests and linters + +To run the tests a dedicated **PostgreSQL** instance is required. The tests assume defaults for the name, user and +password as + +- username: `abrechnung-test` +- database: `abrechnung-test` +- password: `asdf1234` + +In case you want to use a different database / user they can be overwritten using environment variables: + +- `TEST_DB_USER` +- `TEST_DB_HOST` +- `TEST_DB_PASSWORD` +- `TEST_DB_DATABASE` + +Make sure the database user has owner permissions on the `public` schema of the database as dropping and recreating +is used as a means to wipe and repopulate the database between tests. + +```sql +alter schema public owner to "" +``` + +Finally run the tests via + +```shell +make test +``` + +Run the linters via + +```shell +make lint +``` + +Run the formatters via + +```shell +make format +``` + +## Frontend Development + +Working on the frontend is quite easy, simply + +```shell +npm install +npx nx serve web +``` + +and you are good to go! + +## Documentation + +To build the documentation locally simply run + +```shell +pip install -r docs/requires.txt +make docs +``` + +The html docs can then be found in `docs/_build` or served locally with + +```shell +make serve-docs +``` diff --git a/docs/development/setup.rst b/docs/development/setup.rst deleted file mode 100644 index 5e8d11b7..00000000 --- a/docs/development/setup.rst +++ /dev/null @@ -1,145 +0,0 @@ -.. _abrechnung-dev-setup: - -****************** -Setup -****************** - -.. highlight:: shell - -.. contents:: Table of Contents - -Installation ------------- - -Fork and clone the repository - -.. code-block:: shell - - git clone https://github.com/SFTtech/abrechnung.git - cd abrechnung - -Then install the package in local development mode as well as all required dependencies. - -Setup a virtual environment and install the packages via pip - -.. code-block:: shell - - virtualenv -p python3 .venv - source .venv/bin/activate - pip install -e . '[dev,test]' - -Additionally you probably will want to activate the git pre-commit hooks (for formatting and linting) by running - -.. code-block:: shell - - pre-commit install - -Database Setup --------------- - -Have a running **PostgreSQL** database server. -Create the database (in a psql prompt): - -.. code-block:: sql - - create role someuser with login password 'somepassword'; - create database somedatabasename owner someuser; - -* exit the ``psql`` prompt -* Copy ``config/abrechnung.yaml`` to the project root folder and adjust it, e.g. enter someuser, somepassword and somedatabasename -* Populate database by applying all migrations: - -.. code-block:: shell - - abrechnung -c abrechnung.yaml db migrate - -* Launch ``abrechnung -c abrechnung.yaml api`` -* Launch ``abrechnung -c abrechnung.yaml mailer`` to get mail delivery (working mail server in config file required!) - -It is always possible wipe and rebuild the database with - -.. code-block:: shell - - abrechnung -c abrechnung.yaml db rebuild - -Database migrations -------------------- - -In case a new features requires changes to the database schema create a new migration file with - -.. code-block:: shell - - sftkit create-migration - -In case you did not install the abrechnung in development mode it might be necessary to add the project root folder -to your ``PYTHONPATH``. - -Running tests and linters -------------------------- - -To run the tests a dedicated **PostgreSQL** instance is required. The tests assume defaults for the name, user and -password as - -* username: ``abrechnung-test`` -* database: ``abrechnung-test`` -* password: ``asdf1234`` - -In case you want to use a different database / user they can be overwritten using environment variables: - -* ``TEST_DB_USER`` -* ``TEST_DB_HOST`` -* ``TEST_DB_PASSWORD`` -* ``TEST_DB_DATABASE`` - -Make sure the database user has owner permissions on the ``public`` schema of the database as dropping and recreating -is used as a means to wipe and repopulate the database between tests. - -.. code-block:: sql - - alter schema public owner to "" - -Finally run the tests via - -.. code-block:: shell - - make test - -Run the linters via - -.. code-block:: shell - - make lint - -Run the formatters via - -.. code-block:: shell - - make format - -Frontend Development --------------------- - -Working on the frontend is quite easy, simply - -.. code-block:: shell - - npm install - npx nx serve web - -and you are good to go! - -Documentation -------------- - -To build the documentation locally simply run - -.. code-block:: shell - - pip install -r docs/requires.txt - make docs - -The html docs can then be found in ``docs/_build`` or served locally with - -.. code-block:: shell - - make serve-docs diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..ed8c52bd --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +# Welcome to abrechnung's documentation! + +```{toctree} +:maxdepth: 2 +:caption: Contents: +``` + +```{toctree} +:maxdepth: 2 +:caption: Usage + +usage/installation.md +usage/configuration.md +``` + +```{toctree} +:maxdepth: 2 +:caption: Development + +development/contributing.md +development/setup.md +development/architecture.md +development/packaging.md +development/api.rst +``` diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 2c2104e8..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,37 +0,0 @@ -.. abrechnung documentation master file, created by - sphinx-quickstart on Wed Dec 29 11:07:10 2021. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to abrechnung's documentation! -====================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - -.. toctree:: - :maxdepth: 2 - :caption: Usage - - usage/installation.rst - usage/configuration.rst - - -.. toctree:: - :maxdepth: 2 - :caption: Development - - development/contributing.rst - development/setup.rst - development/architecture.rst - development/packaging.rst - development/api.rst - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/requires.txt b/docs/requires.txt deleted file mode 100644 index b720afa9..00000000 --- a/docs/requires.txt +++ /dev/null @@ -1,5 +0,0 @@ -# doc requirements -sphinx-autobuild -sphinx-rtd-theme -sphinx-autodoc-typehints -sphinxcontrib-openapi diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md new file mode 100644 index 00000000..faf05cf1 --- /dev/null +++ b/docs/usage/configuration.md @@ -0,0 +1,171 @@ +# Configuration + +After any change to the configuration make sure to apply it by restarting the respective service. + +## Database + +The first step after installing the **abrechnung** is to setup the database. Due to the use of database specific features +we only support **PostgreSQL** with versions >= 13. Other versions might work but are untested. + +First create a database with an associated user + +```shell +sudo -u postgres psql +create user abrechnung with password ''; +create database abrechnung owner abrechnung; +``` + +Enter the information into the config file in `/etc/abrechnung/abrechnung.yaml` under the section database as + +```yaml +database: + host: "localhost" + user: "abrechnung" + dbname: "abrechnung" + password: "" +``` + +Apply all database migrations with + +```shell +abrechnung db migrate +``` + +## General Options + +```yaml +service: + name: "Abrechnung" +``` + +The `name` is used to populate the email subjects as `[] `. + +## API Config + +Typically the config for the http API does not need to be changed much apart from two important settings! +In the `api` section make sure to insert a newly generated secret key, e.g. with + +```shell +pwgen -S 64 1 +``` + +Additionally you need to configure your base https endpoints to be used in mail +delivery and proper api resource URLs. If `abrechnung.example.lol` is your domain adjust the `api` section as follows. + +```yaml +api: + id: "default" + secret_key: "" + host: "localhost" + port: 8080 # base url is given by the domain the abrechnung instance is hosted at + base_url: "https://abrechnung.example.lol" +``` + +In most cases there is no need to adjust the `host`, `port` or `id` options. + +## E-Mail Delivery + +To setup E-Mail delivery adjust the `email` config section to fit your use case. An example might look like + +```yaml +email: + address: "abrechnung@example.lol" + host: "localhost" + port: 587 + mode: "smtp-starttls" + auth: + username: "abrechnung" + password: "" +``` + +Currently supported `mode` options are + +- `local`, uses lmtp on localhost +- `smtp-ssl`, uses smtp with forced ssl +- `smtp-starttls`, uses smtp with starttls +- if mode is not given plain smtp is used + +The `auth` section is optional, if omitted the mail delivery daemon will try to connect to the mail server +without authentication. + +## User Registration + +This section allows to configure how users can register at the abrechnung instance. +By default open registration is disabled. + +When enabling registration without any additional settings any user will be able to create an account and use it after +a successful email confirmation. + +E-mail confirmation can be turned of by setting the respective config variable to `false`. + +```yaml +registration: + enabled: true + require_email_confirmation: true +``` + +Additionally open registration can be restricted adding domains to the `valid_email_domains` config variable. +This will restrict account creation to users who possess an email from one of the configured domains. +To still allow outside users to take part the `allow_guest_users` flag can be set which enables users to create a +"guest" account when in possession of a valid group invite link. +Guest users will not be able to create new groups themselves but can take part in groups they are invited to normally. + +```yaml +registration: + enabled: true + require_email_confirmation: true + valid_email_domains: ["some-domain.com"] + allow_guest_users: true +``` + +## Prometheus Metrics + +Abrechnung also provides prometheus metrics which are disabled by default. +This includes some general metrics about the abrechnung instance such as + +- http request durations and groupings of error codes +- general python environment metrics such as process utilization and garbage collection performance + +Additionally it currently includes the following set of abrechnung specific metrics + +- number of groups created on the instance +- number of transactions created on the instance +- total amount of money by currency which was cleared via the instance, i.e. the total sum of transaction values per currency over all groups. + This is disabled by default as it may expose private data on very small abrechnung instances. + +To enable metrics under the api endpoint `/api/metrics` simply add the following to the config file + +```yaml +metrics: + enabled: true + expose_money_amounts: false # disabled by default +``` + +## Configuration via Environment Variables + +All of the configuration options set in the config yaml file can also be set via environment variables. +The respective environment variable name for a config variable is in the pattern `ABRECHNUNG___`. + +E.g. to set the email auth username from the config yaml as below we'd use the environment variable `ABRECHNUNG_EMAIL__AUTH__USERNAME`. + +```yaml +email: + auth: + username: "..." +``` + +## Frontend Configuration + +The frontend also has some configuration options which can be configured in the `service` section of the yaml configuration. +This enables server administrators to show information banners / messages on top via the `messages` config key to e.g. announce maintenance. + +Possible config options are + +```yaml +service: # ... + messages: + - type: "info" # "error" | "warning" | "success" + body: "This is an informational message which will be displayed at the top of the application" + title: "My optional title" + imprint_url: "https://my-imprint-for-my-abrechnung-instance.mydomain.com" +``` diff --git a/docs/usage/configuration.rst b/docs/usage/configuration.rst deleted file mode 100644 index 68ead4dd..00000000 --- a/docs/usage/configuration.rst +++ /dev/null @@ -1,188 +0,0 @@ -.. _abrechnung-config: - -****************** -Configuration -****************** - -.. highlight:: shell - -After any change to the configuration make sure to apply it by restarting the respective service. - -.. _abrechnung-database-config: - -Database ---------------- -The first step after installing the **abrechnung** is to setup the database. Due to the use of database specific features -we only support **PostgreSQL** with versions >= 13. Other versions might work but are untested. - -First create a database with an associated user - -.. code-block:: shell - - sudo -u postgres psql - create user abrechnung with password ''; - create database abrechnung owner abrechnung; - -Enter the information into the config file in ``/etc/abrechnung/abrechnung.yaml`` under the section database as - -.. code-block:: yaml - - database: - host: "localhost" - user: "abrechnung" - dbname: "abrechnung" - password: "" - -Apply all database migrations with - -.. code-block:: shell - - abrechnung db migrate - -General Options ---------------- - -.. code-block:: yaml - - service: - name: "Abrechnung" - -The ``name`` is used to populate the email subjects as ``[] ``. - -API Config ---------------- -Typically the config for the http API does not need to be changed much apart from two important settings! -In the ``api`` section make sure to insert a newly generated secret key, e.g. with - -.. code-block:: shell - - pwgen -S 64 1 - -Additionally you need to configure your base https endpoints to be used in mail -delivery and proper api resource URLs. If ``abrechnung.example.lol`` is your domain adjust the ``api`` section as follows. - -.. code-block:: yaml - - api: - id: "default" - secret_key: "" - host: "localhost" - port: 8080 - # base url is given by the domain the abrechnung instance is hosted at - base_url: "https://abrechnung.example.lol" - -In most cases there is no need to adjust the ``host``, ``port`` or ``id`` options. - -E-Mail Delivery ---------------- - -To setup E-Mail delivery adjust the ``email`` config section to fit your use case. An example might look like - -.. code-block:: yaml - - email: - address: "abrechnung@example.lol" - host: "localhost" - port: 587 - mode: "smtp-starttls" - auth: - username: "abrechnung" - password: "" - -Currently supported ``mode`` options are - -* ``local``, uses lmtp on localhost -* ``smtp-ssl``, uses smtp with forced ssl -* ``smtp-starttls``, uses smtp with starttls -* if mode is not given plain smtp is used - -The ``auth`` section is optional, if omitted the mail delivery daemon will try to connect to the mail server -without authentication. - -User Registration ------------------ - -This section allows to configure how users can register at the abrechnung instance. -By default open registration is disabled. - -When enabling registration without any additional settings any user will be able to create an account and use it after -a successful email confirmation. - -E-mail confirmation can be turned of by setting the respective config variable to ``false``. - -.. code-block:: yaml - - registration: - enabled: true - require_email_confirmation: true - -Additionally open registration can be restricted adding domains to the ``valid_email_domains`` config variable. -This will restrict account creation to users who possess an email from one of the configured domains. -To still allow outside users to take part the ``allow_guest_users`` flag can be set which enables users to create a -"guest" account when in possession of a valid group invite link. -Guest users will not be able to create new groups themselves but can take part in groups they are invited to normally. - -.. code-block:: yaml - - registration: - enabled: true - require_email_confirmation: true - valid_email_domains: ["some-domain.com"] - allow_guest_users: true - -Prometheus Metrics ------------------- - -Abrechnung also provides prometheus metrics which are disabled by default. -This includes some general metrics about the abrechnung instance such as - -- http request durations and groupings of error codes -- general python environment metrics such as process utilization and garbage collection performance - -Additionally it currently includes the following set of abrechnung specific metrics - -- number of groups created on the instance -- number of transactions created on the instance -- total amount of money by currency which was cleared via the instance, i.e. the total sum of transaction values per currency over all groups. - This is disabled by default as it may expose private data on very small abrechnung instances. - -To enable metrics under the api endpoint ``/api/metrics`` simply add the following to the config file - -.. code-block:: yaml - - metrics: - enabled: true - expose_money_amounts: false # disabled by default - -Configuration via Environment Variables ---------------------------------------- - -All of the configuration options set in the config yaml file can also be set via environment variables. -The respective environment variable name for a config variable is in the pattern ``ABRECHNUNG___``. - -E.g. to set the email auth username from the config yaml as below we'd use the environment variable ``ABRECHNUNG_EMAIL__AUTH__USERNAME``. - -.. code-block:: yaml - - email: - auth: - username: "..." - - -Frontend Configuration -------------------------- - -The frontend also has some configuration options which can be configured in the ``service`` section of the yaml configuration. -This enables server administrators to show information banners / messages on top via the ``messages`` config key to e.g. announce maintenance. - -Possible config options are - -.. code-block:: yaml - - service: - # ... - messages: - - type: "info" # "error" | "warning" | "success" - body: "This is an informational message which will be displayed at the top of the application" - title: "My optional title" - imprint_url: "https://my-imprint-for-my-abrechnung-instance.mydomain.com" diff --git a/docs/usage/installation.md b/docs/usage/installation.md new file mode 100644 index 00000000..8e20861f --- /dev/null +++ b/docs/usage/installation.md @@ -0,0 +1,58 @@ +# Installation + +(debian-installation)= + +## Debian Trixie + +This is the recommended installation method as it also installs the prebuilt abrechnung web app. + +Simply go to the [github release page](https://github.com/SFTtech/abrechnung/releases) and download +the latest debian package matching your debian version for the latest release. + +Install them via + +```shell +sudo apt install ./abrechnung_.deb +``` + +All dependencies, systemd services, config files in `/etc/abrechnung`, nginx config in `/etc/nginx/sites-available/abrechnung` +as well as static web assets in `/usr/share/abrechnung_web` are installed. + +The only remaining work to be done is to setup the database and customize the configuration (see [Configuration](configuration.md)). + +## Ubuntu Noble + +Follow the installation instructions for [Debian](debian-installation), just make sure to choose the correct +`.deb` package file. + +## Docker Compose + +We provide prebuilt docker containers for the api and the web frontend under `https://quay.io/abrechnung`. + +To use our docker compose clone the github repository + +```shell +git clone https://github.com/SFTtech/abrechnung.git +``` + +Then copy the `.env.example` file to `.env` and configure it to your liking :: + +```shell +cd abrechnung +cp .env.example .env +vim .env +``` + +For production setups we recommend running an external postgres database but if you feel adventurous you +can adapt the docker-compose file to also run a postgres container (which we definitely do not recommend). +In case of using an external postgres database make sure to +follow [the configuration instructions](./configuration.md) on how to create a database. +Afterwards make sure to include the database configuration parameters in the `.env` configuration file. + +Then a simple simple + +```shell +docker-compose -f docker-compose.prod.yaml up +``` + +Should suffice to get you up and running. diff --git a/docs/usage/installation.rst b/docs/usage/installation.rst deleted file mode 100644 index 5d1927aa..00000000 --- a/docs/usage/installation.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. _abrechnung-installation: - -****************** -Installation -****************** - -.. highlight:: shell - -.. _abrechnung-installation-debian: - -Debian Trixie ------------------------------------------ -This is the recommended installation method as it also installs the prebuilt abrechnung web app. - -Simply go to the `github release page `_ and download -the latest debian package matching your debian version for the latest release. - -Install them via :: - - sudo apt install ./abrechnung_.deb - - -All dependencies, systemd services, config files in ``/etc/abrechnung``, nginx config in ``/etc/nginx/sites-available/abrechnung`` -as well as static web assets in ``/usr/share/abrechnung_web`` are installed. - -The only remaining work to be done is to setup the database and customize the configuration (see :ref:`abrechnung-config`). - -Ubuntu Noble --------------------------------- - -Follow the installation instructions for :ref:`Debian `, just make sure to choose the correct -``.deb`` package file. - -.. _abrechnung-installation-docker: - -Docker Compose ----------------- -We provide prebuilt docker containers for the api and the web frontend under `https://quay.io/abrechnung`. - -To use our docker compose clone the github repository :: - - git clone https://github.com/SFTtech/abrechnung.git - -Then copy the ``.env.example`` file to ``.env`` and configure it to your liking :: - - cd abrechnung - cp .env.example .env - vim .env - -For production setups we recommend running an external postgres database but if you feel adventurous you -can adapt the docker-compose file to also run a postgres container (which we definitely do not recommend). -In case of using an external postgres database make sure to -follow :ref:`the configuration instructions ` on how to create a database. -Afterwards make sure to include the database configuration parameters in the ``.env`` configuration file. - -Then a simple simple :: - - docker-compose -f docker-compose.prod.yaml up - -Should suffice to get you up and running. diff --git a/pyproject.toml b/pyproject.toml index 5b886fb8..3df48d0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dev = [ ] lint = ["ruff", "mypy==1.13.0", "types-PyYAML~=6.0", "pylint==3.3.2"] test = ["aiosmtpd~=1.4", "pytest", "pytest-asyncio", "pytest-cov"] -docs = ["sphinx", "sphinx-autobuild", "sphinx-autodoc-typehints"] +docs = ["sphinx", "myst-parser", "sphinx-autobuild", "sphinx-copybutton", "sphinx-rtd-theme", "sphinxcontrib-openapi"] [project.urls] Source = "https://github.com/SFTtech/abrechung" diff --git a/uv.lock b/uv.lock index cb87001c..f347ecac 100644 --- a/uv.lock +++ b/uv.lock @@ -23,6 +23,7 @@ dev = [ { name = "aiosmtpd" }, { name = "bump-my-version" }, { name = "mypy" }, + { name = "myst-parser" }, { name = "pre-commit" }, { name = "pylint" }, { name = "pytest" }, @@ -31,13 +32,18 @@ dev = [ { name = "ruff" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, - { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-openapi" }, { name = "types-pyyaml" }, ] docs = [ + { name = "myst-parser" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, - { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-openapi" }, ] lint = [ { name = "mypy" }, @@ -71,6 +77,7 @@ dev = [ { name = "aiosmtpd", specifier = "~=1.4" }, { name = "bump-my-version", specifier = "~=1.2.6" }, { name = "mypy", specifier = "==1.13.0" }, + { name = "myst-parser" }, { name = "pre-commit" }, { name = "pylint", specifier = "==3.3.2" }, { name = "pytest" }, @@ -79,13 +86,18 @@ dev = [ { name = "ruff" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, - { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-openapi" }, { name = "types-pyyaml", specifier = "~=6.0" }, ] docs = [ + { name = "myst-parser" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, - { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-openapi" }, ] lint = [ { name = "mypy", specifier = "==1.13.0" }, @@ -591,6 +603,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, ] +[[package]] +name = "deepmerge" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, +] + [[package]] name = "dill" version = "0.4.0" @@ -620,11 +641,11 @@ wheels = [ [[package]] name = "docutils" -version = "0.22.4" +version = "0.21.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, ] [[package]] @@ -770,16 +791,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] @@ -854,6 +902,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, ] +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -863,6 +923,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mistune" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, +] + [[package]] name = "mypy" version = "1.13.0" @@ -895,6 +964,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "myst-parser" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, +] + [[package]] name = "nodeenv" version = "1.10.0" @@ -913,6 +999,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] +[[package]] +name = "picobox" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/b1/830714dd6778c1cb45826722b4e9bd21c94b33cca5df9ef2cc0b80c81b25/picobox-4.0.0.tar.gz", hash = "sha256:114da1b5606b2f615e8b0eb68d04198ad9de75af5adbcf5b36fe4f664ab927b6", size = 22666, upload-time = "2023-11-20T00:36:54.951Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/c6/fd64ffd75d47c4fcf6c65808cc5c5c75e5d4357c197d3741ee1339e91257/picobox-4.0.0-py3-none-any.whl", hash = "sha256:4c27eb689fe45dabd9e64c382e04418147d0b746d155b4e80057dbb7ff82027e", size = 11641, upload-time = "2023-11-20T00:36:53.312Z" }, +] + [[package]] name = "platformdirs" version = "4.5.1" @@ -1235,6 +1330,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" }, ] +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "requests" version = "2.32.5" @@ -1286,6 +1395,99 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] +[[package]] +name = "roman-numerals-py" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "roman-numerals" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +] + [[package]] name = "rsa" version = "4.9.1" @@ -1369,7 +1571,7 @@ wheels = [ [[package]] name = "sphinx" -version = "9.0.4" +version = "8.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, @@ -1381,7 +1583,7 @@ dependencies = [ { name = "packaging" }, { name = "pygments" }, { name = "requests" }, - { name = "roman-numerals" }, + { name = "roman-numerals-py" }, { name = "snowballstemmer" }, { name = "sphinxcontrib-applehelp" }, { name = "sphinxcontrib-devhelp" }, @@ -1390,9 +1592,9 @@ dependencies = [ { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, ] [[package]] @@ -1413,15 +1615,44 @@ wheels = [ ] [[package]] -name = "sphinx-autodoc-typehints" -version = "3.6.0" +name = "sphinx-copybutton" +version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/7e/7c29e5e53b7c01b8c40eaedea31f001866b8c7827fe8f3ca792e95105494/sphinx_autodoc_typehints-3.6.0.tar.gz", hash = "sha256:054a12438af70221f27652ac0631e19826ce9a68eaa69ffd07aef6444e60eb7f", size = 37449, upload-time = "2025-12-09T05:49:30.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/ff/e8eb0b0df200ed3f3fc059f926f33d0b5c7cf5354a8e99b29c47404b224f/sphinx_autodoc_typehints-3.6.0-py3-none-any.whl", hash = "sha256:da1fa49ef8184700913ee403180b844f91cdba41b80b3f0af0c506d5286fd690", size = 20817, upload-time = "2025-12-09T05:49:29.077Z" }, + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, +] + +[[package]] +name = "sphinx-mdinclude" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "mistune" }, + { name = "pygments" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/a7/c9a7888bb2187fdb06955d71e75f6f266b7e179b356ac76138d160a5b7eb/sphinx_mdinclude-0.6.2.tar.gz", hash = "sha256:447462e82cb8be61404a2204227f920769eb923d2f57608e3325f3bb88286b4c", size = 65257, upload-time = "2024-08-03T19:07:37.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/3d/6b41fe1637cd53c4b10d56e0e6f396546f837973dabf9c4b2a1de44620ac/sphinx_mdinclude-0.6.2-py3-none-any.whl", hash = "sha256:648e78edb067c0e4bffc22943278d49d54a0714494743592032fa3ad82a86984", size = 16911, upload-time = "2024-08-03T19:07:30.406Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" }, ] [[package]] @@ -1451,6 +1682,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, ] +[[package]] +name = "sphinxcontrib-httpdomain" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/ef/82d3cfafb7febce4f7df8dcf3cde9d072350b41066e05a4f559b4e9105d0/sphinxcontrib-httpdomain-1.8.1.tar.gz", hash = "sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b", size = 19266, upload-time = "2022-11-14T17:22:13.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/49/aad47b8cf27a0d7703f1311aad8c368bb22866ddee1a2d2cd3f69bc45e0c/sphinxcontrib_httpdomain-1.8.1-py2.py3-none-any.whl", hash = "sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5", size = 25513, upload-time = "2022-11-14T17:22:10.555Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -1460,6 +1716,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] +[[package]] +name = "sphinxcontrib-openapi" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deepmerge" }, + { name = "jsonschema" }, + { name = "picobox" }, + { name = "pyyaml" }, + { name = "sphinx" }, + { name = "sphinx-mdinclude" }, + { name = "sphinxcontrib-httpdomain" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/a7/66a5c9aba7dbbb0c2b050f60e71402818cbf5f127ace13ed971029cc745e/sphinxcontrib-openapi-0.8.4.tar.gz", hash = "sha256:df883808a5b5e4b4113ad697185c43a3f42df3dce70453af78ba7076907e9a20", size = 71848, upload-time = "2024-02-13T10:46:10.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/c3/ee00486f38d78309a60ee0d6031b2545b22ac5f0007d841dd174abc68774/sphinxcontrib_openapi-0.8.4-py3-none-any.whl", hash = "sha256:50911c18d452d9390ee3a384ef8dc8bde6135f542ba55691f81e1fbc0b71014e", size = 34510, upload-time = "2024-02-13T10:46:09.271Z" }, +] + [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0"