diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 72822e8..d58cf31 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -24,8 +24,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install mypy - pip install . + pip install uv + uv sync --group typecheckers - name: Run type checker - run: mypy -p argenta + run: uv run python -m mypy -p argenta diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 1b71574..988d9cd 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -24,7 +24,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install ruff + pip install uv + uv sync --group linters - name: Run linter - run: ruff check ./src + run: uv run python -m ruff check ./src diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e893acf..51f84be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,19 +13,22 @@ jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v3 with: - python-version: "3.13" + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install uv - uv sync --group dev + uv sync --group tests - name: Run tests run: uv run python -m pytest tests diff --git a/.gitignore b/.gitignore index 02f9cd8..897b850 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,320 @@ -*venv -.idea -.vscode -dist -uv.lock -*__pycache__/ -*.hist* -build -source -*cache +#### joe made this: http://goel.io/joe + +#### python #### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml +#### joe made this: http://goel.io/joe + +#### visualstudiocode #### +.vscode/ +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +!*.code-workspace + +# Built Visual Studio Code Extensions +*.vsix +#### joe made this: http://goel.io/joe + +#### jetbrains #### +# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea/ + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ +.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119 + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based HTTP Client +.idea/httpRequests +http-client.private.env.json + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# Apifox Helper cache +.idea/.cache/.Apifox_Helper +.idea/ApifoxUploaderProjectSetting.xml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..7f8fd01 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.12" + + jobs: + post_install: + - sphinx-intl build --locale-dir docs/locales + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements.txt diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9397945 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,66 @@ +# Code of Conduct - Argenta + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, level of experience, education, +socio-economic status, nationality, personal appearance, race and religion. + +## Our Standards + +Examples of behaviour that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologising to those affected by our mistakes, +and learning from the experience +* Focusing on what is best not just for us as individuals, but for the +overall community + +Examples of unacceptable behaviour include: + +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email +address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying and enforcing our standards of +acceptable behaviour and will take appropriate and fair corrective action in +response to any instances of unacceptable behaviour. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban +temporarily or permanently any contributor for other behaviours that they deem +inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be +reported to the community leaders responsible for enforcement at . +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org/), version +[1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/code_of_conduct.md) and +[2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct/code_of_conduct.md) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3dedeed --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,264 @@ + +# Contributing to Argenta + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [I Have a Question](#i-have-a-question) +- [I Want To Contribute](#i-want-to-contribute) +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Enhancements](#suggesting-enhancements) +- [Your First Code Contribution](#your-first-code-contribution) +- [Improving The Documentation](#improving-the-documentation) +- [Styleguides](#styleguides) +- [Commit Messages](#commit-messages) +- [Join The Project Team](#join-the-project-team) + + +## Code of Conduct + +This project and everyone participating in it is governed by the +[Argenta Code of Conduct](https://github.com/koloideal/Argenta/blob/main/CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable behavior +to . + +----- + +## I Have a Question + +> If you want to ask a question, we assume that you have read the available [Documentation](https://argenta.readthedocs.io). + +Before you ask a question, it is best to search for existing [Issues](https://github.com/koloideal/Argenta/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we recommend the following: + +- Open an [Issue](https://github.com/koloideal/Argenta/issues/new). +- Provide as much context as you can about what you're running into. +- Provide project and platform versions (cpython, pip, etc), depending on what seems relevant. + +We will then take care of the issue as soon as possible. + + + +----- + +## I Want To Contribute + +> ### Legal Notice +> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence. + +### Reporting Bugs + + +#### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://argenta.readthedocs.io). If you are looking for support, you might want to check [this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/koloideal/Argenta/issues?q=label%3Abug). +- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. +- Collect information about the bug: +- Stack trace (Traceback) +- OS, Platform and Version (Windows, Linux, macOS, x86, ARM) +- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. +- Possibly your input and the output +- Can you reliably reproduce the issue? And can you also reproduce it with older versions? + + +#### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . + + +We use GitHub issues to track bugs and errors. If you run into an issue with the project: + +- Open an [Issue](https://github.com/koloideal/Argenta/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution). + + + +----- + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for Argenta, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. + + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation](https://argenta.readthedocs.io) carefully and find out if the functionality is already covered, maybe by an individual configuration. +- Perform a [search](https://github.com/koloideal/Argenta/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. + + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://github.com/koloideal/Argenta/issues). + +- Use a **clear and descriptive title** for the issue to identify the suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. +- You may want to **include screenshots or screen recordings** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [LICEcap](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and the built-in [screen recorder in GNOME](https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en) or [SimpleScreenRecorder](https://github.com/MaartenBaert/ssr) on Linux. +- **Explain why this enhancement would be useful** to most Argenta users. You may also want to point out the other projects that solved it better and which could serve as inspiration. + + + +----- + +### Your First Code Contribution + +Unsure where to begin contributing to Argenta? You can start by looking through `good first issue` and `help wanted` issues on our GitHub repository. These are issues that are well-suited for new contributors. + +To get started with your first code contribution, please follow these steps to set up your local development environment. + +1. Fork the `Argenta` repository on GitHub. +2. Clone your forked repository to your local machine: + ```bash + git clone https://github.com//Argenta.git + cd Argenta + ``` +3. Create and activate a Python virtual environment. + ```bash + # For macOS/Linux + python3 -m venv .venv + source .venv/bin/activate + + # For Windows + python -m venv .venv + .venv\Scripts\activate + ``` +4. Install the project dependencies, including the development tools. + ```bash + pip install -e .[dev] + ``` +5. Create a new branch for your feature or bug fix. Use a descriptive name, like `fix/login-bug` or `feat/new-widget`. + ```bash + git checkout -b your-new-branch-name + ``` +6. Make your changes! Write your code, and don't forget to add or update tests for your changes. +7. Run the test suite to ensure everything is working correctly. + ```bash + python -m pytest tests + ``` +8. Commit your changes following our commit message styleguide and push them to your fork. + ```bash + git add . + git commit -m "feat(widget): add the new super widget" + git push origin your-new-branch-name + ``` +9. Open a Pull Request from your forked branch to the `main` branch of the official Argenta repository. Provide a clear description of the problem and your solution. Include the relevant issue number if applicable. + +----- + +### Improving The Documentation + +Good documentation is crucial for any project. We use Sphinx to generate our documentation from source files located in the `docs/` directory. We welcome any improvements, from fixing a simple typo to writing a whole new section. + + We support documentation in two languages: Russian and English + +To improve the documentation, you can follow a similar workflow as for code contributions: + +1. Ensure your development environment is set up as described in the "Your First Code Contribution" section. +2. Navigate to the documentation directory. + ```bash + cd docs + ``` +3. Make the necessary changes to the **Russian** version of the documentation - ``docs/index.rst`` and ``docs/root/*`` +4. To build the documentation locally and see your changes, run: + ```bash + make live-ru + ``` +5. Open `127.0.0.1:8000` in your web browser to preview the generated documentation. +6. Make your desired changes to the `.rst` or `.md` files in the `docs/source` directory. +7. After completing the work on the Russian documentation, it is necessary to create an English translation: + + ```bash + make update-langs + ``` +8. After updating the translation template, update the necessary translation files located at ``docs/locales/en/LC_MESSAGES`` +8. Once you are happy with your changes, commit them and open a Pull Request. Use the `docs:` prefix in your commit message. + +--- + +## Styleguides + +### Commit Messages + +We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for our commit messages. This leads to more readable messages that are easy to follow when looking through the project history and allows for automated changelog generation. + +Each commit message consists of a **header**, a **body**, and a **footer**. + +``` +(): + +[optional body] + +[optional footer] +``` + +The `` must be one of the following: + +* **feat**: A new feature for the user. +* **fix**: A bug fix for the user. +* **docs**: Documentation only changes. +* **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc). +* **refactor**: A code change that neither fixes a bug nor adds a feature. +* **perf**: A code change that improves performance. +* **test**: Adding missing tests or correcting existing tests. +* **chore**: Changes to the build process or auxiliary tools and libraries. + +#### Examples + +A simple fix: +``fix: correct typo in user authentication flow`` + +A new feature with a scope: +``feat(api): add new endpoint for user profiles`` + +--- + +## Join The Project Team + +We are always looking for enthusiastic and dedicated people to join our project team. If you are a regular contributor and have shown a deep understanding of the project's goals and architecture, you might be a good candidate to become a maintainer. + +Active members of the community can become team members. This typically involves: + +* Consistently contributing high-quality code and documentation. +* Helping other users by answering questions and triaging issues. +* Reviewing Pull Requests from other contributors with constructive feedback. + +If you are interested in becoming a more permanent part of the team, the best way to start is by being an active and helpful member of the community. The existing maintainers will notice your efforts and may reach out with an invitation to join the team. \ No newline at end of file diff --git a/README.de.md b/README.de.md deleted file mode 100644 index 118d378..0000000 --- a/README.de.md +++ /dev/null @@ -1,69 +0,0 @@ -# Argenta - -### Bibliothek zum Erstellen modularer CLI-Anwendungen - -Mit Argenta können Sie die CLI-Funktionalität in isolierte, abstrahierte Umgebungen einkapseln. Zum Beispiel: Sie erstellen ein Dienstprogramm ähnlich dem Metasploit Framework, bei dem der Benutzer zuerst in einen bestimmten Scoop eintritt (z. B. ein Modul zum Scannen auswählt) und dann auf eine Reihe von Befehlen zugreift, die nur für diesen Kontext spezifisch sind. Argenta bietet eine einfache und prägnante Möglichkeit, eine solche Architektur zu konstruieren. - ---- - -![preview](https://github.com/koloideal/Argenta/blob/main/imgs/mock_app_preview4.png?raw=True) - ---- - -# Installation -```bash -pip install argenta -``` -or -```bash -poetry add argenta -``` - ---- - -# Schnellstart - -Ein Beispiel für eine einfache Anwendung -```python -# routers.py -from argenta.router import Router -from argenta.command import Command -from argenta.response import Response - - -router = Router() - -@router.command(Command("hello")) -def handler(response: Response): - print("Hello, world!") -``` - -```python -# main.py -from argenta.app import App -from argenta.orchestrator import Orchestrator -from routers import router - -app: App = App() -orchestrator: Orchestrator = Orchestrator() - - -def main() -> None: - app.include_router(router) - orchestrator.start_polling(app) - - -if __name__ == '__main__': - main() -``` - ---- - -# Funktionen in der Entwicklung - -- Vollständige Unterstützung für Autocompleter unter Linux - -## Vollständige [Dokumentation](https://argenta-docs.vercel.app) | MIT 2025 kolo | made by [kolo](https://t.me/kolo_id) - - - diff --git a/README.md b/README.md index 5b27c9a..e36e48f 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,105 @@ -# Argenta +![preview](https://i.ibb.co/whkMfHw5/a-minimalist-logo-design-featuring-the-t-t-GXhfz-NFRwi-k-ROq-NMd-LWA-R7-6ru-YSh-G0kz-LKr5t-ZQ.jpg) -### Library for creating modular CLI applications +**Argenta** is a simple and elegant framework for building modular CLI applications. It provides a clean and intuitive way to create context-aware command-line tools with isolated command scopes. -#### RU - [README.ru.md](https://github.com/koloideal/Argenta/blob/main/README.ru.md) • DE - [README.de.md](https://github.com/koloideal/Argenta/blob/main/README.de.md) +Argenta is the **"Simplest"**, **"Most Modular"**, and **"Most Elegant"** way to build interactive CLI applications in Python. + +📖 **Read the full documentation:** [argenta.readthedocs.io](https://argenta.readthedocs.io/)
+🌍 **Other languages:** [RU](https://github.com/koloideal/Argenta/blob/main/README.ru.md) --- -Argenta allows you to encapsulate CLI functionality in isolated, abstracted environments. Eg: you are creating a utility similar to the Metasploit Framework, where the user first logs into a specific scope (for example, selects a module to scan), and then gets access to a set of commands specific only to that context. Argenta provides a simple and concise way to build such an architecture. +![preview](https://i.ibb.co/fzWcfgFq/2025-12-04-173045.png) ---- +**Argenta** allows you to build interactive CLI applications incredibly easily. There's no need to manually parse complex command structures or manage state transitions — just use routers and commands! -![preview](https://github.com/koloideal/Argenta/blob/main/imgs/mock_app_preview4.png?raw=True) +## ✨ Installing Argenta ---- +Argenta is available on ``PyPI``: -# Installing -```bash -pip install argenta -``` -or -```bash -poetry add argenta +```console +$ python -m pip install argenta ``` ---- +or using ``uv``: -# Quick start +```console +$ uv add argenta +``` + +Argenta officially supports Python 3.12+. + +## 🚀 Supported Features & Best Practices + +Argenta is ready for the demands of building scalable, robust and maintainable CLI applications. + +- **Interactive Sessions**: Unlike traditional CLI tools, ``Argenta`` creates cyclic sessions, allowing users to execute commands sequentially without restarting the application. +- **Declarative Syntax**: Commands and their handlers are declared using simple decorators, making the code intuitive and allowing you to focus on "what" you want to do, not "how". +- **Native DI**: Thanks to integration with [dishka](https://dishka.readthedocs.io/en/stable/), you can easily inject dependencies directly into command handlers, simplifying testing, avoiding mutable globals, and much more. +- **Automatic Validation and Parsing**: The library handles command-line flags and arguments processing, including parsing, validation, and type conversion. +- **Flexible Configuration**: You can easily customize system messages, output formatting, create custom handlers for non-standard behavior, and more. + +Need something more? Create an **issue**, we're open to suggestions. + +## 📝 Why did we create this? + +Building complex CLI applications often requires managing different contexts and command scopes. For example, when creating a utility similar to the Metasploit Framework, users need to enter specific scopes (like selecting a scanning module) and then access commands specific only to that context. + +Traditional CLI frameworks don't provide an elegant way to handle this kind of modular, context-aware architecture. Argenta was built to solve this problem by providing a simple and concise way to encapsulate CLI functionality in isolated, abstracted environments. + +We believe that building CLI applications should be as pleasant as building web applications with modern frameworks. Argenta brings the router pattern and clean separation of concerns to the CLI world. + +## 🚀 Quick Start + +Here's a simple example to get you started: -An example of a simple application ```python # routers.py -from argenta.router import Router -from argenta.command import Command -from argenta.response import Response - +from argenta import Router, Response +from argenta.command import Flag, Command router = Router() @router.command(Command("hello")) def handler(response: Response): + """A simple hello world command""" print("Hello, world!") + +@router.command(Command("greet", flags=Flag('name'))) +def greet_handler(response: Response): + """Greet a user by name""" + name_flag = response.input_flags.get_flag_by_name('name') + if name_flag: + print(f"Hello, {name_flag.input_value}!") + else: + print("Hello, Stranger!") ``` ```python # main.py -from argenta.app import App -from argenta.orchestrator import Orchestrator -from routers import router - -app: App = App() -orchestrator: Orchestrator = Orchestrator() +from argenta import App, Orchestrator +from .routers import router +app = App() +orchestrator = Orchestrator() def main() -> None: + # Include your routers app.include_router(router) + + # Start the interactive CLI orchestrator.start_polling(app) - if __name__ == '__main__': main() ``` +That's it! You now have a fully functional interactive CLI application. + +## 📚 Documentation + +Full documentation is available at [argenta.readthedocs.io](https://argenta.readthedocs.io/) + --- -# Features in development - -- Full support for autocompleter on Linux - -## Full [docs](https://argenta-docs.vercel.app) | MIT 2025 kolo | made by [kolo](https://t.me/kolo_id) - - - +MIT 2025 kolo | made by [kolo](https://t.me/kolo_id) diff --git a/README.ru.md b/README.ru.md index a204300..fe00e96 100644 --- a/README.ru.md +++ b/README.ru.md @@ -1,69 +1,108 @@ -# Argenta +![preview](https://i.ibb.co/whkMfHw5/a-minimalist-logo-design-featuring-the-t-t-GXhfz-NFRwi-k-ROq-NMd-LWA-R7-6ru-YSh-G0kz-LKr5t-ZQ.jpg) -### Библиотека для создания модульных CLI приложeний +**Argenta** — это простой и элегантный фреймворк для создания модульных CLI-приложений. Он предоставляет чистый и интуитивный способ создания контекстно-зависимых инструментов командной строки с изолированными областями команд. -Argenta позволяет инкапсулировать CLI фукциональность в изолированные, абстрагированные **среды**. К примеру: вы создаете утилиту, подобную Metasploit Framework, где пользователь сначала входит в определенный скоуп (например, выбирает модуль для сканирования), а затем получает доступ к набору команд, специфичных только для этого контекста. Argenta предоставляет простой и лаконичный способ для построения такой архитектуры. +Argenta — это **"Самый простой"**, **"Самый модульный"** и **"Самый элегантный"** способ создания интерактивных CLI-приложений на Python. + +📖 **Читайте полную документацию:** [argenta.readthedocs.io](https://argenta.readthedocs.io/)
+🌍 **Другие языки:** [EN](https://github.com/koloideal/Argenta/blob/main/README.md) --- -![preview](https://github.com/koloideal/Argenta/blob/main/imgs/mock_app_preview4.png?raw=True) +![preview](https://i.ibb.co/fzWcfgFq/2025-12-04-173045.png) ---- +**Argenta** позволяет создавать интерактивные CLI-приложения невероятно легко. Не нужно вручную парсить сложные структуры команд или управлять переходами состояний — просто используйте роутеры и команды! -# Установка -```bash -pip install argenta -``` -or -```bash -poetry add argenta +## ✨ Установка Argenta + +Argenta доступна на ``PyPI``: + +```console +$ python -m pip install argenta ``` ---- +или с использованием ``uv``: -# Быстрый старт +```console +$ uv add argenta +``` + +Argenta официально поддерживает Python 3.12+. + +## 🚀 Поддерживаемые возможности и лучшие практики + +Argenta готова к требованиям создания масштабируемых, надежных и поддерживаемых CLI-приложений. + +- **Интерактивные сессии**: В отличие от традиционных CLI-инструментов, ``Argenta`` создаёт циклические сессии, позволяя пользователю выполнять команды последовательно, не перезапуская приложение. +- **Декларативный синтаксис**: Команды и их обработчики объявляются с помощью простых декораторов, что делает код интуитивно понятным и позволяет сосредоточиться на том, "что" вы хотите сделать, а не "как". +- **Нативный DI**: Благодаря интеграции с [dishka](https://dishka.readthedocs.io/en/stable/), вы можете легко внедрять зависимости прямо в обработчики команд, что упрощает их тестирование, позволяет избежать мутабельных глобалов и многое другое. +- **Автоматическая валидация и парсинг**: Библиотека берёт на себя обработку флагов и аргументов командной строки, включая их парсинг, валидацию и преобразование типов. +- **Гибкая настройка**: Вы можете легко кастомизировать системные сообщения, форматирование вывода, ссоздавать кастомные обработчики нестандартного поведения и т.д. + +Нужно что-то еще? Создайте **issue**, мы открыты к предложениям. + +## 📝 Зачем мы это создали? + +Создание сложных CLI-приложений часто требует управления различными контекстами и областями команд. Например, при создании утилиты, подобной Metasploit Framework, пользователям нужно входить в определенные области (например, выбирать модуль сканирования), а затем получать доступ к командам, специфичным только для этого контекста. + +Традиционные CLI-фреймворки не предоставляют элегантного способа обработки такой модульной, контекстно-зависимой архитектуры. Argenta была создана для решения этой проблемы, предоставляя простой и лаконичный способ инкапсуляции CLI-функциональности в изолированные, абстрагированные среды. + +Мы считаем, что создание CLI-приложений должно быть таким же приятным, как создание веб-приложений с современными фреймворками. Argenta привносит паттерн роутеров и чистое разделение ответственности в мир CLI. + +## 🚀 Быстрый старт + +Вот простой пример для начала работы: -Пример простейшего приложения ```python # routers.py -from argenta.router import Router -from argenta.command import Command -from argenta.response import Response - +from argenta import Router, Response +from argenta.command import Flag, Command router = Router() @router.command(Command("hello")) def handler(response: Response): - print("Hello, world!") + """Простая команда hello world""" + print("Привет, мир!") + +@router.command(Command("greet", flags=Flag('name'))) +def greet_handler(response: Response): + """Поприветствовать пользователя по имени""" + name_flag = response.input_flags.get_flag_by_name('name') + if name_flag: + print(f"Привет, {name_flag.input_value}!") + else: + print("Привет, Незнакомец!") ``` ```python # main.py -from argenta.app import App -from argenta.orchestrator import Orchestrator -from routers import router - -app: App = App() -orchestrator: Orchestrator = Orchestrator() +from argenta import App, Orchestrator +from .routers import router +app = App() +orchestrator = Orchestrator() def main() -> None: + # Подключите ваши роутеры app.include_router(router) + + # Запустите интерактивный CLI orchestrator.start_polling(app) - if __name__ == '__main__': main() ``` +Вот и всё! Теперь у вас есть полностью функциональное интерактивное CLI-приложение. + +## 📚 Документация + +Полная документация доступна на [argenta.readthedocs.io](https://argenta.readthedocs.io/) + --- -# Фичи в разработке - -- Полноценная поддержка автокомплитера на Linux - -## Полная [документация](https://argenta-docs.vercel.app) | MIT 2025 kolo | made by [kolo](https://t.me/kolo_id) +MIT 2025 kolo | made by [kolo](https://t.me/kolo_id) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9f5766d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,54 @@ +# Security Policy + +The Argenta team takes security seriously. We appreciate your efforts to responsibly disclose your findings, and we will make every effort to acknowledge your contributions. + +## Supported Versions + +This table shows the versions of Argenta that are currently supported with security updates. + +| Version | Supported | +|---------|-----------------| +| 1.1.x | ✅ | +| < 1.1 | ❌ | + +## Reporting a Vulnerability + +If you believe you have found a security vulnerability in Argenta, please report it to us through one of the following methods: + +- **Email**: Send a detailed report to `kolo.is.main@gmailcom`. +- **GitHub Security Advisories**: You can create a new security advisory directly in the Argenta repository. + +**Please do not report security vulnerabilities through public GitHub issues.** + +### What to Include + +To help us understand and resolve the issue quickly, please include the following information in your report: + +- A clear and descriptive title. +- The affected version(s) of Argenta. +- A detailed description of the vulnerability. +- Step-by-step instructions to reproduce the issue (a Proof-of-Concept). +- The potential impact of the vulnerability. +- Any suggested mitigations or fixes, if you have any. + +You can expect a response from us within 48 hours to acknowledge receipt of your report. + +## Disclosure Process + +1. Upon receiving a vulnerability report, we will assign it to a team member and begin our investigation. +2. We will confirm the vulnerability and determine its severity. +3. We will work on a patch to address the issue. +4. Once the patch is ready, we will coordinate with you to schedule a release and a public disclosure. We prefer to disclose vulnerabilities through a GitHub Security Advisory. +5. We will credit you for your discovery in the advisory, unless you prefer to remain anonymous. + +## Security Best Practices + +As a user of Argenta, we recommend the following best practices to keep your application secure: + +- Always use the latest version of Argenta. +- Regularly scan your project's dependencies for known vulnerabilities. +- If your application handles sensitive data, ensure it is stored and transmitted securely. + +## Bug Bounty Program + +Currently, we do not have a formal bug bounty program. However, we deeply appreciate the work of security researchers and may offer non-monetary recognition for significant contributions. diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..ea0faf0 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,3 @@ +_build/ +_static/ +*.mo \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..79f48f4 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,35 @@ +# 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 +LANGUAGES = en ru + +build-all: + sphinx-build -b html -D language=ru $(SOURCEDIR) $(BUILDDIR)/html/ru + sphinx-build -b html -D language=en $(SOURCEDIR) $(BUILDDIR)/html/en + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +live-ru: + sphinx-autobuild -b html . _build/html/ru -D language=ru + +live-en: + sphinx-autobuild -b html . _build/html/en -D language=en + +update-langs: + sphinx-build -b gettext . _build/gettext + sphinx-intl update -p _build/gettext -l en + +.PHONY: help Makefile serve-all + +# 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/code_snippets/argparser/snippet.py b/docs/code_snippets/argparser/snippet.py new file mode 100644 index 0000000..4b4fab5 --- /dev/null +++ b/docs/code_snippets/argparser/snippet.py @@ -0,0 +1,13 @@ +from argenta import App, Orchestrator +from argenta.orchestrator.argparser import ArgParser, BooleanArgument, ValueArgument + +arg_parser = ArgParser(processed_args=[BooleanArgument("dev"), ValueArgument('some', possible_values=['fuck', 'cruck'])]) +orchestrator = Orchestrator( + arg_parser=arg_parser, +) + +if __name__ == "__main__": + if arg_parser.parsed_argspace.get_by_name('dev'): + orchestrator.start_polling(App(initial_message='ArgentaDev')) + else: + orchestrator.start_polling(App()) diff --git a/docs/code_snippets/argspace/snippet.py b/docs/code_snippets/argspace/snippet.py new file mode 100644 index 0000000..f7f8e40 --- /dev/null +++ b/docs/code_snippets/argspace/snippet.py @@ -0,0 +1,33 @@ +from argenta import App, Orchestrator +from argenta.orchestrator.argparser import ArgParser, ValueArgument + +arguments = [ + ValueArgument("host", help="Server host", is_required=True), + ValueArgument("port", help="Server port", is_required=True), +] + +argparser = ArgParser( + processed_args=arguments, + name="WebServer", + description="Simple web server" +) + +app = App() +orchestrator = Orchestrator(argparser) + + +def main(): + argspace = argparser.parsed_argspace + + host = argspace.get_by_name("host") + port = argspace.get_by_name("port") + + print("Server configuration:") + print(f" Host: {host.value}") + print(f" Port: {port.value}") + + orchestrator.start_polling(app) + + +if __name__ == "__main__": + main() diff --git a/docs/code_snippets/argspace/snippet2.py b/docs/code_snippets/argspace/snippet2.py new file mode 100644 index 0000000..14ae759 --- /dev/null +++ b/docs/code_snippets/argspace/snippet2.py @@ -0,0 +1,10 @@ +from argenta import Response, Router +from argenta.di import FromDishka +from argenta.orchestrator.argparser import ArgSpace + +router = Router() + + +@router.command("get_args") +async def get_args(response: Response, argspace: FromDishka[ArgSpace]): + print(argspace.all_arguments) diff --git a/docs/code_snippets/argspace/snippet3.py b/docs/code_snippets/argspace/snippet3.py new file mode 100644 index 0000000..6282661 --- /dev/null +++ b/docs/code_snippets/argspace/snippet3.py @@ -0,0 +1,21 @@ +from argenta import Response, Router +from argenta.di import FromDishka +from argenta.orchestrator.argparser import ArgSpace, BooleanArgument, ValueArgument + +router = Router() + + +@router.command("get_args") +def get_args(response: Response, argspace: FromDishka[ArgSpace]): + # Get all boolean flags + boolean_flags = argspace.get_by_type(BooleanArgument) + print(f"Active flags: {[arg.name for arg in boolean_flags if arg.value]}") + + # Get all value arguments + value_args = argspace.get_by_type(ValueArgument) + for arg in value_args: + print(f"{arg.name} = {arg.value}") + + # Count arguments of each type + print(f"Boolean arguments: {len(argspace.get_by_type(BooleanArgument))}") + print(f"Value arguments: {len(argspace.get_by_type(ValueArgument))}") diff --git a/docs/code_snippets/argspace/snippet4.py b/docs/code_snippets/argspace/snippet4.py new file mode 100644 index 0000000..efb3180 --- /dev/null +++ b/docs/code_snippets/argspace/snippet4.py @@ -0,0 +1,11 @@ +config_arg = argspace.get_by_name("config") +if config_arg: + print(f"Config path: {config_arg.value}") + +verbose_arg = argspace.get_by_name("verbose") +if verbose_arg and verbose_arg.value: + print("Verbose mode enabled") + +unknown_arg = argspace.get_by_name("nonexistent") +if unknown_arg is None: + print("Argument not found") diff --git a/docs/code_snippets/arguments/snippet.py b/docs/code_snippets/arguments/snippet.py new file mode 100644 index 0000000..6e8f8e0 --- /dev/null +++ b/docs/code_snippets/arguments/snippet.py @@ -0,0 +1,28 @@ +from argenta.orchestrator.argparser import ArgParser, ValueArgument + +# Create arguments +config_arg = ValueArgument( + "config", + help="Path to configuration file", + default="config.yaml" +) + +log_level_arg = ValueArgument( + "log-level", + help="Logging level", + possible_values=["DEBUG", "INFO", "WARNING", "ERROR"], + default="INFO" +) + +host_arg = ValueArgument( + "host", + help="Server host address", + is_required=True +) + +# Register in ArgParser +parser = ArgParser( + processed_args=[config_arg, log_level_arg, host_arg], + name="MyApp", + description="My application with CLI arguments" +) \ No newline at end of file diff --git a/docs/code_snippets/arguments/snippet2.py b/docs/code_snippets/arguments/snippet2.py new file mode 100644 index 0000000..254ed82 --- /dev/null +++ b/docs/code_snippets/arguments/snippet2.py @@ -0,0 +1,23 @@ +from argenta.orchestrator.argparser import ArgParser, BooleanArgument + +# Create boolean arguments +verbose_arg = BooleanArgument( + "verbose", + help="Enable verbose output" +) + +debug_arg = BooleanArgument( + "debug", + help="Enable debug mode" +) + +no_cache_arg = BooleanArgument( + "no-cache", + help="Disable caching" +) + +# Register in ArgParser +parser = ArgParser( + processed_args=[verbose_arg, debug_arg, no_cache_arg], + name="MyApp" +) \ No newline at end of file diff --git a/docs/code_snippets/autocompleter/snippet.py b/docs/code_snippets/autocompleter/snippet.py new file mode 100644 index 0000000..90ae304 --- /dev/null +++ b/docs/code_snippets/autocompleter/snippet.py @@ -0,0 +1,10 @@ +from argenta import App +from argenta.app import AutoCompleter + +# Setting up autocompletion with saving history to a file +my_autocompleter = AutoCompleter(history_filename="argenta_history.txt") + +# Passing the configured autocompleter to the application +app = App(autocompleter=my_autocompleter) + +# ... the rest of the application logic diff --git a/docs/code_snippets/command/snippet.py b/docs/code_snippets/command/snippet.py new file mode 100644 index 0000000..4ff62df --- /dev/null +++ b/docs/code_snippets/command/snippet.py @@ -0,0 +1,20 @@ +from argenta.command import Flag, Flags, Command + +# Simple command without flags +hello_cmd = Command("hello", description="Greet the user") + +# Command with description and aliases +quit_cmd = Command("quit", description="Exit the application", aliases=["exit", "q"]) + +# Command with flags +deploy_cmd = Command( + "deploy", + description="Deploy application to server", + flags=Flags( + [ + Flag("env", possible_values=["dev", "prod"]), + Flag("force"), + ] + ), + aliases=["dep"], +) diff --git a/docs/code_snippets/command/snippet2.py b/docs/code_snippets/command/snippet2.py new file mode 100644 index 0000000..b2bbaef --- /dev/null +++ b/docs/code_snippets/command/snippet2.py @@ -0,0 +1,19 @@ +from argenta import Command, Response, Router + +router = Router(title="User Management") + + +@router.command(Command("create-user", description="Create a new user account")) +def handle_create_user(response: Response): + print("Creating new user...") + + +@router.command( + Command( + "delete-user", + description="Delete existing user account", + aliases=["remove-user", "rm-user"], + ) +) +def handle_delete_user(response: Response): + print("Deleting user...") diff --git a/docs/code_snippets/command/snippet3.py b/docs/code_snippets/command/snippet3.py new file mode 100644 index 0000000..ff5b9c8 --- /dev/null +++ b/docs/code_snippets/command/snippet3.py @@ -0,0 +1,33 @@ +from argenta import Command, Response, Router +from argenta.command import Flag, Flags + +router = Router(title="Server Management") + + +@router.command( + Command( + "start", + description="Start the server", + flags=Flags( + [ + Flag("port"), + Flag("host"), + Flag("debug"), + ] + ), + aliases=["run"], + ) +) +def handle_start(response: Response): + input_flags = response.input_flags + port_flag = input_flags.get_flag_by_name("port") + host_flag = input_flags.get_flag_by_name("host") + debug_flag = input_flags.get_flag_by_name("debug") + + host = host_flag.input_value if host_flag else "localhost" + port = port_flag.input_value if port_flag else "8080" + debug = debug_flag and debug_flag.input_value + + print(f"Starting server on {host}:{port}") + if debug: + print("Debug mode: ON") diff --git a/docs/code_snippets/command/snippet4.py b/docs/code_snippets/command/snippet4.py new file mode 100644 index 0000000..b364a7b --- /dev/null +++ b/docs/code_snippets/command/snippet4.py @@ -0,0 +1,11 @@ +from argenta.command import InputCommand + +# Parse command without flags +cmd1 = InputCommand.parse("hello") +print(cmd1.trigger) # "hello" +print(len(cmd1.input_flags)) # 0 + +# Parse command with flags +cmd2 = InputCommand.parse("deploy --env prod --force") +print(cmd2.trigger) # "deploy" +print(len(cmd2.input_flags)) # 2 diff --git a/docs/code_snippets/command/snippet5.py b/docs/code_snippets/command/snippet5.py new file mode 100644 index 0000000..4939f95 --- /dev/null +++ b/docs/code_snippets/command/snippet5.py @@ -0,0 +1,11 @@ +from argenta import Router, Command, Response + +router = Router(title="System") + +@router.command(Command( + "shutdown", + description="Shutdown the system", + aliases=["poweroff", "halt", "stop"] +)) +def handle_shutdown(response: Response): + print("Shutting down the system...") \ No newline at end of file diff --git a/docs/code_snippets/dependency_injection/snippet.py b/docs/code_snippets/dependency_injection/snippet.py new file mode 100644 index 0000000..681a8df --- /dev/null +++ b/docs/code_snippets/dependency_injection/snippet.py @@ -0,0 +1,9 @@ +from sqlite3 import Connection +from argenta import Response, Router +from argenta.di import FromDishka + +router = Router() + +@router.command("connect") +def connect_handler(response: Response, connection: FromDishka[Connection]): + connection.execute("...") diff --git a/docs/code_snippets/dependency_injection/snippet2.py b/docs/code_snippets/dependency_injection/snippet2.py new file mode 100644 index 0000000..2bb4584 --- /dev/null +++ b/docs/code_snippets/dependency_injection/snippet2.py @@ -0,0 +1,13 @@ +import sqlite3 +from sqlite3 import Connection +from typing import Iterable + +from dishka import Provider, Scope, provide + + +class ConnectionProvider(Provider): + @provide(scope=Scope.REQUEST) + def new_connection(self) -> Iterable[Connection]: + conn = sqlite3.connect(":memory:") + yield conn + conn.close() diff --git a/docs/code_snippets/dependency_injection/snippet3.py b/docs/code_snippets/dependency_injection/snippet3.py new file mode 100644 index 0000000..182df70 --- /dev/null +++ b/docs/code_snippets/dependency_injection/snippet3.py @@ -0,0 +1,3 @@ +from argenta import Orchestrator + +orchestrator = Orchestrator(custom_providers=[ConnectionProvider()]) diff --git a/docs/code_snippets/dependency_injection/snippet4.py b/docs/code_snippets/dependency_injection/snippet4.py new file mode 100644 index 0000000..1050d9e --- /dev/null +++ b/docs/code_snippets/dependency_injection/snippet4.py @@ -0,0 +1,9 @@ +from argenta import Response, Router +from argenta.di import FromDishka +from argenta.orchestrator.argparser import ArgSpace + +router = Router() + +@router.command("info") +def connect_handler(response: Response, argspace: FromDishka[ArgSpace]): + print(argspace.get_by_name("type")) diff --git a/docs/code_snippets/error_handling/snippet.py b/docs/code_snippets/error_handling/snippet.py new file mode 100644 index 0000000..a270faa --- /dev/null +++ b/docs/code_snippets/error_handling/snippet.py @@ -0,0 +1,7 @@ +from argenta import App + +def empty_command_handler(): + print("Empty command handler called") + +app: App = App() +app.set_empty_command_handler(empty_command_handler) diff --git a/docs/code_snippets/error_handling/snippet2.py b/docs/code_snippets/error_handling/snippet2.py new file mode 100644 index 0000000..324ade7 --- /dev/null +++ b/docs/code_snippets/error_handling/snippet2.py @@ -0,0 +1,7 @@ +from argenta import App + +def incorrect_input_syntax_handler(raw_command: str): + print(f"Incorrect input syntax for command: {raw_command}") + +app: App = App() +app.set_incorrect_input_syntax_handler(incorrect_input_syntax_handler) diff --git a/docs/code_snippets/error_handling/snippet3.py b/docs/code_snippets/error_handling/snippet3.py new file mode 100644 index 0000000..a5a1f61 --- /dev/null +++ b/docs/code_snippets/error_handling/snippet3.py @@ -0,0 +1,7 @@ +from argenta import App + +def repeated_input_flags_handler(raw_command: str): + print(f"Repeated input flags: {raw_command}") + +app: App = App() +app.set_repeated_input_flags_handler(repeated_input_flags_handler) diff --git a/docs/code_snippets/error_handling/snippet4.py b/docs/code_snippets/error_handling/snippet4.py new file mode 100644 index 0000000..9d6efdf --- /dev/null +++ b/docs/code_snippets/error_handling/snippet4.py @@ -0,0 +1,7 @@ +from argenta import App + +def empty_command_handler(): + print("Empty input command") + +app: App = App() +app.set_empty_command_handler(empty_command_handler) diff --git a/docs/code_snippets/error_handling/snippet5.py b/docs/code_snippets/error_handling/snippet5.py new file mode 100644 index 0000000..2ea24fb --- /dev/null +++ b/docs/code_snippets/error_handling/snippet5.py @@ -0,0 +1,8 @@ +from argenta import App +from argenta.command import InputCommand + +def unknown_command_handler(command: InputCommand): + print(f"Unknown input command with trigger: {command.trigger}") + +app: App = App() +app.set_unknown_command_handler(unknown_command_handler) diff --git a/docs/code_snippets/error_handling/snippet6.py b/docs/code_snippets/error_handling/snippet6.py new file mode 100644 index 0000000..b14f60b --- /dev/null +++ b/docs/code_snippets/error_handling/snippet6.py @@ -0,0 +1,7 @@ +from argenta import App, Response + +def exit_command_handler(response: Response): + print("Exit command handler") + +app: App = App() +app.set_exit_command_handler(exit_command_handler) diff --git a/docs/code_snippets/flag/predefined_flags.py b/docs/code_snippets/flag/predefined_flags.py new file mode 100644 index 0000000..e2176c6 --- /dev/null +++ b/docs/code_snippets/flag/predefined_flags.py @@ -0,0 +1,36 @@ +from argenta.command import Flags, PredefinedFlags + +# Using predefined flags when creating a command +command_flags = Flags( + [ + PredefinedFlags.HELP, + PredefinedFlags.SHORT_HELP, + PredefinedFlags.INFO, + ] +) + +# Using Network Flags +network_flags = Flags( + [ + PredefinedFlags.HOST, + PredefinedFlags.PORT, + ] +) + +# Validating the values of predefined flags +print(PredefinedFlags.HOST.validate_input_flag_value("192.168.1.1")) # True +print(PredefinedFlags.HOST.validate_input_flag_value("invalid")) # False + +print(PredefinedFlags.PORT.validate_input_flag_value("8080")) # True +print(PredefinedFlags.PORT.validate_input_flag_value("99999")) # True +print(PredefinedFlags.PORT.validate_input_flag_value("abc")) # False + +# Flags without values +print(PredefinedFlags.HELP.validate_input_flag_value(None)) # True +print(PredefinedFlags.HELP.validate_input_flag_value("something")) # False + +# Checking string representations +print(PredefinedFlags.HELP.string_entity) # --help +print(PredefinedFlags.SHORT_HELP.string_entity) # -H +print(PredefinedFlags.HOST.string_entity) # --host +print(PredefinedFlags.SHORT_PORT.string_entity) # -P diff --git a/docs/code_snippets/flag/snippet.py b/docs/code_snippets/flag/snippet.py new file mode 100644 index 0000000..a46b1a5 --- /dev/null +++ b/docs/code_snippets/flag/snippet.py @@ -0,0 +1,20 @@ +import re +from argenta.command import Flag, PossibleValues + +# Simple flag with any values +verbose_flag = Flag(name="verbose") + +# Flag with short prefix +short_flag = Flag(name="v", prefix="-") + +# Flag that does not take a value +help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER) + +# Flag with list of possible values +format_flag = Flag(name="format", possible_values=["json", "xml", "csv"]) + +# Flag with regexp for validation input value +email_flag = Flag( + name="email", + possible_values=re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"), +) diff --git a/docs/code_snippets/flag/snippet2.py b/docs/code_snippets/flag/snippet2.py new file mode 100644 index 0000000..f98611d --- /dev/null +++ b/docs/code_snippets/flag/snippet2.py @@ -0,0 +1,20 @@ +import re + +from argenta.command.flag.models import Flag, PossibleValues + +# Flag with list of allowed values +format_flag = Flag(name="format", possible_values=["json", "xml", "csv"]) + +# Value validation +print(format_flag.validate_input_flag_value("json")) # True +print(format_flag.validate_input_flag_value("pdf")) # False + +# Flag without value +help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER) +print(help_flag.validate_input_flag_value(None)) # True +print(help_flag.validate_input_flag_value("value")) # False + +# Flag with regular expression +port_flag = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$")) +print(port_flag.validate_input_flag_value("8080")) # True +print(port_flag.validate_input_flag_value("abc")) # False diff --git a/docs/code_snippets/flag/snippet4.py b/docs/code_snippets/flag/snippet4.py new file mode 100644 index 0000000..ccae89f --- /dev/null +++ b/docs/code_snippets/flag/snippet4.py @@ -0,0 +1,9 @@ +from argenta.command import Flag + +help_flag = Flag(name="help") +version_flag = Flag(name="V", prefix="-") + +print(help_flag) # --help + +message = f"Use {help_flag} to see help" +print(message) # Use --help to see help diff --git a/docs/code_snippets/flag/snippet5.py b/docs/code_snippets/flag/snippet5.py new file mode 100644 index 0000000..7200566 --- /dev/null +++ b/docs/code_snippets/flag/snippet5.py @@ -0,0 +1,12 @@ +from argenta.command import Flag + +verbose_flag = Flag(name="verbose", prefix="--") +short_flag = Flag(name="v", prefix="-") + +# Debug view +print(repr(verbose_flag)) # Flag +print(repr(short_flag)) # Flag + +# In an interactive console or debugger +# >>> verbose_flag +# Flag diff --git a/docs/code_snippets/flag/snippet6.py b/docs/code_snippets/flag/snippet6.py new file mode 100644 index 0000000..0ac23e1 --- /dev/null +++ b/docs/code_snippets/flag/snippet6.py @@ -0,0 +1,21 @@ +from argenta.command import Flag, PossibleValues + +# Creating two flags with the same name and prefix +flag1 = Flag(name="verbose", prefix="--") +flag2 = Flag(name="verbose", prefix="--") + +# Flag comparison +print(flag1 == flag2) # True + +# Flags with different prefixes are not equal +flag3 = Flag(name="verbose", prefix="-") +print(flag1 == flag3) # False + +# Flags with different names are not equal +flag4 = Flag(name="help", prefix="--") +print(flag1 == flag4) # False + +# Different possible_values do not affect equality +flag5 = Flag(name="verbose", prefix="--", possible_values=PossibleValues.NEITHER) +flag6 = Flag(name="verbose", prefix="--", possible_values=["value1", "value2"]) +print(flag5 == flag6) diff --git a/docs/code_snippets/flags/deploy_handler.py b/docs/code_snippets/flags/deploy_handler.py new file mode 100644 index 0000000..d0f2e2b --- /dev/null +++ b/docs/code_snippets/flags/deploy_handler.py @@ -0,0 +1,24 @@ +from argenta import Router, Response +from argenta.command import Command, Flag, PossibleValues +from argenta.command.flag import ValidationStatus + +router = Router() + + +@router.command(Command( + "deploy", + flags=Flag("verbose", possible_values=PossibleValues.NEITHER) +)) +def deploy_handler(response: Response): + # Check for toggle flag presence + verbose_flag = response.input_flags.get_flag_by_name("verbose") + + if verbose_flag and verbose_flag.status == ValidationStatus.VALID: + print("Deploying with verbose output...") + # Detailed logic + elif verbose_flag and verbose_flag.status == ValidationStatus.INVALID: + print("Incorrect flag value") + return + else: + print("Deploying...") + # Normal logic diff --git a/docs/code_snippets/flags/greet_handler.py b/docs/code_snippets/flags/greet_handler.py new file mode 100644 index 0000000..41f215f --- /dev/null +++ b/docs/code_snippets/flags/greet_handler.py @@ -0,0 +1,16 @@ +from argenta import Router, Response +from argenta.command import Command, Flag + +router = Router() + + +@router.command(Command("greet", flags=Flag("name"))) +def greet_handler(response: Response): + # Get flag by name + name_flag = response.input_flags.get_flag_by_name("name") + + # Check if flag was passed + if name_flag: + print(f"Hello, {name_flag.input_value}!") + else: + print("Hello, stranger!") diff --git a/docs/code_snippets/flags/snippet.py b/docs/code_snippets/flags/snippet.py new file mode 100644 index 0000000..5870f86 --- /dev/null +++ b/docs/code_snippets/flags/snippet.py @@ -0,0 +1,11 @@ +import re +from argenta.command import Command, Flag, Flags + +flags = Flags( + [ + Flag("host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")), + Flag("port", possible_values=re.compile(r"^\d{1,5}$")), + ] +) + +cmd = Command("start", description="Start the server", flags=flags) diff --git a/docs/code_snippets/flags/snippet2.py b/docs/code_snippets/flags/snippet2.py new file mode 100644 index 0000000..dd264d8 --- /dev/null +++ b/docs/code_snippets/flags/snippet2.py @@ -0,0 +1,9 @@ +from argenta.command import Flag, Flags + +flags: Flags = Flags() + +flags.add_flag(Flag("config")) +flags.add_flag(Flag("debug")) +flags.add_flag(Flag("log-level", possible_values=["INFO", "DEBUG", "ERROR"])) + +print(len(flags)) # 3 diff --git a/docs/code_snippets/flags/snippet3.py b/docs/code_snippets/flags/snippet3.py new file mode 100644 index 0000000..082247d --- /dev/null +++ b/docs/code_snippets/flags/snippet3.py @@ -0,0 +1,15 @@ +from argenta.command import Flag, Flags +from argenta.command.flag.defaults import PredefinedFlags + +flags = Flags([PredefinedFlags.HOST]) + +additional_flags = [ + PredefinedFlags.PORT, + Flag("database"), + Flag("ssl"), + Flag("verbose"), +] + +flags.add_flags(additional_flags) + +print(len(flags)) # 5 diff --git a/docs/code_snippets/flags/snippet4.py b/docs/code_snippets/flags/snippet4.py new file mode 100644 index 0000000..abd5a8c --- /dev/null +++ b/docs/code_snippets/flags/snippet4.py @@ -0,0 +1,13 @@ +from argenta.command import Flag, Flags +from argenta.command.flag.defaults import PredefinedFlags + + +flags = Flags([PredefinedFlags.HOST, PredefinedFlags.PORT, Flag("verbose")]) + +host_flag = flags.get_flag_by_name("host") +if host_flag: + print(f"Found flag: {host_flag.name}") + +unknown_flag = flags.get_flag_by_name("nonexistent") +if unknown_flag is None: + print("Flag not found") diff --git a/docs/code_snippets/input_flag/snippet3.py b/docs/code_snippets/input_flag/snippet3.py new file mode 100644 index 0000000..bc100f3 --- /dev/null +++ b/docs/code_snippets/input_flag/snippet3.py @@ -0,0 +1,13 @@ +from argenta.command.flag import InputFlag, ValidationStatus + +flag_with_value = InputFlag( + name="output", prefix="--", input_value="result.txt", status=ValidationStatus.VALID +) + +flag_without_value = InputFlag( + name="help", prefix="-", input_value='', status=ValidationStatus.VALID +) + +# String representation includes value +print(str(flag_with_value)) # --output result.txt +print(str(flag_without_value)) # -help diff --git a/docs/code_snippets/input_flag/snippet4.py b/docs/code_snippets/input_flag/snippet4.py new file mode 100644 index 0000000..3e96e08 --- /dev/null +++ b/docs/code_snippets/input_flag/snippet4.py @@ -0,0 +1,12 @@ +from argenta.command.flag import InputFlag, ValidationStatus + +flag = InputFlag( + name="config", + prefix="--", + input_value="settings.json", + status=ValidationStatus.VALID, +) + +# Debug representation of the object +print(repr(flag)) +# InputFlag diff --git a/docs/code_snippets/input_flags/snippet1.py b/docs/code_snippets/input_flags/snippet1.py new file mode 100644 index 0000000..93d528f --- /dev/null +++ b/docs/code_snippets/input_flags/snippet1.py @@ -0,0 +1,23 @@ +from argenta import Command, Response, Router +from argenta.command import Flag, Flags + + +router = Router(title="Example") + +@router.command( + Command( + "example", + description="Example command with flags", + flags=Flags([ + Flag("name"), + Flag("age") + ]), + ) +) +def example_handler(response: Response): + input_flags = response.input_flags + + if input_flags: + print(f"Received {len(input_flags)} flag(s)") + else: + print("No flags provided") diff --git a/docs/code_snippets/input_flags/snippet10.py b/docs/code_snippets/input_flags/snippet10.py new file mode 100644 index 0000000..bf69913 --- /dev/null +++ b/docs/code_snippets/input_flags/snippet10.py @@ -0,0 +1,49 @@ +from argenta import Command, Response, Router +from argenta.command import Flag, Flags, InputFlag +from argenta.command.flag import ValidationStatus + +router = Router(title="Comprehensive Example") + + +@router.command( + Command( + "validate", + description="Validate all flags", + flags=Flags( + [ + Flag("format", possible_values=["json", "xml"]), + Flag("output"), + Flag("force"), + ] + ), + ) +) +def validate_handler(response: Response): + input_flags = response.input_flags + + print("Flag validation results:") + + valid_flags: list[InputFlag] = [] + invalid_flags: list[InputFlag] = [] + undefined_flags: list[InputFlag] = [] + + for flag in input_flags: + if flag.status == ValidationStatus.VALID: + valid_flags.append(flag) + print(f" ✓ {flag.string_entity}: {flag.input_value} (VALID)") + elif flag.status == ValidationStatus.INVALID: + invalid_flags.append(flag) + print(f" ✗ {flag.string_entity}: {flag.input_value} (INVALID)") + elif flag.status == ValidationStatus.UNDEFINED: + undefined_flags.append(flag) + print(f" ? {flag.string_entity}: {flag.input_value} (UNDEFINED)") + + print("\nSummary:") + print(f" Valid flags: {len(valid_flags)}") + print(f" Invalid flags: {len(invalid_flags)}") + print(f" Undefined flags: {len(undefined_flags)}") + + if valid_flags: + print("\nProcessing valid flags:") + for flag in valid_flags: + print(f" Processing {flag.name} = {flag.input_value}") diff --git a/docs/code_snippets/input_flags/snippet2.py b/docs/code_snippets/input_flags/snippet2.py new file mode 100644 index 0000000..6d059b8 --- /dev/null +++ b/docs/code_snippets/input_flags/snippet2.py @@ -0,0 +1,36 @@ +from argenta import Command, Response, Router +from argenta.command import Flag, Flags + +router = Router(title="Get Flag Example") + + +@router.command( + Command( + "config", + description="Configure settings", + flags=Flags([ + Flag("host"), + Flag("port"), + Flag("debug") + ]), + ) +) +def config_handler(response: Response): + input_flags = response.input_flags + + host_flag = input_flags.get_flag_by_name("host") + port_flag = input_flags.get_flag_by_name("port") + debug_flag = input_flags.get_flag_by_name("debug") + + if host_flag: + print(f"Host: {host_flag.input_value}") + + if port_flag: + print(f"Port: {port_flag.input_value}") + + if debug_flag: + print("Debug mode enabled") + + missing_flag = input_flags.get_flag_by_name("nonexistent") + if missing_flag is None: + print("Flag 'nonexistent' not found") diff --git a/docs/code_snippets/input_flags/snippet3.py b/docs/code_snippets/input_flags/snippet3.py new file mode 100644 index 0000000..e4c33e8 --- /dev/null +++ b/docs/code_snippets/input_flags/snippet3.py @@ -0,0 +1,19 @@ +from argenta import Command, Response, Router +from argenta.command.flag import InputFlag, InputFlags, ValidationStatus + +router = Router(title="Add Flag Example") + + +@router.command(Command("test", description="Test command")) +def test_handler(response: Response): + # Create new InputFlags collection + new_flags = InputFlags() + + # Add one flag + test_flag = InputFlag( + name="test", prefix="--", input_value="value", status=ValidationStatus.VALID + ) + new_flags.add_flag(test_flag) + + print(f"Flags count: {len(new_flags.flags)}") + print(f"First flag: {new_flags.flags[0].name}") diff --git a/docs/code_snippets/input_flags/snippet4.py b/docs/code_snippets/input_flags/snippet4.py new file mode 100644 index 0000000..2afbc64 --- /dev/null +++ b/docs/code_snippets/input_flags/snippet4.py @@ -0,0 +1,24 @@ +from argenta.command.flag import InputFlag, InputFlags, ValidationStatus + +# Create InputFlags collection +flags = InputFlags() + +# Create several flags +flag1 = InputFlag( + name="option1", prefix="--", input_value="value1", status=ValidationStatus.VALID +) + +flag2 = InputFlag( + name="option2", prefix="--", input_value="value2", status=ValidationStatus.VALID +) + +flag3 = InputFlag( + name="option3", prefix="---", input_value="value3", status=ValidationStatus.VALID +) + +# Add all flags in one call +flags.add_flags([flag1, flag2, flag3]) + +print(f"Total flags: {len(flags.flags)}") +for flag in flags: + print(f" - {flag.string_entity}: {flag.input_value}") diff --git a/docs/code_snippets/input_flags/snippet7.py b/docs/code_snippets/input_flags/snippet7.py new file mode 100644 index 0000000..f617502 --- /dev/null +++ b/docs/code_snippets/input_flags/snippet7.py @@ -0,0 +1,27 @@ +from argenta import Command, Response, Router +from argenta.command import Flag, Flags + +router = Router(title="Bool Check Example") + + +@router.command( + Command( + "action", + description="Action with optional flags", + flags=Flags([Flag("option1"), Flag("option2")]), + ) +) +def action_handler(response: Response): + input_flags = response.input_flags + + # Check for flags presence + if input_flags: + print("Flags were provided:") + for flag in input_flags: + print(f" - {flag.name}: {flag.input_value}") + else: + print("No flags provided, using defaults") + + # Alternative way to check + has_flags = bool(input_flags) + print(f"\nHas flags: {has_flags}") diff --git a/docs/code_snippets/input_flags/snippet8.py b/docs/code_snippets/input_flags/snippet8.py new file mode 100644 index 0000000..96dafdf --- /dev/null +++ b/docs/code_snippets/input_flags/snippet8.py @@ -0,0 +1,37 @@ +from argenta.command.flag import InputFlag, ValidationStatus +from argenta.command.flag.flags.models import InputFlags + +# Create first collection +flags1 = InputFlags( + [ + InputFlag(name="flag1", input_value="value1", status=ValidationStatus.VALID), + InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID), + ] +) + +# Create second collection with same flags +flags2 = InputFlags( + [ + InputFlag(name="flag1", input_value="value1", status=ValidationStatus.VALID), + InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID), + ] +) + +# Create third collection with different values +flags3 = InputFlags( + [ + InputFlag(name="flag1", input_value="different", status=ValidationStatus.VALID), + InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID), + ] +) + +print(f"flags1 == flags2: {flags1 == flags2}") # True (same names) +print( + f"flags1 == flags3: {flags1 == flags3}" +) # True (same names, values are not considered) + +# Different collections +flags4 = InputFlags( + [InputFlag(name="flag3", input_value="value3", status=ValidationStatus.VALID)] +) +print(f"flags1 == flags4: {flags1 == flags4}") # False (different flags) diff --git a/docs/code_snippets/input_flags/snippet9.py b/docs/code_snippets/input_flags/snippet9.py new file mode 100644 index 0000000..34987ee --- /dev/null +++ b/docs/code_snippets/input_flags/snippet9.py @@ -0,0 +1,33 @@ +from argenta import Command, Response, Router +from argenta.command import Flag, Flags +from argenta.command.flag import InputFlag + +router = Router(title="Contains Example") + + +@router.command( + Command( + "check", + description="Check flags", + flags=Flags([Flag("verbose"), Flag("debug"), Flag("quiet")]), + ) +) +def check_handler(response: Response): + input_flags = response.input_flags + + # Check for specific flag presence + verbose_flag = input_flags.get_flag_by_name("verbose") + debug_flag = input_flags.get_flag_by_name("debug") + + # Use 'in' operator for checking + if verbose_flag and verbose_flag in input_flags: + print("Verbose flag is present") + + if debug_flag and debug_flag in input_flags: + print("Debug flag is present") + + # You can create a flag for checking (comparison is by name) + test_flag = InputFlag(name="verbose", prefix="--", input_value="any", status=None) + + if test_flag in input_flags: + print("Verbose flag found using 'in' operator") diff --git a/docs/code_snippets/orchestrator/snippet.py b/docs/code_snippets/orchestrator/snippet.py new file mode 100644 index 0000000..62404cb --- /dev/null +++ b/docs/code_snippets/orchestrator/snippet.py @@ -0,0 +1,27 @@ +import sqlite3 +from sqlite3 import Connection +from typing import Iterable + +from dishka import Provider, Scope, provide + +from argenta import App, Orchestrator + + +class ConnectionProvider(Provider): + @provide(scope=Scope.REQUEST) + def new_connection(self) -> Iterable[Connection]: + conn = sqlite3.connect(":memory:") + yield conn + conn.close() + + +# 2. Create and configure App +app = App() +# ... you can add routers here ... + +# 3. Create Orchestrator, passing our provider +orchestrator = Orchestrator(custom_providers=[ConnectionProvider()]) + +# 4. Start the application +if __name__ == "__main__": + orchestrator.start_polling(app) diff --git a/docs/code_snippets/possible_values/all.py b/docs/code_snippets/possible_values/all.py new file mode 100644 index 0000000..9be6044 --- /dev/null +++ b/docs/code_snippets/possible_values/all.py @@ -0,0 +1,5 @@ +from argenta.command import Flag, PossibleValues + +# Creating flags with any values +message_flag = Flag(name="message", possible_values=PossibleValues.ALL) +name_flag = Flag(name="name", possible_values=PossibleValues.ALL) diff --git a/docs/code_snippets/possible_values/combined.py b/docs/code_snippets/possible_values/combined.py new file mode 100644 index 0000000..d9167a4 --- /dev/null +++ b/docs/code_snippets/possible_values/combined.py @@ -0,0 +1,14 @@ +import re +from argenta.command import Flag, PossibleValues + +# Flag without value +verbose_flag = Flag(name="verbose", possible_values=PossibleValues.NEITHER) + +# Flag with any value +output_flag = Flag(name="output", possible_values=PossibleValues.ALL) + +# Flag with a list of valid values +format_flag = Flag(name="format", possible_values=["json", "xml", "csv", "yaml"]) + +# Flag with regular expression +email_flag = Flag(name="email", possible_values=re.compile(r"^[\w\.-]+@[\w\.-]+\.\w+$")) diff --git a/docs/code_snippets/possible_values/neither.py b/docs/code_snippets/possible_values/neither.py new file mode 100644 index 0000000..de7d57f --- /dev/null +++ b/docs/code_snippets/possible_values/neither.py @@ -0,0 +1,6 @@ +from argenta.command import Flag, PossibleValues + +# Creating flags without values +help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER) +verbose_flag = Flag(name="verbose", possible_values=PossibleValues.NEITHER) +force_flag = Flag(name="force", possible_values=PossibleValues.NEITHER) diff --git a/docs/code_snippets/quickstart/calculator_app.py b/docs/code_snippets/quickstart/calculator_app.py new file mode 100644 index 0000000..38a42f6 --- /dev/null +++ b/docs/code_snippets/quickstart/calculator_app.py @@ -0,0 +1,68 @@ +import operator +import re + +from argenta import App, Orchestrator, Response, Router +from argenta.app import DynamicDividingLine +from argenta.command import Command, Flag, Flags +from argenta.response.status import ResponseStatus + +router = Router("Calculator") + +operations = { + 'mul': operator.mul, + 'sub': operator.sub, + 'add': operator.add +} + +@router.command( + Command( + "calc", + description="Calculator with two numbers", + flags=Flags( + [ + Flag("a", possible_values=re.compile(r"^\d{,5}$")), # First number + Flag("b", possible_values=re.compile(r"^\d{,5}$")), # Second number + Flag("operation", possible_values=["add", "sub", "mul"]), # Operation: add, sub, mul + ] + ), + ) +) +def calc_handler(response: Response): + # Get flag values + a_flag = response.input_flags.get_flag_by_name("a") + b_flag = response.input_flags.get_flag_by_name("b") + op_flag = response.input_flags.get_flag_by_name("op") + + # Check that all flags are provided + if response.status != ResponseStatus.ALL_FLAGS_VALID or not all([a_flag, b_flag, op_flag]): + print("Error: must specify --a, --b and --op") + return + + a = float(a_flag.input_value) + b = float(b_flag.input_value) + operation = op_flag.input_value + + try: + result = operations[operation](a, b) + except ZeroDivisionError: + print("Can't divide by zero") + else: + print(f"Result: {result}") + + +app = App( + initial_message="Calculator", + repeat_command_groups_printing=False, + prompt=">> ", + dividing_line=DynamicDividingLine("~"), +) +orchestrator = Orchestrator() + + +def main(): + app.include_router(router) + orchestrator.start_polling(app) + + +if __name__ == "__main__": + main() diff --git a/docs/code_snippets/quickstart/main.py b/docs/code_snippets/quickstart/main.py new file mode 100644 index 0000000..8e22500 --- /dev/null +++ b/docs/code_snippets/quickstart/main.py @@ -0,0 +1,16 @@ +# main.py +from .routers import router + +from argenta import App, Orchestrator + +app: App = App() +orchestrator: Orchestrator = Orchestrator() + + +def main() -> None: + app.include_router(router) + orchestrator.start_polling(app) + + +if __name__ == "__main__": + main() diff --git a/docs/code_snippets/quickstart/routers.py b/docs/code_snippets/quickstart/routers.py new file mode 100644 index 0000000..a986681 --- /dev/null +++ b/docs/code_snippets/quickstart/routers.py @@ -0,0 +1,9 @@ +# routers.py +from argenta import Command, Response, Router + +router = Router(title="Quickstart Example") + + +@router.command(Command("hello", description="Say hello")) +def handler(response: Response): + print("Hello, world!") diff --git a/docs/code_snippets/quickstart/simple_app.py b/docs/code_snippets/quickstart/simple_app.py new file mode 100644 index 0000000..a46090e --- /dev/null +++ b/docs/code_snippets/quickstart/simple_app.py @@ -0,0 +1,37 @@ +from argenta import App, Command, Orchestrator, Router, Response +from argenta.command import Flag + +# 1. Create app and orchestrator instances +app = App( + prompt=">> ", + initial_message="Simple App", + farewell_message="Goodbye!", + repeat_command_groups_printing=False +) +orchestrator = Orchestrator() + +# 2. Create router for grouping commands +main_router = Router(title="Main commands") + + +# 3. Define command and its handler +@main_router.command(Command( + "hello", + description="Prints greeting message", + flags=Flag("name") +)) +def hello_handler(response: Response): + """This handler will be called for 'hello' command.""" + name = response.input_flags.get_flag_by_name("name") + if name: + print(f"Hello, {name.input_value}!") + else: + print("Hello, world!") + + +# 4. Include router to application +app.include_router(main_router) + +# 5. Start application +if __name__ == "__main__": + orchestrator.start_polling(app) diff --git a/docs/code_snippets/quickstart/task_manager/handlers.py b/docs/code_snippets/quickstart/task_manager/handlers.py new file mode 100644 index 0000000..826b3ea --- /dev/null +++ b/docs/code_snippets/quickstart/task_manager/handlers.py @@ -0,0 +1,54 @@ +from typing import cast + +from argenta import Command, Response, Router +from argenta.command.flag import Flag, Flags, ValidationStatus +from argenta.di import FromDishka + +from .repository import Priority, Task, TaskRepository + +router = Router(title="Task Manager") + + +@router.command(Command( + "add-task", + description="Add a new task", + flags=Flags([ + Flag("description"), + Flag("priority", possible_values=["low", "medium", "high"]), + ]), + )) +def add_task(response: Response, repo: FromDishka[TaskRepository]): + description_flag = response.input_flags.get_flag_by_name("description") + + if not description_flag or not description_flag.status == ValidationStatus.VALID: + print("Error: --description flag is required.") + return + + task_description = description_flag.input_value or "" + + priority_flag = response.input_flags.get_flag_by_name("priority") + + if priority_flag and priority_flag.status == ValidationStatus.VALID: + priority_value = priority_flag.input_value + else: + priority_value = "medium" + + priority = cast(Priority, priority_value) + + task = Task(description=task_description, priority=priority) + repo.add_task(task) + + print(f"Added task: '{task.description}' with priority '{task.priority}'") + + +@router.command(Command("list-tasks", description="List all tasks")) +def list_tasks(response: Response, repo: FromDishka[TaskRepository]): + tasks = repo.get_all_tasks() + + if not tasks: + print("No tasks found.") + return + + print("Tasks:") + for i, task in enumerate(tasks, 1): + print(f" {i}. {task.description} (Priority: {task.priority})") diff --git a/docs/code_snippets/quickstart/task_manager/main.py b/docs/code_snippets/quickstart/task_manager/main.py new file mode 100644 index 0000000..3d9ffae --- /dev/null +++ b/docs/code_snippets/quickstart/task_manager/main.py @@ -0,0 +1,18 @@ +from argenta import App, Orchestrator + +from .handlers import router +from .provider import TaskProvider + +# 1. Create app and orchestrator instances +app = App( + initial_message="Task Manager", + prompt="Enter a command: ", +) +orchestrator = Orchestrator(custom_providers=[TaskProvider()]) + +# 2. Include router with our commands +app.include_router(router) + +# 3. Start polling via orchestrator +if __name__ == "__main__": + orchestrator.start_polling(app) diff --git a/docs/code_snippets/quickstart/task_manager/provider.py b/docs/code_snippets/quickstart/task_manager/provider.py new file mode 100644 index 0000000..11680af --- /dev/null +++ b/docs/code_snippets/quickstart/task_manager/provider.py @@ -0,0 +1,9 @@ +from dishka import Provider, Scope, provide + +from .repository import TaskRepository + + +class TaskProvider(Provider): + @provide(scope=Scope.APP) + def get_repository(self) -> TaskRepository: + return TaskRepository() diff --git a/docs/code_snippets/quickstart/task_manager/repository.py b/docs/code_snippets/quickstart/task_manager/repository.py new file mode 100644 index 0000000..72abbd6 --- /dev/null +++ b/docs/code_snippets/quickstart/task_manager/repository.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass +from typing import Literal + +Priority = Literal["low", "medium", "high"] + + +@dataclass +class Task: + description: str + priority: Priority = "medium" + + +class TaskRepository: + def __init__(self): + self._tasks: list[Task] = [] + + def add_task(self, task: Task): + self._tasks.append(task) + + def get_all_tasks(self) -> list[Task]: + return self._tasks diff --git a/docs/code_snippets/redirect_stdout/sample.py b/docs/code_snippets/redirect_stdout/sample.py new file mode 100644 index 0000000..b724395 --- /dev/null +++ b/docs/code_snippets/redirect_stdout/sample.py @@ -0,0 +1,10 @@ +from argenta import Response, Router + +# For this router stdout redirect will be disabled +interactive_router = Router(disable_redirect_stdout=True) + + +@interactive_router.command("ask") +def ask_name(response: Response): + name = input("What is your name? ") + print(f"Nice to meet you, {name}!") diff --git a/docs/code_snippets/redirect_stdout/sample2.py b/docs/code_snippets/redirect_stdout/sample2.py new file mode 100644 index 0000000..1260d69 --- /dev/null +++ b/docs/code_snippets/redirect_stdout/sample2.py @@ -0,0 +1,5 @@ +from argenta import App +from argenta.app import StaticDividingLine + +# All routers will use static line with length 50 by default +app = App(dividing_line=StaticDividingLine(length=50)) diff --git a/docs/code_snippets/response/data_sharing.py b/docs/code_snippets/response/data_sharing.py new file mode 100644 index 0000000..5f0ae54 --- /dev/null +++ b/docs/code_snippets/response/data_sharing.py @@ -0,0 +1,42 @@ +from argenta import Router, Response, Command, DataBridge +from argenta.command import Flag +from argenta.di import FromDishka + +router = Router(title="Authentication") + +def authenticate_user(username: str) -> str: + return f"token_for_{username}" + + +@router.command(Command("login", flags=Flag("username"))) +def login_handler(response: Response, data_bridge: FromDishka[DataBridge]): + username_flag = response.input_flags.get_flag_by_name("username") + if not username_flag or not username_flag.input_value: + print("Error: username must be specified using the --username flag.") + return + + username = username_flag.input_value + token = authenticate_user(username) + + data_bridge.update({"auth_token": token}) + print(f"Login successful! User '{username}' authenticated.") + + +@router.command("get-profile") +def get_profile_handler(response: Response, data_bridge: FromDishka[DataBridge]): + token = data_bridge.get_by_key("auth_token") + + if not token: + print("Error: you are not authenticated. Please run the 'login' command first.") + return + + print(f"Loading profile using token: [yellow]{token}[/yellow]") + + +@router.command("logout") +def logout_handler(response: Response, data_bridge: FromDishka[DataBridge]): + try: + data_bridge.delete_by_key("auth_token") + print("Logout successful. Session data cleared.") + except KeyError: + print("You were not authenticated anyway.") diff --git a/docs/code_snippets/response/snippet1.py b/docs/code_snippets/response/snippet1.py new file mode 100644 index 0000000..90d34ad --- /dev/null +++ b/docs/code_snippets/response/snippet1.py @@ -0,0 +1,12 @@ +from argenta import Command, Response, Router +from argenta.response import ResponseStatus + +router = Router(title="Example") + + +@router.command(Command("greet", description="Greet the user")) +def greet_handler(response: Response): + if response.status == ResponseStatus.ALL_FLAGS_VALID: + print("Hello! All flags are valid.") + else: + print("Warning: Some flags have issues.") diff --git a/docs/code_snippets/response/snippet2.py b/docs/code_snippets/response/snippet2.py new file mode 100644 index 0000000..4342302 --- /dev/null +++ b/docs/code_snippets/response/snippet2.py @@ -0,0 +1,25 @@ +from argenta import Command, Response, Router + +router = Router(title="Data Example") + + +@router.command(Command("set", description="Set data")) +def set_handler(response: Response): + # Update global data storage + response.update_data( + { + "user_name": "John", + "timestamp": "2024-01-01", + "settings": {"theme": "dark", "language": "ru"}, + } + ) + print("Data updated successfully") + + +@router.command(Command("show", description="Show data")) +def show_handler(response: Response): + # Get data from global storage + data = response.get_data() + if "user_name" in data: + print(f"User: {data['user_name']}") + print(f"Settings: {data.get('settings', {})}") diff --git a/docs/code_snippets/response/snippet3.py b/docs/code_snippets/response/snippet3.py new file mode 100644 index 0000000..2385169 --- /dev/null +++ b/docs/code_snippets/response/snippet3.py @@ -0,0 +1,16 @@ +from argenta import Command, Response, Router + +router = Router(title="Get Data Example") + + +@router.command(Command("info", description="Show all stored data")) +def info_handler(response: Response): + # Get all data from global storage + all_data = response.get_data() + + if all_data: + print("Stored data:") + for key, value in all_data.items(): + print(f" {key}: {value}") + else: + print("No data stored") diff --git a/docs/code_snippets/response/snippet4.py b/docs/code_snippets/response/snippet4.py new file mode 100644 index 0000000..59c7947 --- /dev/null +++ b/docs/code_snippets/response/snippet4.py @@ -0,0 +1,19 @@ +from argenta import Command, Response, Router + +router = Router(title="Clear Data Example") + + +@router.command(Command("clear", description="Clear all stored data")) +def clear_handler(response: Response): + # Clear all data storage + response.clear_data() + print("All data cleared") + + +@router.command(Command("check", description="Check if data exists")) +def check_handler(response: Response): + data = response.get_data() + if data: + print(f"Storage contains {len(data)} item(s)") + else: + print("Storage is empty") diff --git a/docs/code_snippets/response/snippet5.py b/docs/code_snippets/response/snippet5.py new file mode 100644 index 0000000..b0bde42 --- /dev/null +++ b/docs/code_snippets/response/snippet5.py @@ -0,0 +1,29 @@ +from argenta import Command, Response, Router + +router = Router(title="Delete Data Example") + + +@router.command(Command("store", description="Store data")) +def store_handler(response: Response): + response.update_data( + { + "temp_key": "temporary value", + "important_key": "important value", + "another_key": "another value", + } + ) + print("Data stored") + + +@router.command(Command("remove", description="Remove specific key")) +def remove_handler(response: Response): + # Delete specific key from storage + try: + response.delete_from_data("temp_key") + print("Key 'temp_key' deleted") + + # Check what remains + remaining = response.get_data() + print(f"Remaining keys: {list(remaining.keys())}") + except KeyError: + print("Key not found") diff --git a/docs/code_snippets/response/snippet6.py b/docs/code_snippets/response/snippet6.py new file mode 100644 index 0000000..2f4751c --- /dev/null +++ b/docs/code_snippets/response/snippet6.py @@ -0,0 +1,38 @@ +from argenta import Command, Response, Router +from argenta.command import Flag, Flags +from argenta.command.flag import ValidationStatus +from argenta.response import ResponseStatus + +router = Router(title="Flags Example") + + +@router.command( + Command( + "process", + description="Process with flags", + flags=Flags([ + Flag("format", possible_values=["json", "xml"]), + Flag("verbose") + ]), + ) +) +def process_handler(response: Response): + print(f"Status: {response.status}") + + format_flag = response.input_flags.get_flag_by_name("format") + verbose_flag = response.input_flags.get_flag_by_name("verbose") + + if format_flag: + format_value = format_flag.input_value + print(f"Format: {format_value}") + + if verbose_flag: + print("Verbose mode enabled") + + if response.status == ResponseStatus.ALL_FLAGS_VALID: + print("All flags are valid, proceeding...") + elif response.status == ResponseStatus.INVALID_VALUE_FLAGS: + print("Warning: Some flags have invalid values") + for flag in response.input_flags: + if flag.status == ValidationStatus.INVALID: + print(f" Invalid flag: {flag.string_entity} = {flag.input_value}") diff --git a/docs/code_snippets/router/snippet.py b/docs/code_snippets/router/snippet.py new file mode 100644 index 0000000..a8e1116 --- /dev/null +++ b/docs/code_snippets/router/snippet.py @@ -0,0 +1,7 @@ +from argenta import Command, Response, Router + +user_router = Router(title="User Management") + +@user_router.command(Command("add-user", description="Adds a new user")) +def add_user_handler(response: Response): + print("User added successfully!") diff --git a/docs/code_snippets/testing/app_e2e_test.py b/docs/code_snippets/testing/app_e2e_test.py new file mode 100644 index 0000000..6d0d4a9 --- /dev/null +++ b/docs/code_snippets/testing/app_e2e_test.py @@ -0,0 +1,32 @@ +import sys +from unittest.mock import patch +import pytest +from pytest import CaptureFixture + +from argenta import App, Orchestrator, Router, Command, Response + + +@pytest.fixture(autouse=True) +def patched_argv(): + with patch.object(sys, 'argv', ['program.py']): + yield + +def test_input_incorrect_command(capsys: CaptureFixture[str]): + router = Router() + orchestrator = Orchestrator() + + @router.command(Command('test')) + def test(response: Response) -> None: + print('test command') + + app = App(override_system_messages=True, print_func=print) + app.include_router(router) + app.set_unknown_command_handler( + lambda command: print(f'Unknown command: {command.trigger}') + ) + + with patch("builtins.input", side_effect=["help", "q"]): + orchestrator.start_polling(app) + + output = capsys.readouterr().out + assert "\nUnknown command: help\n" in output diff --git a/docs/code_snippets/testing/app_integration_unittest.py b/docs/code_snippets/testing/app_integration_unittest.py new file mode 100644 index 0000000..f3a0caf --- /dev/null +++ b/docs/code_snippets/testing/app_integration_unittest.py @@ -0,0 +1,21 @@ +import io +from contextlib import redirect_stdout + +from argenta import App, Router, Command, Response +from argenta.command import InputCommand + + +def test_simple_app() -> None: + app = App(override_system_messages=True, repeat_command_groups_printing=False) + router = Router(title="App") + + @router.command(Command("HELP", description="Show help")) + def help_cmd(response: Response): + print("Available commands: HELP") + + app.include_router(router) + + with redirect_stdout(io.StringIO()) as stdout: + router.finds_appropriate_handler(InputCommand.parse("HELP")) + + assert "Available commands:" in stdout.getvalue() diff --git a/docs/code_snippets/testing/di_handler_unittest.py b/docs/code_snippets/testing/di_handler_unittest.py new file mode 100644 index 0000000..62b897c --- /dev/null +++ b/docs/code_snippets/testing/di_handler_unittest.py @@ -0,0 +1,42 @@ +import io +from contextlib import redirect_stdout + +from argenta.command import InputCommand +from dishka import Provider, make_container, Scope + +from argenta import Router, Response +from argenta.di.integration import setup_dishka, FromDishka + + +class Service: + def hello(self) -> str: + return "world" + +def get_service() -> Service: + return Service() + + +router = Router(title="DI") + +@router.command("HELLO") +def hello(response: Response, service: FromDishka[Service]) -> None: + print(f"hello {service.hello()}") + + +class _FakeApp: + # Minimal stub for setup_dishka; app object is not used in unit tests + registered_routers = [router] + + +def test_hello_uses_service(): + provider = Provider(scope=Scope.APP) + provider.provide(get_service) + + container = make_container(provider) + setup_dishka(app=_FakeApp(), container=container, auto_inject=True) + + # Call handler + with redirect_stdout(io.StringIO()) as stdout: + router.finds_appropriate_handler(InputCommand.parse('HELLO')) + + assert "hello world" in stdout.getvalue() diff --git a/docs/code_snippets/testing/simple_handler_unittest.py b/docs/code_snippets/testing/simple_handler_unittest.py new file mode 100644 index 0000000..176b5e6 --- /dev/null +++ b/docs/code_snippets/testing/simple_handler_unittest.py @@ -0,0 +1,19 @@ +import io +from contextlib import redirect_stdout + +from argenta import Router, Command, Response +from argenta.command import InputCommand + + +router = Router(title="Demo") + +@router.command(Command("PING", description="Ping command")) +def ping(response: Response): + print("PONG") + + +def test_ping_prints_pong(): + # Call handler + with redirect_stdout(io.StringIO()) as stdout: + router.finds_appropriate_handler(InputCommand.parse("PING")) + assert "PONG" in stdout.getvalue() diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..9e754a8 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,48 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "Argenta" +copyright = "2025, kolo" +author = "kolo" +root_doc = "index" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +locale_dirs = ['locales/'] +gettext_compact = False + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "shibuya" +html_static_path = ["_static"] + +html_context = { + "languages": [ + ("English", "/en/latest/%s/", "en"), + ("Русский", "/ru/latest/%s/", "ru"), + ] +} + +html_theme_options = { + "accent_color": "cyan", + "nav_links": [ + { + "title": "Sponsor me", + "url": "https://github.com/sponsors/koloideal" + }, + ], + "github_url": "https://github.com/koloideal/Argenta", + "linkedin_url": "https://www.linkedin.com/in/dmitry-shevelev-31b9a6324" +} + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..d14f8fa --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,73 @@ +.. Argenta documentation master file, created by + sphinx-quickstart on Sat Oct 11 19:54:43 2025. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Argenta +======= + +Что это и зачем? +---------------- + +**Библиотека для построения модульных CLI-приложений с простым и приятным API.** + +Если у вас есть функциональность, которую вы хотите предоставить в виде CLI-приложения, Argenta поможет вам в этом. +Основная цель библиотеки — дать разработчикам возможность сосредоточиться на реализации своих идей, предоставляя для этого удобные абстракции. + +.. image:: https://i.ibb.co/fzWcfgFq/2025-12-04-173045.png + :alt: App example + +Argenta предназначена для создания приложений, работающих в собственном контексте (scope). Это означает, что приложение запускается один раз и создаёт интерактивную сессию, похожую на Python REPL или MySQL консоль. При запуске пользователь входит в эту сессию, где ему доступна вся реализованная вами функциональность. + +Один из ключевых принципов библиотеки — цикличность. После выполнения команды пользователь остаётся в интерактивной сессии, в отличие от таких библиотек, как ``argparse``, ``click`` и ``typer``, где приложение завершается после каждой команды. Выход из сессии контролируется пользователем. + +**Ключевые особенности:** + +* **Интерактивные сессии**: В отличие от традиционных CLI-инструментов, ``Argenta`` создаёт циклические сессии, позволяя пользователю выполнять команды последовательно, не перезапуская приложение. +* **Декларативный синтаксис**: Команды и их обработчики объявляются с помощью простых декораторов, что делает код интуитивно понятным и позволяет сосредоточиться на том, "что" вы хотите сделать, а не "как". +* **Нативный DI**: Благодаря интеграции с [dishka](https://dishka.readthedocs.io/en/stable/), вы можете легко внедрять зависимости прямо в обработчики команд, что упрощает их тестирование, позволяет избежать мутабельных глобалов и многое другое. +* **Автоматическая валидация и парсинг**: Библиотека берёт на себя обработку флагов и аргументов командной строки, включая их парсинг, валидацию и преобразование типов. +* **Гибкая настройка**: Вы можете легко кастомизировать системные сообщения, форматирование вывода, создавать кастомные обработчики нестандартного поведения и т.д. + +----- + +Архитектура и жизненный цикл +----------------------------- + +Следующая диаграмма иллюстрирует, как компоненты Argenta взаимодействуют друг с другом, обрабатывая ввод пользователя. + +.. image:: https://i.ibb.co/hF3FdFr1/argenta-intro-drawio-2.png + :alt: Request Lifecycle Diagram + :align: center + +.. toctree:: + :hidden: + :caption: Контент: + + root/quickstart + root/error_handling + root/flags + root/overriding_formatting + root/api/index + +.. toctree:: + :hidden: + :caption: Продвинутое использование: + + root/redirect_stdout + root/dependency_injection + root/testing + +.. toctree:: + :hidden: + :caption: Для разработчиков: + + root/contributing + root/code_of_conduct + +.. toctree:: + :hidden: + :caption: Ссылки проекта: + + GitHub + PyPI diff --git a/docs/justfile b/docs/justfile new file mode 100644 index 0000000..7f8b733 --- /dev/null +++ b/docs/justfile @@ -0,0 +1,30 @@ +set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] +set shell := ["bash", "-c"] + +# Variables +sphinxopts := "" +sphinxbuild := "sphinx-build" +sourcedir := "." +builddir := "_build" + +# Default recipe (help) +default: + @{{sphinxbuild}} -M help "{{sourcedir}}" "{{builddir}}" {{sphinxopts}} + +# Build all language versions +build: + {{sphinxbuild}} -b html -D language=ru {{sourcedir}} {{builddir}}/html/ru + {{sphinxbuild}} -b html -D language =en {{sourcedir}} {{builddir}}/html/en + +# Live preview for Russian version +live-ru: + sphinx-autobuild -b html . _build/html/ru -D language=ru + +# Live preview for English version +live-en: + sphinx-autobuild -b html . _build/html/en -D language=en + +# Update translation files +update-langs: + {{sphinxbuild}} -b gettext . _build/gettext + sphinx-intl update -p _build/gettext -l en diff --git a/docs/locales/en/LC_MESSAGES/index.po b/docs/locales/en/LC_MESSAGES/index.po new file mode 100644 index 0000000..a3121e9 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/index.po @@ -0,0 +1,177 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:41+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../index.rst:43 +msgid "Контент:" +msgstr "Content:" + +#: ../../index.rst:53 +msgid "Продвинутое использование:" +msgstr "Advanced usage:" + +#: ../../index.rst:61 +msgid "Для разработчиков:" +msgstr "For developers:" + +#: ../../index.rst:68 +msgid "GitHub" +msgstr "" + +#: ../../index.rst:68 +msgid "PyPI" +msgstr "" + +#: ../../index.rst:68 +msgid "Ссылки проекта:" +msgstr "Project links:" + +#: ../../index.rst:7 +msgid "Argenta" +msgstr "" + +#: ../../index.rst:10 +msgid "Что это и зачем?" +msgstr "What is it and why?" + +#: ../../index.rst:12 +msgid "" +"**Библиотека для построения модульных CLI-приложений с простым и приятным" +" API.**" +msgstr "" +"**A library for building modular CLI applications with a simple and " +"pleasant API.**" + +#: ../../index.rst:14 +msgid "" +"Если у вас есть функциональность, которую вы хотите предоставить в виде " +"CLI-приложения, Argenta поможет вам в этом. Основная цель библиотеки — " +"дать разработчикам возможность сосредоточиться на реализации своих идей, " +"предоставляя для этого удобные абстракции." +msgstr "" +"If you have functionality that you want to provide as a CLI application, " +"Argenta will help you with that. The main goal of the library is to " +"enable developers to focus on implementing their ideas by providing " +"convenient abstractions." + +#: ../../index.rst:17 +msgid "App example" +msgstr "" + +#: ../../index.rst:20 +msgid "" +"Argenta предназначена для создания приложений, работающих в собственном " +"контексте (scope). Это означает, что приложение запускается один раз и " +"создаёт интерактивную сессию, похожую на Python REPL или MySQL консоль. " +"При запуске пользователь входит в эту сессию, где ему доступна вся " +"реализованная вами функциональность." +msgstr "" +"Argenta is designed for creating applications that work in their own " +"context (scope). This means that the application starts once and creates " +"an interactive session, similar to Python REPL or MySQL console. When " +"launched, the user enters this session where all the functionality you've" +" implemented is available." + +#: ../../index.rst:22 +msgid "" +"Один из ключевых принципов библиотеки — цикличность. После выполнения " +"команды пользователь остаётся в интерактивной сессии, в отличие от таких " +"библиотек, как ``argparse``, ``click`` и ``typer``, где приложение " +"завершается после каждой команды. Выход из сессии контролируется " +"пользователем." +msgstr "" +"One of the key principles of the library is cyclicity. After executing a " +"command, the user remains in the interactive session, " +"unlike libraries such as ``argparse``, ``click``, " +"and ``typer``, where the application terminates after each command. " +"Exiting the session is controlled by the user." + +#: ../../index.rst:24 +msgid "**Ключевые особенности:**" +msgstr "**Key features:**" + +#: ../../index.rst:26 +msgid "" +"**Интерактивные сессии**: В отличие от традиционных CLI-инструментов, " +"``Argenta`` создаёт циклические сессии, позволяя пользователю выполнять " +"команды последовательно, не перезапуская приложение." +msgstr "" +"**Interactive sessions**: Unlike traditional CLI tools, ``Argenta`` " +"creates cyclical sessions, allowing users to execute commands " +"sequentially without restarting the application." + +#: ../../index.rst:27 +msgid "" +"**Декларативный синтаксис**: Команды и их обработчики объявляются с " +"помощью простых декораторов, что делает код интуитивно понятным и " +"позволяет сосредоточиться на том, \"что\" вы хотите сделать, а не " +"\"как\"." +msgstr "" +"**Declarative syntax**: Commands and their handlers are declared using " +"simple decorators, making the code intuitive and allowing you to focus on" +" \"what\" you want to do, not \"how\"." + +#: ../../index.rst:28 +msgid "" +"**Нативный DI**: Благодаря интеграции с " +"[dishka](https://dishka.readthedocs.io/en/stable/), вы можете легко " +"внедрять зависимости прямо в обработчики команд, что упрощает их " +"тестирование, позволяет избежать мутабельных глобалов и многое другое." +msgstr "" +"**Native DI**: Thanks to integration with `dishka " +"`_, you can easily inject " +"dependencies directly into command handlers, simplifying their testing, " +"avoiding mutable globals, and much more." + +#: ../../index.rst:29 +msgid "" +"**Автоматическая валидация и парсинг**: Библиотека берёт на себя " +"обработку флагов и аргументов командной строки, включая их парсинг, " +"валидацию и преобразование типов." +msgstr "" +"**Automatic validation and parsing**: The library handles command-line " +"flags and arguments, including their parsing, validation, and type " +"conversion." + +#: ../../index.rst:30 +msgid "" +"**Гибкая настройка**: Вы можете легко кастомизировать системные " +"сообщения, форматирование вывода, создавать кастомные обработчики " +"нестандартного поведения и т.д." +msgstr "" +"**Flexible configuration**: You can easily customize system messages, " +"output formatting, create custom handlers for non-standard behavior, and " +"more." + +#: ../../index.rst:35 +msgid "Архитектура и жизненный цикл" +msgstr "Architecture and lifecycle" + +#: ../../index.rst:37 +msgid "" +"Следующая диаграмма иллюстрирует, как компоненты Argenta взаимодействуют " +"друг с другом, обрабатывая ввод пользователя." +msgstr "" +"The following diagram illustrates how Argenta components interact with " +"each other while processing user input." + +#: ../../index.rst:39 +msgid "Request Lifecycle Diagram" +msgstr "" + diff --git a/docs/locales/en/LC_MESSAGES/root/api/app/autocompleter.po b/docs/locales/en/LC_MESSAGES/root/api/app/autocompleter.po new file mode 100644 index 0000000..b0db0a9 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/app/autocompleter.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/app/autocompleter.rst:4 +msgid "AutoCompleter" +msgstr "" + +#: ../../root/api/app/autocompleter.rst:6 +msgid "" +"``AutoCompleter`` — это компонент, отвечающий за интерактивное " +"автодополнение команд. Он улучшает пользовательский опыт, предлагая " +"подсказки и завершая ввод на основе истории команд, что ускоряет работу и" +" снижает вероятность опечаток." +msgstr "" +"``AutoCompleter`` is a component responsible for interactive command autocompletion. " +"It improves user experience by offering suggestions and completing input based on " +"command history, which speeds up work and reduces the likelihood of typos." + +#: ../../root/api/app/autocompleter.rst:11 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/app/autocompleter.rst:18 +msgid "Создаёт и настраивает экземпляр ``AutoCompleter``." +msgstr "Creates and configures an ``AutoCompleter`` instance." + +#: ../../root/api/app/autocompleter.rst:20 +msgid "" +"``history_filename``: Имя файла для сохранения истории команд. Если " +"указано, история будет сохраняться между сессиями. При значении ``None`` " +"история хранится только в контексте сессии." +msgstr "" +"``history_filename``: Filename for saving command history. If specified, history " +"will be saved between sessions. When set to ``None``, history is stored only within " +"the session context." + +#: ../../root/api/app/autocompleter.rst:21 +msgid "" +"``autocomplete_button``: Клавиша, активирующая автодополнение. По " +"умолчанию — **Tab**." +msgstr "" +"``autocomplete_button``: Key that activates autocompletion. Defaults to **Tab**." + +#: ../../root/api/app/autocompleter.rst:26 +msgid "Назначение и возможности" +msgstr "Purpose and Features" + +#: ../../root/api/app/autocompleter.rst:28 +msgid "Основные возможности ``AutoCompleter``:" +msgstr "Main features of ``AutoCompleter``:" + +#: ../../root/api/app/autocompleter.rst:30 +msgid "" +"**Автодополнение по истории**: При нажатии клавиши автодополнения (по " +"умолчанию **Tab**) система ищет в истории команды, начинающиеся с уже " +"введённого текста." +msgstr "" +"**History-based autocompletion**: When the autocompletion key is pressed (by default **Tab**), " +"the system searches history for commands starting with the already entered text." + +#: ../../root/api/app/autocompleter.rst:32 +msgid "" +"**Общий префикс**: Если найдено несколько команд с общим префиксом, будет" +" подставлена только общая часть. Например, для команд ``show_users`` и " +"``show_profile`` при вводе ``sho`` и нажатии **Tab** ввод дополнится до " +"``show_``." +msgstr "" +"**Common prefix**: If multiple commands with a common prefix are found, only the common " +"part will be inserted. For example, for commands ``show_users`` and ``show_profile``, " +"when entering ``sho`` and pressing **Tab**, the input will be completed to ``show_``." + +#: ../../root/api/app/autocompleter.rst:34 +msgid "" +"**Постоянная история**: Если указан ``history_filename``, история команд " +"сохраняется в файл при выходе и загружается при следующем запуске. Это " +"делает автодополнение со временем «умнее»." +msgstr "" +"**Persistent history**: If ``history_filename`` is specified, command history is saved " +"to a file on exit and loaded on the next startup. This makes autocompletion \"smarter\" over time." + +#: ../../root/api/app/autocompleter.rst:36 +msgid "" +"**Очистка истории**: При сохранении ``AutoCompleter`` удаляет дубликаты и" +" несуществующие команды, поддерживая историю в актуальном состоянии." +msgstr "" +"**History cleanup**: When saving, ``AutoCompleter`` removes duplicates and non-existent " +"commands, keeping the history up to date." + +#: ../../root/api/app/autocompleter.rst:38 +msgid "" +"**Настройка клавиши**: Клавишу автодополнения можно изменить с помощью " +"параметра ``autocomplete_button``." +msgstr "" +"**Key customization**: The autocompletion key can be changed using the ``autocomplete_button`` parameter." + +#: ../../root/api/app/autocompleter.rst:43 +msgid "Пример использования" +msgstr "Usage Example" + +#: ../../root/api/app/autocompleter.rst:45 +msgid "``AutoCompleter`` передаётся как аргумент при инициализации `App`." +msgstr "``AutoCompleter`` is passed as an argument when initializing `App`." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/app/dividing_lines.po b/docs/locales/en/LC_MESSAGES/root/api/app/dividing_lines.po new file mode 100644 index 0000000..95bec63 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/app/dividing_lines.po @@ -0,0 +1,150 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/app/dividing_lines.rst:4 +msgid "Dividing Lines" +msgstr "Dividing Lines" + +#: ../../root/api/app/dividing_lines.rst:6 +msgid "" +"Разделительные линии в ``Argenta`` используются для визуального " +"структурирования вывода и отделения блоков информации друг от друга. " +"Библиотека предлагает два типа линий: статическую и динамическую." +msgstr "" +"Dividing lines in ``Argenta`` are used for visual structuring of output and separating " +"information blocks from each other. The library offers two types of lines: static and dynamic." + +#: ../../root/api/app/dividing_lines.rst:11 +msgid "``StaticDividingLine``" +msgstr "``StaticDividingLine``" + +#: ../../root/api/app/dividing_lines.rst:13 +msgid "" +"``StaticDividingLine`` создаёт разделительную линию **фиксированной** " +"длины. Этот тип линии полезен для создания предсказуемого и " +"унифицированного интерфейса." +msgstr "" +"``StaticDividingLine`` creates a dividing line of **fixed** length. This type of line " +"is useful for creating a predictable and unified interface." + +#: ../../root/api/app/dividing_lines.rst:21 +msgid "Создаёт экземпляр статической разделительной линии." +msgstr "Creates a static dividing line instance." + +#: ../../root/api/app/dividing_lines.rst:23 +msgid "" +"``unit_part``: Символ для построения линии (учитывается только первый " +"символ). По умолчанию: ``-``." +msgstr "" +"``unit_part``: Character for building the line (only the first character is considered). " +"Defaults to: ``-``." + +#: ../../root/api/app/dividing_lines.rst:24 +msgid "``length``: Фиксированная длина линии. По умолчанию: ``25``." +msgstr "``length``: Fixed line length. Defaults to: ``25``." + +#: ../../root/api/app/dividing_lines.rst:29 +msgid "``DynamicDividingLine``" +msgstr "``DynamicDividingLine``" + +#: ../../root/api/app/dividing_lines.rst:31 +msgid "" +"``DynamicDividingLine`` создаёт линию, длина которой **динамически** " +"подстраивается под самую длинную строку в выводе команды. Это требует " +"перехвата ``stdout``, в результате чего разделители идеально обрамляют " +"выводимый контент." +msgstr "" +"``DynamicDividingLine`` creates a line whose length **dynamically** adjusts to the longest " +"line in the command output. This requires capturing ``stdout``, resulting in dividers that " +"perfectly frame the output content." + +#: ../../root/api/app/dividing_lines.rst:38 +msgid "Создаёт экземпляр динамической разделительной линии." +msgstr "Creates a dynamic dividing line instance." + +#: ../../root/api/app/dividing_lines.rst:40 +msgid "``unit_part``: Символ для построения линии. По умолчанию: ``-``." +msgstr "``unit_part``: Character for building the line. Defaults to: ``-``." + +#: ../../root/api/app/dividing_lines.rst:42 +msgid "Длина вычисляется автоматически и не задаётся при инициализации." +msgstr "Length is calculated automatically and is not set during initialization." + +#: ../../root/api/app/dividing_lines.rst:45 +msgid "" +"Обязательно почитайте про нюансы использования динамических линий и " +"перехвата ``stdout`` в :ref:`этом разделе`." +msgstr "" +"Be sure to read about the nuances of using dynamic lines and capturing ``stdout`` " +"in :ref:`this section`." + +#: ../../root/api/app/dividing_lines.rst:50 +msgid "Назначение и использование" +msgstr "Purpose and Usage" + +#: ../../root/api/app/dividing_lines.rst:52 +msgid "Выбор между статической и динамической линией зависит от ваших задач." +msgstr "The choice between static and dynamic lines depends on your needs." + +#: ../../root/api/app/dividing_lines.rst:54 +msgid "**StaticDividingLine** идеально подходит, если:" +msgstr "**StaticDividingLine** is ideal if:" + +#: ../../root/api/app/dividing_lines.rst:56 +msgid "Вам нужен строгий и консистентный дизайн." +msgstr "You need a strict and consistent design." + +#: ../../root/api/app/dividing_lines.rst:57 +msgid "" +"Вы используете роутеры с отключённым перехватом ``stdout`` " +"(``disable_redirect_stdout=True``), где динамическое вычисление длины " +"невозможно." +msgstr "" +"You are using routers with disabled ``stdout`` capture (``disable_redirect_stdout=True``), " +"where dynamic length calculation is not possible." + +#: ../../root/api/app/dividing_lines.rst:59 +msgid "" +"**DynamicDividingLine** (поведение по умолчанию) — предпочтительный " +"выбор, если:" +msgstr "**DynamicDividingLine** (default behavior) is the preferred choice if:" + +#: ../../root/api/app/dividing_lines.rst:61 +msgid "Вы хотите, чтобы интерфейс был адаптивным." +msgstr "You want the interface to be adaptive." + +#: ../../root/api/app/dividing_lines.rst:62 +msgid "Вывод ваших команд имеет разную длину." +msgstr "Your command output has varying lengths." + +#: ../../root/api/app/dividing_lines.rst:63 +msgid "" +"В ваших обработчиках нет интерактивных операций ввода (например, " +"``input()``)." +msgstr "Your handlers do not have interactive input operations (e.g., ``input()``)." + +#: ../../root/api/app/dividing_lines.rst:65 +msgid "" +"Тип разделителя для всего приложения задаётся при инициализации ``App`` " +"через параметр ``dividing_line``." +msgstr "" +"The divider type for the entire application is set during ``App`` initialization " +"via the ``dividing_line`` parameter." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/app/index.po b/docs/locales/en/LC_MESSAGES/root/api/app/index.po new file mode 100644 index 0000000..d6b43b0 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/app/index.po @@ -0,0 +1,280 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/app/index.rst:4 +msgid "App" +msgstr "App" + +#: ../../root/api/app/index.rst:6 +msgid "" +"Объект ``App`` — это ядро вашего консольного приложения. Он отвечает за " +"конфигурацию, управление жизненным циклом, обработку команд и " +"взаимодействие с пользователем, координируя работу всех компонентов: " +"роутеров, обработчиков и системных сообщений." +msgstr "" +"The ``App`` object is the core of your console application. It handles " +"configuration, lifecycle management, command processing, and user " +"interaction, coordinating the work of all components: routers, handlers, " +"and system messages." + +#: ../../root/api/app/index.rst:11 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/app/index.rst:38 +msgid "Создаёт и настраивает экземпляр приложения." +msgstr "Creates and configures an application instance." + +#: ../../root/api/app/index.rst:40 +msgid "``prompt``: Приглашение к вводу, отображаемое перед каждой командой." +msgstr "``prompt``: Input prompt displayed before each command." + +#: ../../root/api/app/index.rst:41 +msgid "``initial_message``: Сообщение, выводимое при запуске приложения." +msgstr "``initial_message``: Message displayed when the application starts." + +#: ../../root/api/app/index.rst:42 +msgid "``farewell_message``: Сообщение, выводимое при выходе из приложения." +msgstr "``farewell_message``: Message displayed when exiting the application." + +#: ../../root/api/app/index.rst:43 +msgid "" +"``exit_command``: Команда, которая маркируется как триггер для выхода из " +"приложения." +msgstr "" +"``exit_command``: Command that is marked as a trigger for exiting the " +"application." + +#: ../../root/api/app/index.rst:44 +msgid "" +"``system_router_title``: Заголовок для системного роутера (содержит " +"команду выхода)." +msgstr "" +"``system_router_title``: Title for the system router (contains the exit " +"command)." + +#: ../../root/api/app/index.rst:45 +msgid "" +"``ignore_command_register``: Если ``True``, регистр вводимых команд " +"игнорируется при поиске обработчика." +msgstr "" +"``ignore_command_register``: If ``True``, command case is ignored when " +"searching for a handler." + +#: ../../root/api/app/index.rst:46 +msgid "" +"``dividing_line``: Тип разделительной линии (``StaticDividingLine`` или " +"``DynamicDividingLine``)." +msgstr "" +"``dividing_line``: Type of dividing line (``StaticDividingLine`` or " +"``DynamicDividingLine``)." + +#: ../../root/api/app/index.rst:47 +msgid "" +"``repeat_command_groups_printing``: Если ``True``, список доступных " +"команд выводится перед каждым вводом." +msgstr "" +"``repeat_command_groups_printing``: If ``True``, the list of available " +"commands is displayed before each input." + +#: ../../root/api/app/index.rst:48 +msgid "" +"``override_system_messages``: Если ``True``, стандартное форматирование " +"(цвета, ASCII-арт) отключается." +msgstr "" +"``override_system_messages``: If ``True``, standard formatting (colors, " +"ASCII art) is disabled." + +#: ../../root/api/app/index.rst:49 +msgid "" +"``autocompleter``: Экземпляр класса :ref:`AutoCompleter " +"`, отвечающий за автодополнение команд." +msgstr "" +"``autocompleter``: Instance of the :ref:`AutoCompleter " +"` class responsible for command " +"autocompletion." + +#: ../../root/api/app/index.rst:50 +msgid "" +"``print_func``: Функция для вывода всех системных сообщений (по умолчанию" +" ``rich.Console().print``)." +msgstr "" +"``print_func``: Function for outputting all system messages (defaults to " +"``rich.Console().print``)." + +#: ../../root/api/app/index.rst:55 +msgid "Основные методы" +msgstr "Main Methods" + +#: ../../root/api/app/index.rst:59 +msgid "" +"Регистрирует роутер в приложении. Все команды из этого роутера становятся" +" доступными для вызова." +msgstr "" +"Registers a router in the application. All commands from this router " +"become available for invocation." + +#: ../../root/api/app/index.rst +msgid "Parameters" +msgstr "Parameters" + +#: ../../root/api/app/index.rst:61 +msgid "Экземпляр ``Router`` для регистрации." +msgstr "``Router`` instance to register." + +#: ../../root/api/app/index.rst:65 +msgid "Регистрирует несколько роутеров одновременно." +msgstr "Registers multiple routers simultaneously." + +#: ../../root/api/app/index.rst:67 +msgid "Последовательность экземпляров ``Router`` для регистрации." +msgstr "Sequence of ``Router`` instances to register." + +#: ../../root/api/app/index.rst:71 +msgid "" +"Добавляет текстовое сообщение, которое выводится при запуске приложения " +"после ``initial_message``." +msgstr "" +"Adds a text message that is displayed when the application starts after " +"``initial_message``." + +#: ../../root/api/app/index.rst:73 +msgid "Строка с сообщением." +msgstr "String with the message." + +#: ../../root/api/app/index.rst:76 +msgid "" +"Для вывода стандартных сообщений можно использовать готовые шаблоны из " +":ref:`PredefinedMessages `." +msgstr "" +"For outputting standard messages, you can use ready-made templates from " +":ref:`PredefinedMessages `." + +#: ../../root/api/app/index.rst:81 +msgid "Методы установки обработчиков" +msgstr "Handler Setup Methods" + +#: ../../root/api/app/index.rst:83 +msgid "" +"``App`` позволяет настраивать реакцию на различные события, такие как " +"ошибки ввода или неизвестные команды." +msgstr "" +"``App`` allows you to configure responses to various events, such as " +"input errors or unknown commands." + +#: ../../root/api/app/index.rst:86 +msgid "" +"Подробнее об исключениях и их обработке в соответствующем :ref:`разделе " +"документации `." +msgstr "" +"For more details on exceptions and their handling, see the corresponding " +":ref:`documentation section `." + +#: ../../root/api/app/index.rst:92 +msgid "Устанавливает шаблон для форматирования описания команды." +msgstr "Sets the template for formatting command descriptions." + +#: ../../root/api/app/index.rst:94 +msgid "Обработчик принимает триггер команды (``str``) и её описание (``str``)." +msgstr "" +"The handler accepts the command trigger (``str``) and its description " +"(``str``)." + +#: ../../root/api/app/index.rst:100 +msgid "Устанавливает обработчик при некорректном введённом синтаксисе флагов." +msgstr "Sets the handler for incorrect flag syntax input." + +#: ../../root/api/app/index.rst:102 ../../root/api/app/index.rst:110 +msgid "Обработчик принимает строку, введённую пользователем." +msgstr "The handler accepts the string entered by the user." + +#: ../../root/api/app/index.rst:108 +msgid "Устанавливает обработчик при повторяющихся флагах в введённой команде." +msgstr "Sets the handler for duplicate flags in the entered command." + +#: ../../root/api/app/index.rst:116 +msgid "Устанавливает обработчик при вводе неизвестной команды." +msgstr "Sets the handler for entering an unknown command." + +#: ../../root/api/app/index.rst:118 +msgid "Обработчик принимает объект ``InputCommand`` - объект введённой команды." +msgstr "" +"The handler accepts an ``InputCommand`` object - the entered command " +"object." + +#: ../../root/api/app/index.rst:124 +msgid "Устанавливает обработчик при вводе пустой строки." +msgstr "Sets the handler for entering an empty string." + +#: ../../root/api/app/index.rst:126 +msgid "Обработчик не принимает аргументов." +msgstr "The handler accepts no arguments." + +#: ../../root/api/app/index.rst:132 +msgid "Переопределяет стандартное поведение при вызове команды выхода." +msgstr "Overrides the default behavior when the exit command is invoked." + +#: ../../root/api/app/index.rst:134 +msgid "Обработчик принимает объект ``Response``." +msgstr "The handler accepts a ``Response`` object." + +#: ../../root/api/app/index.rst:147 +msgid "PredefinedMessages" +msgstr "PredefinedMessages" + +#: ../../root/api/app/index.rst:149 +msgid "" +"``PredefinedMessages`` — это контейнер, содержащий набор готовых к " +"использованию сообщений. Они отформатированы с использованием синтаксиса " +"``rich`` и предназначены для вывода стандартной информации, такой как " +"подсказки по использованию." +msgstr "" +"``PredefinedMessages`` is a container containing a set of ready-to-use " +"messages. They are formatted using ``rich`` syntax and are intended for " +"displaying standard information, such as usage hints." + +#: ../../root/api/app/index.rst:151 +msgid "Рекомендуется использовать их при старте приложения." +msgstr "It is recommended to use them when starting the application." + +#: ../../root/api/app/index.rst:178 +msgid "Строка: ``[b dim]Usage[/b dim]: [i] <[green]flags[/green]>[/i]``" +msgstr "String: ``[b dim]Usage[/b dim]: [i] <[green]flags[/green]>[/i]``" + +#: ../../root/api/app/index.rst:180 +msgid "Отображается как: ``Usage: ``" +msgstr "Displayed as: ``Usage: ``" + +#: ../../root/api/app/index.rst:184 +msgid "Строка: ``[b dim]Help[/b dim]: [i][/i] [b red]--help[/b red]``" +msgstr "String: ``[b dim]Help[/b dim]: [i][/i] [b red]--help[/b red]``" + +#: ../../root/api/app/index.rst:186 +msgid "Отображается как: ``Help: --help``" +msgstr "Displayed as: ``Help: --help``" + +#: ../../root/api/app/index.rst:190 +msgid "Строка: ``[b dim]Autocomplete[/b dim]: [i][/i] [bold]``" +msgstr "String: ``[b dim]Autocomplete[/b dim]: [i][/i] [bold]``" + +#: ../../root/api/app/index.rst:192 +msgid "Отображается как: ``Autocomplete: ``" +msgstr "Displayed as: ``Autocomplete: ``" + diff --git a/docs/locales/en/LC_MESSAGES/root/api/bridge.po b/docs/locales/en/LC_MESSAGES/root/api/bridge.po new file mode 100644 index 0000000..1d55dcc --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/bridge.po @@ -0,0 +1,105 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/bridge.rst:4 +msgid "DataBridge" +msgstr "DataBridge" + +#: ../../root/api/bridge.rst:6 +msgid "" +"``DataBridge`` — это сущность, предоставляющая временное хранилище " +"данных, которое существует в рамках одной сессии приложения (от запуска " +"до выхода). Она предназначена для обмена данными между обработчиками." +msgstr "" +"``DataBridge`` is an entity that provides temporary data storage that " +"exists within a single application session (from startup to exit). It is " +"designed for data exchange between handlers." + +#: ../../root/api/bridge.rst:8 +msgid "Основной способ получения доступа к ``DataBridge`` — через DI." +msgstr "The main way to access ``DataBridge`` is through DI." + +#: ../../root/api/bridge.rst:21 +msgid "**Практический пример: Аутентификация**" +msgstr "**Practical Example: Authentication**" + +#: ../../root/api/bridge.rst:23 +msgid "" +"Рассмотрим пример, где команда `login` сохраняет токен аутентификации, а " +"команда `get-profile` использует его." +msgstr "" +"Let's consider an example where the `login` command saves an " +"authentication token, and the `get-profile` command uses it." + +#: ../../root/api/bridge.rst:29 +msgid "**Как это работает:**" +msgstr "**How it works:**" + +#: ../../root/api/bridge.rst:31 +msgid "" +"При вызове обработчика ``dishka`` автоматически внедряет экземпляр " +"``DataBridge``." +msgstr "" +"When calling a handler, ``dishka`` automatically injects a ``DataBridge``" +" instance." + +#: ../../root/api/bridge.rst:32 +msgid "" +"Команда ``login --username <имя>`` вызывает ``login_handler``, который " +"через внедрённый ``data_bridge`` сохраняет токен." +msgstr "" +"The ``login --username `` command calls ``login_handler``, which " +"saves the token through the injected ``data_bridge``." + +#: ../../root/api/bridge.rst:33 +msgid "" +"Команда ``get-profile`` вызывает ``get_profile_handler``, который так же " +"получает ``data_bridge`` и извлекает из него токен." +msgstr "" +"The ``get-profile`` command calls ``get_profile_handler``, which also " +"receives ``data_bridge`` and extracts the token from it." + +#: ../../root/api/bridge.rst:42 +msgid "" +"Инициализирует хранилище. При использовании через DI вызывается " +"автоматически." +msgstr "" +"Initializes the storage. When used through DI, it is called automatically." + +#: ../../root/api/bridge.rst:46 +msgid "Обновляет хранилище данными из словаря." +msgstr "Updates the storage with data from a dictionary." + +#: ../../root/api/bridge.rst:50 +msgid "Возвращает все данные из хранилища." +msgstr "Returns all data from the storage." + +#: ../../root/api/bridge.rst:54 +msgid "Возвращает значение по ключу или ``None``, если ключ не найден." +msgstr "Returns the value by key or ``None`` if the key is not found." + +#: ../../root/api/bridge.rst:58 +msgid "Удаляет значение по ключу. Вызывает ``KeyError``, если ключ не найден." +msgstr "Deletes the value by key. Raises ``KeyError`` if the key is not found." + +#: ../../root/api/bridge.rst:62 +msgid "Полностью очищает хранилище." +msgstr "Completely clears the storage." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/command/flag.po b/docs/locales/en/LC_MESSAGES/root/api/command/flag.po new file mode 100644 index 0000000..1222989 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/command/flag.po @@ -0,0 +1,335 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/command/flag.rst:4 +msgid "Flag" +msgstr "" + +#: ../../root/api/command/flag.rst:6 +msgid "" +"``Flag`` — это сущность, описывающая флаг команды. Её основная задача — " +"определить параметры флага, включая его имя, префикс и правила валидации." +msgstr "" +"``Flag`` is an entity describing a command flag. Its main purpose is to define " +"flag parameters, including its name, prefix, and validation rules." + +#: ../../root/api/command/flag.rst:10 +msgid "" +"Документация по :ref:`PossibleValues ` " +"— перечисление, определяющее типы допустимых значений." +msgstr "" +"Documentation for :ref:`PossibleValues ` " +"— an enumeration defining types of allowed values." + +#: ../../root/api/command/flag.rst:12 +msgid "" +"Документация по :ref:`InputFlag ` — объект " +"обработанного флага, введённого пользователем." +msgstr "" +"Documentation for :ref:`InputFlag ` — an object " +"representing a processed flag entered by the user." + +#: ../../root/api/command/flag.rst:14 +msgid "" +":ref:`Общая информация ` о флагах и их использовании в " +"``Argenta``" +msgstr ":ref:`General information ` about flags and their usage in ``Argenta``" + +#: ../../root/api/command/flag.rst:19 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/command/flag.rst:30 +msgid "Создаёт новый флаг для регистрации в команде." +msgstr "Creates a new flag for registration in a command." + +#: ../../root/api/command/flag.rst:32 +msgid "``name``: Имя флага (обязательный параметр)." +msgstr "``name``: Flag name (required parameter)." + +#: ../../root/api/command/flag.rst:33 +msgid "``prefix``: Префикс флага (``-``, ``--``, ``---``). По умолчанию ``--``." +msgstr "``prefix``: Flag prefix (``-``, ``--``, ``---``). Defaults to ``--``." + +#: ../../root/api/command/flag.rst:34 +msgid "" +"``possible_values``: Правила валидации значения. Может быть списком " +"строк, регулярным выражением или значением из ``PossibleValues``. По " +"умолчанию ``PossibleValues.ALL``, то есть любое значение допустимо." +msgstr "" +"``possible_values``: Value validation rules. Can be a list of strings, a regular " +"expression, or a value from ``PossibleValues``. Defaults to ``PossibleValues.ALL``, " +"meaning any value is allowed." + +#: ../../root/api/command/flag.rst:36 +msgid "**Атрибуты:**" +msgstr "**Attributes:**" + +#: ../../root/api/command/flag.rst:40 +msgid "Имя флага в виде строки." +msgstr "Flag name as a string." + +#: ../../root/api/command/flag.rst:44 +msgid "Префикс флага. Один из: ``\"-\"``, ``\"--\"``, ``\"---\"``." +msgstr "Flag prefix. One of: ``\"-\"``, ``\"--\"``, ``\"---\"``." + +#: ../../root/api/command/flag.rst:48 +msgid "Допустимые значения для флага." +msgstr "Allowed values for the flag." + +#: ../../root/api/command/flag.rst:50 ../../root/api/command/flag.rst:93 +#: ../../root/api/command/flag.rst:113 ../../root/api/command/flag.rst:136 +#: ../../root/api/command/flag.rst:253 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/command/flag.rst:59 +msgid "Свойства" +msgstr "Properties" + +#: ../../root/api/command/flag.rst:62 +msgid "string_entity" +msgstr "" + +#: ../../root/api/command/flag.rst:70 +msgid "Возвращает строковое представление флага в формате ``prefix + name``." +msgstr "Returns the string representation of the flag in the format ``prefix + name``." + +#: ../../root/api/command/flag.rst +msgid "return" +msgstr "return" + +#: ../../root/api/command/flag.rst:72 ../../root/api/command/flag.rst:91 +msgid "Строковое представление флага" +msgstr "String representation of the flag" + +#: ../../root/api/command/flag.rst:74 +msgid "" +"Это свойство объединяет префикс и имя в единую строку, которая " +"представляет флаг так, как он выглядел бы в командной строке." +msgstr "" +"This property combines the prefix and name into a single string that represents " +"the flag as it would appear on the command line." + +#: ../../root/api/command/flag.rst:79 +msgid "Магические методы" +msgstr "Magic Methods" + +#: ../../root/api/command/flag.rst:82 +msgid "__str__" +msgstr "" + +#: ../../root/api/command/flag.rst:89 +msgid "Возвращает строковое представление флага (аналогично ``string_entity``)." +msgstr "Returns the string representation of the flag (similar to ``string_entity``)." + +#: ../../root/api/command/flag.rst:102 +msgid "__repr__" +msgstr "" + +#: ../../root/api/command/flag.rst:109 +msgid "Возвращает отладочное представление объекта." +msgstr "Returns the debug representation of the object." + +#: ../../root/api/command/flag.rst:111 +msgid "Строка в формате ``Flag``." +msgstr "String in the format ``Flag``." + +#: ../../root/api/command/flag.rst:122 +msgid "__eq__" +msgstr "" + +#: ../../root/api/command/flag.rst:129 +msgid "" +"Сравнивает два флага на равенство по их строковому представлению " +"(``string_entity``)." +msgstr "" +"Compares two flags for equality based on their string representation (``string_entity``)." + +#: ../../root/api/command/flag.rst +msgid "param other" +msgstr "param other" + +#: ../../root/api/command/flag.rst:131 +msgid "Объект для сравнения" +msgstr "Object to compare" + +#: ../../root/api/command/flag.rst:132 +msgid "**True**, если флаги равны, иначе **False**" +msgstr "**True** if flags are equal, otherwise **False**" + +#: ../../root/api/command/flag.rst:134 +msgid "Два флага считаются равными, если их ``string_entity`` идентичны." +msgstr "Two flags are considered equal if their ``string_entity`` are identical." + +#: ../../root/api/command/flag.rst:147 +msgid "PredefinedFlags" +msgstr "PredefinedFlags" + +#: ../../root/api/command/flag.rst:149 +msgid "``argenta.command.PredefinedFlags``" +msgstr "``argenta.command.PredefinedFlags``" + +#: ../../root/api/command/flag.rst:151 +msgid "" +"Класс ``PredefinedFlags`` предоставляет набор готовых флагов для " +"использования в приложениях без их ручного создания. Эти флаги покрывают " +"распространённые сценарии." +msgstr "" +"The ``PredefinedFlags`` class provides a set of ready-made flags for use in " +"applications without manual creation. These flags cover common scenarios." + +#: ../../root/api/command/flag.rst:153 +msgid "" +"Все предопределённые флаги являются атрибутами класса и представляют " +"собой готовые экземпляры ``Flag``." +msgstr "" +"All predefined flags are class attributes and represent ready-made ``Flag`` instances." + +#: ../../root/api/command/flag.rst:158 +msgid "Информационные флаги" +msgstr "Informational Flags" + +#: ../../root/api/command/flag.rst:163 +msgid "Флаг для отображения справки: ``--help``" +msgstr "Flag for displaying help: ``--help``" + +#: ../../root/api/command/flag.rst:165 +msgid "``name``: ``\"help\"``" +msgstr "``name``: ``\"help\"``" + +#: ../../root/api/command/flag.rst:166 ../../root/api/command/flag.rst:182 +#: ../../root/api/command/flag.rst:224 ../../root/api/command/flag.rst:240 +msgid "``prefix``: ``\"--\"`` (по умолчанию)" +msgstr "``prefix``: ``\"--\"`` (default)" + +#: ../../root/api/command/flag.rst:167 ../../root/api/command/flag.rst:175 +#: ../../root/api/command/flag.rst:183 ../../root/api/command/flag.rst:191 +#: ../../root/api/command/flag.rst:204 ../../root/api/command/flag.rst:212 +msgid "``possible_values``: ``PossibleValues.NEITHER``" +msgstr "``possible_values``: ``PossibleValues.NEITHER``" + +#: ../../root/api/command/flag.rst:171 +msgid "Короткая версия флага справки: ``-H``" +msgstr "Short version of the help flag: ``-H``" + +#: ../../root/api/command/flag.rst:173 +#: ../../root/api/command/flag.rst:231 +msgid "``name``: ``\"H\"``" +msgstr "``name``: ``\"H\"``" + +#: ../../root/api/command/flag.rst:174 +#: ../../root/api/command/flag.rst:190 +#: ../../root/api/command/flag.rst:211 +#: ../../root/api/command/flag.rst:232 +#: ../../root/api/command/flag.rst:248 +msgid "``prefix``: ``\"-\"``" +msgstr "``prefix``: ``\"-\"``" + +#: ../../root/api/command/flag.rst:179 +msgid "Флаг для отображения информации: ``--info``" +msgstr "Flag for displaying information: ``--info``" + +#: ../../root/api/command/flag.rst:181 +msgid "``name``: ``\"info\"``" +msgstr "``name``: ``\"info\"``" + +#: ../../root/api/command/flag.rst:187 +msgid "Короткая версия флага информации: ``-I``" +msgstr "Short version of the info flag: ``-I``" + +#: ../../root/api/command/flag.rst:189 +msgid "``name``: ``\"I\"``" +msgstr "``name``: ``\"I\"``" + +#: ../../root/api/command/flag.rst:196 +msgid "Флаги выбора" +msgstr "Selection Flags" + +#: ../../root/api/command/flag.rst:200 +msgid "Флаг для выбора всех элементов: ``--all``" +msgstr "Flag for selecting all items: ``--all``" + +#: ../../root/api/command/flag.rst:202 +msgid "``name``: ``\"all\"``" +msgstr "``name``: ``\"all\"``" + +#: ../../root/api/command/flag.rst:203 +msgid "``prefix``: ``\"--\"``" +msgstr "``prefix``: ``\"--\"``" + +#: ../../root/api/command/flag.rst:208 +msgid "Короткая версия флага выбора всех элементов: ``-A``" +msgstr "Short version of the select all flag: ``-A``" + +#: ../../root/api/command/flag.rst:210 +msgid "``name``: ``\"A\"``" +msgstr "``name``: ``\"A\"``" + +#: ../../root/api/command/flag.rst:217 +msgid "Сетевые флаги" +msgstr "Network Flags" + +#: ../../root/api/command/flag.rst:221 +msgid "Флаг для указания IP-адреса хоста: ``--host``" +msgstr "Flag for specifying host IP address: ``--host``" + +#: ../../root/api/command/flag.rst:223 +msgid "``name``: ``\"host\"``" +msgstr "``name``: ``\"host\"``" + +#: ../../root/api/command/flag.rst:225 ../../root/api/command/flag.rst:233 +#, python-brace-format +msgid "" +"``possible_values``: Регулярное выражение для валидации IPv4: " +"``r\"^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$\"``" +msgstr "" +"``possible_values``: Regular expression for IPv4 validation: " +"``r\"^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$\"``" + +#: ../../root/api/command/flag.rst:229 +msgid "Короткая версия флага хоста: ``-H``" +msgstr "Short version of the host flag: ``-H``" + +#: ../../root/api/command/flag.rst:237 +msgid "Флаг для указания порта: ``--port``" +msgstr "Flag for specifying port: ``--port``" + +#: ../../root/api/command/flag.rst:239 +msgid "``name``: ``\"port\"``" +msgstr "``name``: ``\"port\"``" + +#: ../../root/api/command/flag.rst:241 ../../root/api/command/flag.rst:249 +#, python-brace-format +msgid "" +"``possible_values``: Регулярное выражение для валидации порта: " +"``r\"^\\d{1,5}$\"``" +msgstr "" +"``possible_values``: Regular expression for port validation: " +"``r\"^\\d{1,5}$\"``" + +#: ../../root/api/command/flag.rst:245 +msgid "Короткая версия флага порта: ``-P``" +msgstr "Short version of the port flag: ``-P``" + +#: ../../root/api/command/flag.rst:247 +msgid "``name``: ``\"P\"``" +msgstr "``name``: ``\"P\"``" + diff --git a/docs/locales/en/LC_MESSAGES/root/api/command/flags.po b/docs/locales/en/LC_MESSAGES/root/api/command/flags.po new file mode 100644 index 0000000..8726137 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/command/flags.po @@ -0,0 +1,172 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/command/flags.rst:4 +msgid "Flags" +msgstr "Flags" + +#: ../../root/api/command/flags.rst:6 +msgid "" +"``Flags`` — это коллекция флагов команды. Её основная задача — " +"группировать и управлять набором флагов, зарегистрированных для " +"конкретной команды. ``Flags`` служит контейнером, который позволяет " +"удобно добавлять, извлекать, итерировать флаги и проверять их наличие." +msgstr "" +"``Flags`` is a collection of command flags. Its main purpose is to group and manage " +"the set of flags registered for a specific command. ``Flags`` serves as a container " +"that allows convenient addition, retrieval, iteration of flags, and checking their presence." + +#: ../../root/api/command/flags.rst:10 +msgid "" +"Документация по отдельным флагам (:ref:`Flag `, " +":ref:`InputFlag `)" +msgstr "" +"Documentation for individual flags (:ref:`Flag `, " +":ref:`InputFlag `)" + +#: ../../root/api/command/flags.rst:12 +msgid "" +"Документация по :ref:`InputFlags ` — " +"коллекция обработанных флагов, введённых пользователем." +msgstr "" +"Documentation for :ref:`InputFlags ` — " +"a collection of processed flags entered by the user." + +#: ../../root/api/command/flags.rst:14 +msgid "" +":ref:`Общая информация ` о флагах и их использовании в " +"приложении ``Argenta``" +msgstr "" +":ref:`General information ` about flags and their usage in " +"the ``Argenta`` application" + +#: ../../root/api/command/flags.rst:19 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/command/flags.rst:26 +msgid "Создаёт новую коллекцию флагов." +msgstr "Creates a new flag collection." + +#: ../../root/api/command/flags.rst:28 +msgid "" +"``flags``: Необязательный список флагов типа ``Flag`` для инициализации " +"коллекции. Если не указан, создаётся пустая коллекция." +msgstr "" +"``flags``: Optional list of flags of type ``Flag`` for initializing the collection. " +"If not specified, an empty collection is created." + +#: ../../root/api/command/flags.rst:30 +msgid "**Атрибуты:**" +msgstr "**Attributes:**" + +#: ../../root/api/command/flags.rst:35 +msgid "Список всех зарегистрированных флагов типа ``Flag``." +msgstr "List of all registered flags of type ``Flag``." + +#: ../../root/api/command/flags.rst:37 ../../root/api/command/flags.rst:63 +#: ../../root/api/command/flags.rst:86 ../../root/api/command/flags.rst:109 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/command/flags.rst:46 +msgid "Методы" +msgstr "Methods" + +#: ../../root/api/command/flags.rst:49 +msgid "add_flag" +msgstr "" + +#: ../../root/api/command/flags.rst:56 +msgid "Добавляет флаг в коллекцию." +msgstr "Adds a flag to the collection." + +#: ../../root/api/command/flags.rst +msgid "param flag" +msgstr "param flag" + +#: ../../root/api/command/flags.rst:58 +msgid "Флаг типа ``Flag`` для добавления." +msgstr "Flag of type ``Flag`` to add." + +#: ../../root/api/command/flags.rst +msgid "return" +msgstr "return" + +#: ../../root/api/command/flags.rst:59 ../../root/api/command/flags.rst:82 +msgid "None." +msgstr "None." + +#: ../../root/api/command/flags.rst:61 +msgid "Используется для динамического расширения набора флагов." +msgstr "Used for dynamically extending the set of flags." + +#: ../../root/api/command/flags.rst:72 +msgid "add_flags" +msgstr "" + +#: ../../root/api/command/flags.rst:79 +msgid "Добавляет в коллекцию список флагов." +msgstr "Adds a list of flags to the collection." + +#: ../../root/api/command/flags.rst +msgid "param flags" +msgstr "param flags" + +#: ../../root/api/command/flags.rst:81 +msgid "Список флагов типа ``Flag`` для добавления." +msgstr "List of flags of type ``Flag`` to add." + +#: ../../root/api/command/flags.rst:84 +msgid "" +"Метод расширяет коллекцию, добавляя в неё все флаги из переданного " +"списка. Эффективен для пакетного добавления." +msgstr "" +"The method extends the collection by adding all flags from the provided list. " +"Efficient for batch addition." + +#: ../../root/api/command/flags.rst:95 +msgid "get_flag_by_name" +msgstr "" + +#: ../../root/api/command/flags.rst:102 +msgid "Возвращает флаг по имени." +msgstr "Returns a flag by name." + +#: ../../root/api/command/flags.rst +msgid "param name" +msgstr "param name" + +#: ../../root/api/command/flags.rst:104 +msgid "Имя искомого флага." +msgstr "Name of the flag to search for." + +#: ../../root/api/command/flags.rst:105 +msgid "Объект ``Flag`` или ``None``, если флаг не найден." +msgstr "``Flag`` object or ``None`` if the flag is not found." + +#: ../../root/api/command/flags.rst:107 +msgid "" +"Метод возвращает флаг с соответствующим именем. Если флаг не найден, " +"возвращается ``None``." +msgstr "" +"The method returns a flag with the corresponding name. If the flag is not found, " +"``None`` is returned." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/command/index.po b/docs/locales/en/LC_MESSAGES/root/api/command/index.po new file mode 100644 index 0000000..b116603 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/command/index.po @@ -0,0 +1,196 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/command/index.rst:4 +msgid "Command" +msgstr "Command" + +#: ../../root/api/command/index.rst:6 +msgid "" +"``Command`` — это основная единица функциональности в приложении. Каждая " +"команда связывает хэндлер с триггером, введя который он будет вызван для " +"обработки." +msgstr "" +"``Command`` is the basic unit of functionality in an application. Each " +"command links a handler to a trigger, which when entered will invoke it " +"for processing." + +#: ../../root/api/command/index.rst:8 +msgid "" +"``Command`` инкапсулирует всю информацию о команде: её триггер (ключевое " +"слово для вызова), описание, набор флагов и список псевдонимов." +msgstr "" +"``Command`` encapsulates all information about a command: its trigger " +"(keyword for invocation), description, set of flags, and list of aliases." + +#: ../../root/api/command/index.rst:13 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/command/index.rst:23 +msgid "Создаёт новую команду для регистрации в роутере." +msgstr "Creates a new command for registration in a router." + +#: ../../root/api/command/index.rst:25 +msgid "" +"``trigger``: Строковый триггер, который пользователь вводит для вызова " +"команды. Является основным идентификатором." +msgstr "" +"``trigger``: String trigger that the user enters to invoke the command. " +"Serves as the primary identifier." + +#: ../../root/api/command/index.rst:26 +msgid "" +"``description``: Необязательное описание, объясняющее назначение команды." +" Отображается в справке." +msgstr "" +"``description``: Optional description explaining the command's purpose. " +"Displayed in help." + +#: ../../root/api/command/index.rst:27 +msgid "" +"``flags``: Набор флагов для настройки поведения. Может быть одиночным " +"объектом ``Flag`` или коллекцией ``Flags``." +msgstr "" +"``flags``: Set of flags for configuring behavior. Can be a single " +"``Flag`` object or a ``Flags`` collection." + +#: ../../root/api/command/index.rst:28 +msgid "``aliases``: Список строковых псевдонимов для основного триггера." +msgstr "``aliases``: List of string aliases for the main trigger." + +#: ../../root/api/command/index.rst:30 ../../root/api/command/index.rst:108 +msgid "**Атрибуты:**" +msgstr "**Attributes:**" + +#: ../../root/api/command/index.rst:34 +msgid "" +"Основной триггер команды. Используется для её идентификации при обработке" +" пользовательского ввода." +msgstr "" +"The main command trigger. Used for its identification when processing " +"user input." + +#: ../../root/api/command/index.rst:38 +msgid "" +"Текстовое описание команды. Если не передано, используется значение по " +"умолчанию." +msgstr "" +"Text description of the command. If not provided, the default value is " +"used." + +#: ../../root/api/command/index.rst:42 +msgid "" +"Объект ``Flags``, содержащий все зарегистрированные флаги. Если был " +"передан ``Flag``, то автоматически конвертируется из одиночного в " +"коллекцию при инициализации." +msgstr "" +"A ``Flags`` object containing all registered flags. If a ``Flag`` was " +"passed, it is automatically converted from a single flag to a collection " +"during initialization." + +#: ../../root/api/command/index.rst:46 +msgid "Список строковых псевдонимов. Пуст, если псевдонимы не заданы." +msgstr "List of string aliases. Empty if no aliases are defined." + +#: ../../root/api/command/index.rst:48 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/command/index.rst:54 +msgid "" +"Подробнее про флаги: :ref:`Flags ` и :ref:`Флаги " +"команд `." +msgstr "" +"More about flags: :ref:`Flags ` and :ref:`Command " +"flags `." + +#: ../../root/api/command/index.rst:59 +msgid "Регистрация команд" +msgstr "Command Registration" + +#: ../../root/api/command/index.rst:61 +msgid "Команды передаются в качестве аргумента в декоратор ``@router.command()``." +msgstr "Commands are passed as an argument to the ``@router.command()`` decorator." + +#: ../../root/api/command/index.rst:63 +msgid "**Базовый пример:**" +msgstr "**Basic example:**" + +#: ../../root/api/command/index.rst:68 +msgid "**Команды с флагами:**" +msgstr "**Commands with flags:**" + +#: ../../root/api/command/index.rst:76 +msgid "Работа с псевдонимами" +msgstr "Working with Aliases" + +#: ../../root/api/command/index.rst:78 +msgid "" +"Псевдонимы позволяют вызывать один и тот же обработчик разными " +"триггерами, сохраняя флаги и описание команды." +msgstr "" +"Aliases allow invoking the same handler with different triggers while " +"preserving the command's flags and description." + +#: ../../root/api/command/index.rst:80 +msgid "**Пример с псевдонимами:**" +msgstr "**Example with aliases:**" + +#: ../../root/api/command/index.rst:85 +msgid "Теперь пользователь может вызвать команду любым из способов:" +msgstr "Now the user can invoke the command in any of the following ways:" + +#: ../../root/api/command/index.rst:94 +msgid "Все эти варианты вызовут один и тот же хэндлер ``handle_shutdown``." +msgstr "All these variants will invoke the same handler ``handle_shutdown``." + +#: ../../root/api/command/index.rst:101 +msgid "InputCommand" +msgstr "InputCommand" + +#: ../../root/api/command/index.rst:103 +msgid "" +"``InputCommand`` представляет собой обработанную команду, введённую " +"пользователем. Этот внутренний класс создаётся автоматически при " +"обработке пользовательского ввода. Прямая работа с ним возможна при " +"создании пользовательского обработчика для неизвестных команд." +msgstr "" +"``InputCommand`` represents a processed command entered by the user. This" +" internal class is created automatically when processing user input. " +"Direct work with it is possible when creating a custom handler for " +"unknown commands." + +#: ../../root/api/command/index.rst:106 +msgid "" +"Подробнее о пользовательских обработчиках исключений см. :ref:`здесь " +"`." +msgstr "" +"For more details on custom exception handlers, see :ref:`here " +"`." + +#: ../../root/api/command/index.rst:113 +msgid "Строковый триггер, введённый пользователем." +msgstr "String trigger entered by the user." + +#: ../../root/api/command/index.rst:118 +msgid "Объект ``InputFlags``, содержащий все введённые и распаршенные флаги." +msgstr "An ``InputFlags`` object containing all entered and parsed flags." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/command/input_flag.po b/docs/locales/en/LC_MESSAGES/root/api/command/input_flag.po new file mode 100644 index 0000000..0c3e40a --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/command/input_flag.po @@ -0,0 +1,171 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/command/input_flag.rst:4 +msgid "InputFlag" +msgstr "InputFlag" + +#: ../../root/api/command/input_flag.rst:6 +msgid "" +"Объект ``InputFlag`` представляет собой флаг, введённый пользователем. Он" +" создаётся в результате обработки пользовательского ввода и содержит " +"информацию о распознанном флаге: его имя, префикс, значение и статус " +"валидации." +msgstr "" +"The ``InputFlag`` object represents a flag entered by the user. It is " +"created as a result of processing user input and contains information " +"about the recognized flag: its name, prefix, value, and validation " +"status." + +#: ../../root/api/command/input_flag.rst:10 +msgid "" +"Документация по :ref:`Flag ` — класс для " +"регистрации флага." +msgstr "" +"Documentation for :ref:`Flag ` — class for " +"registering a flag." + +#: ../../root/api/command/input_flag.rst:12 +msgid "" +"Документация по :ref:`ValidationStatus " +"` — статусы валидации флагов." +msgstr "" +"Documentation for :ref:`ValidationStatus " +"` — flag validation statuses." + +#: ../../root/api/command/input_flag.rst:17 +msgid "" +"Экземпляры этого класса не предназначены для прямого создания. Они " +"содержатся в объекте :ref:`Response `." +msgstr "" +"Instances of this class are not intended for direct creation. They are " +"contained in the :ref:`Response ` object." + +#: ../../root/api/command/input_flag.rst:19 +msgid "**Атрибуты:**" +msgstr "**Attributes:**" + +#: ../../root/api/command/input_flag.rst:24 +msgid "Имя введённого флага." +msgstr "Name of the entered flag." + +#: ../../root/api/command/input_flag.rst:29 +msgid "Префикс флага: ``-``, ``--`` или ``---``." +msgstr "Flag prefix: ``-``, ``--``, or ``---``." + +#: ../../root/api/command/input_flag.rst:33 +msgid "" +"Значение, переданное с флагом. Может быть ``''`` (пустой строкой) для " +"флагов без значений." +msgstr "" +"Value passed with the flag. Can be ``''`` (empty string) for flags " +"without values." + +#: ../../root/api/command/input_flag.rst:38 +msgid "" +"Статус валидации флага: ``ValidationStatus.VALID``, " +"``ValidationStatus.INVALID`` или ``ValidationStatus.UNDEFINED``." +msgstr "" +"Flag validation status: ``ValidationStatus.VALID``, " +"``ValidationStatus.INVALID``, or ``ValidationStatus.UNDEFINED``." + +#: ../../root/api/command/input_flag.rst:43 +msgid "Свойства" +msgstr "Properties" + +#: ../../root/api/command/input_flag.rst:46 +msgid "string_entity" +msgstr "string_entity" + +#: ../../root/api/command/input_flag.rst:54 +msgid "Возвращает строковое представление флага в формате ``prefix + name``." +msgstr "" +"Returns the string representation of the flag in the format ``prefix + " +"name``." + +#: ../../root/api/command/input_flag.rst +msgid "return" +msgstr "return" + +#: ../../root/api/command/input_flag.rst:56 +msgid "Строковое представление флага" +msgstr "String representation of the flag" + +#: ../../root/api/command/input_flag.rst:61 +msgid "Магические методы" +msgstr "Magic Methods" + +#: ../../root/api/command/input_flag.rst:64 +msgid "__str__" +msgstr "__str__" + +#: ../../root/api/command/input_flag.rst:71 +msgid "Возвращает строковое представление флага вместе с его значением." +msgstr "Returns the string representation of the flag along with its value." + +#: ../../root/api/command/input_flag.rst:73 +msgid "Строка в формате ``флаг значение``." +msgstr "String in the format ``flag value``." + +#: ../../root/api/command/input_flag.rst:75 +#: ../../root/api/command/input_flag.rst:95 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/command/input_flag.rst:84 +msgid "__repr__" +msgstr "__repr__" + +#: ../../root/api/command/input_flag.rst:91 +msgid "Возвращает отладочное представление объекта." +msgstr "Returns the debug representation of the object." + +#: ../../root/api/command/input_flag.rst:93 +msgid "" +"Строка в формате ``InputFlag``." +msgstr "" +"String in the format ``InputFlag``." + +#: ../../root/api/command/input_flag.rst:104 +msgid "__eq__" +msgstr "__eq__" + +#: ../../root/api/command/input_flag.rst:111 +msgid "Сравнивает два введённых флага на равенство по имени." +msgstr "Compares two entered flags for equality by name." + +#: ../../root/api/command/input_flag.rst +msgid "param other" +msgstr "param other" + +#: ../../root/api/command/input_flag.rst:113 +msgid "Объект для сравнения." +msgstr "Object to compare." + +#: ../../root/api/command/input_flag.rst:114 +msgid "**True**, если имена флагов совпадают, иначе **False**." +msgstr "**True** if flag names match, otherwise **False**." + +#: ../../root/api/command/input_flag.rst:116 +msgid "Два введённых флага считаются равными, если их имена совпадают." +msgstr "Two entered flags are considered equal if their names match." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/command/input_flags.po b/docs/locales/en/LC_MESSAGES/root/api/command/input_flags.po new file mode 100644 index 0000000..bec64b9 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/command/input_flags.po @@ -0,0 +1,219 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/command/input_flags.rst:4 +msgid "InputFlags" +msgstr "InputFlags" + +#: ../../root/api/command/input_flags.rst:6 +msgid "" +"``InputFlags`` — это коллекция флагов, введённых пользователем. Её " +"основная задача — группировать и управлять набором флагов, переданных " +"вместе с командой. ``InputFlags`` служит контейнером, который позволяет " +"удобно извлекать, итерировать и проверять наличие флагов, а также " +"работать с их значениями и статусами валидации." +msgstr "" +"``InputFlags`` is a collection of flags entered by the user. Its main purpose is to " +"group and manage the set of flags passed with a command. ``InputFlags`` serves as a " +"container that allows convenient retrieval, iteration, and checking of flag presence, " +"as well as working with their values and validation statuses." + +#: ../../root/api/command/input_flags.rst:10 +msgid "" +"Документация по отдельным флагам (:ref:`Flag `, " +":ref:`InputFlag `)" +msgstr "" +"Documentation for individual flags (:ref:`Flag `, " +":ref:`InputFlag `)" + +#: ../../root/api/command/input_flags.rst:12 +msgid "" +"Документация по :ref:`InputFlags ` — " +"коллекция обработанных флагов, введённых пользователем." +msgstr "" +"Documentation for :ref:`InputFlags ` — " +"a collection of processed flags entered by the user." + +#: ../../root/api/command/input_flags.rst:14 +msgid "" +"Документация по :ref:`Response ` — объект ответа, " +"содержащий ``InputFlags``" +msgstr "" +"Documentation for :ref:`Response ` — response object " +"containing ``InputFlags``" + +#: ../../root/api/command/input_flags.rst:16 +msgid "" +":ref:`Общая информация ` о флагах и их использовании в " +"приложении ``Argenta``" +msgstr "" +":ref:`General information ` about flags and their usage in " +"the ``Argenta`` application" + +#: ../../root/api/command/input_flags.rst:21 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/command/input_flags.rst:28 +msgid "Создаёт новую коллекцию введённых флагов." +msgstr "Creates a new collection of entered flags." + +#: ../../root/api/command/input_flags.rst:30 +msgid "" +"``flags``: Необязательный список флагов типа ``InputFlag`` для " +"инициализации коллекции. Если не указан, создаётся пустая коллекция." +msgstr "" +"``flags``: Optional list of flags of type ``InputFlag`` for initializing the collection. " +"If not specified, an empty collection is created." + +#: ../../root/api/command/input_flags.rst:33 +msgid "" +"Экземпляры этого класса обычно не создаются напрямую. Они автоматически " +"формируются системой при обработке пользовательского ввода и доступны " +"через атрибут ``input_flags`` объекта ``Response``." +msgstr "" +"Instances of this class are usually not created directly. They are automatically " +"formed by the system when processing user input and are accessible through the " +"``input_flags`` attribute of the ``Response`` object." + +#: ../../root/api/command/input_flags.rst:35 +msgid "**Атрибуты:**" +msgstr "**Attributes:**" + +#: ../../root/api/command/input_flags.rst:40 +msgid "" +"Список всех введённых флагов типа ``InputFlag``. Пуст, если флаги не были" +" переданы при инициализации или пользователь не ввёл их с командой." +msgstr "" +"List of all entered flags of type ``InputFlag``. Empty if flags were not passed " +"during initialization or the user did not enter them with the command." + +#: ../../root/api/command/input_flags.rst:42 +#: ../../root/api/command/input_flags.rst:68 +#: ../../root/api/command/input_flags.rst:94 +#: ../../root/api/command/input_flags.rst:117 +#: ../../root/api/command/input_flags.rst:131 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/command/input_flags.rst:51 +msgid "Методы" +msgstr "Methods" + +#: ../../root/api/command/input_flags.rst:54 +msgid "get_flag_by_name" +msgstr "" + +#: ../../root/api/command/input_flags.rst:61 +msgid "Возвращает флаг по имени." +msgstr "Returns a flag by name." + +#: ../../root/api/command/input_flags.rst +msgid "param name" +msgstr "param name" + +#: ../../root/api/command/input_flags.rst:63 +msgid "Имя искомого флага (без префикса)." +msgstr "Name of the flag to search for (without prefix)." + +#: ../../root/api/command/input_flags.rst +msgid "return" +msgstr "return" + +#: ../../root/api/command/input_flags.rst:64 +msgid "Объект ``InputFlag`` или ``None``, если флаг не найден." +msgstr "``InputFlag`` object or ``None`` if the flag is not found." + +#: ../../root/api/command/input_flags.rst:66 +msgid "" +"Метод возвращает первый флаг с соответствующим именем (без учёта " +"префикса)." +msgstr "" +"The method returns the first flag with the corresponding name (ignoring the prefix)." + +#: ../../root/api/command/input_flags.rst:77 +msgid "add_flag" +msgstr "" + +#: ../../root/api/command/input_flags.rst:84 +msgid "Добавляет введённый флаг в коллекцию." +msgstr "Adds an entered flag to the collection." + +#: ../../root/api/command/input_flags.rst +msgid "param flag" +msgstr "param flag" + +#: ../../root/api/command/input_flags.rst:86 +msgid "Флаг типа ``InputFlag`` для добавления." +msgstr "Flag of type ``InputFlag`` to add." + +#: ../../root/api/command/input_flags.rst:87 +#: ../../root/api/command/input_flags.rst:113 +msgid "None." +msgstr "None." + +#: ../../root/api/command/input_flags.rst:89 +msgid "" +"Метод добавляет флаг в конец списка ``flags``. Используется для " +"динамического расширения коллекции." +msgstr "" +"The method adds a flag to the end of the ``flags`` list. Used for dynamically extending the collection." + +#: ../../root/api/command/input_flags.rst:92 +msgid "" +"Этот метод используется редко, так как `InputFlags` обычно создаётся " +"автоматически. Однако он может быть полезен для тестирования или ручного " +"создания коллекций." +msgstr "" +"This method is rarely used, as `InputFlags` is usually created automatically. " +"However, it can be useful for testing or manual collection creation." + +#: ../../root/api/command/input_flags.rst:103 +msgid "add_flags" +msgstr "" + +#: ../../root/api/command/input_flags.rst:110 +msgid "Добавляет в коллекцию список введённых флагов." +msgstr "Adds a list of entered flags to the collection." + +#: ../../root/api/command/input_flags.rst +msgid "param flags" +msgstr "param flags" + +#: ../../root/api/command/input_flags.rst:112 +msgid "Список флагов типа ``InputFlag`` для добавления." +msgstr "List of flags of type ``InputFlag`` to add." + +#: ../../root/api/command/input_flags.rst:115 +msgid "" +"Метод расширяет коллекцию, добавляя в неё все флаги из переданного " +"списка. Эффективен для пакетного добавления." +msgstr "" +"The method extends the collection by adding all flags from the provided list. " +"Efficient for batch addition." + +#: ../../root/api/command/input_flags.rst:126 +msgid "Практические примеры" +msgstr "Practical Examples" + +#: ../../root/api/command/input_flags.rst:129 +msgid "Обработка всех флагов с проверкой статусов" +msgstr "Processing All Flags with Status Checking" + diff --git a/docs/locales/en/LC_MESSAGES/root/api/command/possible_values.po b/docs/locales/en/LC_MESSAGES/root/api/command/possible_values.po new file mode 100644 index 0000000..eace953 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/command/possible_values.po @@ -0,0 +1,190 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/command/possible_values.rst:5 +msgid "PossibleValues" +msgstr "PossibleValues" + +#: ../../root/api/command/possible_values.rst:7 +msgid "" +"``PossibleValues`` — это перечисление, которое определяет " +"специальные режимы валидации для значений флагов. ``PossibleValues`` " +"используется в параметре ``possible_values`` класса ``Flag``, чтобы " +"указать, может ли флаг принимать значения и какие ограничения на них " +"накладываются." +msgstr "" +"``PossibleValues`` is an enumeration that defines special validation " +"modes for flag values. ``PossibleValues`` is used in the ``possible_values`` parameter " +"of the ``Flag`` class to specify whether a flag can accept values and what restrictions " +"are imposed on them." + +#: ../../root/api/command/possible_values.rst:9 +msgid "" +"``PossibleValues`` содержит два основных значения: ``NEITHER`` (для " +"флагов, которые не могут принимать значения) и ``ALL`` (для флагов, " +"принимающих любые значения). Это перечисление используется вместе со " +"списками строк и регулярными выражениями для создания гибкой системы " +"валидации." +msgstr "" +"``PossibleValues`` contains two main values: ``NEITHER`` (for flags that cannot " +"accept values) and ``ALL`` (for flags accepting any values). This enumeration is " +"used together with string lists and regular expressions to create a flexible " +"validation system." + +#: ../../root/api/command/possible_values.rst:12 +msgid "" +"Результат валидации доступен через атрибут ``status`` у экземпляра " +"``InputFlag``. Подробнее см. :ref:`здесь `." +msgstr "" +"The validation result is available through the ``status`` attribute of the " +"``InputFlag`` instance. For more details, see :ref:`here `." + +#: ../../root/api/command/possible_values.rst:16 +msgid "" +"Документация по :ref:`Flag ` — класс флага, " +"использующий ``PossibleValues``." +msgstr "" +"Documentation for :ref:`Flag ` — flag class " +"using ``PossibleValues``." + +#: ../../root/api/command/possible_values.rst:18 +msgid "" +"Документация по :ref:`ValidationStatus " +"` — результат валидации ввёденного " +"флага." +msgstr "" +"Documentation for :ref:`ValidationStatus " +"` — validation result of the entered flag." + +#: ../../root/api/command/possible_values.rst:20 +msgid "" +":ref:`Общая информация ` о флагах и их использовании в " +"приложении ``Argenta``" +msgstr "" +":ref:`General information ` about flags and their usage in " +"the ``Argenta`` application" + +#: ../../root/api/command/possible_values.rst:25 +msgid "NEITHER" +msgstr "NEITHER" + +#: ../../root/api/command/possible_values.rst:32 +msgid "Указывает, что флаг **не должен** иметь значения." +msgstr "Indicates that the flag **should not** have a value." + +#: ../../root/api/command/possible_values.rst:34 +msgid "" +"Флаги с этим значением работают как булевы переключатели: их наличие в " +"командной строке само по себе является информацией. Попытка передать " +"такому флагу значение приведёт к ошибке валидации." +msgstr "" +"Flags with this value work as boolean switches: their presence on the command line " +"is information in itself. Attempting to pass a value to such a flag will result in " +"a validation error." + +#: ../../root/api/command/possible_values.rst:36 +msgid "**Примеры флагов с** ``NEITHER``:" +msgstr "**Examples of flags with** ``NEITHER``:" + +#: ../../root/api/command/possible_values.rst:38 +msgid "``--help`` — флаг справки" +msgstr "``--help`` — help flag" + +#: ../../root/api/command/possible_values.rst:39 +msgid "``--verbose`` — флаг подробного вывода" +msgstr "``--verbose`` — verbose output flag" + +#: ../../root/api/command/possible_values.rst:40 +msgid "``--force`` — флаг принудительного выполнения" +msgstr "``--force`` — forced execution flag" + +#: ../../root/api/command/possible_values.rst:41 +msgid "``-A`` / ``--all`` — флаг выбора всех элементов" +msgstr "``-A`` / ``--all`` — select all items flag" + +#: ../../root/api/command/possible_values.rst:43 +#: ../../root/api/command/possible_values.rst:68 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/command/possible_values.rst:52 +msgid "ALL" +msgstr "ALL" + +#: ../../root/api/command/possible_values.rst:59 +msgid "Указывает, что флаг может принимать **любое** значение." +msgstr "Indicates that the flag can accept **any** value." + +#: ../../root/api/command/possible_values.rst:61 +msgid "" +"Флаги с этим значением универсальны и не накладывают ограничений на " +"передаваемые данные. Валидация всегда будет успешной." +msgstr "" +"Flags with this value are universal and do not impose restrictions on the data passed. " +"Validation will always be successful." + +#: ../../root/api/command/possible_values.rst:63 +msgid "**Примеры флагов с** ``ALL``:" +msgstr "**Examples of flags with** ``ALL``:" + +#: ../../root/api/command/possible_values.rst:65 +msgid "``--message`` — произвольное текстовое сообщение" +msgstr "``--message`` — arbitrary text message" + +#: ../../root/api/command/possible_values.rst:66 +msgid "``--name`` — произвольное имя" +msgstr "``--name`` — arbitrary name" + +#: ../../root/api/command/possible_values.rst:77 +msgid "Параметр possible_values" +msgstr "The possible_values Parameter" + +#: ../../root/api/command/possible_values.rst:79 +msgid "" +"``PossibleValues`` используется как один из возможных типов для параметра" +" ``possible_values`` при создании экземпляра ``Flag``." +msgstr "" +"``PossibleValues`` is used as one of the possible types for the ``possible_values`` " +"parameter when creating a ``Flag`` instance." + +#: ../../root/api/command/possible_values.rst:81 +msgid "**Доступные типы для** ``possible_values``:" +msgstr "**Available types for** ``possible_values``:" + +#: ../../root/api/command/possible_values.rst:83 +msgid "``PossibleValues.NEITHER``: флаг без значения." +msgstr "``PossibleValues.NEITHER``: flag without a value." + +#: ../../root/api/command/possible_values.rst:84 +msgid "``PossibleValues.ALL``: флаг с любым значением (по умолчанию)." +msgstr "``PossibleValues.ALL``: flag with any value (default)." + +#: ../../root/api/command/possible_values.rst:85 +msgid "``list[str]``: флаг с ограниченным набором значений." +msgstr "``list[str]``: flag with a limited set of values." + +#: ../../root/api/command/possible_values.rst:86 +msgid "``Pattern[str]``: флаг со значением, проверяемым по регулярному выражению." +msgstr "``Pattern[str]``: flag with a value validated by a regular expression." + +#: ../../root/api/command/possible_values.rst:88 +msgid "**Пример комбинированного использования:**" +msgstr "**Combined usage example:**" + diff --git a/docs/locales/en/LC_MESSAGES/root/api/command/validation_status.po b/docs/locales/en/LC_MESSAGES/root/api/command/validation_status.po new file mode 100644 index 0000000..83f12d8 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/command/validation_status.po @@ -0,0 +1,165 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/command/validation_status.rst:4 +msgid "ValidationStatus" +msgstr "ValidationStatus" + +#: ../../root/api/command/validation_status.rst:6 +msgid "" +"``ValidationStatus`` — это перечисление, которое определяет состояние " +"валидации флага. Его задача — предоставить стандартные константы для " +"отображения результата проверки. ``ValidationStatus`` используется в " +"атрибуте ``status`` класса ``InputFlag``." +msgstr "" +"``ValidationStatus`` is an enumeration that defines the validation state of a flag. " +"Its purpose is to provide standard constants for displaying the validation result. " +"``ValidationStatus`` is used in the ``status`` attribute of the ``InputFlag`` class." + +#: ../../root/api/command/validation_status.rst:8 +msgid "" +"``ValidationStatus`` содержит три значения: **VALID** (корректный флаг), " +"**INVALID** (некорректный) и **UNDEFINED** (незарегистрированный)." +msgstr "" +"``ValidationStatus`` contains three values: **VALID** (valid flag), " +"**INVALID** (invalid), and **UNDEFINED** (unregistered)." + +#: ../../root/api/command/validation_status.rst:12 +msgid "" +"Статус валидации устанавливается автоматически при создании экземпляра " +"``InputFlag`` на основе правил, заданных в соответствующем ``Flag``." +msgstr "" +"The validation status is set automatically when creating an ``InputFlag`` instance " +"based on the rules defined in the corresponding ``Flag``." + +#: ../../root/api/command/validation_status.rst:16 +msgid "" +"Документация по :ref:`InputFlag ` — класс " +"введённого флага, использующий ``ValidationStatus``." +msgstr "" +"Documentation for :ref:`InputFlag ` — entered flag class " +"using ``ValidationStatus``." + +#: ../../root/api/command/validation_status.rst:18 +msgid "" +"Документация по :ref:`Flag ` — класс флага с " +"правилами валидации." +msgstr "" +"Documentation for :ref:`Flag ` — flag class with " +"validation rules." + +#: ../../root/api/command/validation_status.rst:20 +msgid "" +"Документация по :ref:`PossibleValues ` " +"— типы допустимых значений." +msgstr "" +"Documentation for :ref:`PossibleValues ` " +"— types of allowed values." + +#: ../../root/api/command/validation_status.rst:25 +msgid "VALID" +msgstr "VALID" + +#: ../../root/api/command/validation_status.rst:32 +msgid "Указывает, что флаг и его значение **прошли** валидацию." +msgstr "Indicates that the flag and its value **passed** validation." + +#: ../../root/api/command/validation_status.rst:34 +msgid "" +"Флаги с этим статусом соответствуют правилам, заданным в " +"``possible_values`` соответствующего ``Flag``. Их можно безопасно " +"использовать в логике приложения без дополнительных проверок." +msgstr "" +"Flags with this status comply with the rules defined in the ``possible_values`` " +"of the corresponding ``Flag``. They can be safely used in application logic without " +"additional checks." + +#: ../../root/api/command/validation_status.rst:36 +msgid "**Условия получения статуса** ``VALID``:" +msgstr "**Conditions for receiving** ``VALID`` **status:**" + +#: ../../root/api/command/validation_status.rst:38 +msgid "Флаг с ``PossibleValues.NEITHER`` передан без значения." +msgstr "Flag with ``PossibleValues.NEITHER`` passed without a value." + +#: ../../root/api/command/validation_status.rst:39 +msgid "Флаг с ``PossibleValues.ALL`` передан с любым значением или без него." +msgstr "Flag with ``PossibleValues.ALL`` passed with any value or without one." + +#: ../../root/api/command/validation_status.rst:40 +msgid "Значение флага входит в список разрешённых." +msgstr "Flag value is in the list of allowed values." + +#: ../../root/api/command/validation_status.rst:41 +msgid "Значение флага соответствует регулярному выражению." +msgstr "Flag value matches the regular expression." + +#: ../../root/api/command/validation_status.rst:46 +msgid "INVALID" +msgstr "INVALID" + +#: ../../root/api/command/validation_status.rst:53 +msgid "Указывает, что флаг или его значение **не прошли** валидацию." +msgstr "Indicates that the flag or its value **did not pass** validation." + +#: ../../root/api/command/validation_status.rst:55 +msgid "" +"Флаги с этим статусом нарушают правила, заданные в ``possible_values`` " +"соответствующего ``Flag``. Их следует обрабатывать как ошибочные." +msgstr "" +"Flags with this status violate the rules defined in the ``possible_values`` " +"of the corresponding ``Flag``. They should be treated as erroneous." + +#: ../../root/api/command/validation_status.rst:57 +msgid "**Условия получения статуса** ``INVALID``:" +msgstr "**Conditions for receiving** ``INVALID`` **status:**" + +#: ../../root/api/command/validation_status.rst:59 +msgid "Флаг с ``PossibleValues.NEITHER`` передан со значением." +msgstr "Flag with ``PossibleValues.NEITHER`` passed with a value." + +#: ../../root/api/command/validation_status.rst:60 +msgid "Значение флага не входит в список разрешённых." +msgstr "Flag value is not in the list of allowed values." + +#: ../../root/api/command/validation_status.rst:61 +msgid "Значение флага не соответствует регулярному выражению." +msgstr "Flag value does not match the regular expression." + +#: ../../root/api/command/validation_status.rst:62 +msgid "Флаг требует значение, но передан без него." +msgstr "Flag requires a value but was passed without one." + +#: ../../root/api/command/validation_status.rst:67 +msgid "UNDEFINED" +msgstr "UNDEFINED" + +#: ../../root/api/command/validation_status.rst:74 +msgid "Указывает, что введённый флаг не был зарегистрирован в команде." +msgstr "Indicates that the entered flag was not registered in the command." + +#: ../../root/api/command/validation_status.rst:76 +msgid "**Условия получения статуса** ``UNDEFINED``:" +msgstr "**Conditions for receiving** ``UNDEFINED`` **status:**" + +#: ../../root/api/command/validation_status.rst:78 +msgid "Введённый флаг не найден среди зарегистрированных для данной команды." +msgstr "The entered flag is not found among those registered for this command." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/index.po b/docs/locales/en/LC_MESSAGES/root/api/index.po new file mode 100644 index 0000000..9e95caa --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/index.po @@ -0,0 +1,217 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/index.rst:5 +msgid "Публичное API" +msgstr "Public API" + +#: ../../root/api/index.rst:8 +msgid "Описание раздела" +msgstr "Section Description" + +#: ../../root/api/index.rst:10 +msgid "В этом разделе описан публичный API библиотеки. Он включает:" +msgstr "This section describes the library's public API. It includes:" + +#: ../../root/api/index.rst:12 +msgid "Классы и функции для интеграции в ваши приложения." +msgstr "Classes and functions for integration into your applications." + +#: ../../root/api/index.rst:13 +msgid "Рекомендации по использованию и поддерживаемые сценарии." +msgstr "Usage recommendations and supported scenarios." + +#: ../../root/api/index.rst:14 +msgid "Примеры кода, подробные сигнатуры и описание возвращаемых значений." +msgstr "Code examples, detailed signatures, and descriptions of return values." + +#: ../../root/api/index.rst:15 +msgid "Гарантии стабильности и обратной совместимости." +msgstr "Stability and backward compatibility guarantees." + +#: ../../root/api/index.rst:17 +msgid "" +"Интерфейсы, не описанные в этом разделе, считаются внутренними. Их " +"использование может привести к ошибкам при обновлении библиотеки. При " +"разработке собственных решений используйте только компоненты, описанные " +"здесь. Это обеспечит стабильность и совместимость ваших продуктов с " +"будущими версиями ``Argenta``." +msgstr "" +"Interfaces not described in this section are considered internal. Using them may " +"lead to errors when updating the library. When developing your own solutions, use " +"only the components described here. This will ensure the stability and compatibility " +"of your products with future versions of ``Argenta``." + +#: ../../root/api/index.rst:22 +msgid "Публичные импорты" +msgstr "Public Imports" + +#: ../../root/api/index.rst:24 +msgid "" +"Все основные компоненты библиотеки доступны для прямого импорта из " +"корневого пакета ``argenta`` или его подмодулей." +msgstr "" +"All main library components are available for direct import from the root package " +"``argenta`` or its submodules." + +#: ../../root/api/index.rst:27 +msgid "Основные компоненты" +msgstr "Main Components" + +#: ../../root/api/index.rst:32 +msgid "" +":ref:`App ` — Объект приложения, который отвечает за " +"логику роутинга, настройки, валидации и т.д." +msgstr "" +":ref:`App ` — Application object responsible for routing logic, " +"settings, validation, etc." + +#: ../../root/api/index.rst:33 +msgid "" +":ref:`Orchestrator ` — Класс для " +"конфигурирования и запуска всего приложения." +msgstr "" +":ref:`Orchestrator ` — Class for configuring and " +"launching the entire application." + +#: ../../root/api/index.rst:34 +msgid "" +":ref:`Router ` — Класс для группировки и регистрации " +"команд." +msgstr ":ref:`Router ` — Class for grouping and registering commands." + +#: ../../root/api/index.rst:35 +msgid "" +":ref:`Command ` — Класс для создания команд при " +"инициализации хэндлеров." +msgstr "" +":ref:`Command ` — Class for creating commands when " +"initializing handlers." + +#: ../../root/api/index.rst:36 +msgid "" +":ref:`Response ` — Объект ответа, передаваемый в " +"обработчики." +msgstr ":ref:`Response ` — Response object passed to handlers." + +#: ../../root/api/index.rst:39 +msgid "Команды и флаги" +msgstr "Commands and Flags" + +#: ../../root/api/index.rst:52 +msgid ":ref:`Flag ` — Класс для описания флага." +msgstr ":ref:`Flag ` — Class for describing a flag." + +#: ../../root/api/index.rst:53 +msgid ":ref:`Flags ` — Коллекция для регистрации флагов." +msgstr ":ref:`Flags ` — Collection for registering flags." + +#: ../../root/api/index.rst:54 +msgid "" +":ref:`InputFlag ` — Класс для введённого " +"пользователем флага." +msgstr "" +":ref:`InputFlag ` — Class for a user-entered flag." + +#: ../../root/api/index.rst:55 +msgid "" +":ref:`InputFlags ` — Коллекция введённых " +"флагов." +msgstr ":ref:`InputFlags ` — Collection of entered flags." + +#: ../../root/api/index.rst:56 +msgid "" +":ref:`PossibleValues ` — Правила " +"валидации значений флага." +msgstr "" +":ref:`PossibleValues ` — Validation rules for " +"flag values." + +#: ../../root/api/index.rst:57 +msgid "" +":ref:`ValidationStatus ` — Статусы " +"валидации флагов." +msgstr "" +":ref:`ValidationStatus ` — Flag validation statuses." + +#: ../../root/api/index.rst:58 +msgid "" +":ref:`PredefinedFlags ` — " +"Коллекция предопределённых флагов." +msgstr "" +":ref:`PredefinedFlags ` — Collection of " +"predefined flags." + +#: ../../root/api/index.rst:61 +msgid "Настройка приложения" +msgstr "Application Configuration" + +#: ../../root/api/index.rst:71 +msgid "" +":ref:`AutoCompleter ` - Класс для настройки " +"автодополнения." +msgstr "" +":ref:`AutoCompleter ` - Class for configuring " +"autocompletion." + +#: ../../root/api/index.rst:72 +msgid "" +":ref:`StaticDividingLine ` — Статическая " +"разделительная линия для оформления вывода." +msgstr "" +":ref:`StaticDividingLine ` — Static dividing line for " +"output formatting." + +#: ../../root/api/index.rst:73 +#: ../../root/api/index.rst:73 +msgid "" +":ref:`DynamicDividingLine ` — Динамическая " +"разделительная линия для оформления вывода." +msgstr "" +":ref:`DynamicDividingLine ` — Dynamic dividing line " +"for output formatting." +#: ../../root/api/index.rst:74 +msgid "" +":ref:`PredefinedMessages ` — Готовые " +"сообщения для вывода при старте приложения." +msgstr "" +":ref:`PredefinedMessages ` — Ready-made messages for " +"output at application startup." + +#: ../../root/api/index.rst:77 +msgid "Внедрение зависимостей" +msgstr "Dependency Injection" + +#: ../../root/api/index.rst:85 +msgid "" +":ref:`FromDishka ` — Маркер аргумента функции " +"как зависимости, которая должна быть инжектирована." +msgstr "" +":ref:`FromDishka ` — Marker for a function argument as a " +"dependency that should be injected." + +#: ../../root/api/index.rst:86 +msgid "" +":ref:`inject ` — Декоратор для инжектирования " +"зависимостей, указанных в сигнатуре." +msgstr "" +":ref:`inject ` — Decorator for injecting dependencies " +"specified in the signature." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/orchestrator/argparser.po b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/argparser.po new file mode 100644 index 0000000..b45ecd7 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/argparser.po @@ -0,0 +1,154 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:29+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/orchestrator/argparser.rst:4 +msgid "ArgParser" +msgstr "ArgParser" + +#: ../../root/api/orchestrator/argparser.rst:6 +msgid "" +"``ArgParser`` предназначен для обработки **аргументов командной строки**," +" передаваемых приложению при запуске. Важно не путать их с флагами, " +"которые пользователь вводит в интерактивном режиме. ``ArgParser`` " +"позволяет получать внешнюю конфигурацию в момент старта (например, путь к" +" файлу настроек, флаги отладки или режим запуска)." +msgstr "" +"``ArgParser`` is designed for processing **command-line arguments** passed to the " +"application at startup. It's important not to confuse them with flags that the user " +"enters in interactive mode. ``ArgParser`` allows receiving external configuration at " +"startup (e.g., path to settings file, debug flags, or launch mode)." + +#: ../../root/api/orchestrator/argparser.rst:11 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/orchestrator/argparser.rst:21 +msgid "Создаёт экземпляр парсера аргументов командной строки." +msgstr "Creates an instance of the command-line argument parser." + +#: ../../root/api/orchestrator/argparser.rst:23 +msgid "" +"``processed_args``: Список аргументов для обработки при запуске " +"приложения. Подробнее см. :ref:`здесь `." +msgstr "" +"``processed_args``: List of arguments to process at application startup. " +"For more details, see :ref:`here `." + +#: ../../root/api/orchestrator/argparser.rst:24 +msgid "``name``: Имя приложения для отображения в справке." +msgstr "``name``: Application name for display in help." + +#: ../../root/api/orchestrator/argparser.rst:25 +msgid "``description``: Описание приложения для отображения в справке." +msgstr "``description``: Application description for display in help." + +#: ../../root/api/orchestrator/argparser.rst:26 +msgid "``epilog``: Дополнительная информация для отображения в конце справки." +msgstr "``epilog``: Additional information for display at the end of help." + +#: ../../root/api/orchestrator/argparser.rst:31 +msgid "Атрибуты" +msgstr "Attributes" + +#: ../../root/api/orchestrator/argparser.rst:35 +msgid "" +"Экземпляр ``ArgSpace``, содержащий все обработанные аргументы командной " +"строки. Подробнее см. :ref:`здесь `." +msgstr "" +"``ArgSpace`` instance containing all processed command-line arguments. " +"For more details, see :ref:`here `." + +#: ../../root/api/orchestrator/argparser.rst:38 +msgid "" +"До инициализации ``Orchestrator``, в конструктор которого был передан " +"экземпляр ``ArgParser``, атрибут ``parsed_argspace`` будет содержать " +"пустой ``ArgSpace``." +msgstr "" +"Before initializing ``Orchestrator``, to whose constructor an ``ArgParser`` instance " +"was passed, the ``parsed_argspace`` attribute will contain an empty ``ArgSpace``." + +#: ../../root/api/orchestrator/argparser.rst:40 +msgid "" +"Парсинг и валидация аргументов происходят при инициализации " +"``Orchestrator``, поэтому использовать ``parsed_argspace`` " +"**целесообразно только после** этого." +msgstr "" +"Parsing and validation of arguments occur during ``Orchestrator`` initialization, " +"so using ``parsed_argspace`` is **advisable only after** that." + +#: ../../root/api/orchestrator/argparser.rst:45 +msgid "Лучшие практики" +msgstr "Best Practices" + +#: ../../root/api/orchestrator/argparser.rst:47 +msgid "" +"Использовать атрибут ``parsed_argspace`` рекомендуется только на этапе " +"настройки приложения. В обработчиках лучшей практикой является получение " +"``ArgSpace`` через DI. Подробнее см. :ref:`здесь " +"`." +msgstr "" +"Using the ``parsed_argspace`` attribute is recommended only during the application " +"setup phase. In handlers, the best practice is to obtain ``ArgSpace`` through DI. " +"For more details, see :ref:`here `." + +#: ../../root/api/orchestrator/argparser.rst:49 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/orchestrator/argparser.rst:56 +msgid "Обработка ошибок" +msgstr "Error Handling" + +#: ../../root/api/orchestrator/argparser.rst:59 +msgid "" +"Про типы аргументов подробнее в :ref:`Arguments " +"`" +msgstr "" +"For more details on argument types, see :ref:`Arguments " +"`" + +#: ../../root/api/orchestrator/argparser.rst:61 +msgid "" +"При работе с аргументами командной строки стандартный ``ArgumentParser`` " +"автоматически обрабатывает следующие ситуации:" +msgstr "" +"When working with command-line arguments, the standard ``ArgumentParser`` " +"automatically handles the following situations:" + +#: ../../root/api/orchestrator/argparser.rst:63 +msgid "**Отсутствие обязательного аргумента:**" +msgstr "**Missing required argument:**" + +#: ../../root/api/orchestrator/argparser.rst:71 +msgid "**Недопустимое значение из списка possible_values:**" +msgstr "**Invalid value from possible_values list:**" + +#: ../../root/api/orchestrator/argparser.rst:79 +msgid "**Использование устаревшего аргумента:**" +msgstr "**Using a deprecated argument:**" + +#: ../../root/api/orchestrator/argparser.rst:81 +msgid "" +"При использовании аргумента с ``is_deprecated=True`` выводится " +"предупреждение, но выполнение продолжается:" +msgstr "" +"When using an argument with ``is_deprecated=True``, a warning is displayed, " +"but execution continues:" + diff --git a/docs/locales/en/LC_MESSAGES/root/api/orchestrator/argspace.po b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/argspace.po new file mode 100644 index 0000000..d62a651 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/argspace.po @@ -0,0 +1,166 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/orchestrator/argspace.rst:4 +msgid "ArgSpace" +msgstr "ArgSpace" + +#: ../../root/api/orchestrator/argspace.rst:6 +msgid "" +"``ArgSpace`` — это контейнер для хранения и управления обработанными " +"аргументами командной строки. Его основная задача — предоставить удобный " +"интерфейс для доступа к значениям, переданным при запуске приложения." +msgstr "" +"``ArgSpace`` is a container for storing and managing processed command-" +"line arguments. Its main purpose is to provide a convenient interface for" +" accessing values passed at application startup." + +#: ../../root/api/orchestrator/argspace.rst:8 +msgid "" +"``ArgSpace`` создаётся автоматически после обработки аргументов с помощью" +" ``ArgParser`` и содержит коллекцию объектов ``InputArgument``." +msgstr "" +"``ArgSpace`` is created automatically after processing arguments using " +"``ArgParser`` and contains a collection of ``InputArgument`` objects." + +#: ../../root/api/orchestrator/argspace.rst:13 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/orchestrator/argspace.rst:15 +msgid "" +"Создание экземпляров класса ``ArgSpace`` происходит под `капотом`, вам не" +" нужно создавать их вручную." +msgstr "" +"Creation of ``ArgSpace`` class instances happens under the hood, you " +"don't need to create them manually." + +#: ../../root/api/orchestrator/argspace.rst:17 +msgid "**Атрибуты:**" +msgstr "**Attributes:**" + +#: ../../root/api/orchestrator/argspace.rst:21 +msgid "Список всех обработанных аргументов типа ``InputArgument``." +msgstr "List of all processed arguments of type ``InputArgument``." + +#: ../../root/api/orchestrator/argspace.rst:26 +msgid "Методы" +msgstr "Methods" + +#: ../../root/api/orchestrator/argspace.rst:29 +msgid "get_by_name" +msgstr "get_by_name" + +#: ../../root/api/orchestrator/argspace.rst:36 +msgid "Возвращает аргумент по имени." +msgstr "Returns an argument by name." + +#: ../../root/api/orchestrator/argspace.rst +msgid "param name" +msgstr "param name" + +#: ../../root/api/orchestrator/argspace.rst:38 +msgid "Имя искомого аргумента." +msgstr "Name of the argument to search for." + +#: ../../root/api/orchestrator/argspace.rst +msgid "return" +msgstr "return" + +#: ../../root/api/orchestrator/argspace.rst:39 +msgid "Объект ``InputArgument`` или ``None``, если аргумент не найден." +msgstr "``InputArgument`` object or ``None`` if the argument is not found." + +#: ../../root/api/orchestrator/argspace.rst:41 +#: ../../root/api/orchestrator/argspace.rst:63 +#: ../../root/api/orchestrator/argspace.rst:90 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/orchestrator/argspace.rst:49 +msgid "get_by_type" +msgstr "get_by_type" + +#: ../../root/api/orchestrator/argspace.rst:56 +msgid "Возвращает все аргументы определённого типа." +msgstr "Returns all arguments of a specific type." + +#: ../../root/api/orchestrator/argspace.rst +msgid "param arg_type" +msgstr "param arg_type" + +#: ../../root/api/orchestrator/argspace.rst:58 +msgid "Тип аргумента (``BooleanArgument`` или ``ValueArgument``)." +msgstr "Argument type (``BooleanArgument`` or ``ValueArgument``)." + +#: ../../root/api/orchestrator/argspace.rst:59 +msgid "Список аргументов указанного типа или пустой список." +msgstr "List of arguments of the specified type or an empty list." + +#: ../../root/api/orchestrator/argspace.rst:61 +msgid "" +"Метод фильтрует ``all_arguments`` по атрибуту ``founder_class`` и " +"возвращает аргументы, созданные из указанного типа." +msgstr "" +"The method filters ``all_arguments`` by the ``founder_class`` attribute " +"and returns arguments created from the specified type." + +#: ../../root/api/orchestrator/argspace.rst:71 +msgid "InputArgument" +msgstr "InputArgument" + +#: ../../root/api/orchestrator/argspace.rst:74 +msgid "" +"Документация по ``InputArgument`` находится :ref:`здесь " +"`." +msgstr "" +"Documentation for ``InputArgument`` is located :ref:`here " +"`." + +#: ../../root/api/orchestrator/argspace.rst:79 +msgid "Примеры использования" +msgstr "Usage Examples" + +#: ../../root/api/orchestrator/argspace.rst:81 +msgid "" +"``ArgSpace`` используется для доступа к значениям аргументов после " +"запуска приложения. Типичный сценарий включает обработку аргументов через" +" ``ArgParser`` и последующее извлечение значений из ``ArgSpace``." +msgstr "" +"``ArgSpace`` is used to access argument values after the application " +"starts. A typical scenario includes processing arguments through " +"``ArgParser`` and subsequent extraction of values from ``ArgSpace``." + +#: ../../root/api/orchestrator/argspace.rst:83 +msgid "**Полный пример:**" +msgstr "**Complete example:**" + +#: ../../root/api/orchestrator/argspace.rst:88 +msgid "" +"Доступ к аргументам из обработчиков осуществляется с помощью DI. " +"Подробнее см. :ref:`здесь `." +msgstr "" +"Access to arguments from handlers is done using DI. For more details, see" +" :ref:`here `." + +#: ../../root/api/orchestrator/argspace.rst:95 +msgid "**Запуск приложения:**" +msgstr "**Running the application:**" + diff --git a/docs/locales/en/LC_MESSAGES/root/api/orchestrator/arguments.po b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/arguments.po new file mode 100644 index 0000000..63ccdfe --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/arguments.po @@ -0,0 +1,227 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/orchestrator/arguments.rst:4 +msgid "Arguments" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:6 +msgid "" +"Модуль ``Arguments`` предоставляет классы для работы с аргументами " +"командной строки. Они позволяют настраивать поведение приложения в момент" +" его запуска, передавая различные параметры конфигурации." +msgstr "" +"The ``Arguments`` module provides classes for working with command-line arguments. " +"They allow configuring application behavior at startup by passing various configuration parameters." + +#: ../../root/api/orchestrator/arguments.rst:8 +msgid "" +"Аргументы регистрируются в ``ArgParser`` и после обработки становятся " +"доступными в объекте ``ArgSpace``." +msgstr "" +"Arguments are registered in ``ArgParser`` and after processing become available in the ``ArgSpace`` object." + +#: ../../root/api/orchestrator/arguments.rst:13 +msgid "ValueArgument" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:15 +msgid "Класс для аргументов, требующих передачи значения." +msgstr "Class for arguments that require passing a value." + +#: ../../root/api/orchestrator/arguments.rst:31 +msgid "Создаёт аргумент командной строки, требующий значения." +msgstr "Creates a command-line argument that requires a value." + +#: ../../root/api/orchestrator/arguments.rst +msgid "param name" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:33 +#: ../../root/api/orchestrator/arguments.rst:74 +#: ../../root/api/orchestrator/arguments.rst:117 +msgid "Имя аргумента" +msgstr "Argument name" + +#: ../../root/api/orchestrator/arguments.rst +msgid "param prefix" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:34 +#: ../../root/api/orchestrator/arguments.rst:75 +msgid "Префикс (по умолчанию ``--``)" +msgstr "Prefix (defaults to ``--``)" + +#: ../../root/api/orchestrator/arguments.rst +msgid "param help" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:35 +#: ../../root/api/orchestrator/arguments.rst:76 +msgid "Сообщение для справки (``--help``)" +msgstr "Help message (``--help``)" + +#: ../../root/api/orchestrator/arguments.rst +msgid "param possible_values" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:36 +msgid "Список допустимых значений" +msgstr "List of allowed values" + +#: ../../root/api/orchestrator/arguments.rst +msgid "param default" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:37 +msgid "Значение по умолчанию, если аргумент не передан" +msgstr "Default value if the argument is not passed" + +#: ../../root/api/orchestrator/arguments.rst +msgid "param is_required" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:38 +msgid "" +"Если ``True``, аргумент становится обязательным. Если не передать при " +"запуске, приложение не запустится" +msgstr "" +"If ``True``, the argument becomes required. If not passed at startup, the application will not start" + +#: ../../root/api/orchestrator/arguments.rst +msgid "param is_deprecated" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:39 +msgid "" +"Если ``True``, помечает аргумент как устаревший. Если передать при " +"запуске, будет выведено предупреждение в консоль" +msgstr "" +"If ``True``, marks the argument as deprecated. If passed at startup, a warning will be displayed in the console" + +#: ../../root/api/orchestrator/arguments.rst:41 +#: ../../root/api/orchestrator/arguments.rst:79 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/orchestrator/arguments.rst:47 +#: ../../root/api/orchestrator/arguments.rst:85 +msgid "**Запуск приложения:**" +msgstr "**Running the application:**" + +#: ../../root/api/orchestrator/arguments.rst:57 +msgid "BooleanArgument" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:59 +msgid "" +"Класс для булевых аргументов, не требующих значения. Их наличие при " +"запуске устанавливает значение в **True**, отсутствие — в **False**." +msgstr "" +"Class for boolean arguments that do not require a value. Their presence at startup " +"sets the value to **True**, absence to **False**." + +#: ../../root/api/orchestrator/arguments.rst:72 +msgid "Создаёт булев аргумент командной строки без значения." +msgstr "Creates a boolean command-line argument without a value." + +#: ../../root/api/orchestrator/arguments.rst:77 +msgid "Если ``True``, помечает аргумент как устаревший" +msgstr "If ``True``, marks the argument as deprecated" + +#: ../../root/api/orchestrator/arguments.rst:98 +msgid "InputArgument" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:101 +msgid "" +"``InputArgument`` напрямую связан с контейнером ``ArgSpace`` и является " +"его наполнителем. Подробнее о нём см. :ref:`здесь " +"`." +msgstr "" +"``InputArgument`` is directly related to the ``ArgSpace`` container and serves as its filler. " +"For more details, see :ref:`here `." + +#: ../../root/api/orchestrator/arguments.rst:103 +msgid "" +"Представляет собой обработанный аргумент командной строки. Этот класс " +"используется внутри ``ArgSpace`` для хранения значений, полученных после " +"парсинга." +msgstr "" +"Represents a processed command-line argument. This class is used inside ``ArgSpace`` " +"to store values obtained after parsing." + +#: ../../root/api/orchestrator/arguments.rst:115 +msgid "Создаёт экземпляр обработанного входного аргумента." +msgstr "Creates an instance of a processed input argument." + +#: ../../root/api/orchestrator/arguments.rst +msgid "param value" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:118 +msgid "" +"Значение аргумента. Для ``BooleanArgument`` — **True**, если аргумент " +"передан, и **False**, если нет; для ``ValueArgument`` — введённая строка" +msgstr "" +"Argument value. For ``BooleanArgument`` — **True** if the argument is passed, and **False** if not; " +"for ``ValueArgument`` — the entered string" + +#: ../../root/api/orchestrator/arguments.rst +msgid "param founder_class" +msgstr "" + +#: ../../root/api/orchestrator/arguments.rst:119 +msgid "" +"Класс-родитель, из которого был создан аргумент (``BooleanArgument`` или " +"``ValueArgument``)" +msgstr "" +"Parent class from which the argument was created (``BooleanArgument`` or ``ValueArgument``)" + +#: ../../root/api/orchestrator/arguments.rst:121 +msgid "**Атрибуты:**" +msgstr "**Attributes:**" + +#: ../../root/api/orchestrator/arguments.rst:125 +msgid "" +"Имя аргумента, указанное при создании ``ValueArgument`` или " +"``BooleanArgument``." +msgstr "" +"Argument name specified when creating ``ValueArgument`` or ``BooleanArgument``." + +#: ../../root/api/orchestrator/arguments.rst:129 +msgid "Значение аргумента. Тип зависит от исходного класса:" +msgstr "Argument value. Type depends on the source class:" + +#: ../../root/api/orchestrator/arguments.rst:131 +msgid "Для ``BooleanArgument``: **True**, если аргумент был передан" +msgstr "For ``BooleanArgument``: **True** if the argument was passed" + +#: ../../root/api/orchestrator/arguments.rst:132 +msgid "" +"Для ``ValueArgument``: строка с переданным значением или значением по " +"умолчанию" +msgstr "" +"For ``ValueArgument``: string with the passed value or default value" + +#: ../../root/api/orchestrator/arguments.rst:136 +msgid "Ссылка на класс-родитель. Используется для определения типа и фильтрации." +msgstr "Reference to the parent class. Used for type determination and filtering." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/orchestrator/index.po b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/index.po new file mode 100644 index 0000000..5c1561b --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/index.po @@ -0,0 +1,128 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/orchestrator/index.rst:4 +msgid "Orchestrator" +msgstr "Orchestrator" + +#: ../../root/api/orchestrator/index.rst:6 +msgid "" +"``Orchestrator`` — это высокоуровневый компонент, который конфигурирует и" +" оркестрирует приложение, парсер командной строки, DI и остальные " +"компоненты, находящиеся по иерархии на уровне с ``App``." +msgstr "" +"``Orchestrator`` is a high-level component that configures and " +"orchestrates the application, command-line parser, DI, and other " +"components at the same hierarchical level as ``App``." + +#: ../../root/api/orchestrator/index.rst:8 +msgid "" +"В то время как ``App`` отвечает за логику интерактивной сессии (ввод " +"команд, маршрутизация), ``Orchestrator`` подготавливает окружение для его" +" работы и служит точкой входа в приложение." +msgstr "" +"While ``App`` is responsible for interactive session logic (command " +"input, routing), ``Orchestrator`` prepares the environment for its " +"operation and serves as the entry point to the application." + +#: ../../root/api/orchestrator/index.rst:13 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/orchestrator/index.rst:28 +msgid "Создаёт и конфигурирует экземпляр ``Orchestrator``." +msgstr "Creates and configures an ``Orchestrator`` instance." + +#: ../../root/api/orchestrator/index.rst:30 +msgid "" +"``arg_parser``: Экземпляр ``ArgParser``, отвечающий за парсинг аргументов" +" командной строки при запуске скрипта (не путать с командами в " +"интерактивном режиме)." +msgstr "" +"``arg_parser``: ``ArgParser`` instance responsible for parsing command-" +"line arguments at script startup (not to be confused with commands in " +"interactive mode)." + +#: ../../root/api/orchestrator/index.rst:31 +msgid "" +"``custom_providers``: Список пользовательских провайдеров " +"``dishka.Provider`` для добавления ваших сервисов (например, подключений " +"к БД или API-клиентов) в di-контейнер." +msgstr "" +"``custom_providers``: List of custom ``dishka.Provider`` providers for " +"adding your services (e.g., database connections or API clients) to the " +"DI container." + +#: ../../root/api/orchestrator/index.rst:32 +msgid "" +"``auto_inject_handlers``: Если **True** (по умолчанию), ``dishka`` " +"автоматически внедрит зависимости в обработчики команд, инспектируя их " +"сигнатуры." +msgstr "" +"``auto_inject_handlers``: If **True** (default), ``dishka`` will " +"automatically inject dependencies into command handlers by inspecting " +"their signatures." + +#: ../../root/api/orchestrator/index.rst:37 +msgid "Основные методы" +msgstr "Main Methods" + +#: ../../root/api/orchestrator/index.rst:41 +msgid "" +"Это главный метод, который запускает приложение. Он запускает бесконечный" +" цикл ввода -> вывода." +msgstr "" +"This is the main method that starts the application. It launches an " +"infinite input -> output loop." + +#: ../../root/api/orchestrator/index.rst +msgid "Parameters" +msgstr "Parameters" + +#: ../../root/api/orchestrator/index.rst:43 +msgid "Экземпляр ``App``, который будет запущен." +msgstr "``App`` instance to be launched." + +#: ../../root/api/orchestrator/index.rst:48 +msgid "Назначение и использование" +msgstr "Purpose and Usage" + +#: ../../root/api/orchestrator/index.rst:50 +msgid "" +"``Orchestrator`` абстрагирует сложность, связанную с настройкой DI и " +"парсингом стартовых аргументов." +msgstr "" +"``Orchestrator`` abstracts the complexity associated with setting up DI " +"and parsing startup arguments." + +#: ../../root/api/orchestrator/index.rst:52 +msgid "" +"Такой подход разделяет ответственности: ``App`` отвечает за логику " +"интерактивной сессии, а ``Orchestrator`` — за подготовку окружения и " +"запуск приложения." +msgstr "" +"This approach separates responsibilities: ``App`` is responsible for " +"interactive session logic, while ``Orchestrator`` handles environment " +"preparation and application launch." + +#: ../../root/api/orchestrator/index.rst:54 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + diff --git a/docs/locales/en/LC_MESSAGES/root/api/response.po b/docs/locales/en/LC_MESSAGES/root/api/response.po new file mode 100644 index 0000000..191c7cb --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/response.po @@ -0,0 +1,185 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/response.rst:4 +msgid "Response" +msgstr "Response" + +#: ../../root/api/response.rst:6 +msgid "" +"``Response`` — это объект, который передаётся в обработчик команды. Он " +"создаётся автоматически при обработке пользовательского ввода и содержит " +"статус валидации, введённые флаги." +msgstr "" +"``Response`` is an object that is passed to the command handler. It is created " +"automatically when processing user input and contains validation status and entered flags." + +#: ../../root/api/response.rst:11 +msgid "" +"Документация по :ref:`InputFlags ` — " +"коллекция введённых флагов команды." +msgstr "" +"Documentation for :ref:`InputFlags ` — collection of " +"entered command flags." + +#: ../../root/api/response.rst:13 +msgid "" +"Документация по :ref:`ResponseStatus ` — " +"статусы валидации флагов команды." +msgstr "" +"Documentation for :ref:`ResponseStatus ` — command flag " +"validation statuses." + +#: ../../root/api/response.rst:15 +msgid "" +"Документация по :ref:`InputFlag ` — " +"отдельный введённый флаг." +msgstr "" +"Documentation for :ref:`InputFlag ` — individual " +"entered flag." + +#: ../../root/api/response.rst:20 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/response.rst:30 +msgid "Создаёт новый объект ответа." +msgstr "Creates a new response object." + +#: ../../root/api/response.rst:32 +msgid "" +"``status``: Общий статус валидации флагов из перечисления " +"``ResponseStatus``." +msgstr "``status``: Overall flag validation status from the ``ResponseStatus`` enumeration." + +#: ../../root/api/response.rst:33 +msgid "" +"``input_flags``: Коллекция введённых флагов (``InputFlags``). По " +"умолчанию — пустая." +msgstr "``input_flags``: Collection of entered flags (``InputFlags``). Empty by default." + +#: ../../root/api/response.rst:36 +msgid "" +"Экземпляры этого класса не предназначены для прямого создания. Они " +"автоматически формируются системой и передаются в обработчик команды в " +"качестве первого обязательного аргумента." +msgstr "" +"Instances of this class are not intended for direct creation. They are automatically " +"formed by the system and passed to the command handler as the first required argument." + +#: ../../root/api/response.rst:38 +msgid "**Атрибуты:**" +msgstr "**Attributes:**" + +#: ../../root/api/response.rst:43 +msgid "" +"Общий статус валидации всех флагов команды (``ResponseStatus``). " +"Указывает, были ли среди введённых флагов некорректные или " +"незарегистрированные." +msgstr "" +"Overall validation status of all command flags (``ResponseStatus``). Indicates " +"whether there were any incorrect or unregistered flags among the entered ones." + +#: ../../root/api/response.rst:48 +msgid "" +"Коллекция всех флагов, переданных с командой (``InputFlags``). Содержит " +"все обработанные флаги с их значениями и статусами валидации." +msgstr "" +"Collection of all flags passed with the command (``InputFlags``). Contains all " +"processed flags with their values and validation statuses." + +#: ../../root/api/response.rst:50 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/response.rst:59 +msgid "Работа с флагами" +msgstr "Working with Flags" + +#: ../../root/api/response.rst:61 +msgid "" +"``Response`` предоставляет доступ к введённым флагам через атрибут " +"``input_flags``. Вы можете проверять их наличие, получать значения и " +"статусы валидации." +msgstr "" +"``Response`` provides access to entered flags through the ``input_flags`` attribute. " +"You can check their presence, get values, and validation statuses." + +#: ../../root/api/response.rst:63 +msgid "**Пример работы с флагами:**" +msgstr "**Example of working with flags:**" + +#: ../../root/api/response.rst:74 +msgid "ResponseStatus" +msgstr "ResponseStatus" + +#: ../../root/api/response.rst:76 +msgid "" +"``ResponseStatus`` — это перечисление, которое определяет общий статус " +"валидации всех флагов команды. Используется в атрибуте ``status`` объекта" +" ``Response``." +msgstr "" +"``ResponseStatus`` is an enumeration that defines the overall validation status of " +"all command flags. Used in the ``status`` attribute of the ``Response`` object." + +#: ../../root/api/response.rst:79 +msgid "ALL_FLAGS_VALID" +msgstr "" + +#: ../../root/api/response.rst:86 +msgid "" +"Все введённые флаги прошли валидацию. Нет ни некорректных, ни " +"незарегистрированных флагов." +msgstr "" +"All entered flags passed validation. There are no incorrect or unregistered flags." + +#: ../../root/api/response.rst:89 +msgid "UNDEFINED_FLAGS" +msgstr "" + +#: ../../root/api/response.rst:96 +msgid "" +"Среди введённых флагов есть незарегистрированные, но нет флагов с " +"некорректными значениями." +msgstr "" +"Among the entered flags, there are unregistered ones, but no flags with incorrect values." + +#: ../../root/api/response.rst:99 +msgid "INVALID_VALUE_FLAGS" +msgstr "" + +#: ../../root/api/response.rst:106 +msgid "" +"Среди введённых флагов есть флаги с некорректными значениями, но нет " +"незарегистрированных." +msgstr "" +"Among the entered flags, there are flags with incorrect values, but no unregistered ones." + +#: ../../root/api/response.rst:109 +msgid "UNDEFINED_AND_INVALID_FLAGS" +msgstr "" + +#: ../../root/api/response.rst:116 +msgid "" +"Среди введённых флагов есть как незарегистрированные, так и флаги с " +"некорректными значениями." +msgstr "" +"Among the entered flags, there are both unregistered flags and flags with incorrect values." + diff --git a/docs/locales/en/LC_MESSAGES/root/api/router.po b/docs/locales/en/LC_MESSAGES/root/api/router.po new file mode 100644 index 0000000..c572a30 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/api/router.po @@ -0,0 +1,186 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/api/router.rst:4 +msgid "Router" +msgstr "" + +#: ../../root/api/router.rst:6 +msgid "" +"``Router`` — это основной строительный блок для организации логики в " +"приложении. Его задача — группировать связанные команды и их обработчики." +" Каждый роутер представляет собой логический контейнер для определённого " +"набора функций." +msgstr "" +"``Router`` is the main building block for organizing logic in an " +"application. Its purpose is to group related commands and their handlers. " +"Each router represents a logical container for a specific set of functions." + +#: ../../root/api/router.rst:8 +msgid "" +"Например, в приложении для управления пользователями один роутер может " +"отвечать за аутентификацию (``login``, ``logout``), а другой — за " +"операции с профилем (``profile-show``, ``profile-edit``)." +msgstr "" +"For example, in a user management application, one router can handle " +"authentication (``login``, ``logout``), while another handles profile " +"operations (``profile-show``, ``profile-edit``)." + +#: ../../root/api/router.rst:13 +msgid "Инициализация" +msgstr "Initialization" + +#: ../../root/api/router.rst:21 +msgid "Создаёт новый экземпляр роутера." +msgstr "Creates a new router instance." + +#: ../../root/api/router.rst:23 +msgid "" +"``title``: Необязательный заголовок для группы команд. Отображается в " +"списке доступных команд, помогая пользователю ориентироваться." +msgstr "" +"``title``: Optional title for the command group. Displayed in the list of " +"available commands to help users navigate." + +#: ../../root/api/router.rst:24 +msgid "" +"``disable_redirect_stdout``: Если ``True``, отключает перехват ``stdout``" +" для всех команд этого роутера. Это необходимо для интерактивных команд " +"(например, с ``input()``). При отключении перехвата автоматически " +"используется статическая разделительная линия. Подробнее см. в разделе " +":ref:`Переопределение стандартного вывода `." +msgstr "" +"``disable_redirect_stdout``: If ``True``, disables ``stdout`` capture for " +"all commands in this router. This is necessary for interactive commands " +"(e.g., with ``input()``). When capture is disabled, a static separator line " +"is automatically used. See :ref:`Overriding standard output ` " +"for more details." + +#: ../../root/api/router.rst:29 +msgid "Регистрация команд" +msgstr "Command Registration" + +#: ../../root/api/router.rst:31 +msgid "" +"Для регистрации команды и привязки к ней обработчика используется " +"декоратор ``@command``." +msgstr "" +"The ``@command`` decorator is used to register a command and bind a handler to it." + +#: ../../root/api/router.rst:35 +msgid "Декоратор для регистрации функции как обработчика команды." +msgstr "Decorator for registering a function as a command handler." + +#: ../../root/api/router.rst +msgid "Parameters" +msgstr "Parameters" + +#: ../../root/api/router.rst:37 +msgid "" +"Экземпляр ``Command``, описывающий триггер, флаги и описание команды. " +"Может быть строкой, которая станет триггером (без возможности настройки " +"флагов и описания)." +msgstr "" +"A ``Command`` instance describing the trigger, flags, and command description. " +"Can be a string that will become the trigger (without the ability to configure " +"flags and description)." + +#: ../../root/api/router.rst:39 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/api/router.rst:48 +msgid "Системный роутер" +msgstr "System Router" + +#: ../../root/api/router.rst:50 +msgid "" +"``Argenta`` поставляется со встроенным системным роутером, который " +"автоматически подключается к каждому приложению." +msgstr "" +"``Argenta`` comes with a built-in system router that is automatically " +"connected to every application." + +#: ../../root/api/router.rst:55 +msgid "" +"Предопределённый экземпляр ``Router`` с базовыми системными командами (по" +" умолчанию — команда выхода). Имеет заголовок **«System points:»**, " +"который можно переопределить в ``App``." +msgstr "" +"A predefined ``Router`` instance with basic system commands (by default, " +"the exit command). Has the title **\"System points:\"**, which can be " +"overridden in ``App``." + +#: ../../root/api/router.rst:57 +msgid "" +"Вы можете добавлять свои команды в этот роутер. Для этого импортируйте " +"``argenta.router.defaults.system_router`` и используйте его декоратор " +"``@command``." +msgstr "" +"You can add your own commands to this router. To do this, import " +"``argenta.router.defaults.system_router`` and use its ``@command`` decorator." + +#: ../../root/api/router.rst:62 +msgid "Возможные исключения" +msgstr "Possible Exceptions" + +#: ../../root/api/router.rst:64 +msgid "" +"При регистрации команд и флагов в ``Router`` могут возникнуть следующие " +"исключения:" +msgstr "" +"The following exceptions may occur when registering commands and flags in ``Router``:" + +#: ../../root/api/router.rst:68 +msgid "" +"Выбрасывается, если триггер команды в ``Command`` содержит пробелы. " +"Триггеры должны быть одним словом." +msgstr "" +"Raised if the command trigger in ``Command`` contains spaces. " +"Triggers must be a single word." + +#: ../../root/api/router.rst:70 +msgid "**Неправильно:** ``Command(\"add user\")``" +msgstr "**Incorrect:** ``Command(\"add user\")``" + +#: ../../root/api/router.rst:72 +msgid "**Правильно:** ``Command(\"add-user\")``" +msgstr "**Correct:** ``Command(\"add-user\")``" + +#: ../../root/api/router.rst:76 +msgid "" +"Возникает, если при определении флагов для команды были использованы " +"дублирующиеся имена. Имена флагов в рамках одной команды должны быть " +"уникальны." +msgstr "" +"Raised if duplicate names were used when defining flags for a command. " +"Flag names within a single command must be unique." + +#: ../../root/api/router.rst:78 +msgid "**Пример, вызывающий исключение:**" +msgstr "**Example that raises an exception:**" + +#: ../../root/api/router.rst:90 +msgid "" +"Возникает, если обработчик команды не принимает обязательный аргумент " +"``Response``." +msgstr "" +"Raised if the command handler does not accept the required ``Response`` argument." + diff --git a/docs/locales/en/LC_MESSAGES/root/code_of_conduct.po b/docs/locales/en/LC_MESSAGES/root/code_of_conduct.po new file mode 100644 index 0000000..75d8584 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/code_of_conduct.po @@ -0,0 +1,189 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/code_of_conduct.rst:4 +msgid "Правила сообщества" +msgstr "Community Guidelines" + +#: ../../root/code_of_conduct.rst:7 +msgid "Наше обязательство" +msgstr "Our Pledge" + +#: ../../root/code_of_conduct.rst:9 +msgid "" +"В целях создания открытой и гостеприимной атмосферы мы, как участники и " +"мейнтейнеры, обязуемся сделать участие в нашем проекте и сообществе " +"свободным от преследований для всех, независимо от возраста, " +"телосложения, инвалидности, этнической принадлежности, уровня опыта, " +"образования, социально-экономического статуса, национальности, внешности," +" расы или религии." +msgstr "" +"In the interest of fostering an open and welcoming environment, we as " +"contributors and maintainers pledge to make participation in our project and " +"community a harassment-free experience for everyone, regardless of age, body " +"size, disability, ethnicity, level of experience, education, socio-economic " +"status, nationality, personal appearance, race, or religion." + +#: ../../root/code_of_conduct.rst:14 +msgid "Наши стандарты" +msgstr "Our Standards" + +#: ../../root/code_of_conduct.rst:16 +msgid "Примеры поведения, которые способствуют созданию позитивной среды:" +msgstr "Examples of behavior that contributes to creating a positive environment:" + +#: ../../root/code_of_conduct.rst:18 +msgid "Проявление эмпатии и доброты по отношению к другим." +msgstr "Demonstrating empathy and kindness toward other people." + +#: ../../root/code_of_conduct.rst:19 +msgid "Уважение к различным мнениям, точкам зрения и опыту." +msgstr "Being respectful of differing opinions, viewpoints, and experiences." + +#: ../../root/code_of_conduct.rst:20 +msgid "Предоставление и тактичное принятие конструктивной обратной связи." +msgstr "Giving and gracefully accepting constructive feedback." + +#: ../../root/code_of_conduct.rst:21 +msgid "" +"Принятие ответственности и извинения перед теми, кого затронули наши " +"ошибки, а также извлечение уроков из этого опыта." +msgstr "" +"Accepting responsibility and apologizing to those affected by our mistakes, " +"and learning from the experience." + +#: ../../root/code_of_conduct.rst:22 +msgid "Фокус на том, что лучше для всего сообщества." +msgstr "Focusing on what is best for the overall community." + +#: ../../root/code_of_conduct.rst:24 +msgid "Примеры недопустимого поведения включают:" +msgstr "Examples of unacceptable behavior include:" + +#: ../../root/code_of_conduct.rst:26 +msgid "" +"Троллинг, оскорбительные или уничижительные комментарии, а также личные " +"или политические нападки." +msgstr "" +"Trolling, insulting or derogatory comments, and personal or political attacks." + +#: ../../root/code_of_conduct.rst:27 +msgid "Публичное или частное преследование." +msgstr "Public or private harassment." + +#: ../../root/code_of_conduct.rst:28 +msgid "" +"Публикация личной информации других лиц (например, физического или " +"электронного адреса) без их явного разрешения." +msgstr "" +"Publishing others' private information, such as a physical or email address, " +"without their explicit permission." + +#: ../../root/code_of_conduct.rst:29 +msgid "" +"Любое другое поведение, которое можно обоснованно считать неуместным в " +"профессиональной среде." +msgstr "" +"Other conduct which could reasonably be considered inappropriate in a " +"professional setting." + +#: ../../root/code_of_conduct.rst:34 +msgid "Наши обязанности" +msgstr "Our Responsibilities" + +#: ../../root/code_of_conduct.rst:36 +msgid "" +"Мейнтейнеры проекта несут ответственность за разъяснение и обеспечение " +"соблюдения стандартов приемлемого поведения и предпримут справедливые " +"корректирующие действия в ответ на любые случаи неприемлемого поведения." +msgstr "" +"Project maintainers are responsible for clarifying and enforcing standards of " +"acceptable behavior and will take appropriate and fair corrective action in " +"response to any instances of unacceptable behavior." + +#: ../../root/code_of_conduct.rst:38 +msgid "" +"Мейнтейнеры проекта имеют право и обязанность удалять, редактировать или " +"отклонять комментарии, коммиты, код, правки в вики, задачи и другие " +"вклады, которые не соответствуют настоящему Кодексу поведения, а также " +"временно или навсегда блокировать любого участника за поведение, которое " +"они сочтут неуместным, угрожающим, оскорбительным или вредным." +msgstr "" +"Project maintainers have the right and responsibility to remove, edit, or reject " +"comments, commits, code, wiki edits, issues, and other contributions that are not " +"aligned with this Code of Conduct, and will ban temporarily or permanently any " +"contributor for behaviors that they deem inappropriate, threatening, offensive, or harmful." + +#: ../../root/code_of_conduct.rst:43 +msgid "Сфера применения" +msgstr "Scope" + +#: ../../root/code_of_conduct.rst:45 +msgid "" +"Настоящий Кодекс поведения применяется как в рамках проекта, так и в " +"публичных пространствах, когда человек официально представляет " +"сообщество. Примеры такого представительства включают использование " +"официального адреса электронной почты, публикации через официальный " +"аккаунт в социальных сетях или выступление в качестве назначенного " +"представителя на онлайн- или офлайн-мероприятии." +msgstr "" +"This Code of Conduct applies both within project spaces and in public spaces " +"when an individual is officially representing the community. Examples of " +"representing the community include using an official project email address, " +"posting via an official social media account, or acting as an appointed " +"representative at an online or offline event." + +#: ../../root/code_of_conduct.rst:50 +msgid "Обеспечение соблюдения" +msgstr "Enforcement" + +#: ../../root/code_of_conduct.rst:52 +msgid "" +"О случаях оскорбительного, преследовательского или иного неприемлемого " +"поведения можно сообщить команде проекта по адресу " +"kolo.is.main@gmail.com. Все жалобы будут рассмотрены и расследованы " +"оперативно и справедливо." +msgstr "" +"Instances of abusive, harassing, or otherwise unacceptable behavior may be " +"reported to the project team at kolo.is.main@gmail.com. All complaints will be " +"reviewed and investigated promptly and fairly." + +#: ../../root/code_of_conduct.rst:54 +msgid "Команда проекта обязуется уважать частную жизнь и безопасность заявителя." +msgstr "The project team is obligated to respect the privacy and security of the reporter." + +#: ../../root/code_of_conduct.rst:59 +msgid "Атрибуция" +msgstr "Attribution" + +#: ../../root/code_of_conduct.rst:61 +msgid "" +"Настоящий Кодекс поведения адаптирован из `Contributor Covenant " +"`__, версии `1.4 `__ и `2.0 `__." +msgstr "" +"This Code of Conduct is adapted from the `Contributor Covenant " +"`__, version `1.4 `__ and `2.0 `__." + diff --git a/docs/locales/en/LC_MESSAGES/root/contributing.po b/docs/locales/en/LC_MESSAGES/root/contributing.po new file mode 100644 index 0000000..dcedf27 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/contributing.po @@ -0,0 +1,739 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-03 13:42+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/contributing.rst:4 +msgid "Вклад в проект" +msgstr "Contributing to the Project" + +#: ../../root/contributing.rst:8 +msgid "Прежде всего, спасибо, что уделили время для внесения своего вклада! ❤️" +msgstr "First of all, thank you for taking the time to contribute! ❤️" + +#: ../../root/contributing.rst:10 +msgid "" +"Мы приветствуем и ценим любой вклад. Пожалуйста, прочтите соответствующий" +" раздел, прежде чем начать. Это облегчит работу мейнтейнеров и сделает " +"процесс более гладким для всех. Сообщество с нетерпением ждёт ваших идей!" +" 🎉" +msgstr "" +"We welcome and appreciate any contribution. Please read the relevant " +"section before getting started. This will make it easier for maintainers " +"and make the process smoother for everyone. The community is looking " +"forward to your ideas! 🎉" + +#: ../../root/contributing.rst:14 +msgid "" +"Если вам нравится проект, но у вас нет времени на активный вклад, вы " +"можете поддержать нас другими способами:" +msgstr "" +"If you like the project but don't have time to actively contribute, you " +"can support us in other ways:" + +#: ../../root/contributing.rst:16 +msgid "Поставить звезду на GitHub." +msgstr "Star the project on GitHub." + +#: ../../root/contributing.rst:17 +msgid "Написать о проекте в Twitter или других социальных сетях." +msgstr "Write about the project on Twitter or other social media." + +#: ../../root/contributing.rst:18 +msgid "Сослаться на проект в `README` вашего репозитория." +msgstr "Reference the project in your repository's `README`." + +#: ../../root/contributing.rst:19 +msgid "Упомянуть проект на митапах и рассказать о нём друзьям и коллегам." +msgstr "" +"Mention the project at meetups and tell your friends and colleagues about" +" it." + +#: ../../root/contributing.rst:24 +msgid "Содержание" +msgstr "Contents" + +#: ../../root/contributing.rst:26 +msgid ":ref:`Кодекс поведения `" +msgstr ":ref:`Code of Conduct `" + +#: ../../root/contributing.rst:27 +msgid ":ref:`У меня есть вопрос `" +msgstr ":ref:`I Have a Question `" + +#: ../../root/contributing.rst:28 +msgid ":ref:`Я хочу внести вклад `" +msgstr ":ref:`I Want to Contribute `" + +#: ../../root/contributing.rst:29 +msgid ":ref:`Сообщение об ошибках `" +msgstr ":ref:`Reporting Bugs `" + +#: ../../root/contributing.rst:30 +msgid ":ref:`Предложение улучшений `" +msgstr ":ref:`Suggesting Enhancements `" + +#: ../../root/contributing.rst:31 +msgid ":ref:`Ваш первый вклад в код `" +msgstr ":ref:`Your First Code Contribution `" + +#: ../../root/contributing.rst:32 +msgid ":ref:`Улучшение документации `" +msgstr ":ref:`Improving Documentation `" + +#: ../../root/contributing.rst:33 +msgid ":ref:`Руководства по стилю `" +msgstr ":ref:`Style Guides `" + +#: ../../root/contributing.rst:34 +msgid ":ref:`Присоединяйтесь к команде проекта `" +msgstr ":ref:`Join the Project Team `" + +#: ../../root/contributing.rst:39 +msgid "Кодекс поведения" +msgstr "Code of Conduct" + +#: ../../root/contributing.rst:41 +msgid "" +"Этот проект и все его участники руководствуются :ref:`Кодексом поведения " +"Argenta `. Участвуя, вы обязуетесь соблюдать этот " +"кодекс. Пожалуйста, сообщайте о недопустимом поведении." +msgstr "" +"This project and all its participants are governed by the :ref:`Argenta " +"Code of Conduct `. By participating, you are " +"expected to uphold this code. Please report unacceptable behavior." + +#: ../../root/contributing.rst:49 +msgid "У меня есть вопрос" +msgstr "I Have a Question" + +#: ../../root/contributing.rst:53 +msgid "" +"Прежде чем задать вопрос, пожалуйста, ознакомьтесь с `документацией " +"`_." +msgstr "" +"Before asking a question, please check the `documentation " +"`_." + +#: ../../root/contributing.rst:55 +msgid "" +"Поищите ответ в существующих `Issues " +"`_. Если вы нашли похожий " +"вопрос, но всё ещё нуждаетесь в разъяснениях, можете написать в нём. " +"Также рекомендуем поискать ответ в интернете." +msgstr "" +"Search for an answer in existing `Issues " +"`_. If you found a similar " +"question but still need clarification, you can comment on it. We also " +"recommend searching the internet for an answer." + +#: ../../root/contributing.rst:57 +msgid "" +"Если ответа не нашлось, создайте новый `Issue " +"`_ и предоставьте как " +"можно больше контекста, включая версии проекта и платформы." +msgstr "" +"If you can't find an answer, create a new `Issue " +"`_ and provide as much " +"context as possible, including project and platform versions." + +#: ../../root/contributing.rst:59 +msgid "Мы займемся вашей задачей как можно скорее." +msgstr "We will address your issue as soon as possible." + +#: ../../root/contributing.rst:66 +msgid "Я хочу внести вклад" +msgstr "I Want to Contribute" + +#: ../../root/contributing.rst:69 +msgid "Правовое уведомление" +msgstr "Legal Notice" + +#: ../../root/contributing.rst:72 +msgid "" +"Внося вклад в этот проект, вы подтверждаете, что являетесь автором 100% " +"контента, обладаете необходимыми правами на него и соглашаетесь, что он " +"может распространяться под лицензией проекта." +msgstr "" +"By contributing to this project, you confirm that you are the author of " +"100% of the content, have the necessary rights to it, and agree that it " +"may be distributed under the project's license." + +#: ../../root/contributing.rst:77 +msgid "Сообщение об ошибках" +msgstr "Reporting Bugs" + +#: ../../root/contributing.rst:80 +msgid "Перед отправкой отчета об ошибке" +msgstr "Before Submitting a Bug Report" + +#: ../../root/contributing.rst:81 +msgid "" +"Хороший отчёт об ошибке не должен заставлять других вытягивать из вас " +"дополнительную информацию. Пожалуйста, тщательно всё изучите, соберите " +"информацию и подробно опишите проблему. Это поможет нам исправить её как " +"можно быстрее." +msgstr "" +"A good bug report shouldn't require others to extract additional " +"information from you. Please investigate thoroughly, gather information, " +"and describe the problem in detail. This will help us fix it as quickly " +"as possible." + +#: ../../root/contributing.rst:83 ../../root/contributing.rst:124 +msgid "Убедитесь, что вы используете последнюю версию." +msgstr "Make sure you are using the latest version." + +#: ../../root/contributing.rst:84 +msgid "" +"Убедитесь, что проблема действительно является ошибкой, а не вызвана, " +"например, использованием несовместимых версий окружения. Прочтите " +"`документацию `_ и, если нужна поддержка," +" загляните в раздел :ref:`У меня есть вопрос `." +msgstr "" +"Make sure the issue is actually a bug and not caused by, for example, " +"using incompatible environment versions. Read the `documentation " +"`_ and, if you need support, check out " +"the :ref:`I Have a Question ` section." + +#: ../../root/contributing.rst:85 +msgid "" +"Проверьте, нет ли уже отчёта о вашей ошибке в `трекере " +"`_." +msgstr "" +"Check if there is already a report about your bug in the `tracker " +"`_." + +#: ../../root/contributing.rst:86 +msgid "" +"Также поищите в интернете (включая `Stack Overflow`), чтобы узнать, " +"обсуждалась ли проблема за пределами `GitHub`." +msgstr "" +"Also search the internet (including `Stack Overflow`) to see if the issue" +" has been discussed outside of `GitHub`." + +#: ../../root/contributing.rst:87 +msgid "Соберите информацию об ошибке:" +msgstr "Collect information about the bug:" + +#: ../../root/contributing.rst:88 +msgid "Трассировка стека." +msgstr "Stack trace." + +#: ../../root/contributing.rst:89 +msgid "ОС, платформа и версия (Windows, Linux, macOS, x86, ARM)." +msgstr "OS, platform, and version (Windows, Linux, macOS, x86, ARM)." + +#: ../../root/contributing.rst:90 +msgid "" +"Версия интерпретатора, компилятора, SDK, среды выполнения, менеджера " +"пакетов и т.д." +msgstr "" +"Version of interpreter, compiler, SDK, runtime environment, package " +"manager, etc." + +#: ../../root/contributing.rst:91 +msgid "Входные данные и полученный результат." +msgstr "Input data and output received." + +#: ../../root/contributing.rst:92 +msgid "" +"Можете ли вы надёжно воспроизвести проблему? Воспроизводится ли она на " +"старых версиях?" +msgstr "Can you reliably reproduce the issue? Does it reproduce on older versions?" + +#: ../../root/contributing.rst:95 +msgid "Как мне отправить хороший отчет об ошибке?" +msgstr "How Do I Submit a Good Bug Report?" + +#: ../../root/contributing.rst:98 +msgid "" +"Никогда не сообщайте о проблемах безопасности, уязвимостях или ошибках с " +"конфиденциальной информацией в публичном трекере. Для этого используйте " +"электронную почту." +msgstr "" +"Never report security issues, vulnerabilities, or bugs with sensitive " +"information in the public tracker. Use email for this purpose." + +#: ../../root/contributing.rst:100 +msgid "" +"Мы используем `GitHub Issues` для отслеживания ошибок. Если вы " +"столкнулись с проблемой:" +msgstr "We use `GitHub Issues` to track bugs. If you encounter a problem:" + +#: ../../root/contributing.rst:102 +msgid "" +"Откройте новый `Issue " +"`_. На этом этапе не " +"нужно присваивать ему метки." +msgstr "" +"Open a new `Issue `_. At" +" this stage, you don't need to assign labels to it." + +#: ../../root/contributing.rst:103 +msgid "Объясните ожидаемое и фактическое поведение." +msgstr "Explain the expected and actual behavior." + +#: ../../root/contributing.rst:104 +msgid "" +"Предоставьте как можно больше контекста и опишите **шаги для " +"воспроизведения**, чтобы проблему можно было воссоздать. Лучше всего " +"изолировать её и создать минимальный тестовый пример." +msgstr "" +"Provide as much context as possible and describe **reproduction steps** " +"so the issue can be recreated. It's best to isolate it and create a " +"minimal test case." + +#: ../../root/contributing.rst:105 +msgid "Предоставьте информацию, которую вы собрали в предыдущем разделе." +msgstr "Provide the information you collected in the previous section." + +#: ../../root/contributing.rst:107 +msgid "После того, как задача будет создана:" +msgstr "Once the issue is created:" + +#: ../../root/contributing.rst:109 +msgid "Команда проекта присвоит задаче соответствующую метку." +msgstr "The project team will assign an appropriate label to the issue." + +#: ../../root/contributing.rst:110 +msgid "" +"Член команды попытается воспроизвести проблему. Если шагов нет или они не" +" приводят к результату, команда попросит вас предоставить их и пометит " +"задачу как `needs-repro`. Такие задачи не будут рассматриваться до тех " +"пор, пока проблема не будет воспроизведена." +msgstr "" +"A team member will try to reproduce the issue. If there are no steps or " +"they don't lead to the result, the team will ask you to provide them and " +"mark the issue as `needs-repro`. Such issues will not be addressed until " +"the problem is reproduced." + +#: ../../root/contributing.rst:111 +msgid "" +"Если проблема будет воспроизведена, она будет помечена как `needs-fix` " +"(и, возможно, другими метками, например `critical`), после чего её сможет" +" взять в работу :ref:`любой желающий `." +msgstr "" +"If the issue is reproduced, it will be marked as `needs-fix` (and " +"possibly with other labels, such as `critical`), after which :ref:`anyone" +" willing ` can take it on." + +#: ../../root/contributing.rst:118 +msgid "Предложение улучшений" +msgstr "Suggesting Enhancements" + +#: ../../root/contributing.rst:120 +msgid "" +"Этот раздел поможет вам отправить предложение по улучшению `Argenta`, " +"**включая как новые функции, так и незначительные улучшения**. Следование" +" этим рекомендациям поможет мейнтейнерам и сообществу лучше понять вашу " +"идею." +msgstr "" +"This section will help you submit an enhancement suggestion for " +"`Argenta`, **including both new features and minor improvements**. " +"Following these guidelines will help maintainers and the community better" +" understand your idea." + +#: ../../root/contributing.rst:123 +msgid "Перед отправкой предложения по улучшению" +msgstr "Before Submitting an Enhancement Suggestion" + +#: ../../root/contributing.rst:125 +msgid "" +"Внимательно прочтите `документацию `_ и " +"убедитесь, что предлагаемая функциональность ещё не реализована " +"(возможно, через конфигурацию)." +msgstr "" +"Carefully read the `documentation `_ and " +"make sure the proposed functionality is not already implemented (perhaps " +"through configuration)." + +#: ../../root/contributing.rst:126 +msgid "" +"Выполните `поиск `_, чтобы " +"проверить, не предлагалось ли это улучшение ранее. Если да, добавьте " +"комментарий к существующей задаче." +msgstr "" +"Perform a `search `_ to " +"check if this enhancement has been suggested before. If so, add a comment" +" to the existing issue." + +#: ../../root/contributing.rst:127 +msgid "" +"Определите, соответствует ли ваша идея масштабу и целям проекта. Вам " +"предстоит убедительно доказать её пользу. Мы хотим видеть функции, " +"которые будут полезны большинству пользователей. Если ваша идея " +"ориентирована на узкий круг, рассмотрите возможность создания плагина." +msgstr "" +"Determine if your idea fits the scope and goals of the project. You will " +"need to convincingly demonstrate its value. We want to see features that " +"will be useful to most users. If your idea targets a narrow audience, " +"consider creating a plugin." + +#: ../../root/contributing.rst:130 +msgid "Как мне отправить хорошее предложение по улучшению?" +msgstr "How Do I Submit a Good Enhancement Suggestion?" + +#: ../../root/contributing.rst:131 +msgid "" +"Предложения по улучшению отслеживаются в `GitHub Issues " +"`_." +msgstr "" +"Enhancement suggestions are tracked in `GitHub Issues " +"`_." + +#: ../../root/contributing.rst:133 +msgid "" +"Используйте **чёткий и описательный заголовок**, чтобы идентифицировать " +"предложение." +msgstr "Use a **clear and descriptive title** to identify the suggestion." + +#: ../../root/contributing.rst:134 +msgid "Предоставьте **пошаговое и подробное описание** предлагаемого улучшения." +msgstr "" +"Provide a **step-by-step and detailed description** of the proposed " +"enhancement." + +#: ../../root/contributing.rst:135 +msgid "" +"**Опишите текущее поведение** и **объясните, какое вы ожидали увидеть " +"вместо этого** и почему. Здесь же можно указать, какие альтернативы вам " +"не подходят." +msgstr "" +"**Describe the current behavior** and **explain what you expected to see " +"instead** and why. You can also mention which alternatives don't work for" +" you." + +#: ../../root/contributing.rst:136 +msgid "" +"**Приложите скриншоты или видео**, которые помогут продемонстрировать " +"шаги или указать на часть, к которой относится предложение." +msgstr "" +"**Attach screenshots or videos** that help demonstrate the steps or point" +" to the part the suggestion relates to." + +#: ../../root/contributing.rst:137 +msgid "" +"**Объясните, почему это улучшение будет полезно** большинству " +"пользователей `Argenta`. Вы также можете указать на другие проекты, " +"которые решили эту проблему и могут послужить источником вдохновения." +msgstr "" +"**Explain why this enhancement would be useful** to most `Argenta` users." +" You can also point to other projects that have solved this problem and " +"could serve as inspiration." + +#: ../../root/contributing.rst:144 +msgid "Ваш первый вклад в код" +msgstr "Your First Code Contribution" + +#: ../../root/contributing.rst:146 +msgid "" +"Не знаете, с чего начать? Посмотрите на задачи с метками `good first " +"issue` и `help wanted` в нашем репозитории на `GitHub`. Они хорошо " +"подходят для новичков." +msgstr "" +"Don't know where to start? Look at issues labeled `good first issue` and " +"`help wanted` in our `GitHub` repository. They are well-suited for " +"beginners." + +#: ../../root/contributing.rst:148 +msgid "" +"Чтобы начать, настройте локальное окружение для разработки, следуя этим " +"шагам." +msgstr "" +"To get started, set up a local development environment by following these" +" steps." + +#: ../../root/contributing.rst:150 +msgid "Сделайте форк репозитория ``Argenta`` на ``GitHub``." +msgstr "Fork the ``Argenta`` repository on ``GitHub``." + +#: ../../root/contributing.rst:151 +msgid "Клонируйте ваш форк на локальную машину:" +msgstr "Clone your fork to your local machine:" + +#: ../../root/contributing.rst:158 +msgid "Создайте и активируйте виртуальное окружение." +msgstr "Create and activate a virtual environment." + +#: ../../root/contributing.rst:170 +msgid "Установите зависимости проекта, включая инструменты для разработки." +msgstr "Install project dependencies, including development tools." + +#: ../../root/contributing.rst:176 +msgid "" +"Создайте новую ветку для вашей функции или исправления. Используйте " +"описательное имя, например `fix/login-bug` или `feat/new-widget`." +msgstr "" +"Create a new branch for your feature or fix. Use a descriptive name, such" +" as `fix/login-bug` or `feat/new-widget`." + +#: ../../root/contributing.rst:182 +msgid "" +"Внесите свои изменения. Напишите код и не забудьте добавить или обновить " +"тесты." +msgstr "Make your changes. Write code and don't forget to add or update tests." + +#: ../../root/contributing.rst:183 +msgid "Запустите тесты, чтобы убедиться, что все работает корректно." +msgstr "Run tests to make sure everything works correctly." + +#: ../../root/contributing.rst:189 +msgid "" +"Сделайте коммит, следуя :ref:`нашему руководству по стилю `, " +"и отправьте изменения в ваш форк." +msgstr "" +"Commit following :ref:`our style guide ` and push the changes to your fork." + +#: ../../root/contributing.rst:197 +msgid "" +"Откройте `Pull Request` из вашей ветки в ветку `main` официального " +"репозитория. Предоставьте чёткое описание проблемы и вашего решения. " +"Укажите номер связанной задачи, если она есть." +msgstr "" +"Open a `Pull Request` from your branch to the `main` branch of the " +"official repository. Provide a clear description of the problem and your " +"solution. Include the related issue number if there is one." + +#: ../../root/contributing.rst:204 +msgid "Улучшение документации" +msgstr "Improving Documentation" + +#: ../../root/contributing.rst:206 +msgid "" +"Хорошая документация крайне важна. Мы используем `Sphinx` для её " +"генерации из исходных файлов в директории `docs/`. Мы приветствуем любые " +"улучшения: от исправления опечатки до написания нового раздела." +msgstr "" +"Good documentation is crucial. We use `Sphinx` to generate it from source" +" files in the `docs/` directory. We welcome any improvements: from fixing" +" a typo to writing a new section." + +#: ../../root/contributing.rst:210 +msgid "Мы поддерживаем документацию на двух языках: русском и английском." +msgstr "We maintain documentation in two languages: Russian and English." + +#: ../../root/contributing.rst:214 +msgid "" +"Для инкапсуляции различных команд, необходимых для настройки и запуска " +"проекта мы используем ``just``, он же фигурирует в различных примерах в " +"документации, поэтому рекомендуем вам `установить его " +"`_" +msgstr "" +"To encapsulate various commands needed for setting up and running the " +"project, we use ``just``, which also appears in various examples in the " +"documentation, so we recommend you `install it " +"`_" + +#: ../../root/contributing.rst:216 +msgid "" +"Для улучшения документации вы можете следовать процессу, похожему на " +"внесение вклада в код:" +msgstr "" +"To improve documentation, you can follow a process similar to " +"contributing code:" + +#: ../../root/contributing.rst:218 +msgid "" +"Убедитесь, что ваше окружение для разработки настроено, как описано в " +"разделе :ref:`Ваш первый вклад в код `." +msgstr "" +"Make sure your development environment is set up as described in the " +":ref:`Your First Code Contribution ` section." + +#: ../../root/contributing.rst:219 +msgid "Перейдите в директорию с документацией." +msgstr "Navigate to the documentation directory." + +#: ../../root/contributing.rst:225 +msgid "" +"Внесите изменения в **русскую** версию документации (`docs/index.rst` " +"и/или `docs/root/*`)." +msgstr "" +"Make changes to the **Russian** version of the documentation " +"(`docs/index.rst` and/or `docs/root/*`)." + +#: ../../root/contributing.rst:226 +msgid "" +"Чтобы собрать документацию локально в режиме автоматического ребилда и " +"увидеть изменения, выполните:" +msgstr "" +"To build the documentation locally in auto-rebuild mode and see the " +"changes, run:" + +#: ../../root/contributing.rst:232 +msgid "" +"Откройте `127.0.0.1:8000` в браузере, чтобы просмотреть сгенерированную " +"документацию." +msgstr "Open `127.0.0.1:8000` in your browser to view the generated documentation." + +#: ../../root/contributing.rst:233 +msgid "" +"После завершения работы над русской версией необходимо создать английский" +" перевод:" +msgstr "" +"After completing work on the Russian version, you need to create an " +"English translation:" + +#: ../../root/contributing.rst:239 +msgid "" +"После обновления шаблона обновите файлы перевода, расположенные в " +"`docs/locales/en/LC_MESSAGES/`." +msgstr "" +"After updating the template, update the translation files located in " +"`docs/locales/en/LC_MESSAGES/`." + +#: ../../root/contributing.rst:240 +msgid "" +"Когда изменения будут готовы, сделайте коммит и откройте `Pull Request`. " +"Используйте префикс `docs:` в сообщении коммита." +msgstr "" +"When the changes are ready, commit and open a `Pull Request`. Use the " +"`docs:` prefix in the commit message." + +#: ../../root/contributing.rst:247 +msgid "Руководства по стилю" +msgstr "Style Guides" + +#: ../../root/contributing.rst:251 +msgid "**Сообщения коммитов**" +msgstr "**Commit Messages**" + +#: ../../root/contributing.rst:253 +msgid "" +"Мы следуем спецификации `Conventional Commits " +"`_. Это делает историю " +"проекта более читаемой и позволяет автоматически генерировать журнал " +"изменений." +msgstr "" +"We follow the `Conventional Commits " +"`_ specification. This " +"makes the project history more readable and allows automatic changelog " +"generation." + +#: ../../root/contributing.rst:255 +msgid "" +"Каждое сообщение коммита состоит из **заголовка**, **тела** и **нижнего " +"колонтитула**." +msgstr "Each commit message consists of a **header**, **body**, and **footer**." + +#: ../../root/contributing.rst:265 +msgid "``<тип>`` должен быть одним из следующих:" +msgstr "```` must be one of the following:" + +#: ../../root/contributing.rst:267 +msgid "**feat**: Новая функция для пользователя." +msgstr "**feat**: A new feature for the user." + +#: ../../root/contributing.rst:268 +msgid "**fix**: Исправление ошибки для пользователя." +msgstr "**fix**: A bug fix for the user." + +#: ../../root/contributing.rst:269 +msgid "**docs**: Только изменения в документации." +msgstr "**docs**: Documentation changes only." + +#: ../../root/contributing.rst:270 +msgid "" +"**style**: Изменения, не влияющие на смысл кода (пробелы, форматирование " +"и т.д.)." +msgstr "" +"**style**: Changes that don't affect the meaning of the code (whitespace," +" formatting, etc.)." + +#: ../../root/contributing.rst:271 +msgid "" +"**refactor**: Изменение кода, которое не исправляет ошибку и не добавляет" +" новую функцию." +msgstr "**refactor**: A code change that neither fixes a bug nor adds a feature." + +#: ../../root/contributing.rst:272 +msgid "**perf**: Изменение кода, улучшающее производительность." +msgstr "**perf**: A code change that improves performance." + +#: ../../root/contributing.rst:273 +msgid "**test**: Добавление недостающих тестов или исправление существующих." +msgstr "**test**: Adding missing tests or correcting existing tests." + +#: ../../root/contributing.rst:274 +msgid "" +"**chore**: Изменения в процессе сборки или вспомогательных инструментах и" +" библиотеках." +msgstr "**chore**: Changes to the build process or auxiliary tools and libraries." + +#: ../../root/contributing.rst:277 +msgid "Примеры" +msgstr "Examples" + +#: ../../root/contributing.rst:278 +msgid "Простое исправление: ``fix: correct typo in user authentication flow``" +msgstr "Simple fix: ``fix: correct typo in user authentication flow``" + +#: ../../root/contributing.rst:281 +msgid "" +"Новая функция с областью видимости: ``feat(api): add new endpoint for " +"user profiles``" +msgstr "New feature with scope: ``feat(api): add new endpoint for user profiles``" + +#: ../../root/contributing.rst:289 +msgid "Присоединяйтесь к команде проекта" +msgstr "Join the Project Team" + +#: ../../root/contributing.rst:291 +msgid "" +"Мы всегда ищем энтузиастов для присоединения к команде. Если вы являетесь" +" постоянным участником и продемонстрировали глубокое понимание целей и " +"архитектуры проекта, вы можете стать хорошим кандидатом на роль " +"мейнтейнера." +msgstr "" +"We are always looking for enthusiasts to join the team. If you are a " +"regular contributor and have demonstrated a deep understanding of the " +"project's goals and architecture, you may be a good candidate for a " +"maintainer role." + +#: ../../root/contributing.rst:293 +msgid "" +"Активные члены сообщества могут стать членами команды. Обычно это " +"включает:" +msgstr "Active community members can become team members. This typically includes:" + +#: ../../root/contributing.rst:295 +msgid "Постоянный вклад в виде качественного кода и документации." +msgstr "Consistent contributions of quality code and documentation." + +#: ../../root/contributing.rst:296 +msgid "Помощь другим пользователям с их вопросами и проблемами." +msgstr "Helping other users with their questions and issues." + +#: ../../root/contributing.rst:297 +msgid "" +"Проверку `Pull Request`'ов от других участников с конструктивной обратной" +" связью." +msgstr "" +"Reviewing `Pull Requests` from other contributors with constructive " +"feedback." + +#: ../../root/contributing.rst:299 +msgid "" +"Если вы заинтересованы в том, чтобы стать постоянным членом команды, " +"лучший способ — быть активным и полезным участником сообщества. " +"Существующие мейнтейнеры заметят ваши усилия и могут связаться с вами." +msgstr "" +"If you are interested in becoming a permanent team member, the best way " +"is to be an active and helpful community contributor. Existing " +"maintainers will notice your efforts and may reach out to you." + diff --git a/docs/locales/en/LC_MESSAGES/root/dependency_injection.po b/docs/locales/en/LC_MESSAGES/root/dependency_injection.po new file mode 100644 index 0000000..b4d124d --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/dependency_injection.po @@ -0,0 +1,175 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/dependency_injection.rst:4 +msgid "Внедрение зависимостей" +msgstr "Dependency Injection" + +#: ../../root/dependency_injection.rst:6 +msgid "" +"Внедрение зависимостей (Dependency Injection, DI) — это паттерн " +"проектирования, который помогает писать слабосвязанный, легко тестируемый" +" и расширяемый код. Вместо того чтобы обработчики сами создавали нужные " +"им объекты (зависимости), они получают их извне." +msgstr "" +"Dependency Injection (DI) is a design pattern that helps write loosely " +"coupled, easily testable, and extensible code. Instead of handlers " +"creating the objects (dependencies) they need themselves, they receive " +"them from outside." + +#: ../../root/dependency_injection.rst:8 +msgid "" +"``Argenta`` использует библиотеку ``dishka`` для реализации DI, что " +"позволяет декларативно объявлять зависимости прямо в сигнатурах ваших " +"обработчиков. Подробнее о DI, IoC и API для создания провайдеров можно " +"прочитать в `официальной документации dishka " +"`_." +msgstr "" +"``Argenta`` uses the ``dishka`` library to implement DI, which allows you" +" to declaratively declare dependencies directly in your handler " +"signatures. You can read more about DI, IoC, and the API for " +"creating providers in the `official dishka documentation " +"`_." + +#: ../../root/dependency_injection.rst:14 +msgid "Основная идея" +msgstr "Main Idea" + +#: ../../root/dependency_injection.rst:16 +msgid "" +"Представьте, что вашему обработчику для работы нужен доступ к базе " +"данных. Вместо импорта и инициализации соединения внутри функции, вы " +"просто объявляете его как аргумент с аннотацией типа:" +msgstr "" +"Imagine your handler needs access to a database to work. Instead of " +"importing and initializing the connection inside the function, you simply" +" declare it as an argument with a type annotation:" + +#: ../../root/dependency_injection.rst:19 +msgid "" +"``argenta.di.FromDishka`` является алиасом для ``dishka.FromDishka``, и " +"они полностью взаимозаменяемы." +msgstr "" +"``argenta.di.FromDishka`` is an alias for ``dishka.FromDishka``, and they" +" are fully interchangeable." + +#: ../../root/dependency_injection.rst:21 +#: ../../root/dependency_injection.rst:29 +#: ../../root/dependency_injection.rst:40 +#: ../../root/dependency_injection.rst:63 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/dependency_injection.rst:27 +msgid "" +"``Argenta`` с помощью ``dishka`` разрешит зависимость по типу " +"``Connection`` и внедрит её. Но прежде чем использовать зависимость, её " +"необходимо объявить в провайдере:" +msgstr "" +"``Argenta`` with ``dishka`` will resolve the dependency by type " +"``Connection`` and inject it. But before using the dependency, it must be" +" declared in a provider:" + +#: ../../root/dependency_injection.rst:35 +msgid "После создания провайдера его необходимо зарегистрировать в оркестраторе." +msgstr "After creating the provider, it must be registered in the orchestrator." + +#: ../../root/dependency_injection.rst:38 +msgid "" +"Провайдеры регистрируются в ``Orchestrator``, а не в ``App``, так как " +"оркестратор отвечает за настройку DI-контейнера на уровне всего " +"приложения. Вы можете передать список из нескольких провайдеров через " +"параметр ``custom_providers``." +msgstr "" +"Providers are registered in ``Orchestrator``, not in ``App``, because " +"the orchestrator is responsible for configuring the DI container at the " +"application level. You can pass a list of multiple providers through the " +"``custom_providers`` parameter." + +#: ../../root/dependency_injection.rst:49 +msgid "Как это работает?" +msgstr "How Does It Work?" + +#: ../../root/dependency_injection.rst:51 +msgid "В основе DI в Argenta лежат **провайдеры** и **контейнер**." +msgstr "At the core of DI in Argenta are **providers** and a **container**." + +#: ../../root/dependency_injection.rst:53 +msgid "" +"**Провайдер (Provider)** — это \"рецепт\", который объясняет, как " +"создавать и настраивать ту или иную зависимость (например, подключение к " +"БД, API-клиент или любой другой сервис)." +msgstr "" +"**Provider (Provider)** is a \"recipe\" that explains how to create and " +"configure a particular dependency (for example, a database connection, " +"API client, or any other service)." + +#: ../../root/dependency_injection.rst:54 +msgid "" +"**Контейнер (IoC Container)** — это \"фабрика\", которая хранит все " +"рецепты (провайдеры) и по запросу создаёт и выдаёт готовые зависимости." +msgstr "" +"**Container (IoC Container)** is a \"factory\" that stores all recipes " +"(providers) and creates and provides ready dependencies on request." + +#: ../../root/dependency_injection.rst:59 +msgid "Встроенные провайдеры" +msgstr "Built-in Providers" + +#: ../../root/dependency_injection.rst:61 +msgid "" +"``Argenta`` поставляется со встроенным провайдером, который даёт доступ к" +" важным системным зависимостям без дополнительной настройки. Например, вы" +" можете получить объект :ref:`ArgSpace `," +" который содержит аргументы командной строки, переданные при запуске " +"приложения." +msgstr "" +"``Argenta`` comes with a built-in provider that gives access to important" +" system dependencies without additional configuration. For example, you " +"can get the :ref:`ArgSpace ` object, " +"which contains the command-line arguments passed when the application was " +"launched." + +#: ../../root/dependency_injection.rst:72 +msgid "Обмен данными между обработчиками" +msgstr "Data Exchange Between Handlers" + +#: ../../root/dependency_injection.rst:74 +msgid "" +"Помимо DI, обработчики могут обмениваться данными в рамках сессии через " +"**объект контекста**. В ``Argenta`` эту роль выполняет объект " +"``DataBridge``." +msgstr "" +"In addition to DI, handlers can exchange data within a session through a " +"**context object**. In ``Argenta``, this role is performed by the " +"``DataBridge`` object." + +#: ../../root/dependency_injection.rst:76 +msgid "" +"Каждый обработчик может записывать в него данные, а также читать, " +"обновлять и удалять их." +msgstr "" +"Each handler can write data to it, as well as read, update, and delete " +"data." + +#: ../../root/dependency_injection.rst:79 +msgid "Подробнее об этом можно прочитать в разделе :ref:`root_api_bridge`." +msgstr "You can read more about this in the :ref:`root_api_bridge` section." + diff --git a/docs/locales/en/LC_MESSAGES/root/error_handling.po b/docs/locales/en/LC_MESSAGES/root/error_handling.po new file mode 100644 index 0000000..9e95b4a --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/error_handling.po @@ -0,0 +1,195 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-02 22:27+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/error_handling.rst:4 +msgid "Обработка ошибок" +msgstr "Error Handling" + +#: ../../root/error_handling.rst:6 +msgid "" +"``Argenta`` выбрасывает исключения в пограничных случаях, связанных с " +"пользовательским вводом. По умолчанию они обрабатываются системными " +"обработчиками, но вы можете их переопределить. Это делается с помощью " +"сеттеров экземпляра ``App`` вида ``.set_*_handler()``. Подробнее о каждом" +" из них рассказано :ref:`ниже `." +msgstr "" +"``Argenta`` throws exceptions in edge cases related to user input. By default, " +"they are handled by system handlers, but you can override them. This is done using " +"``App`` instance setters of the form ``.set_*_handler()``. More details about each " +"of them are described :ref:`below `." + +#: ../../root/error_handling.rst:10 +msgid "" +"Ни одно исключение не остаётся необработанным, так как для каждого случая" +" предусмотрен стандартный обработчик. Поэтому переопределение является " +"опциональным." +msgstr "" +"No exception goes unhandled, as a default handler is provided for each case. " +"Therefore, overriding is optional." + +#: ../../root/error_handling.rst:12 ../../root/error_handling.rst:37 +#: ../../root/error_handling.rst:61 ../../root/error_handling.rst:82 +#: ../../root/error_handling.rst:105 ../../root/error_handling.rst:126 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/error_handling.rst:22 +msgid "Возможные исключения и нестандартное поведение" +msgstr "Possible Exceptions and Non-Standard Behavior" + +#: ../../root/error_handling.rst:25 +msgid "``UnprocessedInputFlagException``: Некорректный синтаксис флагов" +msgstr "``UnprocessedInputFlagException``: Incorrect Flag Syntax" + +#: ../../root/error_handling.rst:27 +msgid "" +"Это исключение выбрасывается, когда парсер не может обработать команду " +"из-за некорректного синтаксиса. Чаще всего это связано с ошибкой в " +"синтаксисе флагов. Подробнее о них можно прочитать в разделе :ref:`Flags " +"`." +msgstr "" +"This exception is thrown when the parser cannot process a command due to incorrect " +"syntax. Most often this is related to an error in flag syntax. You can read more " +"about them in the :ref:`Flags ` section." + +#: ../../root/error_handling.rst:29 ../../root/error_handling.rst:53 +#: ../../root/error_handling.rst:74 ../../root/error_handling.rst:97 +msgid "Стандартный обработчик выводит в консоль:" +msgstr "The default handler outputs to the console:" + +#: ../../root/error_handling.rst:35 +msgid "" +"Для переопределения используется сеттер " +"``.set_incorrect_input_syntax_handler()``. Он принимает на вход " +"обработчик с сигнатурой ``Callable[[str], None]``, где единственный " +"аргумент — это строка с необработанной командой." +msgstr "" +"To override, use the ``.set_incorrect_input_syntax_handler()`` setter. It accepts " +"a handler with the signature ``Callable[[str], None]``, where the only argument is " +"a string with the unprocessed command." + +#: ../../root/error_handling.rst:46 +msgid "``RepeatedInputFlagsException``: Повторяющиеся флаги в команде" +msgstr "``RepeatedInputFlagsException``: Repeated Flags in Command" + +#: ../../root/error_handling.rst:48 +msgid "" +"Исключение выбрасывается, если пользователь ввёл команду с повторяющимися" +" флагами. Два флага (:ref:`InputFlag `) " +"считаются одинаковыми, если у них совпадают имена. Подробнее о флагах и " +"их синтаксисе — в разделе :ref:`Flags `." +msgstr "" +"The exception is thrown if the user entered a command with repeated flags. Two " +"flags (:ref:`InputFlag `) are considered the same if " +"their names match. More about flags and their syntax in the :ref:`Flags " +"` section." + +#: ../../root/error_handling.rst:51 +msgid "" +"Сравнение на равенство у регистрируемых флагов (``Flag``) происходит " +"иначе, подробнее в :ref:`Flag `." +msgstr "" +"Equality comparison for registered flags (``Flag``) works differently, see " +":ref:`Flag ` for details." + +#: ../../root/error_handling.rst:59 +msgid "" +"Для переопределения используется сеттер " +"``.set_repeated_input_flags_handler()``. Он принимает на вход обработчик " +"с сигнатурой ``Callable[[str], None]``, где единственный аргумент — это " +"строка с необработанной командой." +msgstr "" +"To override, use the ``.set_repeated_input_flags_handler()`` setter. It accepts a " +"handler with the signature ``Callable[[str], None]``, where the only argument is a " +"string with the unprocessed command." + +#: ../../root/error_handling.rst:70 +msgid "``EmptyInputCommandException``: Введена пустая команда" +msgstr "``EmptyInputCommandException``: Empty Command Entered" + +#: ../../root/error_handling.rst:72 +msgid "" +"Исключение выбрасывается, если пользователь ввёл пустую строку или " +"строку, состоящую только из пробельных символов (``\\n``, ``\\t``, пробел" +" и т.д.)." +msgstr "" +"The exception is thrown if the user entered an empty string or a string consisting " +"only of whitespace characters (``\\n``, ``\\t``, space, etc.)." + +#: ../../root/error_handling.rst:80 +msgid "" +"Для переопределения используется сеттер ``.set_empty_command_handler()``." +" Он принимает на вход обработчик с сигнатурой ``Callable[[], None]`` (без" +" аргументов)." +msgstr "" +"To override, use the ``.set_empty_command_handler()`` setter. It accepts a handler " +"with the signature ``Callable[[], None]`` (no arguments)." + +#: ../../root/error_handling.rst:93 +msgid "Обработка неизвестной команды" +msgstr "Handling Unknown Commands" + +#: ../../root/error_handling.rst:95 +msgid "" +"Это поведение активируется, когда пользователь вводит команду, которая не" +" зарегистрирована ни в одном из роутеров и не является псевдонимом " +"(alias) для существующей команды." +msgstr "" +"This behavior is triggered when the user enters a command that is not registered " +"in any of the routers and is not an alias for an existing command." + +#: ../../root/error_handling.rst:103 +msgid "" +"Для переопределения используется сеттер " +"``.set_unknown_command_handler()``. Он принимает на вход обработчик с " +"сигнатурой ``Callable[[InputCommand], None]``, где аргумент — объект " +":ref:`InputCommand `." +msgstr "" +"To override, use the ``.set_unknown_command_handler()`` setter. It accepts a " +"handler with the signature ``Callable[[InputCommand], None]``, where the argument " +"is an :ref:`InputCommand ` object." + +#: ../../root/error_handling.rst:114 +msgid "Выход из приложения" +msgstr "Exiting the Application" + +#: ../../root/error_handling.rst:116 +msgid "" +"Это поведение активируется, когда пользователь вводит команду, помеченную" +" как команда выхода." +msgstr "" +"This behavior is triggered when the user enters a command marked as an exit command." + +#: ../../root/error_handling.rst:118 +msgid "" +"Стандартный обработчик выводит в консоль текст и завершает работу " +"приложения:" +msgstr "The default handler outputs text to the console and terminates the application:" + +#: ../../root/error_handling.rst:124 +msgid "" +"Для переопределения используется сеттер ``.set_exit_command_handler()``. " +"Он принимает на вход обработчик с сигнатурой ``Callable[[Response], " +"None]``, где аргумент — объект :ref:`Response `." +msgstr "" +"To override, use the ``.set_exit_command_handler()`` setter. It accepts a handler " +"with the signature ``Callable[[Response], None]``, where the argument is a " +":ref:`Response ` object." + diff --git a/docs/locales/en/LC_MESSAGES/root/flags.po b/docs/locales/en/LC_MESSAGES/root/flags.po new file mode 100644 index 0000000..cc51eb2 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/flags.po @@ -0,0 +1,440 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/flags.rst:4 +msgid "Флаги вводимых команд" +msgstr "Input Command Flags" + +#: ../../root/flags.rst:6 +msgid "" +"Флаги — это специальные параметры, которые пользователь может добавлять к" +" командам для управления их поведением." +msgstr "" +"Flags are special parameters that users can add to commands to control " +"their behavior." + +#: ../../root/flags.rst:9 +msgid "Зачем нужны флаги в командах" +msgstr "Why Flags Are Needed in Commands" + +#: ../../root/flags.rst:12 +msgid "Управление поведением команды" +msgstr "Controlling Command Behavior" + +#: ../../root/flags.rst:14 +msgid "" +"Основная цель флагов — предоставить способ изменить логику работы команды" +" без её переработки. Команда может работать в нескольких режимах: " +"стандартном, подробном, отладочном или упрощённом. Флаги переключают эти " +"режимы по требованию пользователя, оставляя основную функциональность " +"неизменной." +msgstr "" +"The main purpose of flags is to provide a way to change the command's " +"logic without reworking it. A command can operate in several modes: " +"standard, verbose, debug, or simplified. Flags switch these modes on user" +" demand, keeping the core functionality unchanged." + +#: ../../root/flags.rst:17 +msgid "Опциональность и удобство" +msgstr "Optionality and Convenience" + +#: ../../root/flags.rst:19 +msgid "" +"Флаги решают проблему обязательности параметров. Если все параметры " +"команды сделать обязательными, это затруднит использование команды. Флаги" +" же позволяют задать значения только необходимые в конкретной ситуации, " +"остальные используют значения по умолчанию." +msgstr "" +"Flags solve the problem of mandatory parameters. If all command " +"parameters are made required, it makes the command difficult to use. " +"Flags allow you to specify only the values needed in a specific " +"situation, while others use default values." + +#: ../../root/flags.rst:22 +msgid "Когда могут понадобиться флаги" +msgstr "When Flags Might Be Needed" + +#: ../../root/flags.rst:24 +msgid "**Переключение режимов работы**" +msgstr "**Switching Operation Modes**" + +#: ../../root/flags.rst:25 +msgid "" +"Команда выполняет развёртывание приложения обычно, но нужен режим без " +"фактического развёртывания (dry-run) для проверки. Флаг ``--dry-run`` " +"переключит режим работы." +msgstr "" +"A command deploys an application normally, but a mode without actual " +"deployment (dry-run) is needed for verification. The ``--dry-run`` flag " +"will switch the mode." + +#: ../../root/flags.rst:27 +msgid "**Настройка уровня детальности**" +msgstr "**Adjusting Verbosity Level**" + +#: ../../root/flags.rst:28 +msgid "" +"При отладке или анализе требуется больше информации о процессе выполнения" +" команды. Флаги ``--verbose`` или ``--debug`` предоставляют подробный " +"вывод." +msgstr "" +"When debugging or analyzing, more information about the command execution" +" process is required. The ``--verbose`` or ``--debug`` flags provide " +"detailed output." + +#: ../../root/flags.rst:30 +msgid "**Управление поведением при ошибках**" +msgstr "**Managing Error Behavior**" + +#: ../../root/flags.rst:31 +msgid "" +"По умолчанию команда может прерваться при первой ошибке. Флаг ``--force``" +" позволит продолжить работу, пропуская некритичные ошибки." +msgstr "" +"By default, a command may abort on the first error. The ``--force`` flag " +"allows continuing execution, skipping non-critical errors." + +#: ../../root/flags.rst:33 +msgid "**Форматирование вывода**" +msgstr "**Output Formatting**" + +#: ../../root/flags.rst:34 +msgid "" +"Команда выводит данные текстом, но в некоторых сценариях нужен JSON или " +"CSV. Флаг ``--format=json`` переключит формат вывода." +msgstr "" +"A command outputs data as text, but in some scenarios JSON or CSV is " +"needed. The ``--format=json`` flag will switch the output format." + +#: ../../root/flags.rst:36 +msgid "**Комбинирование опций**" +msgstr "**Combining Options**" + +#: ../../root/flags.rst:37 +msgid "" +"Часто нужна комбинация нескольких изменений: подробный вывод, dry-run " +"режим и JSON формат. Несколько флагов решают эту задачу одновременно." +msgstr "" +"Often a combination of several changes is needed: verbose output, dry-run" +" mode, and JSON format. Multiple flags solve this task simultaneously." + +#: ../../root/flags.rst:40 +msgid "Практическое значение" +msgstr "Practical Significance" + +#: ../../root/flags.rst:42 +msgid "" +"Флаги делают команды более предсказуемыми и контролируемыми. Пользователь" +" может начать с простого использования, а затем добавлять флаги по мере " +"необходимости. Это особенно важно при автоматизации задач в скриптах, где" +" гибкость интерфейса критична." +msgstr "" +"Flags make commands more predictable and controllable. Users can start " +"with simple usage and then add flags as needed. This is especially " +"important when automating tasks in scripts, where interface flexibility " +"is critical." + +#: ../../root/flags.rst:44 +msgid "" +"Флаги также облегчают интеграцию команд в различные системы, так как " +"дополнительное поведение достигается без изменения структуры команды, а " +"только через передачу опциональных параметров." +msgstr "" +"Flags also facilitate command integration into various systems, as " +"additional behavior is achieved without changing the command structure, " +"only through passing optional parameters." + +#: ../../root/flags.rst:49 +msgid "Синтаксис флагов" +msgstr "Flag Syntax" + +#: ../../root/flags.rst:51 +msgid "Общий синтаксис выглядит так:" +msgstr "The general syntax looks like this:" + +#: ../../root/flags.rst:57 +msgid "" +"Флаг состоит из префикса (``-``, ``--`` или ``---``), имени и, " +"опционально, значения, которое указывается через пробел." +msgstr "" +"A flag consists of a prefix (``-``, ``--``, or ``---``), a name, and " +"optionally a value, which is specified with a space." + +#: ../../root/flags.rst:59 +msgid "**Примеры:**" +msgstr "**Examples:**" + +#: ../../root/flags.rst:70 +msgid "Работа с флагами в обработчиках" +msgstr "Working with Flags in Handlers" + +#: ../../root/flags.rst:72 +msgid "" +"Чтобы получить значение флага в обработчике, используйте объект " +"``response.input_flags`` типа :ref:`InputFlags " +"`." +msgstr "" +"To get the flag value in a handler, use the ``response.input_flags`` " +"object of type :ref:`InputFlags `." + +#: ../../root/flags.rst:74 +msgid "**Пример с флагом, имеющим значение:**" +msgstr "**Example with a flag that has a value:**" + +#: ../../root/flags.rst:80 +msgid "**Пример с флагом-переключателем:**" +msgstr "**Example with a toggle flag:**" + +#: ../../root/flags.rst:87 +msgid "" +"Подробнее о работе с объектом ``InputFlags`` см. в разделе " +":ref:`InputFlags `." +msgstr "" +"For more details on working with the ``InputFlags`` object, see the " +":ref:`InputFlags ` section." + +#: ../../root/flags.rst:92 +msgid "Два типа флагов" +msgstr "Two Types of Flags" + +#: ../../root/flags.rst:94 +msgid "Флаги бывают двух основных видов:" +msgstr "Flags come in two main types:" + +#: ../../root/flags.rst:96 +msgid "" +"**Флаги со значениями** — принимают параметр после имени флага (например," +" ``--name John``, ``--port 8080``)" +msgstr "" +"**Flags with values** — accept a parameter after the flag name (for " +"example, ``--name John``, ``--port 8080``)" + +#: ../../root/flags.rst:97 +msgid "" +"**Флаги-переключатели** — не принимают значения, их наличие само по себе " +"является сигналом (например, ``--verbose``, ``--force``)" +msgstr "" +"**Toggle flags** — do not accept values, their presence itself is a " +"signal (for example, ``--verbose``, ``--force``)" + +#: ../../root/flags.rst:99 +msgid "" +"``Argenta`` позволяет регистрировать и вводить флаги обоих типов в любой " +"последовательности для одной команды." +msgstr "" +"``Argenta`` allows registering and entering flags of both types in any " +"sequence for a single command." + +#: ../../root/flags.rst:102 +msgid "" +"Ошибки валидации не выбрасывают исключений. Вместо этого у каждого " +"объекта :ref:`InputFlag ` есть атрибут " +"``status``, по которому можно определить, прошла ли валидация успешно. " +"Подробное описание API для создания флагов находится в разделе :ref:`Flag" +" `." +msgstr "" +"Validation errors do not throw exceptions. Instead, each :ref:`InputFlag " +"` object has a ``status`` attribute that can" +" be used to determine if validation was successful. A detailed " +"description of the API for creating flags is in the :ref:`Flag " +"` section." + +#: ../../root/flags.rst:104 +msgid "" +"При регистрации флага можно задать правила валидации для его значения. По" +" умолчанию любое значение считается корректным. Валидацию можно настроить" +" несколькими способами:" +msgstr "" +"When registering a flag, you can set validation rules for its value. By " +"default, any value is considered valid. Validation can be configured in " +"several ways:" + +#: ../../root/flags.rst:109 +msgid "Флаги против аргументов" +msgstr "Flags vs Arguments" + +#: ../../root/flags.rst:111 +msgid "" +"В контексте Argenta флаги и аргументы относятся к разным уровням " +"взаимодействия с приложением." +msgstr "" +"In the context of Argenta, flags and arguments belong to different levels" +" of interaction with the application." + +#: ../../root/flags.rst:113 +msgid "" +"**Аргументы** — это параметры, передаваемые при запуске приложения. Они " +"определяют глобальную конфигурацию на протяжении всей его работы " +"(например, адрес базы данных, уровень логирования)." +msgstr "" +"**Arguments** are parameters passed when launching the application. They " +"define the global configuration throughout its operation (for example, " +"database address, logging level)." + +#: ../../root/flags.rst:115 +msgid "" +"API и более подробное описание в разделах :ref:`ArgParser " +"` и :ref:`Arguments " +"`." +msgstr "" +"API and more detailed description in the :ref:`ArgParser " +"` and :ref:`Arguments " +"` sections." + +#: ../../root/flags.rst:117 +msgid "" +"**Флаги** — это параметры командных операций, доступные в рамках " +"интерактивной сессии при вводе каждой новой команды. Они позволяют " +"модифицировать поведение конкретной команды без перезагрузки приложения." +msgstr "" +"**Flags** are command operation parameters available within an " +"interactive session when entering each new command. They allow modifying " +"the behavior of a specific command without restarting the application." + +#: ../../root/flags.rst:119 +msgid "" +"API и более подробное описание в разделе :ref:`Flag " +"`." +msgstr "" +"API and more detailed description in the :ref:`Flag " +"` section." + +#: ../../root/flags.rst:124 +msgid "Ключевые различия" +msgstr "Key Differences" + +#: ../../root/flags.rst:126 +msgid "**Время жизни**" +msgstr "**Lifetime**" + +#: ../../root/flags.rst:127 +msgid "" +"Аргументы передаются один раз при запуске и действуют на весь период " +"работы приложения. Флаги локальны и существуют только в рамках выполнения" +" команды." +msgstr "" +"Arguments are passed once at startup and remain in effect for the entire " +"application runtime. Flags are local and exist only within the execution " +"of a command." + +#: ../../root/flags.rst:129 +msgid "**Изменяемость**" +msgstr "**Mutability**" + +#: ../../root/flags.rst:130 +msgid "" +"Для изменения аргументов необходимо перезапустить приложение. Флаги можно" +" менять при каждом вводе команды." +msgstr "" +"To change arguments, the application must be restarted. Flags can be " +"changed with each command input." + +#: ../../root/flags.rst:132 +msgid "**Назначение**" +msgstr "**Purpose**" + +#: ../../root/flags.rst:133 +msgid "" +"Аргументы управляют глобальной конфигурацией приложения. Флаги управляют " +"поведением отдельных команд." +msgstr "" +"Arguments control the global configuration of the application. Flags " +"control the behavior of individual commands." + +#: ../../root/flags.rst:138 +msgid "Практические примеры" +msgstr "Practical Examples" + +#: ../../root/flags.rst:140 +msgid "Аргументы при запуске приложения:" +msgstr "Arguments at application startup:" + +#: ../../root/flags.rst:142 +msgid "Адрес подключения к базе данных" +msgstr "Database connection address" + +#: ../../root/flags.rst:143 +msgid "Режим работы (production, development, testing)" +msgstr "Operation mode (production, development, testing)" + +#: ../../root/flags.rst:144 +msgid "Уровень логирования" +msgstr "Logging level" + +#: ../../root/flags.rst:146 +msgid "Флаги в интерактивной сессии:" +msgstr "Flags in an interactive session:" + +#: ../../root/flags.rst:148 +msgid "``deploy --verbose --dry-run`` — для команды развёртывания" +msgstr "``deploy --verbose --dry-run`` — for the deployment command" + +#: ../../root/flags.rst:149 +msgid "``backup --compress --encrypted`` — для команды резервного копирования" +msgstr "``backup --compress --encrypted`` — for the backup command" + +#: ../../root/flags.rst:150 +msgid "``test --parallel --coverage`` — для команды тестирования" +msgstr "``test --parallel --coverage`` — for the testing command" + +#~ msgid "Определение и назначение" +#~ msgstr "Definition and Purpose" + +#~ msgid "**Частота изменения**" +#~ msgstr "**Change Frequency**" + +#~ msgid "**Уровень конфигурации**" +#~ msgstr "**Configuration Level**" + +#~ msgid "**Использование**" +#~ msgstr "**Usage**" + +#~ msgid "" +#~ "Аргументы задают начальное состояние системы:" +#~ " что подключить, как работать. Флаги " +#~ "управляют тактикой выполнения команд: как " +#~ "её выполнить, с какими изменениями." +#~ msgstr "" +#~ "Arguments set the initial state of " +#~ "the system: what to connect, how " +#~ "to operate. Flags control the tactics" +#~ " of command execution: how to execute" +#~ " it, with what changes." + +#~ msgid "При запуске приложения Argenta передаются аргументы:" +#~ msgstr "When launching an Argenta application, arguments are passed:" + +#~ msgid "Путь к конфигурационным файлам" +#~ msgstr "Path to configuration files" + +#~ msgid "В интерактивной сессии для каждой команды указываются флаги:" +#~ msgstr "In an interactive session, flags are specified for each command:" + +#~ msgid "" +#~ "Один пользователь может выполнить разные " +#~ "команды с разными флагами в одной " +#~ "сессии приложения, без необходимости " +#~ "перезапуска с новыми аргументами." +#~ msgstr "" +#~ "A single user can execute different " +#~ "commands with different flags in one " +#~ "application session, without needing to " +#~ "restart with new arguments." + diff --git a/docs/locales/en/LC_MESSAGES/root/overriding_formatting.po b/docs/locales/en/LC_MESSAGES/root/overriding_formatting.po new file mode 100644 index 0000000..9c72754 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/overriding_formatting.po @@ -0,0 +1,123 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/overriding_formatting.rst:4 +msgid "Форматирование вывода" +msgstr "Output Formatting" + +#: ../../root/overriding_formatting.rst:6 +msgid "" +"По умолчанию ``Argenta`` использует библиотеку ``rich`` для вывода текста" +" с расширенным форматированием. Она позволяет применять цвета и стили, " +"создавать таблицы, подсвечивать синтаксис и многое другое, что улучшает " +"визуальное восприятие информации." +msgstr "" +"By default, ``Argenta`` uses the ``rich`` library for text output with " +"enhanced formatting. It allows applying colors and styles, creating " +"tables, highlighting syntax, and much more, which improves the visual " +"perception of information." + +#: ../../root/overriding_formatting.rst:11 +msgid "Управление стандартным форматированием" +msgstr "Managing Standard Formatting" + +#: ../../root/overriding_formatting.rst:13 +msgid "" +"При создании экземпляра ``App`` можно использовать параметр " +"``override_system_messages: bool`` (по умолчанию ``False``), который " +"позволяет отключать стандартное форматирование." +msgstr "" +"When creating an ``App`` instance, you can use the " +"``override_system_messages: bool`` parameter (default ``False``), which " +"allows disabling standard formatting." + +#: ../../root/overriding_formatting.rst:15 +msgid "" +"Если установить его в ``True``, стилизация текста и ASCII-графика будут " +"отключены, а системные сообщения — выводиться в «сыром» виде." +msgstr "" +"If set to ``True``, text styling and ASCII graphics will be disabled, and" +" system messages will be output in \"raw\" form." + +#: ../../root/overriding_formatting.rst:20 +msgid "Приветственное и прощальное сообщения" +msgstr "Welcome and Farewell Messages" + +#: ../../root/overriding_formatting.rst:22 +msgid "" +"Приветственное (``initial_message``) и прощальное (``farewell_message``) " +"сообщения по умолчанию выводятся в виде ASCII-графики." +msgstr "" +"Welcome (``initial_message``) and farewell (``farewell_message``) " +"messages are displayed as ASCII art by default." + +#: ../../root/overriding_formatting.rst:25 +msgid "" +"Библиотека ``art`` ориентирована на работу с ASCII-символами и **не " +"поддерживает кириллицу**. Это приводит к искажению символов русского и " +"других кириллических алфавитов. Если ваше сообщение содержит кириллицу, " +"рекомендуется отключить форматирование с помощью " +"``override_system_messages=True`` или использовать только латинские " +"символы." +msgstr "" +"The ``art`` library is designed to work with ASCII characters and **does " +"not support Cyrillic**. This leads to distortion of Russian and other " +"Cyrillic alphabet characters. If your message contains Cyrillic, it is " +"recommended to disable formatting using ``override_system_messages=True``" +" or use only Latin characters." + +#: ../../root/overriding_formatting.rst:30 +msgid "Кастомизация вывода" +msgstr "Output Customization" + +#: ../../root/overriding_formatting.rst:32 +msgid "" +"Для полной замены логики вывода текста в конструкторе ``App`` " +"предусмотрен параметр ``print_func``." +msgstr "" +"For complete replacement of text output logic, the ``App`` constructor " +"provides the ``print_func`` parameter." + +#: ../../root/overriding_formatting.rst:34 +msgid "" +"**print_func**: ``Callable[[str], None]`` Этот параметр позволяет " +"передать любую вызываемую сущность (например, функцию), которая будет " +"использоваться для вывода всех системных сообщений. По умолчанию это " +"``rich.console.Console().print``. Вы можете передать сюда свою функцию, " +"чтобы, например, логировать вывод в файл или отправлять его по сети." +msgstr "" +"**print_func**: ``Callable[[str], None]`` This parameter allows passing " +"any callable entity (for example, a function) that will be used to output" +" all system messages. By default, this is " +"``rich.console.Console().print``. You can pass your own function here to," +" for example, log output to a file or send it over the network." + +#: ../../root/overriding_formatting.rst:38 +msgid "" +"При переопределении функции вывода вам следует убедиться, что она " +"поддерживает разметку ``rich``, иначе системные сообщения будут " +"выводиться в сыром виде, в этом случае рекомендуется переопределить " +"стандартное форматирование с помощью ``override_system_messages=True``." +msgstr "" +"When overriding the output function, you should ensure it supports " +"``rich`` markup, otherwise system messages will be output in raw form. In" +" this case, it is recommended to override standard formatting using " +"``override_system_messages=True``." + diff --git a/docs/locales/en/LC_MESSAGES/root/quickstart.po b/docs/locales/en/LC_MESSAGES/root/quickstart.po new file mode 100644 index 0000000..19ca959 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/quickstart.po @@ -0,0 +1,182 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/quickstart.rst:4 +msgid "Быстрый старт" +msgstr "Quick Start" + +#: ../../root/quickstart.rst:6 +msgid "" +"В этом руководстве мы рассмотрим два примера создания CLI-приложения с " +"помощью Argenta:" +msgstr "" +"In this guide, we will look at two examples of creating a CLI application" +" with Argenta:" + +#: ../../root/quickstart.rst:8 +msgid "" +"**Простой пример**: минимальное приложение для быстрого знакомства с " +"основными компонентами." +msgstr "" +"**Simple example**: a minimal application for quick introduction to the " +"main components." + +#: ../../root/quickstart.rst:9 +msgid "" +"**Более сложный пример**: полнофункциональное приложение «Менеджер задач»" +" с внедрением зависимостей и бизнес-логикой." +msgstr "" +"**More complex example**: a full-featured \"Task Manager\" application " +"with dependency injection and business logic." + +#: ../../root/quickstart.rst:12 +msgid "Простой пример" +msgstr "Simple Example" + +#: ../../root/quickstart.rst:14 ../../root/quickstart.rst:74 +msgid "**Установка**" +msgstr "**Installation**" + +#: ../../root/quickstart.rst:20 +msgid "" +"Этот пример демонстрирует абсолютный минимум, необходимый для создания и " +"запуска приложения. Вы можете скопировать этот код, запустить его и сразу" +" увидеть результат." +msgstr "" +"This example demonstrates the absolute minimum required to create and run" +" an application. You can copy this code, run it, and immediately see the " +"result." + +#: ../../root/quickstart.rst:26 +msgid "**Запуск**" +msgstr "**Running**" + +#: ../../root/quickstart.rst:28 +msgid "Сохраните код в файл (например, ``main.py``) и запустите:" +msgstr "Save the code to a file (for example, ``main.py``) and run:" + +#: ../../root/quickstart.rst:34 ../../root/quickstart.rst:112 +msgid "**Результат**" +msgstr "**Result**" + +#: ../../root/quickstart.rst:36 +msgid "Simple App Example" +msgstr "Simple App Example" + +#: ../../root/quickstart.rst:42 +msgid "Промежуточный пример: Калькулятор с флагами" +msgstr "Intermediate Example: Calculator with Flags" + +#: ../../root/quickstart.rst:44 +msgid "" +"Прежде чем перейти к сложному примеру с DI, рассмотрим промежуточный " +"вариант — калькулятор, который использует флаги для управления " +"поведением." +msgstr "" +"Before moving to a complex example with DI, let's consider an " +"intermediate option — a calculator that uses flags to control behavior." + +#: ../../root/quickstart.rst:50 +msgid "**Запуск:**" +msgstr "**Running:**" + +#: ../../root/quickstart.rst:52 +msgid "Сохраните код в файл ``calculator.py`` и запустите:" +msgstr "Save the code to a file ``calculator.py`` and run:" + +#: ../../root/quickstart.rst:58 +msgid "**Использование:**" +msgstr "**Usage:**" + +#: ../../root/quickstart.rst:65 +msgid "" +"Этот пример показывает, как работать с флагами без использования DI. " +"Теперь перейдём к более сложному примеру." +msgstr "" +"This example shows how to work with flags without using DI. Now let's " +"move on to a more complex example." + +#: ../../root/quickstart.rst:70 +msgid "Сложный пример: Менеджер задач с DI" +msgstr "Complex Example: Task Manager with DI" + +#: ../../root/quickstart.rst:72 +msgid "" +"В этом руководстве мы создадим полнофункциональное CLI-приложение " +"«Менеджер задач», которое продемонстрирует работу с внедрением " +"зависимостей." +msgstr "" +"In this guide, we will create a full-featured CLI application \"Task " +"Manager\" that will demonstrate working with dependency injection." + +#: ../../root/quickstart.rst:80 +msgid "**Определение моделей данных и репозитория**" +msgstr "**Defining Data Models and Repository**" + +#: ../../root/quickstart.rst:82 +msgid "Сначала определим модели данных для задачи и репозиторий для их хранения." +msgstr "First, let's define data models for tasks and a repository to store them." + +#: ../../root/quickstart.rst:88 +msgid "**Создание провайдера для DI**" +msgstr "**Creating a Provider for DI**" + +#: ../../root/quickstart.rst:90 +msgid "" +"Чтобы Argenta могла внедрять ``TaskRepository`` в наши обработчики, мы " +"создадим провайдер для ``dishka``." +msgstr "" +"To allow Argenta to inject ``TaskRepository`` into our handlers, we will " +"create a provider for ``dishka``." + +#: ../../root/quickstart.rst:96 +msgid "**Создание обработчиков команд**" +msgstr "**Creating Command Handlers**" + +#: ../../root/quickstart.rst:98 +msgid "" +"Теперь создадим обработчики для команд ``add-task`` и ``list-tasks``. " +"Обратите внимание, как мы используем флаги и внедряем ``TaskRepository``." +msgstr "" +"Now let's create handlers for the ``add-task`` and ``list-tasks`` " +"commands. Notice how we use flags and inject ``TaskRepository``." + +#: ../../root/quickstart.rst:104 +msgid "**Сборка и запуск приложения**" +msgstr "**Building and Running the Application**" + +#: ../../root/quickstart.rst:106 +msgid "" +"Наконец, соберем все вместе: создадим экземпляр ``App``, подключим роутер" +" и провайдер, а затем запустим приложение." +msgstr "" +"Finally, let's put it all together: create an ``App`` instance, connect " +"the router and provider, and then run the application." + +#: ../../root/quickstart.rst:114 +msgid "" +"Теперь вы можете запустить ``main.py`` и взаимодействовать с вашим новым " +"CLI-приложением." +msgstr "Now you can run ``main.py`` and interact with your new CLI application." + +#: ../../root/quickstart.rst:116 +msgid "Task Manager Example" +msgstr "Task Manager Example" + diff --git a/docs/locales/en/LC_MESSAGES/root/redirect_stdout.po b/docs/locales/en/LC_MESSAGES/root/redirect_stdout.po new file mode 100644 index 0000000..f558928 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/redirect_stdout.po @@ -0,0 +1,310 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/redirect_stdout.rst:4 +msgid "Переопределение стандартного вывода" +msgstr "Overriding Standard Output" + +#: ../../root/redirect_stdout.rst:6 +msgid "" +"``Argenta`` предоставляет гибкие механизмы для форматирования вывода, " +"включая динамические разделительные линии. Это достигается за счёт " +"перехвата стандартного потока вывода (``stdout``), что накладывает " +"некоторые особенности." +msgstr "" +"``Argenta`` provides flexible mechanisms for output formatting, including" +" dynamic dividing lines. This is achieved by intercepting the standard " +"output stream (``stdout``), which imposes certain specifics." + +#: ../../root/redirect_stdout.rst:11 +msgid "Когда нужно отключать перехват stdout" +msgstr "When to Disable stdout Interception" + +#: ../../root/redirect_stdout.rst:13 +msgid "" +"Отключайте перехват ``stdout`` (``disable_redirect_stdout=True`` в " +"``Router``), если ваши команды:" +msgstr "" +"Disable ``stdout`` interception (``disable_redirect_stdout=True`` in " +"``Router``) if your commands:" + +#: ../../root/redirect_stdout.rst:15 +msgid "" +"✓ Используют ``input()`` для интерактивного ввода данных от пользователя " +"✓ Используют прогресс-бары (``tqdm``, ``rich.progress``) ✓ Выводят данные" +" в реальном времени (streaming, логи) ✓ Используют библиотеки, которые " +"напрямую работают с ``stdout``" +msgstr "" +"✓ Use ``input()`` for interactive user input ✓ Use progress bars " +"(``tqdm``, ``rich.progress``) ✓ Output data in real-time (streaming, " +"logs) ✓ Use libraries that work directly with ``stdout``" + +#: ../../root/redirect_stdout.rst:20 +msgid "" +"Для обычных команд с ``print()`` перехват можно оставить включённым — это" +" не влияет на их работу." +msgstr "" +"For regular commands with ``print()``, interception can be left enabled —" +" it does not affect their operation." + +#: ../../root/redirect_stdout.rst:25 +msgid "Механизм перехвата ``stdout``" +msgstr "``stdout`` Interception Mechanism" + +#: ../../root/redirect_stdout.rst:27 +msgid "" +"По умолчанию ``Argenta`` перехватывает весь текст, выводимый в ``stdout``" +" внутри обработчика команды. Это необходимо для реализации **динамических" +" разделителей**: система анализирует вывод, находит самую длинную строку " +"и использует её для отрисовки верхней и нижней границ. Такой подход " +"создаёт аккуратный интерфейс, где вывод команды «обёрнут» в рамку, " +"подогнанную под его содержимое." +msgstr "" +"By default, ``Argenta`` intercepts all text output to ``stdout`` inside a" +" command handler. This is necessary to implement **dynamic dividers**: " +"the system analyzes the output, finds the longest line, and uses it to " +"draw the top and bottom borders. This approach creates a neat interface " +"where the command output is \"wrapped\" in a frame fitted to its content." + +#: ../../root/redirect_stdout.rst:29 +msgid "Пример приложения с динамической разделительной линией:" +msgstr "Example of an application with a dynamic dividing line:" + +#: ../../root/redirect_stdout.rst:31 +msgid "Example of an application with a dynamic dividing line" +msgstr "Example of an application with a dynamic dividing line" + +#: ../../root/redirect_stdout.rst:34 +msgid "" +"Как вы можете заметить, разделительная линия ровно той же длины, что и " +"самая длинная строка в выводе." +msgstr "" +"As you can see, the dividing line is exactly the same length as the " +"longest line in the output." + +#: ../../root/redirect_stdout.rst:36 +msgid "То же приложение с статической линией:" +msgstr "The same application with a static line:" + +#: ../../root/redirect_stdout.rst:38 +msgid "Example of an application with a static dividing line" +msgstr "Example of an application with a static dividing line" + +#: ../../root/redirect_stdout.rst:41 +msgid "" +"В этом примере разделительная линия имеет фиксированную длину (по " +"умолчанию 25 символов)." +msgstr "" +"In this example, the dividing line has a fixed length (25 characters by " +"default)." + +#: ../../root/redirect_stdout.rst:46 +msgid "Побочные эффекты перехвата ``stdout``" +msgstr "Side Effects of ``stdout`` Interception" + +#: ../../root/redirect_stdout.rst:48 +msgid "" +"Побочный эффект этого механизма проявляется при использовании функций, " +"которые последовательно выводят текст в консоль и ожидают ввод от " +"пользователя. Классический пример — стандартная функция ``input()``." +msgstr "" +"A side effect of this mechanism manifests when using functions that " +"sequentially output text to the console and expect user input. A classic " +"example is the standard ``input()`` function." + +#: ../../root/redirect_stdout.rst:57 +msgid "" +"При включённом перехвате ``stdout`` текст (например, ``\"Введите ваше " +"имя: \"``) **не будет выведен в консоль немедленно**. Он попадёт в буфер " +"и отобразится лишь после завершения работы обработчика вместе с остальным" +" выводом. Это может сбить пользователя с толку." +msgstr "" +"With ``stdout`` interception enabled, text (for example, ``\"Enter your " +"name: \"``) **will not be output to the console immediately**. It will go" +" into a buffer and appear only after the handler finishes, along with the" +" rest of the output. This can confuse the user." + +#: ../../root/redirect_stdout.rst:62 +msgid "Отключение перехвата ``stdout`` с помощью ``disable_redirect_stdout``" +msgstr "Disabling ``stdout`` Interception with ``disable_redirect_stdout``" + +#: ../../root/redirect_stdout.rst:64 +msgid "" +"Чтобы решить эту проблему, в конструкторе ``Router`` предусмотрен " +"специальный аргумент:" +msgstr "" +"To solve this problem, the ``Router`` constructor provides a special " +"argument:" + +#: ../../root/redirect_stdout.rst:66 +msgid "**disable_redirect_stdout** (``bool``, по умолчанию ``False``)" +msgstr "**disable_redirect_stdout** (``bool``, default ``False``)" + +#: ../../root/redirect_stdout.rst:68 +msgid "" +"Если при создании роутера установить ``disable_redirect_stdout=True``, " +"механизм перехвата ``stdout`` будет отключён для всех его обработчиков." +msgstr "" +"If you set ``disable_redirect_stdout=True`` when creating a router, the " +"``stdout`` interception mechanism will be disabled for all its handlers." + +#: ../../root/redirect_stdout.rst:70 ../../root/redirect_stdout.rst:100 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/redirect_stdout.rst:76 +msgid "" +"В этом случае ``input()`` будет работать как обычно, и пользователь сразу" +" увидит приглашение к вводу." +msgstr "" +"In this case, ``input()`` will work as usual, and the user will " +"immediately see the input prompt." + +#: ../../root/redirect_stdout.rst:81 +msgid "Типы разделительных линий" +msgstr "Types of Dividing Lines" + +#: ../../root/redirect_stdout.rst:83 +msgid "" +"``Argenta`` поддерживает два типа разделителей, которые настраиваются при" +" инициализации ``App``:" +msgstr "" +"``Argenta`` supports two types of dividers, which are configured during " +"``App`` initialization:" + +#: ../../root/redirect_stdout.rst:85 +msgid "**``DynamicDividingLine()``**" +msgstr "**``DynamicDividingLine()``**" + +#: ../../root/redirect_stdout.rst:86 +msgid "" +"Поведение по умолчанию. Длина линии динамически подстраивается под самый " +"длинный текст в выводе." +msgstr "" +"Default behavior. The line length dynamically adjusts to the longest text" +" in the output." + +#: ../../root/redirect_stdout.rst:87 +msgid "" +"Требует включённого перехвата ``stdout`` " +"(``disable_redirect_stdout=False`` в роутере)." +msgstr "" +"Requires enabled ``stdout`` interception " +"(``disable_redirect_stdout=False`` in the router)." + +#: ../../root/redirect_stdout.rst:89 +msgid "**``StaticDividingLine(length: int = 25)``**" +msgstr "**``StaticDividingLine(length: int = 25)``**" + +#: ../../root/redirect_stdout.rst:90 +msgid "" +"Линия имеет фиксированную длину (по умолчанию 25 символов), которую можно" +" задать через аргумент ``length``." +msgstr "" +"The line has a fixed length (25 characters by default), which can be set " +"via the ``length`` argument." + +#: ../../root/redirect_stdout.rst:91 +msgid "" +"Используется принудительно для роутеров с " +"``disable_redirect_stdout=True``, так как без перехвата вывода невозможно" +" определить динамическую длину." +msgstr "" +"Used forcibly for routers with ``disable_redirect_stdout=True``, as it is" +" impossible to determine dynamic length without output interception." + +#: ../../root/redirect_stdout.rst:96 +msgid "Настройка разделительной линии в ``App``" +msgstr "Configuring the Dividing Line in ``App``" + +#: ../../root/redirect_stdout.rst:98 +msgid "" +"Вы можете глобально задать тип разделителя для всего приложения через " +"аргумент ``dividing_line`` в конструкторе ``App``." +msgstr "" +"You can globally set the divider type for the entire application via the " +"``dividing_line`` argument in the ``App`` constructor." + +#: ../../root/redirect_stdout.rst:109 +msgid "Итоговое поведение" +msgstr "Resulting Behavior" + +#: ../../root/redirect_stdout.rst:115 +msgid "``disable_redirect_stdout`` на ``Router``" +msgstr "``disable_redirect_stdout`` on ``Router``" + +#: ../../root/redirect_stdout.rst:116 +msgid "Тип линии в ``App``" +msgstr "Line type in ``App``" + +#: ../../root/redirect_stdout.rst:117 +msgid "Фактическое поведение" +msgstr "Actual behavior" + +#: ../../root/redirect_stdout.rst:118 +msgid "``input()`` работает корректно?" +msgstr "Does ``input()`` work correctly?" + +#: ../../root/redirect_stdout.rst:119 ../../root/redirect_stdout.rst:123 +msgid "``False`` (по умолчанию)" +msgstr "``False`` (default)" + +#: ../../root/redirect_stdout.rst:120 ../../root/redirect_stdout.rst:128 +msgid "``DynamicDividingLine``" +msgstr "``DynamicDividingLine``" + +#: ../../root/redirect_stdout.rst:121 +msgid "Динамическая линия, длина по содержимому" +msgstr "Dynamic line, length by content" + +#: ../../root/redirect_stdout.rst:122 ../../root/redirect_stdout.rst:126 +msgid "Нет" +msgstr "No" + +#: ../../root/redirect_stdout.rst:124 ../../root/redirect_stdout.rst:132 +msgid "``StaticDividingLine``" +msgstr "``StaticDividingLine``" + +#: ../../root/redirect_stdout.rst:125 ../../root/redirect_stdout.rst:133 +msgid "Статическая линия указанной длины" +msgstr "Static line of specified length" + +#: ../../root/redirect_stdout.rst:127 ../../root/redirect_stdout.rst:131 +msgid "``True``" +msgstr "``True``" + +#: ../../root/redirect_stdout.rst:129 +msgid "**Принудительно статическая линия** (длина по умолч.)" +msgstr "**Forcibly static line** (default length)" + +#: ../../root/redirect_stdout.rst:130 ../../root/redirect_stdout.rst:134 +msgid "Да" +msgstr "Yes" + +#: ../../root/redirect_stdout.rst:136 +msgid "" +"Таким образом, для интерактивных команд, требующих ввода от пользователя," +" отключайте перехват ``stdout`` на уровне роутера. Для всех остальных " +"команд можно оставить поведение по умолчанию." +msgstr "" +"Thus, for interactive commands that require user input, disable " +"``stdout`` interception at the router level. For all other commands, you " +"can leave the default behavior." + diff --git a/docs/locales/en/LC_MESSAGES/root/testing.po b/docs/locales/en/LC_MESSAGES/root/testing.po new file mode 100644 index 0000000..cf97512 --- /dev/null +++ b/docs/locales/en/LC_MESSAGES/root/testing.po @@ -0,0 +1,143 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, kolo +# This file is distributed under the same license as the Argenta package. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: Argenta \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-04 20:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../root/testing.rst:2 +msgid "Тестирование" +msgstr "Testing" + +#: ../../root/testing.rst:4 +msgid "" +"В этом разделе описаны практики тестирования приложений на основе " +"``Argenta``. Примеры основаны на фактическом публичном API." +msgstr "" +"This section describes testing practices for applications based on " +"``Argenta``. Examples are based on the actual public API." + +#: ../../root/testing.rst:7 +msgid "Модульное тестирование обработчиков" +msgstr "Unit Testing Handlers" + +#: ../../root/testing.rst:9 +msgid "" +"Обработчики в ``Argenta`` — обычные функции. Их удобно тестировать как " +"чистые функции, не поднимая весь цикл приложения. Рекомендуются " +"``unittest`` или ``pytest``." +msgstr "" +"Handlers in ``Argenta`` are regular functions. They are convenient to " +"test as pure functions without starting the entire application cycle. " +"``unittest`` or ``pytest`` are recommended." + +#: ../../root/testing.rst:11 ../../root/testing.rst:24 +#: ../../root/testing.rst:37 ../../root/testing.rst:55 +msgid "**Пример использования:**" +msgstr "**Usage example:**" + +#: ../../root/testing.rst:20 +msgid "Тестирование с внедрением зависимостей (DI)" +msgstr "Testing with Dependency Injection (DI)" + +#: ../../root/testing.rst:22 +msgid "" +"Если обработчику нужны зависимости, используйте ``dishka`` и интеграцию " +"``Argenta``:" +msgstr "" +"If a handler needs dependencies, use ``dishka`` and ``Argenta`` " +"integration:" + +#: ../../root/testing.rst:33 +msgid "Интеграционное тестирование приложения" +msgstr "Integration Testing of the Application" + +#: ../../root/testing.rst:35 +msgid "" +"Для более высокого уровня тестов собирайте ``App`` и ``Router`` и " +"вызывайте обработчики через парсинг команд, обходя бесконечный цикл " +"ввода. Это даёт близкое к реальности поведение без необходимости " +"симулировать ``stdin``." +msgstr "" +"For higher-level tests, assemble ``App`` and ``Router`` and call handlers" +" through command parsing, bypassing the infinite input loop. This " +"provides behavior close to reality without the need to simulate " +"``stdin``." + +#: ../../root/testing.rst:46 +msgid "E2E-тестирование цикла" +msgstr "E2E Testing of the Loop" + +#: ../../root/testing.rst:48 +msgid "" +"Полный запуск цикла ``start_polling`` можно покрывать через подпроцесс с " +"передачей строк в ``stdin``. Это тяжелее и обычно не требуется. Если всё " +"же необходимо — пример ниже." +msgstr "" +"Full execution of the ``start_polling`` loop can be covered through a " +"subprocess with passing strings to ``stdin``. This is heavier and usually" +" not required. If still necessary, an example is below." + +#: ../../root/testing.rst:51 +msgid "" +"**Важно:** Обязательно передавайте строковый триггер команды выхода " +"последним элементом в списке ``side_effects`` при патче ``input``." +msgstr "" +"**Important:** Always pass the exit command string trigger as the last " +"element in the ``side_effects`` list when patching ``input``." + +#: ../../root/testing.rst:53 +msgid "" +"Иначе тестируемое приложение будет ожидать ввода следующей команды и не " +"сможет корректно завершиться." +msgstr "" +"Otherwise, the application under test will wait for the next command " +"input and will not be able to terminate correctly." + +#: ../../root/testing.rst:64 +msgid "Советы по тестированию" +msgstr "Testing Tips" + +#: ../../root/testing.rst:66 +msgid "**Изолируйте тесты**: Каждый тест должен быть независимым от других." +msgstr "**Isolate tests**: Each test should be independent of others." + +#: ../../root/testing.rst:67 +msgid "" +"**Моки для внешних интеграций**: БД, HTTP-клиенты и т.п. подменяйте " +"заглушками и провайдерами ``dishka``." +msgstr "" +"**Mocks for external integrations**: Replace databases, HTTP clients, " +"etc. with stubs and ``dishka`` providers." + +#: ../../root/testing.rst:68 +msgid "" +"**Покрывайте ошибочные сценарии**: Некорректные флаги, неизвестные " +"команды, пустой ввод." +msgstr "**Cover error scenarios**: Incorrect flags, unknown commands, empty input." + +#: ../../root/testing.rst:69 +msgid "" +"**Минимизируйте зависимость от форматирования**: Сравнивайте ключевые " +"фрагменты вывода, а не весь блок целиком." +msgstr "" +"**Minimize formatting dependency**: Compare key output fragments, not the" +" entire block." + +#: ../../root/testing.rst:70 +msgid "**Измеряйте покрытие**: Используйте ``pytest-cov``." +msgstr "**Measure coverage**: Use ``pytest-cov``." + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..954237b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..938e411 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +sphinx +shibuya +sphinx-intl +sphinx-autobuild diff --git a/docs/root/api/app/autocompleter.rst b/docs/root/api/app/autocompleter.rst new file mode 100644 index 0000000..ede5993 --- /dev/null +++ b/docs/root/api/app/autocompleter.rst @@ -0,0 +1,49 @@ +.. _root_api_app_autocompleter: + +AutoCompleter +===================== + +``AutoCompleter`` — это компонент, отвечающий за интерактивное автодополнение команд. Он улучшает пользовательский опыт, предлагая подсказки и завершая ввод на основе истории команд, что ускоряет работу и снижает вероятность опечаток. + +----- + +Инициализация +------------- + +.. code-block:: python + + __init__(self, history_filename: str | None = None, + autocomplete_button: str = "tab") -> None + +Создаёт и настраивает экземпляр ``AutoCompleter``. + +* ``history_filename``: Имя файла для сохранения истории команд. Если указано, история будет сохраняться между сессиями. При значении ``None`` история хранится только в контексте сессии. +* ``autocomplete_button``: Клавиша, активирующая автодополнение. По умолчанию — **Tab**. + +----- + +Назначение и возможности +------------------------- + +Основные возможности ``AutoCompleter``: + +* **Автодополнение по истории**: При нажатии клавиши автодополнения (по умолчанию **Tab**) система ищет в истории команды, начинающиеся с уже введённого текста. + +* **Общий префикс**: Если найдено несколько команд с общим префиксом, будет подставлена только общая часть. Например, для команд ``show_users`` и ``show_profile`` при вводе ``sho`` и нажатии **Tab** ввод дополнится до ``show_``. + +* **Постоянная история**: Если указан ``history_filename``, история команд сохраняется в файл при выходе и загружается при следующем запуске. Это делает автодополнение со временем «умнее». + +* **Очистка истории**: При сохранении ``AutoCompleter`` удаляет дубликаты и несуществующие команды, поддерживая историю в актуальном состоянии. + +* **Настройка клавиши**: Клавишу автодополнения можно изменить с помощью параметра ``autocomplete_button``. + +----- + +Пример использования +-------------------- + +``AutoCompleter`` передаётся как аргумент при инициализации `App`. + +.. literalinclude:: ../../../code_snippets/autocompleter/snippet.py + :language: python + :linenos: diff --git a/docs/root/api/app/dividing_lines.rst b/docs/root/api/app/dividing_lines.rst new file mode 100644 index 0000000..bcb70fd --- /dev/null +++ b/docs/root/api/app/dividing_lines.rst @@ -0,0 +1,65 @@ +.. _root_api_app_dividing_lines: + +Dividing Lines +============== + +Разделительные линии в ``Argenta`` используются для визуального структурирования вывода и отделения блоков информации друг от друга. Библиотека предлагает два типа линий: статическую и динамическую. + +----- + +``StaticDividingLine`` +---------------------- + +``StaticDividingLine`` создаёт разделительную линию **фиксированной** длины. Этот тип линии полезен для создания предсказуемого и унифицированного интерфейса. + +.. code-block:: python + :linenos: + + def __init__(self, unit_part: str = "-", *, + length: int = 25) -> None + +Создаёт экземпляр статической разделительной линии. + +* ``unit_part``: Символ для построения линии (учитывается только первый символ). По умолчанию: ``-``. +* ``length``: Фиксированная длина линии. По умолчанию: ``25``. + +----- + +``DynamicDividingLine`` +----------------------- + +``DynamicDividingLine`` создаёт линию, длина которой **динамически** подстраивается под самую длинную строку в выводе команды. Это требует перехвата ``stdout``, в результате чего разделители идеально обрамляют выводимый контент. + +.. code-block:: python + :linenos: + + __init__(self, unit_part: str = "-") -> None + +Создаёт экземпляр динамической разделительной линии. + +* ``unit_part``: Символ для построения линии. По умолчанию: ``-``. + +Длина вычисляется автоматически и не задаётся при инициализации. + +.. warning:: + Обязательно почитайте про нюансы использования динамических линий и перехвата ``stdout`` в :ref:`этом разделе`. + +----- + +Назначение и использование +--------------------------- + +Выбор между статической и динамической линией зависит от ваших задач. + +* **StaticDividingLine** идеально подходит, если: + + * Вам нужен строгий и консистентный дизайн. + * Вы используете роутеры с отключённым перехватом ``stdout`` (``disable_redirect_stdout=True``), где динамическое вычисление длины невозможно. + +* **DynamicDividingLine** (поведение по умолчанию) — предпочтительный выбор, если: + + * Вы хотите, чтобы интерфейс был адаптивным. + * Вывод ваших команд имеет разную длину. + * В ваших обработчиках нет интерактивных операций ввода (например, ``input()``). + +Тип разделителя для всего приложения задаётся при инициализации ``App`` через параметр ``dividing_line``. diff --git a/docs/root/api/app/index.rst b/docs/root/api/app/index.rst new file mode 100644 index 0000000..1fa883d --- /dev/null +++ b/docs/root/api/app/index.rst @@ -0,0 +1,192 @@ +.. _root_api_app_index: + +App +=== + +Объект ``App`` — это ядро вашего консольного приложения. Он отвечает за конфигурацию, управление жизненным циклом, обработку команд и взаимодействие с пользователем, координируя работу всех компонентов: роутеров, обработчиков и системных сообщений. + +------ + +Инициализация +------------- + +.. code-block:: python + :linenos: + + AVAILABLE_DIVIDING_LINES: TypeAlias = StaticDividingLine | DynamicDividingLine + DEFAULT_DIVIDING_LINE: StaticDividingLine = StaticDividingLine() + + DEFAULT_PRINT_FUNC: Printer = Console().print + DEFAULT_AUTOCOMPLETER: AutoCompleter = AutoCompleter() + DEFAULT_EXIT_COMMAND: Command = Command("Q", description="Exit command") + +.. code-block:: python + :linenos: + + def __init__(self, *, prompt: str = "What do you want to do?\n\n", + initial_message: str = "Argenta\n", + farewell_message: str = "\nSee you\n", + exit_command: Command = DEFAULT_EXIT_COMMAND, + system_router_title: str | None = "System points:", + ignore_command_register: bool = True, + dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE, + repeat_command_groups_printing: bool = True, + override_system_messages: bool = False, + autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER, + print_func: Printer = DEFAULT_PRINT_FUNC) -> None + +Создаёт и настраивает экземпляр приложения. + + * ``prompt``: Приглашение к вводу, отображаемое перед каждой командой. + * ``initial_message``: Сообщение, выводимое при запуске приложения. + * ``farewell_message``: Сообщение, выводимое при выходе из приложения. + * ``exit_command``: Команда, которая маркируется как триггер для выхода из приложения. + * ``system_router_title``: Заголовок для системного роутера (содержит команду выхода). + * ``ignore_command_register``: Если ``True``, регистр вводимых команд игнорируется при поиске обработчика. + * ``dividing_line``: Тип разделительной линии (``StaticDividingLine`` или ``DynamicDividingLine``). + * ``repeat_command_groups_printing``: Если ``True``, список доступных команд выводится перед каждым вводом. + * ``override_system_messages``: Если ``True``, стандартное форматирование (цвета, ASCII-арт) отключается. + * ``autocompleter``: Экземпляр класса :ref:`AutoCompleter `, отвечающий за автодополнение команд. + * ``print_func``: Функция для вывода всех системных сообщений (по умолчанию ``rich.Console().print``). + +----- + +Основные методы +--------------- + +- .. py:method:: include_router(self, router: Router) -> None + + Регистрирует роутер в приложении. Все команды из этого роутера становятся доступными для вызова. + + :param router: Экземпляр ``Router`` для регистрации. + +- .. py:method:: include_routers(self, *routers: Router) -> None + + Регистрирует несколько роутеров одновременно. + + :param routers: Последовательность экземпляров ``Router`` для регистрации. + +- .. py:method:: add_message_on_startup(self, message: str) -> None + + Добавляет текстовое сообщение, которое выводится при запуске приложения после ``initial_message``. + + :param message: Строка с сообщением. + + .. seealso:: + Для вывода стандартных сообщений можно использовать готовые шаблоны из :ref:`PredefinedMessages `. + +----- + +Методы установки обработчиков +------------------------------- + +``App`` позволяет настраивать реакцию на различные события, такие как ошибки ввода или неизвестные команды. + +.. hint:: + Подробнее об исключениях и их обработке в соответствующем :ref:`разделе документации `. + +----- + +.. py:method:: set_description_message_pattern(self, handler: Callable[[str, str], str]) -> None + + Устанавливает шаблон для форматирования описания команды. + + Обработчик принимает триггер команды (``str``) и её описание (``str``). + +------ + +.. py:method:: set_incorrect_input_syntax_handler(self, handler: Callable[[str], None]) -> None + + Устанавливает обработчик при некорректном введённом синтаксисе флагов. + + Обработчик принимает строку, введённую пользователем. + +------ + +.. py:method:: set_repeated_input_flags_handler(self, handler: Callable[[str], None]) -> None + + Устанавливает обработчик при повторяющихся флагах в введённой команде. + + Обработчик принимает строку, введённую пользователем. + +------ + +.. py:method:: set_unknown_command_handler(self, handler: Callable[[InputCommand], None]) -> None + + Устанавливает обработчик при вводе неизвестной команды. + + Обработчик принимает объект ``InputCommand`` - объект введённой команды. + +----- + +.. py:method:: set_empty_command_handler(self, handler: Callable[[], None]) -> None + + Устанавливает обработчик при вводе пустой строки. + + Обработчик не принимает аргументов. + +----- + +.. py:method:: set_exit_command_handler(self, handler: Callable[[Response], None]) -> None + + Переопределяет стандартное поведение при вызове команды выхода. + + Обработчик принимает объект ``Response``. + +.. toctree:: + :hidden: + + autocompleter + dividing_lines + +----- + +.. _root_api_predefined_messages: + +PredefinedMessages +------------------ + +``PredefinedMessages`` — это контейнер, содержащий набор готовых к использованию сообщений. Они отформатированы с использованием синтаксиса ``rich`` и предназначены для вывода стандартной информации, такой как подсказки по использованию. + +Рекомендуется использовать их при старте приложения. + +.. code-block:: python + :linenos: + + from argenta import App, Orchestrator + from argenta.app import PredefinedMessages + + app: App = App() + orchestrator: Orchestrator = Orchestrator() + + def main(): + app.add_message_on_startup(PredefinedMessages.USAGE) + app.add_message_on_startup(PredefinedMessages.AUTOCOMPLETE) + app.add_message_on_startup(PredefinedMessages.HELP) + + orchestrator.start_polling(app) + + if __name__ == "__main__": + main() + + +.. py:class:: PredefinedMessages + :no-index: + + .. py:attribute:: USAGE + + Строка: ``[b dim]Usage[/b dim]: [i] <[green]flags[/green]>[/i]`` + + Отображается как: ``Usage: `` + + .. py:attribute:: HELP + + Строка: ``[b dim]Help[/b dim]: [i][/i] [b red]--help[/b red]`` + + Отображается как: ``Help: --help`` + + .. py:attribute:: AUTOCOMPLETE + + Строка: ``[b dim]Autocomplete[/b dim]: [i][/i] [bold]`` + + Отображается как: ``Autocomplete: `` diff --git a/docs/root/api/bridge.rst b/docs/root/api/bridge.rst new file mode 100644 index 0000000..33c8862 --- /dev/null +++ b/docs/root/api/bridge.rst @@ -0,0 +1,62 @@ +.. _root_api_bridge: + +DataBridge +========== + +``DataBridge`` — это сущность, предоставляющая временное хранилище данных, которое существует в рамках одной сессии приложения (от запуска до выхода). Она предназначена для обмена данными между обработчиками. + +Основной способ получения доступа к ``DataBridge`` — через DI. + +.. code-block:: python + :linenos: + + from argenta.di import FromDishka + from argenta import DataBridge, Response + + # ... setting up router and other + + def my_handler(response: Response, data_bridge: FromDishka[DataBridge]): + # ... your code + +**Практический пример: Аутентификация** + +Рассмотрим пример, где команда `login` сохраняет токен аутентификации, а команда `get-profile` использует его. + +.. literalinclude:: ../../code_snippets/response/data_sharing.py + :language: python + :linenos: + +**Как это работает:** + +1. При вызове обработчика ``dishka`` автоматически внедряет экземпляр ``DataBridge``. +2. Команда ``login --username <имя>`` вызывает ``login_handler``, который через внедрённый ``data_bridge`` сохраняет токен. +3. Команда ``get-profile`` вызывает ``get_profile_handler``, который так же получает ``data_bridge`` и извлекает из него токен. + +----------- + +.. py:class:: DataBridge + + .. py:method:: __init__(self, initial_data: dict | None = None) + :no-index: + + Инициализирует хранилище. При использовании через DI вызывается автоматически. + + .. py:method:: update(self, data: dict) -> None + + Обновляет хранилище данными из словаря. + + .. py:method:: get_all(self) -> dict + + Возвращает все данные из хранилища. + + .. py:method:: get_by_key(self, key: str) -> Any + + Возвращает значение по ключу или ``None``, если ключ не найден. + + .. py:method:: delete_by_key(self, key: str) -> None + + Удаляет значение по ключу. Вызывает ``KeyError``, если ключ не найден. + + .. py:method:: clear_all(self) -> None + + Полностью очищает хранилище. diff --git a/docs/root/api/command/flag.rst b/docs/root/api/command/flag.rst new file mode 100644 index 0000000..1979edb --- /dev/null +++ b/docs/root/api/command/flag.rst @@ -0,0 +1,257 @@ +.. _root_api_command_flag: + +Flag +===== + +``Flag`` — это сущность, описывающая флаг команды. Её основная задача — определить параметры флага, включая его имя, префикс и правила валидации. + +.. seealso:: + + Документация по :ref:`PossibleValues ` — перечисление, определяющее типы допустимых значений. + + Документация по :ref:`InputFlag ` — объект обработанного флага, введённого пользователем. + + :ref:`Общая информация ` о флагах и их использовании в ``Argenta`` + +----- + +Инициализация +------------- + +.. code-block:: python + :linenos: + + __init__( + self, name: str, *, + prefix: Literal["-", "--", "---"] = "--", + possible_values: list[str] | Pattern[str] | PossibleValues = PossibleValues.ALL, + ) -> None + +Создаёт новый флаг для регистрации в команде. + +* ``name``: Имя флага (обязательный параметр). +* ``prefix``: Префикс флага (``-``, ``--``, ``---``). По умолчанию ``--``. +* ``possible_values``: Правила валидации значения. Может быть списком строк, регулярным выражением или значением из ``PossibleValues``. По умолчанию ``PossibleValues.ALL``, то есть любое значение допустимо. + +**Атрибуты:** + +.. py:attribute:: name + + Имя флага в виде строки. + +.. py:attribute:: prefix + + Префикс флага. Один из: ``"-"``, ``"--"``, ``"---"``. + +.. py:attribute:: possible_values + + Допустимые значения для флага. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flag/snippet.py + :linenos: + :language: python + +----- + +Свойства +-------- + +string_entity +~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + @property + string_entity(self) -> str + +Возвращает строковое представление флага в формате ``prefix + name``. + +:return: Строковое представление флага + +Это свойство объединяет префикс и имя в единую строку, которая представляет флаг так, как он выглядел бы в командной строке. + +----- + +Магические методы +----------------- + +__str__ +~~~~~~~ + +.. code-block:: python + :linenos: + + __str__(self) -> str + +Возвращает строковое представление флага (аналогично ``string_entity``). + +:return: Строковое представление флага + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flag/snippet4.py + :linenos: + :language: python + +----- + +__repr__ +~~~~~~~~ + +.. code-block:: python + :linenos: + + __repr__(self) -> str + +Возвращает отладочное представление объекта. + +:return: Строка в формате ``Flag``. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flag/snippet5.py + :linenos: + :language: python + +----- + +__eq__ +~~~~~~ + +.. code-block:: python + :linenos: + + __eq__(self, other: object) -> bool + +Сравнивает два флага на равенство по их строковому представлению (``string_entity``). + +:param other: Объект для сравнения +:return: **True**, если флаги равны, иначе **False** + +Два флага считаются равными, если их ``string_entity`` идентичны. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flag/snippet6.py + :linenos: + :language: python + +----- + +.. _root_api_command_flag_predefined_flags: + +PredefinedFlags +--------------- + +``argenta.command.PredefinedFlags`` + +Класс ``PredefinedFlags`` предоставляет набор готовых флагов для использования в приложениях без их ручного создания. Эти флаги покрывают распространённые сценарии. + +Все предопределённые флаги являются атрибутами класса и представляют собой готовые экземпляры ``Flag``. + +----- + +Информационные флаги +~~~~~~~~~~~~~~~~~~~~ + + +.. py:attribute:: PredefinedFlags.HELP + + Флаг для отображения справки: ``--help`` + + * ``name``: ``"help"`` + * ``prefix``: ``"--"`` (по умолчанию) + * ``possible_values``: ``PossibleValues.NEITHER`` + +.. py:attribute:: PredefinedFlags.SHORT_HELP + + Короткая версия флага справки: ``-H`` + + * ``name``: ``"H"`` + * ``prefix``: ``"-"`` + * ``possible_values``: ``PossibleValues.NEITHER`` + +.. py:attribute:: PredefinedFlags.INFO + + Флаг для отображения информации: ``--info`` + + * ``name``: ``"info"`` + * ``prefix``: ``"--"`` (по умолчанию) + * ``possible_values``: ``PossibleValues.NEITHER`` + +.. py:attribute:: PredefinedFlags.SHORT_INFO + + Короткая версия флага информации: ``-I`` + + * ``name``: ``"I"`` + * ``prefix``: ``"-"`` + * ``possible_values``: ``PossibleValues.NEITHER`` + +----- + +Флаги выбора +~~~~~~~~~~~~ + +.. py:attribute:: PredefinedFlags.ALL + + Флаг для выбора всех элементов: ``--all`` + + * ``name``: ``"all"`` + * ``prefix``: ``"--"`` + * ``possible_values``: ``PossibleValues.NEITHER`` + +.. py:attribute:: PredefinedFlags.SHORT_ALL + + Короткая версия флага выбора всех элементов: ``-A`` + + * ``name``: ``"A"`` + * ``prefix``: ``"-"`` + * ``possible_values``: ``PossibleValues.NEITHER`` + +----- + +Сетевые флаги +~~~~~~~~~~~~~ + +.. py:attribute:: PredefinedFlags.HOST + + Флаг для указания IP-адреса хоста: ``--host`` + + * ``name``: ``"host"`` + * ``prefix``: ``"--"`` (по умолчанию) + * ``possible_values``: Регулярное выражение для валидации IPv4: ``r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"`` + +.. py:attribute:: PredefinedFlags.SHORT_HOST + + Короткая версия флага хоста: ``-H`` + + * ``name``: ``"H"`` + * ``prefix``: ``"-"`` + * ``possible_values``: Регулярное выражение для валидации IPv4: ``r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"`` + +.. py:attribute:: PredefinedFlags.PORT + + Флаг для указания порта: ``--port`` + + * ``name``: ``"port"`` + * ``prefix``: ``"--"`` (по умолчанию) + * ``possible_values``: Регулярное выражение для валидации порта: ``r"^\d{1,5}$"`` + +.. py:attribute:: PredefinedFlags.SHORT_PORT + + Короткая версия флага порта: ``-P`` + + * ``name``: ``"P"`` + * ``prefix``: ``"-"`` + * ``possible_values``: Регулярное выражение для валидации порта: ``r"^\d{1,5}$"`` + +----- + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flag/predefined_flags.py + :linenos: + :language: python diff --git a/docs/root/api/command/flags.rst b/docs/root/api/command/flags.rst new file mode 100644 index 0000000..89fb525 --- /dev/null +++ b/docs/root/api/command/flags.rst @@ -0,0 +1,113 @@ +.. _root_api_command_flags: + +Flags +====== + +``Flags`` — это коллекция флагов команды. Её основная задача — группировать и управлять набором флагов, зарегистрированных для конкретной команды. ``Flags`` служит контейнером, который позволяет удобно добавлять, извлекать, итерировать флаги и проверять их наличие. + +.. seealso:: + + Документация по отдельным флагам (:ref:`Flag `, :ref:`InputFlag `) + + Документация по :ref:`InputFlags ` — коллекция обработанных флагов, введённых пользователем. + + :ref:`Общая информация ` о флагах и их использовании в приложении ``Argenta`` + +----- + +Инициализация +------------- + +.. code-block:: python + :linenos: + + __init__(self, flags: list[Flag] | None = None) -> None + +Создаёт новую коллекцию флагов. + +* ``flags``: Необязательный список флагов типа ``Flag`` для инициализации коллекции. Если не указан, создаётся пустая коллекция. + +**Атрибуты:** + +.. py:attribute:: flags + :no-index: + + Список всех зарегистрированных флагов типа ``Flag``. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flags/snippet.py + :linenos: + :language: python + +----- + +Методы +------ + +add_flag +~~~~~~~~ + +.. code-block:: python + :linenos: + + add_flag(self, flag: Flag) -> None + +Добавляет флаг в коллекцию. + +:param flag: Флаг типа ``Flag`` для добавления. +:return: None. + +Используется для динамического расширения набора флагов. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flags/snippet2.py + :linenos: + :language: python + +----- + +add_flags +~~~~~~~~~ + +.. code-block:: python + :linenos: + + add_flags(self, flags: list[Flag]) -> None + +Добавляет в коллекцию список флагов. + +:param flags: Список флагов типа ``Flag`` для добавления. +:return: None. + +Метод расширяет коллекцию, добавляя в неё все флаги из переданного списка. Эффективен для пакетного добавления. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flags/snippet3.py + :linenos: + :language: python + +----- + +get_flag_by_name +~~~~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + get_flag_by_name(self, name: str) -> Flag | None + +Возвращает флаг по имени. + +:param name: Имя искомого флага. +:return: Объект ``Flag`` или ``None``, если флаг не найден. + +Метод возвращает флаг с соответствующим именем. Если флаг не найден, возвращается ``None``. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/flags/snippet4.py + :linenos: + :language: python diff --git a/docs/root/api/command/index.rst b/docs/root/api/command/index.rst new file mode 100644 index 0000000..f3afcdf --- /dev/null +++ b/docs/root/api/command/index.rst @@ -0,0 +1,128 @@ +.. _root_api_command_index: + +Command +======= + +``Command`` — это основная единица функциональности в приложении. Каждая команда связывает хэндлер с триггером, введя который он будет вызван для обработки. + +``Command`` инкапсулирует всю информацию о команде: её триггер (ключевое слово для вызова), описание, набор флагов и список псевдонимов. + +----- + +Инициализация +------------- + +.. code-block:: python + :linenos: + + __init__(self, trigger: str, *, + description: str | None = None, + flags: Flag | Flags = DEFAULT_WITHOUT_FLAGS, + aliases: list[str] | list[Never] = DEFAULT_WITHOUT_ALIASES) -> None + +Создаёт новую команду для регистрации в роутере. + +* ``trigger``: Строковый триггер, который пользователь вводит для вызова команды. Является основным идентификатором. +* ``description``: Необязательное описание, объясняющее назначение команды. Отображается в справке. +* ``flags``: Набор флагов для настройки поведения. Может быть одиночным объектом ``Flag`` или коллекцией ``Flags``. +* ``aliases``: Список строковых псевдонимов для основного триггера. + +**Атрибуты:** + +.. py:attribute:: trigger + + Основной триггер команды. Используется для её идентификации при обработке пользовательского ввода. + +.. py:attribute:: description + + Текстовое описание команды. Если не передано, используется значение по умолчанию. + +.. py:attribute:: registered_flags + + Объект ``Flags``, содержащий все зарегистрированные флаги. Если был передан ``Flag``, то автоматически конвертируется из одиночного в коллекцию при инициализации. + +.. py:attribute:: aliases + + Список строковых псевдонимов. Пуст, если псевдонимы не заданы. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/command/snippet.py + :linenos: + +.. seealso :: + Подробнее про флаги: :ref:`Flags ` и :ref:`Флаги команд `. + +----- + +Регистрация команд +------------------ + +Команды передаются в качестве аргумента в декоратор ``@router.command()``. + +**Базовый пример:** + +.. literalinclude:: ../../../code_snippets/command/snippet2.py + :linenos: + +**Команды с флагами:** + +.. literalinclude:: ../../../code_snippets/command/snippet3.py + :linenos: + +----- + +Работа с псевдонимами +--------------------- + +Псевдонимы позволяют вызывать один и тот же обработчик разными триггерами, сохраняя флаги и описание команды. + +**Пример с псевдонимами:** + +.. literalinclude:: ../../../code_snippets/command/snippet5.py + :linenos: + +Теперь пользователь может вызвать команду любым из способов: + +.. code-block:: bash + + shutdown + poweroff + halt + stop + +Все эти варианты вызовут один и тот же хэндлер ``handle_shutdown``. + +----- + +.. _root_api_command_input_command: + +InputCommand +------------ + +``InputCommand`` представляет собой обработанную команду, введённую пользователем. Этот внутренний класс создаётся автоматически при обработке пользовательского ввода. Прямая работа с ним возможна при создании пользовательского обработчика для неизвестных команд. + +.. seealso :: + Подробнее о пользовательских обработчиках исключений см. :ref:`здесь `. + +**Атрибуты:** + +.. py:attribute:: trigger + :no-index: + + Строковый триггер, введённый пользователем. + +.. py:attribute:: input_flags + :no-index: + + Объект ``InputFlags``, содержащий все введённые и распаршенные флаги. + +.. toctree :: + :hidden: + + flag + possible_values + input_flag + validation_status + flags + input_flags diff --git a/docs/root/api/command/input_flag.rst b/docs/root/api/command/input_flag.rst new file mode 100644 index 0000000..1932707 --- /dev/null +++ b/docs/root/api/command/input_flag.rst @@ -0,0 +1,116 @@ +.. _root_api_command_input_flag: + +InputFlag +========= + +Объект ``InputFlag`` представляет собой флаг, введённый пользователем. Он создаётся в результате обработки пользовательского ввода и содержит информацию о распознанном флаге: его имя, префикс, значение и статус валидации. + +.. seealso:: + + Документация по :ref:`Flag ` — класс для регистрации флага. + + Документация по :ref:`ValidationStatus ` — статусы валидации флагов. + +----- + +.. warning :: + Экземпляры этого класса не предназначены для прямого создания. Они содержатся в объекте :ref:`Response `. + +**Атрибуты:** + +.. py:attribute:: name + :no-index: + + Имя введённого флага. + +.. py:attribute:: prefix + :no-index: + + Префикс флага: ``-``, ``--`` или ``---``. + +.. py:attribute:: input_value + + Значение, переданное с флагом. Может быть ``''`` (пустой строкой) для флагов без значений. + +.. py:attribute:: status + :no-index: + + Статус валидации флага: ``ValidationStatus.VALID``, ``ValidationStatus.INVALID`` или ``ValidationStatus.UNDEFINED``. + +----- + +Свойства +-------- + +string_entity +~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + @property + string_entity(self) -> str + +Возвращает строковое представление флага в формате ``prefix + name``. + +:return: Строковое представление флага + +----- + +Магические методы +----------------- + +__str__ +~~~~~~~ + +.. code-block:: python + :linenos: + + __str__(self) -> str + +Возвращает строковое представление флага вместе с его значением. + +:return: Строка в формате ``флаг значение``. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/input_flag/snippet3.py + :linenos: + :language: python + +----- + +__repr__ +~~~~~~~~ + +.. code-block:: python + :linenos: + + __repr__(self) -> str + +Возвращает отладочное представление объекта. + +:return: Строка в формате ``InputFlag``. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/input_flag/snippet4.py + :linenos: + :language: python + +----- + +__eq__ +~~~~~~ + +.. code-block:: python + :linenos: + + __eq__(self, other: object) -> bool + +Сравнивает два введённых флага на равенство по имени. + +:param other: Объект для сравнения. +:return: **True**, если имена флагов совпадают, иначе **False**. + +Два введённых флага считаются равными, если их имена совпадают. diff --git a/docs/root/api/command/input_flags.rst b/docs/root/api/command/input_flags.rst new file mode 100644 index 0000000..64bea46 --- /dev/null +++ b/docs/root/api/command/input_flags.rst @@ -0,0 +1,135 @@ +.. _root_api_command_input_flags: + +InputFlags +========== + +``InputFlags`` — это коллекция флагов, введённых пользователем. Её основная задача — группировать и управлять набором флагов, переданных вместе с командой. ``InputFlags`` служит контейнером, который позволяет удобно извлекать, итерировать и проверять наличие флагов, а также работать с их значениями и статусами валидации. + +.. seealso:: + + Документация по отдельным флагам (:ref:`Flag `, :ref:`InputFlag `) + + Документация по :ref:`InputFlags ` — коллекция обработанных флагов, введённых пользователем. + + Документация по :ref:`Response ` — объект ответа, содержащий ``InputFlags`` + + :ref:`Общая информация ` о флагах и их использовании в приложении ``Argenta`` + +----- + +Инициализация +------------- + +.. code-block:: python + :linenos: + + __init__(self, flags: list[InputFlag] | None = None) -> None + +Создаёт новую коллекцию введённых флагов. + +* ``flags``: Необязательный список флагов типа ``InputFlag`` для инициализации коллекции. Если не указан, создаётся пустая коллекция. + +.. warning :: + Экземпляры этого класса обычно не создаются напрямую. Они автоматически формируются системой при обработке пользовательского ввода и доступны через атрибут ``input_flags`` объекта ``Response``. + +**Атрибуты:** + +.. py:attribute:: flags + :no-index: + + Список всех введённых флагов типа ``InputFlag``. Пуст, если флаги не были переданы при инициализации или пользователь не ввёл их с командой. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/input_flags/snippet1.py + :linenos: + :language: python + +----- + +Методы +------ + +get_flag_by_name +~~~~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + get_flag_by_name(self, name: str) -> InputFlag | None + +Возвращает флаг по имени. + +:param name: Имя искомого флага (без префикса). +:return: Объект ``InputFlag`` или ``None``, если флаг не найден. + +Метод возвращает первый флаг с соответствующим именем (без учёта префикса). + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/input_flags/snippet2.py + :linenos: + :language: python + +----- + +add_flag +~~~~~~~~ + +.. code-block:: python + :linenos: + + add_flag(self, flag: InputFlag) -> None + +Добавляет введённый флаг в коллекцию. + +:param flag: Флаг типа ``InputFlag`` для добавления. +:return: None. + +Метод добавляет флаг в конец списка ``flags``. Используется для динамического расширения коллекции. + +.. note:: + Этот метод используется редко, так как `InputFlags` обычно создаётся автоматически. Однако он может быть полезен для тестирования или ручного создания коллекций. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/input_flags/snippet3.py + :linenos: + :language: python + +----- + +add_flags +~~~~~~~~~ + +.. code-block:: python + :linenos: + + add_flags(self, flags: list[InputFlag]) -> None + +Добавляет в коллекцию список введённых флагов. + +:param flags: Список флагов типа ``InputFlag`` для добавления. +:return: None. + +Метод расширяет коллекцию, добавляя в неё все флаги из переданного списка. Эффективен для пакетного добавления. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/input_flags/snippet4.py + :linenos: + :language: python + +----- + +Практические примеры +-------------------- + +Обработка всех флагов с проверкой статусов +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/input_flags/snippet10.py + :linenos: + :language: python diff --git a/docs/root/api/command/possible_values.rst b/docs/root/api/command/possible_values.rst new file mode 100644 index 0000000..87b4b98 --- /dev/null +++ b/docs/root/api/command/possible_values.rst @@ -0,0 +1,92 @@ +.. _root_api_command_possible_values: + + +PossibleValues +============== + +``PossibleValues`` — это перечисление, которое определяет специальные режимы валидации для значений флагов. ``PossibleValues`` используется в параметре ``possible_values`` класса ``Flag``, чтобы указать, может ли флаг принимать значения и какие ограничения на них накладываются. + +``PossibleValues`` содержит два основных значения: ``NEITHER`` (для флагов, которые не могут принимать значения) и ``ALL`` (для флагов, принимающих любые значения). Это перечисление используется вместе со списками строк и регулярными выражениями для создания гибкой системы валидации. + +.. note:: + Результат валидации доступен через атрибут ``status`` у экземпляра ``InputFlag``. Подробнее см. :ref:`здесь `. + +.. seealso:: + + Документация по :ref:`Flag ` — класс флага, использующий ``PossibleValues``. + + Документация по :ref:`ValidationStatus ` — результат валидации ввёденного флага. + + :ref:`Общая информация ` о флагах и их использовании в приложении ``Argenta`` + +----- + +NEITHER +~~~~~~~ + +.. code-block:: python + :linenos: + + PossibleValues.NEITHER = 'NEITHER' + +Указывает, что флаг **не должен** иметь значения. + +Флаги с этим значением работают как булевы переключатели: их наличие в командной строке само по себе является информацией. Попытка передать такому флагу значение приведёт к ошибке валидации. + +**Примеры флагов с** ``NEITHER``: + +* ``--help`` — флаг справки +* ``--verbose`` — флаг подробного вывода +* ``--force`` — флаг принудительного выполнения +* ``-A`` / ``--all`` — флаг выбора всех элементов + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/possible_values/neither.py + :linenos: + :language: python + +----- + +ALL +~~~ + +.. code-block:: python + :linenos: + + PossibleValues.ALL = 'ALL' + +Указывает, что флаг может принимать **любое** значение. + +Флаги с этим значением универсальны и не накладывают ограничений на передаваемые данные. Валидация всегда будет успешной. + +**Примеры флагов с** ``ALL``: + +* ``--message`` — произвольное текстовое сообщение +* ``--name`` — произвольное имя + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/possible_values/all.py + :linenos: + :language: python + +----- + +Параметр possible_values +~~~~~~~~~~~~~~~~~~~~~~~~~ + +``PossibleValues`` используется как один из возможных типов для параметра ``possible_values`` при создании экземпляра ``Flag``. + +**Доступные типы для** ``possible_values``: + +1. ``PossibleValues.NEITHER``: флаг без значения. +2. ``PossibleValues.ALL``: флаг с любым значением (по умолчанию). +3. ``list[str]``: флаг с ограниченным набором значений. +4. ``Pattern[str]``: флаг со значением, проверяемым по регулярному выражению. + +**Пример комбинированного использования:** + +.. literalinclude:: ../../../code_snippets/possible_values/combined.py + :linenos: + :language: python diff --git a/docs/root/api/command/validation_status.rst b/docs/root/api/command/validation_status.rst new file mode 100644 index 0000000..9a22eca --- /dev/null +++ b/docs/root/api/command/validation_status.rst @@ -0,0 +1,78 @@ +.. _root_api_command_validation_status: + +ValidationStatus +================ + +``ValidationStatus`` — это перечисление, которое определяет состояние валидации флага. Его задача — предоставить стандартные константы для отображения результата проверки. ``ValidationStatus`` используется в атрибуте ``status`` класса ``InputFlag``. + +``ValidationStatus`` содержит три значения: **VALID** (корректный флаг), **INVALID** (некорректный) и **UNDEFINED** (незарегистрированный). + +.. note:: + + Статус валидации устанавливается автоматически при создании экземпляра ``InputFlag`` на основе правил, заданных в соответствующем ``Flag``. + +.. seealso:: + + Документация по :ref:`InputFlag ` — класс введённого флага, использующий ``ValidationStatus``. + + Документация по :ref:`Flag ` — класс флага с правилами валидации. + + Документация по :ref:`PossibleValues ` — типы допустимых значений. + +----- + +VALID +~~~~~ + +.. code-block:: python + :linenos: + + ValidationStatus.VALID = 'VALID' + +Указывает, что флаг и его значение **прошли** валидацию. + +Флаги с этим статусом соответствуют правилам, заданным в ``possible_values`` соответствующего ``Flag``. Их можно безопасно использовать в логике приложения без дополнительных проверок. + +**Условия получения статуса** ``VALID``: + +* Флаг с ``PossibleValues.NEITHER`` передан без значения. +* Флаг с ``PossibleValues.ALL`` передан с любым значением или без него. +* Значение флага входит в список разрешённых. +* Значение флага соответствует регулярному выражению. + +----- + +INVALID +~~~~~~~ + +.. code-block:: python + :linenos: + + ValidationStatus.INVALID = 'INVALID' + +Указывает, что флаг или его значение **не прошли** валидацию. + +Флаги с этим статусом нарушают правила, заданные в ``possible_values`` соответствующего ``Flag``. Их следует обрабатывать как ошибочные. + +**Условия получения статуса** ``INVALID``: + +* Флаг с ``PossibleValues.NEITHER`` передан со значением. +* Значение флага не входит в список разрешённых. +* Значение флага не соответствует регулярному выражению. +* Флаг требует значение, но передан без него. + +----- + +UNDEFINED +~~~~~~~~~ + +.. code-block:: python + :linenos: + + ValidationStatus.UNDEFINED = 'UNDEFINED' + +Указывает, что введённый флаг не был зарегистрирован в команде. + +**Условия получения статуса** ``UNDEFINED``: + +* Введённый флаг не найден среди зарегистрированных для данной команды. diff --git a/docs/root/api/index.rst b/docs/root/api/index.rst new file mode 100644 index 0000000..b360276 --- /dev/null +++ b/docs/root/api/index.rst @@ -0,0 +1,97 @@ +.. _root_api_index: + + +Публичное API +============= + +Описание раздела +---------------- + +В этом разделе описан публичный API библиотеки. Он включает: + +- Классы и функции для интеграции в ваши приложения. +- Рекомендации по использованию и поддерживаемые сценарии. +- Примеры кода, подробные сигнатуры и описание возвращаемых значений. +- Гарантии стабильности и обратной совместимости. + +Интерфейсы, не описанные в этом разделе, считаются внутренними. Их использование может привести к ошибкам при обновлении библиотеки. При разработке собственных решений используйте только компоненты, описанные здесь. Это обеспечит стабильность и совместимость ваших продуктов с будущими версиями ``Argenta``. + +----- + +Публичные импорты +----------------- + +Все основные компоненты библиотеки доступны для прямого импорта из корневого пакета ``argenta`` или его подмодулей. + +.. rubric:: Основные компоненты + +.. code-block:: python + + from argenta import App, Orchestrator, Router, Command, Response + +* :ref:`App ` — Объект приложения, который отвечает за логику роутинга, настройки, валидации и т.д. +* :ref:`Orchestrator ` — Класс для конфигурирования и запуска всего приложения. +* :ref:`Router ` — Класс для группировки и регистрации команд. +* :ref:`Command ` — Класс для создания команд при инициализации хэндлеров. +* :ref:`Response ` — Объект ответа, передаваемый в обработчики. + +.. rubric:: Команды и флаги + +.. code-block:: python + + from argenta.command import ( + Flag, + Flags, + InputFlag, + InputFlags, + PossibleValues, + ValidationStatus, + PredefinedFlags + ) + +* :ref:`Flag ` — Класс для описания флага. +* :ref:`Flags ` — Коллекция для регистрации флагов. +* :ref:`InputFlag ` — Класс для введённого пользователем флага. +* :ref:`InputFlags ` — Коллекция введённых флагов. +* :ref:`PossibleValues ` — Правила валидации значений флага. +* :ref:`ValidationStatus ` — Статусы валидации флагов. +* :ref:`PredefinedFlags ` — Коллекция предопределённых флагов. + +.. rubric:: Настройка приложения + +.. code-block:: python + + from argenta.app import ( + AutoCompleter, + StaticDividingLine, + DynamicDividingLine, + PredefinedMessages + ) + +* :ref:`AutoCompleter ` - Класс для настройки автодополнения. +* :ref:`StaticDividingLine ` — Статическая разделительная линия для оформления вывода. +* :ref:`DynamicDividingLine ` — Динамическая разделительная линия для оформления вывода. +* :ref:`PredefinedMessages ` — Готовые сообщения для вывода при старте приложения. + +.. rubric:: Внедрение зависимостей + +.. code-block:: python + + from argenta.di import ( + FromDishka, + inject + ) + +* :ref:`FromDishka ` — Маркер аргумента функции как зависимости, которая должна быть инжектирована. +* :ref:`inject ` — Декоратор для инжектирования зависимостей, указанных в сигнатуре. + + +.. toctree:: + :hidden: + + app/index + router + orchestrator/index + command/index + response + bridge \ No newline at end of file diff --git a/docs/root/api/orchestrator/argparser.rst b/docs/root/api/orchestrator/argparser.rst new file mode 100644 index 0000000..60eb582 --- /dev/null +++ b/docs/root/api/orchestrator/argparser.rst @@ -0,0 +1,86 @@ +.. _root_api_orchestrator_argparser: + +ArgParser +========== + +``ArgParser`` предназначен для обработки **аргументов командной строки**, передаваемых приложению при запуске. Важно не путать их с флагами, которые пользователь вводит в интерактивном режиме. ``ArgParser`` позволяет получать внешнюю конфигурацию в момент старта (например, путь к файлу настроек, флаги отладки или режим запуска). + +----- + +Инициализация +------------- + +.. code-block:: python + :linenos: + + def __init__(self, processed_args: list[ValueArgument | BooleanArgument], *, + name: str = "Argenta", + description: str = "Argenta available arguments", + epilog: str = "github.com/koloideal/Argenta | made by kolo") + +Создаёт экземпляр парсера аргументов командной строки. + +* ``processed_args``: Список аргументов для обработки при запуске приложения. Подробнее см. :ref:`здесь `. +* ``name``: Имя приложения для отображения в справке. +* ``description``: Описание приложения для отображения в справке. +* ``epilog``: Дополнительная информация для отображения в конце справки. + +----- + +Атрибуты +-------- + +.. py:attribute:: parsed_argspace: ArgSpace + + Экземпляр ``ArgSpace``, содержащий все обработанные аргументы командной строки. Подробнее см. :ref:`здесь `. + +.. caution:: + До инициализации ``Orchestrator``, в конструктор которого был передан экземпляр ``ArgParser``, атрибут ``parsed_argspace`` будет содержать пустой ``ArgSpace``. + + Парсинг и валидация аргументов происходят при инициализации ``Orchestrator``, поэтому использовать ``parsed_argspace`` **целесообразно только после** этого. + +----- + +Лучшие практики +--------------- + +Использовать атрибут ``parsed_argspace`` рекомендуется только на этапе настройки приложения. В обработчиках лучшей практикой является получение ``ArgSpace`` через DI. Подробнее см. :ref:`здесь `. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/argparser/snippet.py + :language: python + :linenos: + +Обработка ошибок +---------------- + +.. seealso:: + Про типы аргументов подробнее в :ref:`Arguments ` + +При работе с аргументами командной строки стандартный ``ArgumentParser`` автоматически обрабатывает следующие ситуации: + +**Отсутствие обязательного аргумента:** + +.. code-block:: bash + + $ python app.py + usage: Argenta [-h] --config CONFIG + Argenta: error: the following arguments are required: --config + +**Недопустимое значение из списка possible_values:** + +.. code-block:: bash + + $ python app.py --config app.yaml --log-level TRACE + usage: Argenta [-h] --log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL} + Argenta: error: argument --log-level: invalid choice: 'TRACE' + +**Использование устаревшего аргумента:** + +При использовании аргумента с ``is_deprecated=True`` выводится предупреждение, но выполнение продолжается: + +.. code-block:: bash + + $ python app.py --old-param value + Warning: argument --old-param is deprecated diff --git a/docs/root/api/orchestrator/argspace.rst b/docs/root/api/orchestrator/argspace.rst new file mode 100644 index 0000000..06a5c7f --- /dev/null +++ b/docs/root/api/orchestrator/argspace.rst @@ -0,0 +1,103 @@ +.. _root_api_orchestrator_argspace: + +ArgSpace +========== + +``ArgSpace`` — это контейнер для хранения и управления обработанными аргументами командной строки. Его основная задача — предоставить удобный интерфейс для доступа к значениям, переданным при запуске приложения. + +``ArgSpace`` создаётся автоматически после обработки аргументов с помощью ``ArgParser`` и содержит коллекцию объектов ``InputArgument``. + +----- + +Инициализация +------------- + +Создание экземпляров класса ``ArgSpace`` происходит под `капотом`, вам не нужно создавать их вручную. + +**Атрибуты:** + +.. py:attribute:: all_arguments + + Список всех обработанных аргументов типа ``InputArgument``. + +----- + +Методы +------ + +get_by_name +~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + get_by_name(self, name: str) -> InputArgument | None + +Возвращает аргумент по имени. + +:param name: Имя искомого аргумента. +:return: Объект ``InputArgument`` или ``None``, если аргумент не найден. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/argspace/snippet4.py + :linenos: + +----- + +get_by_type +~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + get_by_type(self, arg_type: type[BaseArgument]) -> list[InputArgument] | list[Never] + +Возвращает все аргументы определённого типа. + +:param arg_type: Тип аргумента (``BooleanArgument`` или ``ValueArgument``). +:return: Список аргументов указанного типа или пустой список. + +Метод фильтрует ``all_arguments`` по атрибуту ``founder_class`` и возвращает аргументы, созданные из указанного типа. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/argspace/snippet3.py + :linenos: + +----- + +InputArgument +------------- + +.. seealso :: + Документация по ``InputArgument`` находится :ref:`здесь `. + +----- + +Примеры использования +--------------------- + +``ArgSpace`` используется для доступа к значениям аргументов после запуска приложения. Типичный сценарий включает обработку аргументов через ``ArgParser`` и последующее извлечение значений из ``ArgSpace``. + +**Полный пример:** + +.. literalinclude:: ../../../code_snippets/argspace/snippet.py + :linenos: + +Доступ к аргументам из обработчиков осуществляется с помощью DI. Подробнее см. :ref:`здесь `. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/argspace/snippet2.py + :linenos: + +**Запуск приложения:** + +.. code-block:: bash + + python server.py --host 0.0.0.0 --port 9000 + # Output: + # Server configuration: + # Host: 0.0.0.0 + # Port: 9000 diff --git a/docs/root/api/orchestrator/arguments.rst b/docs/root/api/orchestrator/arguments.rst new file mode 100644 index 0000000..43467eb --- /dev/null +++ b/docs/root/api/orchestrator/arguments.rst @@ -0,0 +1,137 @@ +.. _root_api_orchestrator_arguments: + +Arguments +========= + +Модуль ``Arguments`` предоставляет классы для работы с аргументами командной строки. Они позволяют настраивать поведение приложения в момент его запуска, передавая различные параметры конфигурации. + +Аргументы регистрируются в ``ArgParser`` и после обработки становятся доступными в объекте ``ArgSpace``. + +----- + +ValueArgument +------------- + +Класс для аргументов, требующих передачи значения. + +.. py:class:: ValueArgument(BaseArgument) + :no-index: + +.. code-block:: python + :linenos: + + __init__(self, name: str, *, + prefix: Literal["-", "--", "---"] = "--", + help: str = "Help message for the value argument", + possible_values: list[str] | None = None, + default: str | None = None, + is_required: bool = False, + is_deprecated: bool = False) -> None + +Создаёт аргумент командной строки, требующий значения. + +:param name: Имя аргумента +:param prefix: Префикс (по умолчанию ``--``) +:param help: Сообщение для справки (``--help``) +:param possible_values: Список допустимых значений +:param default: Значение по умолчанию, если аргумент не передан +:param is_required: Если ``True``, аргумент становится обязательным. Если не передать при запуске, приложение не запустится +:param is_deprecated: Если ``True``, помечает аргумент как устаревший. Если передать при запуске, будет выведено предупреждение в консоль + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/arguments/snippet.py + :language: python + :linenos: + +**Запуск приложения:** + +.. code-block:: bash + + python app.py --host 127.0.0.1 + python app.py --host 127.0.0.1 --config custom.yaml --log-level DEBUG + +----- + +BooleanArgument +--------------- + +Класс для булевых аргументов, не требующих значения. Их наличие при запуске устанавливает значение в **True**, отсутствие — в **False**. + +.. py:class:: BooleanArgument(BaseArgument) + :no-index: + +.. code-block:: python + :linenos: + + __init__(self, name: str, *, + prefix: Literal["-", "--", "---"] = "--", + help: str = "Help message for the boolean argument", + is_deprecated: bool = False) -> None + +Создаёт булев аргумент командной строки без значения. + +:param name: Имя аргумента +:param prefix: Префикс (по умолчанию ``--``) +:param help: Сообщение для справки (``--help``) +:param is_deprecated: Если ``True``, помечает аргумент как устаревший + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/arguments/snippet2.py + :language: python + :linenos: + +**Запуск приложения:** + +.. code-block:: bash + + python app.py --verbose + python app.py --debug --no-cache + python app.py # without arguments + +----- + +.. _root_api_orchestrator_arguments_inputargument: + +InputArgument +------------- + +.. seealso:: + ``InputArgument`` напрямую связан с контейнером ``ArgSpace`` и является его наполнителем. Подробнее о нём см. :ref:`здесь `. + +Представляет собой обработанный аргумент командной строки. Этот класс используется внутри ``ArgSpace`` для хранения значений, полученных после парсинга. + +.. py:class:: InputArgument + :no-index: + +.. code-block:: python + :linenos: + + __init__(self, name: str, + value: str | Literal[True], + founder_class: type[BaseArgument]) -> None + +Создаёт экземпляр обработанного входного аргумента. + +:param name: Имя аргумента +:param value: Значение аргумента. Для ``BooleanArgument`` — **True**, если аргумент передан, и **False**, если нет; для ``ValueArgument`` — введённая строка +:param founder_class: Класс-родитель, из которого был создан аргумент (``BooleanArgument`` или ``ValueArgument``) + +**Атрибуты:** + +.. py:attribute:: name + :no-index: + + Имя аргумента, указанное при создании ``ValueArgument`` или ``BooleanArgument``. + +.. py:attribute:: value + + Значение аргумента. Тип зависит от исходного класса: + + * Для ``BooleanArgument``: **True**, если аргумент был передан + * Для ``ValueArgument``: строка с переданным значением или значением по умолчанию + +.. py:attribute:: founder_class + + Ссылка на класс-родитель. Используется для определения типа и фильтрации. diff --git a/docs/root/api/orchestrator/index.rst b/docs/root/api/orchestrator/index.rst new file mode 100644 index 0000000..949126a --- /dev/null +++ b/docs/root/api/orchestrator/index.rst @@ -0,0 +1,64 @@ +.. _root_api_orchestrator_index: + +Orchestrator +==================== + +``Orchestrator`` — это высокоуровневый компонент, который конфигурирует и оркестрирует приложение, парсер командной строки, DI и остальные компоненты, находящиеся по иерархии на уровне с ``App``. + +В то время как ``App`` отвечает за логику интерактивной сессии (ввод команд, маршрутизация), ``Orchestrator`` подготавливает окружение для его работы и служит точкой входа в приложение. + +----- + +Инициализация +------------- + +.. code-block:: python + :linenos: + + DEFAULT_ARGPARSER: ArgParser = ArgParser(processed_args=[]) + + +.. code-block:: python + :linenos: + + def __init__(self, arg_parser: ArgParser = DEFAULT_ARGPARSER, + custom_providers: list[Provider] = [], + auto_inject_handlers: bool = True) -> None + +Создаёт и конфигурирует экземпляр ``Orchestrator``. + +* ``arg_parser``: Экземпляр ``ArgParser``, отвечающий за парсинг аргументов командной строки при запуске скрипта (не путать с командами в интерактивном режиме). +* ``custom_providers``: Список пользовательских провайдеров ``dishka.Provider`` для добавления ваших сервисов (например, подключений к БД или API-клиентов) в di-контейнер. +* ``auto_inject_handlers``: Если **True** (по умолчанию), ``dishka`` автоматически внедрит зависимости в обработчики команд, инспектируя их сигнатуры. + +----- + +Основные методы +---------------- + +.. py:method:: start_polling(self, app: App) -> None + + Это главный метод, который запускает приложение. Он запускает бесконечный цикл ввода -> вывода. + + :param app: Экземпляр ``App``, который будет запущен. + +----- + +Назначение и использование +---------------------------- + +``Orchestrator`` абстрагирует сложность, связанную с настройкой DI и парсингом стартовых аргументов. + +Такой подход разделяет ответственности: ``App`` отвечает за логику интерактивной сессии, а ``Orchestrator`` — за подготовку окружения и запуск приложения. + +**Пример использования:** + +.. literalinclude:: ../../../code_snippets/orchestrator/snippet.py + :language: python + +.. toctree:: + :hidden: + + argparser + arguments + argspace diff --git a/docs/root/api/response.rst b/docs/root/api/response.rst new file mode 100644 index 0000000..3979c15 --- /dev/null +++ b/docs/root/api/response.rst @@ -0,0 +1,116 @@ +.. _root_api_response: + +Response +======== + +``Response`` — это объект, который передаётся в обработчик команды. Он создаётся автоматически при обработке пользовательского ввода и содержит статус валидации, введённые флаги. + + +.. seealso:: + + Документация по :ref:`InputFlags ` — коллекция введённых флагов команды. + + Документация по :ref:`ResponseStatus ` — статусы валидации флагов команды. + + Документация по :ref:`InputFlag ` — отдельный введённый флаг. + +----- + +Инициализация +------------- + +.. code-block:: python + :linenos: + + __init__( + self, status: ResponseStatus, + input_flags: InputFlags = EMPTY_INPUT_FLAGS, + ) + +Создаёт новый объект ответа. + +* ``status``: Общий статус валидации флагов из перечисления ``ResponseStatus``. +* ``input_flags``: Коллекция введённых флагов (``InputFlags``). По умолчанию — пустая. + +.. warning:: + Экземпляры этого класса не предназначены для прямого создания. Они автоматически формируются системой и передаются в обработчик команды в качестве первого обязательного аргумента. + +**Атрибуты:** + +.. py:attribute:: status + :no-index: + + Общий статус валидации всех флагов команды (``ResponseStatus``). Указывает, были ли среди введённых флагов некорректные или незарегистрированные. + +.. py:attribute:: input_flags + :no-index: + + Коллекция всех флагов, переданных с командой (``InputFlags``). Содержит все обработанные флаги с их значениями и статусами валидации. + +**Пример использования:** + +.. literalinclude:: ../../code_snippets/response/snippet1.py + :linenos: + :language: python + +----- + +Работа с флагами +---------------- + +``Response`` предоставляет доступ к введённым флагам через атрибут ``input_flags``. Вы можете проверять их наличие, получать значения и статусы валидации. + +**Пример работы с флагами:** + +.. literalinclude:: ../../code_snippets/response/snippet6.py + :linenos: + :language: python + +----- + +.. _root_api_response_status: + +ResponseStatus +-------------- + +``ResponseStatus`` — это перечисление, которое определяет общий статус валидации всех флагов команды. Используется в атрибуте ``status`` объекта ``Response``. + +ALL_FLAGS_VALID +~~~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + ResponseStatus.ALL_FLAGS_VALID = 'ALL_FLAGS_VALID' + +Все введённые флаги прошли валидацию. Нет ни некорректных, ни незарегистрированных флагов. + +UNDEFINED_FLAGS +~~~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + ResponseStatus.UNDEFINED_FLAGS = 'UNDEFINED_FLAGS' + +Среди введённых флагов есть незарегистрированные, но нет флагов с некорректными значениями. + +INVALID_VALUE_FLAGS +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + ResponseStatus.INVALID_VALUE_FLAGS = 'INVALID_VALUE_FLAGS' + +Среди введённых флагов есть флаги с некорректными значениями, но нет незарегистрированных. + +UNDEFINED_AND_INVALID_FLAGS +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + ResponseStatus.UNDEFINED_AND_INVALID_FLAGS = 'UNDEFINED_AND_INVALID_FLAGS' + +Среди введённых флагов есть как незарегистрированные, так и флаги с некорректными значениями. diff --git a/docs/root/api/router.rst b/docs/root/api/router.rst new file mode 100644 index 0000000..5bd3ca2 --- /dev/null +++ b/docs/root/api/router.rst @@ -0,0 +1,91 @@ +.. _root_api_router: + +Router +============= + +``Router`` — это основной строительный блок для организации логики в приложении. Его задача — группировать связанные команды и их обработчики. Каждый роутер представляет собой логический контейнер для определённого набора функций. + +Например, в приложении для управления пользователями один роутер может отвечать за аутентификацию (``login``, ``logout``), а другой — за операции с профилем (``profile-show``, ``profile-edit``). + +----- + +Инициализация +------------- + +.. code-block:: python + :linenos: + + __init__(self, title: str | None = None, + disable_redirect_stdout: bool = False) -> None + +Создаёт новый экземпляр роутера. + +* ``title``: Необязательный заголовок для группы команд. Отображается в списке доступных команд, помогая пользователю ориентироваться. +* ``disable_redirect_stdout``: Если ``True``, отключает перехват ``stdout`` для всех команд этого роутера. Это необходимо для интерактивных команд (например, с ``input()``). При отключении перехвата автоматически используется статическая разделительная линия. Подробнее см. в разделе :ref:`Переопределение стандартного вывода `. + +----- + +Регистрация команд +------------------ + +Для регистрации команды и привязки к ней обработчика используется декоратор ``@command``. + +.. py:method:: @command(self, command: Command | str) + + Декоратор для регистрации функции как обработчика команды. + + :param command: Экземпляр ``Command``, описывающий триггер, флаги и описание команды. Может быть строкой, которая станет триггером (без возможности настройки флагов и описания). + + **Пример использования:** + + .. literalinclude:: ../../code_snippets/router/snippet.py + :linenos: + :language: python + +----- + +Системный роутер +----------------------------- + +``Argenta`` поставляется со встроенным системным роутером, который автоматически подключается к каждому приложению. + +.. py:data:: system_router + :no-index: + + Предопределённый экземпляр ``Router`` с базовыми системными командами (по умолчанию — команда выхода). Имеет заголовок **«System points:»**, который можно переопределить в ``App``. + + Вы можете добавлять свои команды в этот роутер. Для этого импортируйте ``argenta.router.defaults.system_router`` и используйте его декоратор ``@command``. + +----- + +Возможные исключения +-------------------- + +При регистрации команд и флагов в ``Router`` могут возникнуть следующие исключения: + +.. py:exception:: TriggerContainSpacesException + + Выбрасывается, если триггер команды в ``Command`` содержит пробелы. Триггеры должны быть одним словом. + + **Неправильно:** ``Command("add user")`` + + **Правильно:** ``Command("add-user")`` + +.. py:exception:: RepeatedFlagNameException + + Возникает, если при определении флагов для команды были использованы дублирующиеся имена. Имена флагов в рамках одной команды должны быть уникальны. + + **Пример, вызывающий исключение:** + + .. code-block:: python + :linenos: + + Command("send", flags=[ + Flag("recipient"), + Flag("recipient") # Duplicate! + ]) + +.. py:exception:: RequiredArgumentNotPassedException + + Возникает, если обработчик команды не принимает обязательный аргумент ``Response``. + diff --git a/docs/root/code_of_conduct.rst b/docs/root/code_of_conduct.rst new file mode 100644 index 0000000..5ccd41d --- /dev/null +++ b/docs/root/code_of_conduct.rst @@ -0,0 +1,64 @@ +.. _root_code_of_conduct: + +Правила сообщества +========================== + +Наше обязательство +------------------ + +В целях создания открытой и гостеприимной атмосферы мы, как участники и мейнтейнеры, обязуемся сделать участие в нашем проекте и сообществе свободным от преследований для всех, независимо от возраста, телосложения, инвалидности, этнической принадлежности, уровня опыта, образования, социально-экономического статуса, национальности, внешности, расы или религии. + +----- + +Наши стандарты +-------------- + +Примеры поведения, которые способствуют созданию позитивной среды: + +* Проявление эмпатии и доброты по отношению к другим. +* Уважение к различным мнениям, точкам зрения и опыту. +* Предоставление и тактичное принятие конструктивной обратной связи. +* Принятие ответственности и извинения перед теми, кого затронули наши ошибки, а также извлечение уроков из этого опыта. +* Фокус на том, что лучше для всего сообщества. + +Примеры недопустимого поведения включают: + +* Троллинг, оскорбительные или уничижительные комментарии, а также личные или политические нападки. +* Публичное или частное преследование. +* Публикация личной информации других лиц (например, физического или электронного адреса) без их явного разрешения. +* Любое другое поведение, которое можно обоснованно считать неуместным в профессиональной среде. + +----- + +Наши обязанности +---------------- + +Мейнтейнеры проекта несут ответственность за разъяснение и обеспечение соблюдения стандартов приемлемого поведения и предпримут справедливые корректирующие действия в ответ на любые случаи неприемлемого поведения. + +Мейнтейнеры проекта имеют право и обязанность удалять, редактировать или отклонять комментарии, коммиты, код, правки в вики, задачи и другие вклады, которые не соответствуют настоящему Кодексу поведения, а также временно или навсегда блокировать любого участника за поведение, которое они сочтут неуместным, угрожающим, оскорбительным или вредным. + +----- + +Сфера применения +---------------- + +Настоящий Кодекс поведения применяется как в рамках проекта, так и в публичных пространствах, когда человек официально представляет сообщество. Примеры такого представительства включают использование официального адреса электронной почты, публикации через официальный аккаунт в социальных сетях или выступление в качестве назначенного представителя на онлайн- или офлайн-мероприятии. + +----- + +Обеспечение соблюдения +---------------------- + +О случаях оскорбительного, преследовательского или иного неприемлемого поведения можно сообщить команде проекта по адресу kolo.is.main@gmail.com. Все жалобы будут рассмотрены и расследованы оперативно и справедливо. + +Команда проекта обязуется уважать частную жизнь и безопасность заявителя. + +----- + +Атрибуция +---------- + +Настоящий Кодекс поведения адаптирован из `Contributor Covenant `__, версии +`1.4 `__ и +`2.0 `__. + diff --git a/docs/root/contributing.rst b/docs/root/contributing.rst new file mode 100644 index 0000000..e98f4f7 --- /dev/null +++ b/docs/root/contributing.rst @@ -0,0 +1,300 @@ +.. _root_contributing: + +Вклад в проект +============== + +.. default-role:: code + +Прежде всего, спасибо, что уделили время для внесения своего вклада! ❤️ + +Мы приветствуем и ценим любой вклад. Пожалуйста, прочтите соответствующий раздел, прежде чем начать. Это облегчит работу мейнтейнеров и сделает процесс более гладким для всех. Сообщество с нетерпением ждёт ваших идей! 🎉 + +.. note:: + + Если вам нравится проект, но у вас нет времени на активный вклад, вы можете поддержать нас другими способами: + +* Поставить звезду на GitHub. +* Написать о проекте в Twitter или других социальных сетях. +* Сослаться на проект в `README` вашего репозитория. +* Упомянуть проект на митапах и рассказать о нём друзьям и коллегам. + +.. _contents: + +Содержание +---------- + +* :ref:`Кодекс поведения ` +* :ref:`У меня есть вопрос ` +* :ref:`Я хочу внести вклад ` +* :ref:`Сообщение об ошибках ` +* :ref:`Предложение улучшений ` +* :ref:`Ваш первый вклад в код ` +* :ref:`Улучшение документации ` +* :ref:`Руководства по стилю ` +* :ref:`Присоединяйтесь к команде проекта ` + +.. _code-of-conduct: + +Кодекс поведения +---------------- + +Этот проект и все его участники руководствуются :ref:`Кодексом поведения Argenta `. +Участвуя, вы обязуетесь соблюдать этот кодекс. Пожалуйста, сообщайте о недопустимом поведении. + +----- + +.. _i-have-a-question: + +У меня есть вопрос +------------------ + +.. note:: + + Прежде чем задать вопрос, пожалуйста, ознакомьтесь с `документацией `_. + +Поищите ответ в существующих `Issues `_. Если вы нашли похожий вопрос, но всё ещё нуждаетесь в разъяснениях, можете написать в нём. Также рекомендуем поискать ответ в интернете. + +Если ответа не нашлось, создайте новый `Issue `_ и предоставьте как можно больше контекста, включая версии проекта и платформы. + +Мы займемся вашей задачей как можно скорее. + +----- + +.. _i-want-to-contribute: + +Я хочу внести вклад +------------------- + +.. rubric:: Правовое уведомление + +.. note:: + + Внося вклад в этот проект, вы подтверждаете, что являетесь автором 100% контента, обладаете необходимыми правами на него и соглашаетесь, что он может распространяться под лицензией проекта. + +.. _reporting-bugs: + +Сообщение об ошибках +-------------------- + +.. rubric:: Перед отправкой отчета об ошибке + +Хороший отчёт об ошибке не должен заставлять других вытягивать из вас дополнительную информацию. Пожалуйста, тщательно всё изучите, соберите информацию и подробно опишите проблему. Это поможет нам исправить её как можно быстрее. + +* Убедитесь, что вы используете последнюю версию. +* Убедитесь, что проблема действительно является ошибкой, а не вызвана, например, использованием несовместимых версий окружения. Прочтите `документацию `_ и, если нужна поддержка, загляните в раздел :ref:`У меня есть вопрос `. +* Проверьте, нет ли уже отчёта о вашей ошибке в `трекере `_. +* Также поищите в интернете (включая `Stack Overflow`), чтобы узнать, обсуждалась ли проблема за пределами `GitHub`. +* Соберите информацию об ошибке: + * Трассировка стека. + * ОС, платформа и версия (Windows, Linux, macOS, x86, ARM). + * Версия интерпретатора, компилятора, SDK, среды выполнения, менеджера пакетов и т.д. + * Входные данные и полученный результат. + * Можете ли вы надёжно воспроизвести проблему? Воспроизводится ли она на старых версиях? + +.. rubric:: Как мне отправить хороший отчет об ошибке? + +.. note:: + + Никогда не сообщайте о проблемах безопасности, уязвимостях или ошибках с конфиденциальной информацией в публичном трекере. Для этого используйте электронную почту. + +Мы используем `GitHub Issues` для отслеживания ошибок. Если вы столкнулись с проблемой: + +* Откройте новый `Issue `_. На этом этапе не нужно присваивать ему метки. +* Объясните ожидаемое и фактическое поведение. +* Предоставьте как можно больше контекста и опишите **шаги для воспроизведения**, чтобы проблему можно было воссоздать. Лучше всего изолировать её и создать минимальный тестовый пример. +* Предоставьте информацию, которую вы собрали в предыдущем разделе. + +После того, как задача будет создана: + +* Команда проекта присвоит задаче соответствующую метку. +* Член команды попытается воспроизвести проблему. Если шагов нет или они не приводят к результату, команда попросит вас предоставить их и пометит задачу как `needs-repro`. Такие задачи не будут рассматриваться до тех пор, пока проблема не будет воспроизведена. +* Если проблема будет воспроизведена, она будет помечена как `needs-fix` (и, возможно, другими метками, например `critical`), после чего её сможет взять в работу :ref:`любой желающий `. + +----- + +.. _suggesting-enhancements: + +Предложение улучшений +--------------------- + +Этот раздел поможет вам отправить предложение по улучшению `Argenta`, **включая как новые функции, так и незначительные улучшения**. Следование этим рекомендациям поможет мейнтейнерам и сообществу лучше понять вашу идею. + +.. rubric:: Перед отправкой предложения по улучшению + +* Убедитесь, что вы используете последнюю версию. +* Внимательно прочтите `документацию `_ и убедитесь, что предлагаемая функциональность ещё не реализована (возможно, через конфигурацию). +* Выполните `поиск `_, чтобы проверить, не предлагалось ли это улучшение ранее. Если да, добавьте комментарий к существующей задаче. +* Определите, соответствует ли ваша идея масштабу и целям проекта. Вам предстоит убедительно доказать её пользу. Мы хотим видеть функции, которые будут полезны большинству пользователей. Если ваша идея ориентирована на узкий круг, рассмотрите возможность создания плагина. + +.. rubric:: Как мне отправить хорошее предложение по улучшению? + +Предложения по улучшению отслеживаются в `GitHub Issues `_. + +* Используйте **чёткий и описательный заголовок**, чтобы идентифицировать предложение. +* Предоставьте **пошаговое и подробное описание** предлагаемого улучшения. +* **Опишите текущее поведение** и **объясните, какое вы ожидали увидеть вместо этого** и почему. Здесь же можно указать, какие альтернативы вам не подходят. +* **Приложите скриншоты или видео**, которые помогут продемонстрировать шаги или указать на часть, к которой относится предложение. +* **Объясните, почему это улучшение будет полезно** большинству пользователей `Argenta`. Вы также можете указать на другие проекты, которые решили эту проблему и могут послужить источником вдохновения. + +----- + +.. _your-first-code-contribution: + +Ваш первый вклад в код +----------------------- + +Не знаете, с чего начать? Посмотрите на задачи с метками `good first issue` и `help wanted` в нашем репозитории на `GitHub`. Они хорошо подходят для новичков. + +Чтобы начать, настройте локальное окружение для разработки, следуя этим шагам. + +#. Сделайте форк репозитория ``Argenta`` на ``GitHub``. +#. Клонируйте ваш форк на локальную машину: + + .. code-block:: bash + + git clone https://github.com/<ВАШ_НИКНЕЙМ>/Argenta.git + cd Argenta + +#. Создайте и активируйте виртуальное окружение. + + .. code-block:: bash + + # Для macOS/Linux + python3 -m venv .venv + source .venv/bin/activate + + # Для Windows + python -m venv .venv + .venv\Scripts\activate + +#. Установите зависимости проекта, включая инструменты для разработки. + + .. code-block:: bash + + pip install -e .[dev] + +#. Создайте новую ветку для вашей функции или исправления. Используйте описательное имя, например `fix/login-bug` или `feat/new-widget`. + + .. code-block:: bash + + git switch -c your-new-branch-name + +#. Внесите свои изменения. Напишите код и не забудьте добавить или обновить тесты. +#. Запустите тесты, чтобы убедиться, что все работает корректно. + + .. code-block:: bash + + python -m pytest tests + +#. Сделайте коммит, следуя :ref:`нашему руководству по стилю `, и отправьте изменения в ваш форк. + + .. code-block:: bash + + git add . + git commit -m "feat(widget): add the new super widget" + git push origin your-new-branch-name + +#. Откройте `Pull Request` из вашей ветки в ветку `main` официального репозитория. Предоставьте чёткое описание проблемы и вашего решения. Укажите номер связанной задачи, если она есть. + +----- + +.. _improving-documentation: + +Улучшение документации +---------------------- + +Хорошая документация крайне важна. Мы используем `Sphinx` для её генерации из исходных файлов в директории `docs/`. Мы приветствуем любые улучшения: от исправления опечатки до написания нового раздела. + +.. note:: + + Мы поддерживаем документацию на двух языках: русском и английском. + +.. important:: + + Для инкапсуляции различных команд, необходимых для настройки и запуска проекта мы используем ``just``, он же фигурирует в различных примерах в документации, поэтому рекомендуем вам `установить его `_ + +Для улучшения документации вы можете следовать процессу, похожему на внесение вклада в код: + +#. Убедитесь, что ваше окружение для разработки настроено, как описано в разделе :ref:`Ваш первый вклад в код `. +#. Перейдите в директорию с документацией. + + .. code-block:: bash + + cd docs + +#. Внесите изменения в **русскую** версию документации (`docs/index.rst` и/или `docs/root/*`). +#. Чтобы собрать документацию локально в режиме автоматического ребилда и увидеть изменения, выполните: + + .. code-block:: bash + + just live-ru + +#. Откройте `127.0.0.1:8000` в браузере, чтобы просмотреть сгенерированную документацию. +#. После завершения работы над русской версией необходимо создать английский перевод: + + .. code-block:: bash + + just update-langs + +#. После обновления шаблона обновите файлы перевода, расположенные в `docs/locales/en/LC_MESSAGES/`. +#. Когда изменения будут готовы, сделайте коммит и откройте `Pull Request`. Используйте префикс `docs:` в сообщении коммита. + +----- + +.. _styleguide: + +Руководства по стилю +-------------------- + +.. _commits_messages: + +**Сообщения коммитов** + +Мы следуем спецификации `Conventional Commits `_. Это делает историю проекта более читаемой и позволяет автоматически генерировать журнал изменений. + +Каждое сообщение коммита состоит из **заголовка**, **тела** и **нижнего колонтитула**. + +.. code-block:: text + + <тип>(<область>): <тема> + + [опциональное тело] + + [опциональный нижний колонтитул] + +``<тип>`` должен быть одним из следующих: + +* **feat**: Новая функция для пользователя. +* **fix**: Исправление ошибки для пользователя. +* **docs**: Только изменения в документации. +* **style**: Изменения, не влияющие на смысл кода (пробелы, форматирование и т.д.). +* **refactor**: Изменение кода, которое не исправляет ошибку и не добавляет новую функцию. +* **perf**: Изменение кода, улучшающее производительность. +* **test**: Добавление недостающих тестов или исправление существующих. +* **chore**: Изменения в процессе сборки или вспомогательных инструментах и библиотеках. + +.. rubric:: Примеры + +Простое исправление: +``fix: correct typo in user authentication flow`` + +Новая функция с областью видимости: +``feat(api): add new endpoint for user profiles`` + +----- + +.. _join-the-project-team: + +Присоединяйтесь к команде проекта +--------------------------------- + +Мы всегда ищем энтузиастов для присоединения к команде. Если вы являетесь постоянным участником и продемонстрировали глубокое понимание целей и архитектуры проекта, вы можете стать хорошим кандидатом на роль мейнтейнера. + +Активные члены сообщества могут стать членами команды. Обычно это включает: + +* Постоянный вклад в виде качественного кода и документации. +* Помощь другим пользователям с их вопросами и проблемами. +* Проверку `Pull Request`'ов от других участников с конструктивной обратной связью. + +Если вы заинтересованы в том, чтобы стать постоянным членом команды, лучший способ — быть активным и полезным участником сообщества. Существующие мейнтейнеры заметят ваши усилия и могут связаться с вами. + diff --git a/docs/root/dependency_injection.rst b/docs/root/dependency_injection.rst new file mode 100644 index 0000000..1f8af92 --- /dev/null +++ b/docs/root/dependency_injection.rst @@ -0,0 +1,79 @@ +.. _root_dependency_injection: + +Внедрение зависимостей +======================= + +Внедрение зависимостей (Dependency Injection, DI) — это паттерн проектирования, который помогает писать слабосвязанный, легко тестируемый и расширяемый код. Вместо того чтобы обработчики сами создавали нужные им объекты (зависимости), они получают их извне. + +``Argenta`` использует библиотеку ``dishka`` для реализации DI, что позволяет декларативно объявлять зависимости прямо в сигнатурах ваших обработчиков. +Подробнее о DI, IoC и API для создания провайдеров можно прочитать в `официальной документации dishka `_. + +----- + +Основная идея +------------- + +Представьте, что вашему обработчику для работы нужен доступ к базе данных. Вместо импорта и инициализации соединения внутри функции, вы просто объявляете его как аргумент с аннотацией типа: + +.. note:: + ``argenta.di.FromDishka`` является алиасом для ``dishka.FromDishka``, и они полностью взаимозаменяемы. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/dependency_injection/snippet.py + :language: python + :linenos: + +``Argenta`` с помощью ``dishka`` разрешит зависимость по типу ``Connection`` и внедрит её. Но прежде чем использовать зависимость, её необходимо объявить в провайдере: + +**Пример использования:** + +.. literalinclude:: ../code_snippets/dependency_injection/snippet2.py + :language: python + :linenos: + +После создания провайдера его необходимо зарегистрировать в оркестраторе. + +.. note:: + Провайдеры регистрируются в ``Orchestrator``, а не в ``App``, так как оркестратор отвечает за настройку DI-контейнера на уровне всего приложения. Вы можете передать список из нескольких провайдеров через параметр ``custom_providers``. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/dependency_injection/snippet3.py + :language: python + :linenos: + +----- + +Как это работает? +----------------- + +В основе DI в Argenta лежат **провайдеры** и **контейнер**. + +* **Провайдер (Provider)** — это "рецепт", который объясняет, как создавать и настраивать ту или иную зависимость (например, подключение к БД, API-клиент или любой другой сервис). +* **Контейнер (IoC Container)** — это "фабрика", которая хранит все рецепты (провайдеры) и по запросу создаёт и выдаёт готовые зависимости. + +----- + +Встроенные провайдеры +----------------------- + +``Argenta`` поставляется со встроенным провайдером, который даёт доступ к важным системным зависимостям без дополнительной настройки. Например, вы можете получить объект :ref:`ArgSpace `, который содержит аргументы командной строки, переданные при запуске приложения. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/dependency_injection/snippet4.py + :language: python + :linenos: + +----- + +Обмен данными между обработчиками +---------------------------------- + +Помимо DI, обработчики могут обмениваться данными в рамках сессии через **объект контекста**. В ``Argenta`` эту роль выполняет объект ``DataBridge``. + +Каждый обработчик может записывать в него данные, а также читать, обновлять и удалять их. + +.. seealso:: + Подробнее об этом можно прочитать в разделе :ref:`root_api_bridge`. diff --git a/docs/root/error_handling.rst b/docs/root/error_handling.rst new file mode 100644 index 0000000..d269478 --- /dev/null +++ b/docs/root/error_handling.rst @@ -0,0 +1,130 @@ +.. _root_error_handling: + +Обработка ошибок +========================================== + +``Argenta`` выбрасывает исключения в пограничных случаях, связанных с пользовательским вводом. +По умолчанию они обрабатываются системными обработчиками, но вы можете их переопределить. Это делается с помощью сеттеров экземпляра ``App`` вида ``.set_*_handler()``. Подробнее о каждом из них рассказано :ref:`ниже `. + +.. note:: + Ни одно исключение не остаётся необработанным, так как для каждого случая предусмотрен стандартный обработчик. Поэтому переопределение является опциональным. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/error_handling/snippet.py + :language: python + :linenos: + + +.. _possible_errors: + +Возможные исключения и нестандартное поведение +---------------------------------------------- + +``UnprocessedInputFlagException``: Некорректный синтаксис флагов +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Это исключение выбрасывается, когда парсер не может обработать команду из-за некорректного синтаксиса. Чаще всего это связано с ошибкой в синтаксисе флагов. Подробнее о них можно прочитать в разделе :ref:`Flags `. + +Стандартный обработчик выводит в консоль: + +.. code-block:: shell + + Incorrect flag syntax: + +Для переопределения используется сеттер ``.set_incorrect_input_syntax_handler()``. Он принимает на вход обработчик с сигнатурой ``Callable[[str], None]``, где единственный аргумент — это строка с необработанной командой. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/error_handling/snippet2.py + :language: python + :linenos: + +--------------- + +``RepeatedInputFlagsException``: Повторяющиеся флаги в команде +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Исключение выбрасывается, если пользователь ввёл команду с повторяющимися флагами. Два флага (:ref:`InputFlag `) считаются одинаковыми, если у них совпадают имена. Подробнее о флагах и их синтаксисе — в разделе :ref:`Flags `. + +.. note:: + Сравнение на равенство у регистрируемых флагов (``Flag``) происходит иначе, подробнее в :ref:`Flag `. + +Стандартный обработчик выводит в консоль: + +.. code-block:: shell + + Repeated input flags: + +Для переопределения используется сеттер ``.set_repeated_input_flags_handler()``. Он принимает на вход обработчик с сигнатурой ``Callable[[str], None]``, где единственный аргумент — это строка с необработанной командой. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/error_handling/snippet3.py + :language: python + :linenos: + +--------------- + +``EmptyInputCommandException``: Введена пустая команда +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Исключение выбрасывается, если пользователь ввёл пустую строку или строку, состоящую только из пробельных символов (``\n``, ``\t``, пробел и т.д.). + +Стандартный обработчик выводит в консоль: + +.. code-block:: shell + + Empty input command + +Для переопределения используется сеттер ``.set_empty_command_handler()``. Он принимает на вход обработчик с сигнатурой ``Callable[[], None]`` (без аргументов). + +**Пример использования:** + +.. literalinclude:: ../code_snippets/error_handling/snippet4.py + :language: python + :linenos: + +--------------- + +.. _root_error_handling_unknown_command: + +Обработка неизвестной команды +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Это поведение активируется, когда пользователь вводит команду, которая не зарегистрирована ни в одном из роутеров и не является псевдонимом (alias) для существующей команды. + +Стандартный обработчик выводит в консоль: + +.. code-block:: shell + + Unknown command: + +Для переопределения используется сеттер ``.set_unknown_command_handler()``. Он принимает на вход обработчик с сигнатурой ``Callable[[InputCommand], None]``, где аргумент — объект :ref:`InputCommand `. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/error_handling/snippet5.py + :language: python + :linenos: + +--------------- + +Выход из приложения +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Это поведение активируется, когда пользователь вводит команду, помеченную как команда выхода. + +Стандартный обработчик выводит в консоль текст и завершает работу приложения: + +.. code-block:: shell + + See you + +Для переопределения используется сеттер ``.set_exit_command_handler()``. Он принимает на вход обработчик с сигнатурой ``Callable[[Response], None]``, где аргумент — объект :ref:`Response `. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/error_handling/snippet6.py + :language: python + :linenos: diff --git a/docs/root/flags.rst b/docs/root/flags.rst new file mode 100644 index 0000000..9b3b05a --- /dev/null +++ b/docs/root/flags.rst @@ -0,0 +1,150 @@ +.. _root_flags: + +Флаги вводимых команд +===================== + +Флаги — это специальные параметры, которые пользователь может добавлять к командам для управления их поведением. + +Зачем нужны флаги в командах +---------------------------- + +Управление поведением команды +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Основная цель флагов — предоставить способ изменить логику работы команды без её переработки. Команда может работать в нескольких режимах: стандартном, подробном, отладочном или упрощённом. Флаги переключают эти режимы по требованию пользователя, оставляя основную функциональность неизменной. + +Опциональность и удобство +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Флаги решают проблему обязательности параметров. Если все параметры команды сделать обязательными, это затруднит использование команды. Флаги же позволяют задать значения только необходимые в конкретной ситуации, остальные используют значения по умолчанию. + +Когда могут понадобиться флаги +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Переключение режимов работы** + Команда выполняет развёртывание приложения обычно, но нужен режим без фактического развёртывания (dry-run) для проверки. Флаг ``--dry-run`` переключит режим работы. + +**Настройка уровня детальности** + При отладке или анализе требуется больше информации о процессе выполнения команды. Флаги ``--verbose`` или ``--debug`` предоставляют подробный вывод. + +**Управление поведением при ошибках** + По умолчанию команда может прерваться при первой ошибке. Флаг ``--force`` позволит продолжить работу, пропуская некритичные ошибки. + +**Форматирование вывода** + Команда выводит данные текстом, но в некоторых сценариях нужен JSON или CSV. Флаг ``--format=json`` переключит формат вывода. + +**Комбинирование опций** + Часто нужна комбинация нескольких изменений: подробный вывод, dry-run режим и JSON формат. Несколько флагов решают эту задачу одновременно. + +Практическое значение +~~~~~~~~~~~~~~~~~~~~~ + +Флаги делают команды более предсказуемыми и контролируемыми. Пользователь может начать с простого использования, а затем добавлять флаги по мере необходимости. Это особенно важно при автоматизации задач в скриптах, где гибкость интерфейса критична. + +Флаги также облегчают интеграцию команд в различные системы, так как дополнительное поведение достигается без изменения структуры команды, а только через передачу опциональных параметров. + +----- + +Синтаксис флагов +----------------- + +Общий синтаксис выглядит так: + +.. code-block:: py + + + +Флаг состоит из префикса (``-``, ``--`` или ``---``), имени и, опционально, значения, которое указывается через пробел. + +**Примеры:** + +.. code-block:: shell + + greet --name John + deploy --verbose + backup -f --compress + +----- + +Работа с флагами в обработчиках +-------------------------------- + +Чтобы получить значение флага в обработчике, используйте объект ``response.input_flags`` типа :ref:`InputFlags `. + +**Пример с флагом, имеющим значение:** + +.. literalinclude:: ../code_snippets/flags/greet_handler.py + :language: python + :linenos: + +**Пример с флагом-переключателем:** + +.. literalinclude:: ../code_snippets/flags/deploy_handler.py + :language: python + :linenos: + +.. seealso:: + Подробнее о работе с объектом ``InputFlags`` см. в разделе :ref:`InputFlags `. + +----- + +Два типа флагов +--------------- + +Флаги бывают двух основных видов: + +1. **Флаги со значениями** — принимают параметр после имени флага (например, ``--name John``, ``--port 8080``) +2. **Флаги-переключатели** — не принимают значения, их наличие само по себе является сигналом (например, ``--verbose``, ``--force``) + +``Argenta`` позволяет регистрировать и вводить флаги обоих типов в любой последовательности для одной команды. + +.. note:: + Ошибки валидации не выбрасывают исключений. Вместо этого у каждого объекта :ref:`InputFlag ` есть атрибут ``status``, по которому можно определить, прошла ли валидация успешно. Подробное описание API для создания флагов находится в разделе :ref:`Flag `. + +При регистрации флага можно задать правила валидации для его значения. По умолчанию любое значение считается корректным. Валидацию можно настроить несколькими способами: + +----- + +Флаги против аргументов +----------------------- + +В контексте Argenta флаги и аргументы относятся к разным уровням взаимодействия с приложением. + +**Аргументы** — это параметры, передаваемые при запуске приложения. Они определяют глобальную конфигурацию на протяжении всей его работы (например, адрес базы данных, уровень логирования). + +.. seealso:: API и более подробное описание в разделах :ref:`ArgParser ` и :ref:`Arguments `. + +**Флаги** — это параметры командных операций, доступные в рамках интерактивной сессии при вводе каждой новой команды. Они позволяют модифицировать поведение конкретной команды без перезагрузки приложения. + +.. seealso:: API и более подробное описание в разделе :ref:`Flag `. + +----- + +Ключевые различия +~~~~~~~~~~~~~~~~~ + +**Время жизни** + Аргументы передаются один раз при запуске и действуют на весь период работы приложения. Флаги локальны и существуют только в рамках выполнения команды. + +**Изменяемость** + Для изменения аргументов необходимо перезапустить приложение. Флаги можно менять при каждом вводе команды. + +**Назначение** + Аргументы управляют глобальной конфигурацией приложения. Флаги управляют поведением отдельных команд. + +----- + +Практические примеры +~~~~~~~~~~~~~~~~~~~~ + +Аргументы при запуске приложения: + +- Адрес подключения к базе данных +- Режим работы (production, development, testing) +- Уровень логирования + +Флаги в интерактивной сессии: + +- ``deploy --verbose --dry-run`` — для команды развёртывания +- ``backup --compress --encrypted`` — для команды резервного копирования +- ``test --parallel --coverage`` — для команды тестирования diff --git a/docs/root/overriding_formatting.rst b/docs/root/overriding_formatting.rst new file mode 100644 index 0000000..1d279af --- /dev/null +++ b/docs/root/overriding_formatting.rst @@ -0,0 +1,38 @@ +.. _root_overriding_formatting: + +Форматирование вывода +================================= + +По умолчанию ``Argenta`` использует библиотеку ``rich`` для вывода текста с расширенным форматированием. Она позволяет применять цвета и стили, создавать таблицы, подсвечивать синтаксис и многое другое, что улучшает визуальное восприятие информации. + +------ + +Управление стандартным форматированием +-------------------------------------- + +При создании экземпляра ``App`` можно использовать параметр ``override_system_messages: bool`` (по умолчанию ``False``), который позволяет отключать стандартное форматирование. + +Если установить его в ``True``, стилизация текста и ASCII-графика будут отключены, а системные сообщения — выводиться в «сыром» виде. + +----- + +Приветственное и прощальное сообщения +-------------------------------------- + +Приветственное (``initial_message``) и прощальное (``farewell_message``) сообщения по умолчанию выводятся в виде ASCII-графики. + +.. warning:: + Библиотека ``art`` ориентирована на работу с ASCII-символами и **не поддерживает кириллицу**. Это приводит к искажению символов русского и других кириллических алфавитов. Если ваше сообщение содержит кириллицу, рекомендуется отключить форматирование с помощью ``override_system_messages=True`` или использовать только латинские символы. + +----- + +Кастомизация вывода +------------------- + +Для полной замены логики вывода текста в конструкторе ``App`` предусмотрен параметр ``print_func``. + +* **print_func**: ``Callable[[str], None]`` + Этот параметр позволяет передать любую вызываемую сущность (например, функцию), которая будет использоваться для вывода всех системных сообщений. По умолчанию это ``rich.console.Console().print``. Вы можете передать сюда свою функцию, чтобы, например, логировать вывод в файл или отправлять его по сети. + +.. important:: + При переопределении функции вывода вам следует убедиться, что она поддерживает разметку ``rich``, иначе системные сообщения будут выводиться в сыром виде, в этом случае рекомендуется переопределить стандартное форматирование с помощью ``override_system_messages=True``. diff --git a/docs/root/quickstart.rst b/docs/root/quickstart.rst new file mode 100644 index 0000000..938bd07 --- /dev/null +++ b/docs/root/quickstart.rst @@ -0,0 +1,117 @@ +.. _root_quickstart: + +Быстрый старт +============= + +В этом руководстве мы рассмотрим два примера создания CLI-приложения с помощью Argenta: + +* **Простой пример**: минимальное приложение для быстрого знакомства с основными компонентами. +* **Более сложный пример**: полнофункциональное приложение «Менеджер задач» с внедрением зависимостей и бизнес-логикой. + +Простой пример +--------------- + +**Установка** + +.. code-block:: shell + + pip install argenta + +Этот пример демонстрирует абсолютный минимум, необходимый для создания и запуска приложения. Вы можете скопировать этот код, запустить его и сразу увидеть результат. + +.. literalinclude:: ../code_snippets/quickstart/simple_app.py + :language: python + :linenos: + +**Запуск** + +Сохраните код в файл (например, ``main.py``) и запустите: + +.. code-block:: shell + + python main.py + +**Результат** + +.. image:: https://i.ibb.co/35q24Bh8/image.png + :alt: Simple App Example + +----- + +Промежуточный пример: Калькулятор с флагами +-------------------------------------------- + +Прежде чем перейти к сложному примеру с DI, рассмотрим промежуточный вариант — калькулятор, который использует флаги для управления поведением. + +.. literalinclude:: ../code_snippets/quickstart/calculator_app.py + :language: python + :linenos: + +**Запуск:** + +Сохраните код в файл ``calculator.py`` и запустите: + +.. code-block:: shell + + python calculator.py + +**Использование:** + +.. code-block:: shell + + calc --a 10 --b 5 --operation add + calc --a 10 --b 5 --operation mul + +Этот пример показывает, как работать с флагами без использования DI. Теперь перейдём к более сложному примеру. + +----- + +Сложный пример: Менеджер задач с DI +------------------------------------ + +В этом руководстве мы создадим полнофункциональное CLI-приложение «Менеджер задач», которое продемонстрирует работу с внедрением зависимостей. + +1. **Установка** + +.. code-block:: shell + + pip install argenta + +2. **Определение моделей данных и репозитория** + +Сначала определим модели данных для задачи и репозиторий для их хранения. + +.. literalinclude:: ../code_snippets/quickstart/task_manager/repository.py + :language: python + :linenos: + +3. **Создание провайдера для DI** + +Чтобы Argenta могла внедрять ``TaskRepository`` в наши обработчики, мы создадим провайдер для ``dishka``. + +.. literalinclude:: ../code_snippets/quickstart/task_manager/provider.py + :language: python + :linenos: + +4. **Создание обработчиков команд** + +Теперь создадим обработчики для команд ``add-task`` и ``list-tasks``. Обратите внимание, как мы используем флаги и внедряем ``TaskRepository``. + +.. literalinclude:: ../code_snippets/quickstart/task_manager/handlers.py + :language: python + :linenos: + +5. **Сборка и запуск приложения** + +Наконец, соберем все вместе: создадим экземпляр ``App``, подключим роутер и провайдер, а затем запустим приложение. + +.. literalinclude:: ../code_snippets/quickstart/task_manager/main.py + :language: python + :linenos: + +6. **Результат** + +Теперь вы можете запустить ``main.py`` и взаимодействовать с вашим новым CLI-приложением. + +.. image:: https://i.ibb.co/bgsCLZhP/image.png + :alt: Task Manager Example diff --git a/docs/root/redirect_stdout.rst b/docs/root/redirect_stdout.rst new file mode 100644 index 0000000..2fca44c --- /dev/null +++ b/docs/root/redirect_stdout.rst @@ -0,0 +1,136 @@ +.. _root_redirect_stdout: + +Переопределение стандартного вывода +=================================== + +``Argenta`` предоставляет гибкие механизмы для форматирования вывода, включая динамические разделительные линии. Это достигается за счёт перехвата стандартного потока вывода (``stdout``), что накладывает некоторые особенности. + +----- + +Когда нужно отключать перехват stdout +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Отключайте перехват ``stdout`` (``disable_redirect_stdout=True`` в ``Router``), если ваши команды: + +✓ Используют ``input()`` для интерактивного ввода данных от пользователя +✓ Используют прогресс-бары (``tqdm``, ``rich.progress``) +✓ Выводят данные в реальном времени (streaming, логи) +✓ Используют библиотеки, которые напрямую работают с ``stdout`` + +Для обычных команд с ``print()`` перехват можно оставить включённым — это не влияет на их работу. + +----- + +Механизм перехвата ``stdout`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +По умолчанию ``Argenta`` перехватывает весь текст, выводимый в ``stdout`` внутри обработчика команды. Это необходимо для реализации **динамических разделителей**: система анализирует вывод, находит самую длинную строку и использует её для отрисовки верхней и нижней границ. Такой подход создаёт аккуратный интерфейс, где вывод команды «обёрнут» в рамку, подогнанную под его содержимое. + +Пример приложения с динамической разделительной линией: + +.. image:: https://i.ibb.co/yn9rWnNC/2025-11-07-180751.png + :alt: Example of an application with a dynamic dividing line + +Как вы можете заметить, разделительная линия ровно той же длины, что и самая длинная строка в выводе. + +То же приложение с статической линией: + +.. image:: https://i.ibb.co/P8B4xyp/2025-11-07-180330.png + :alt: Example of an application with a static dividing line + +В этом примере разделительная линия имеет фиксированную длину (по умолчанию 25 символов). + +----- + +Побочные эффекты перехвата ``stdout`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Побочный эффект этого механизма проявляется при использовании функций, которые последовательно выводят текст в консоль и ожидают ввод от пользователя. Классический пример — стандартная функция ``input()``. + +.. code-block:: python + :linenos: + + user_name = input("Enter your name: ") + print(f"Привет, {user_name}!") + +.. warning:: + При включённом перехвате ``stdout`` текст (например, ``"Введите ваше имя: "``) **не будет выведен в консоль немедленно**. Он попадёт в буфер и отобразится лишь после завершения работы обработчика вместе с остальным выводом. Это может сбить пользователя с толку. + +----- + +Отключение перехвата ``stdout`` с помощью ``disable_redirect_stdout`` +--------------------------------------------------------------------- + +Чтобы решить эту проблему, в конструкторе ``Router`` предусмотрен специальный аргумент: + +* **disable_redirect_stdout** (``bool``, по умолчанию ``False``) + +Если при создании роутера установить ``disable_redirect_stdout=True``, механизм перехвата ``stdout`` будет отключён для всех его обработчиков. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/redirect_stdout/sample.py + :language: python + :linenos: + +В этом случае ``input()`` будет работать как обычно, и пользователь сразу увидит приглашение к вводу. + +----- + +Типы разделительных линий +-------------------------- + +``Argenta`` поддерживает два типа разделителей, которые настраиваются при инициализации ``App``: + +1. **``DynamicDividingLine()``** + * Поведение по умолчанию. Длина линии динамически подстраивается под самый длинный текст в выводе. + * Требует включённого перехвата ``stdout`` (``disable_redirect_stdout=False`` в роутере). + +2. **``StaticDividingLine(length: int = 25)``** + * Линия имеет фиксированную длину (по умолчанию 25 символов), которую можно задать через аргумент ``length``. + * Используется принудительно для роутеров с ``disable_redirect_stdout=True``, так как без перехвата вывода невозможно определить динамическую длину. + +----- + +Настройка разделительной линии в ``App`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Вы можете глобально задать тип разделителя для всего приложения через аргумент ``dividing_line`` в конструкторе ``App``. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/redirect_stdout/sample2.py + :language: python + :linenos: + +----- + +Итоговое поведение +------------------ + +.. list-table:: + :widths: 25 25 35 15 + :header-rows: 1 + + * - ``disable_redirect_stdout`` на ``Router`` + - Тип линии в ``App`` + - Фактическое поведение + - ``input()`` работает корректно? + * - ``False`` (по умолчанию) + - ``DynamicDividingLine`` + - Динамическая линия, длина по содержимому + - Нет + * - ``False`` (по умолчанию) + - ``StaticDividingLine`` + - Статическая линия указанной длины + - Нет + * - ``True`` + - ``DynamicDividingLine`` + - **Принудительно статическая линия** (длина по умолч.) + - Да + * - ``True`` + - ``StaticDividingLine`` + - Статическая линия указанной длины + - Да + +Таким образом, для интерактивных команд, требующих ввода от пользователя, отключайте перехват ``stdout`` на уровне роутера. Для всех остальных команд можно оставить поведение по умолчанию. diff --git a/docs/root/testing.rst b/docs/root/testing.rst new file mode 100644 index 0000000..f5a5be6 --- /dev/null +++ b/docs/root/testing.rst @@ -0,0 +1,70 @@ +Тестирование +============ + +В этом разделе описаны практики тестирования приложений на основе ``Argenta``. Примеры основаны на фактическом публичном API. + +Модульное тестирование обработчиков +------------------------------------ + +Обработчики в ``Argenta`` — обычные функции. Их удобно тестировать как чистые функции, не поднимая весь цикл приложения. Рекомендуются ``unittest`` или ``pytest``. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/testing/simple_handler_unittest.py + :language: python + :linenos: + +----- + +Тестирование с внедрением зависимостей (DI) +------------------------------------------- + +Если обработчику нужны зависимости, используйте ``dishka`` и интеграцию ``Argenta``: + +**Пример использования:** + +.. literalinclude:: ../code_snippets/testing/di_handler_unittest.py + :language: python + :linenos: + +----- + +Интеграционное тестирование приложения +-------------------------------------- + +Для более высокого уровня тестов собирайте ``App`` и ``Router`` и вызывайте обработчики через парсинг команд, обходя бесконечный цикл ввода. Это даёт близкое к реальности поведение без необходимости симулировать ``stdin``. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/testing/app_integration_unittest.py + :language: python + :linenos: + +----- + +E2E-тестирование цикла +---------------------- + +Полный запуск цикла ``start_polling`` можно покрывать через подпроцесс с передачей строк в ``stdin``. Это тяжелее и обычно не требуется. Если всё же необходимо — пример ниже. + +.. danger:: + **Важно:** Обязательно передавайте строковый триггер команды выхода последним элементом в списке ``side_effects`` при патче ``input``. + + Иначе тестируемое приложение будет ожидать ввода следующей команды и не сможет корректно завершиться. + +**Пример использования:** + +.. literalinclude:: ../code_snippets/testing/app_e2e_test.py + :language: python + :linenos: + +----- + +Советы по тестированию +---------------------- + +1. **Изолируйте тесты**: Каждый тест должен быть независимым от других. +2. **Моки для внешних интеграций**: БД, HTTP-клиенты и т.п. подменяйте заглушками и провайдерами ``dishka``. +3. **Покрывайте ошибочные сценарии**: Некорректные флаги, неизвестные команды, пустой ввод. +4. **Минимизируйте зависимость от форматирования**: Сравнивайте ключевые фрагменты вывода, а не весь блок целиком. +5. **Измеряйте покрытие**: Используйте ``pytest-cov``. diff --git a/imgs/argenta_banner.png b/imgs/argenta_banner.png deleted file mode 100644 index 297aded..0000000 Binary files a/imgs/argenta_banner.png and /dev/null differ diff --git a/imgs/argenta_logo.png b/imgs/argenta_logo.png deleted file mode 100644 index c99e421..0000000 Binary files a/imgs/argenta_logo.png and /dev/null differ diff --git a/imgs/argenta_logo_strip.png b/imgs/argenta_logo_strip.png deleted file mode 100644 index ce2f6b2..0000000 Binary files a/imgs/argenta_logo_strip.png and /dev/null differ diff --git a/imgs/argenta_logo_strip.svg b/imgs/argenta_logo_strip.svg deleted file mode 100644 index 19a1a0a..0000000 --- a/imgs/argenta_logo_strip.svg +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - diff --git a/imgs/mock_app_preview1.png b/imgs/mock_app_preview1.png deleted file mode 100644 index 1be0ae7..0000000 Binary files a/imgs/mock_app_preview1.png and /dev/null differ diff --git a/imgs/mock_app_preview2.png b/imgs/mock_app_preview2.png deleted file mode 100644 index c74ec2b..0000000 Binary files a/imgs/mock_app_preview2.png and /dev/null differ diff --git a/imgs/mock_app_preview3.png b/imgs/mock_app_preview3.png deleted file mode 100644 index c8a9fc3..0000000 Binary files a/imgs/mock_app_preview3.png and /dev/null differ diff --git a/imgs/mock_app_preview4.png b/imgs/mock_app_preview4.png deleted file mode 100644 index 15ffda1..0000000 Binary files a/imgs/mock_app_preview4.png and /dev/null differ diff --git a/justfile b/justfile new file mode 100644 index 0000000..1649082 --- /dev/null +++ b/justfile @@ -0,0 +1,32 @@ +set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] +set shell := ["bash", "-c"] + +# Вывести список всех рецептов +default: + @just --list + +# Запустить тесты через pytest +tests: + python -m pytest tests + +# Запустить тесты с отчетом о покрытии +tests-cov: + python -m pytest --cov=argenta tests + +# Отформатировать код (Ruff + isort) +format: + python -m ruff format ./src + python -m isort ./src + +# Проверить типы через mypy (strict) +mypy: + python -m mypy -p argenta --strict + +# Проверить стиль через wemake-python-styleguide +wps: + python -m flake8 --format=wemake ./src + +# Запустить линтер Ruff +ruff: + python -m ruff check ./src + diff --git a/metrics_tests/time_of_precycle_setup.py b/metrics_tests/time_of_precycle_setup.py index a750432..83e36f5 100644 --- a/metrics_tests/time_of_precycle_setup.py +++ b/metrics_tests/time_of_precycle_setup.py @@ -1,9 +1,8 @@ +from argenta.app import App from argenta.command import Command from argenta.metrics import get_time_of_pre_cycle_setup from argenta.response import Response from argenta.router import Router -from argenta.app import App - def commands_with_two_aliases(num_of_commands: int): diff --git a/mock/local_test.py b/mock/local_test.py index a9c4c13..285a5ec 100644 --- a/mock/local_test.py +++ b/mock/local_test.py @@ -1,3 +1,2 @@ -arg = '-repeat' - -print(arg[:arg.rfind('-')+1]) \ No newline at end of file +import sys +print(sys.version_info >= (3, 13)) \ No newline at end of file diff --git a/mock/min_app/main.py b/mock/min_app/main.py new file mode 100644 index 0000000..97eb5e9 --- /dev/null +++ b/mock/min_app/main.py @@ -0,0 +1,15 @@ +# main.py +from argenta import App, Orchestrator + +from .routers import router + +app: App = App() +orchestrator: Orchestrator = Orchestrator() + +def main() -> None: + app.include_router(router) + orchestrator.start_polling(app) + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/mock/min_app/routers.py b/mock/min_app/routers.py new file mode 100644 index 0000000..c3492f8 --- /dev/null +++ b/mock/min_app/routers.py @@ -0,0 +1,9 @@ +# routers.py +from argenta import Command, Response, Router + +router = Router(title="Quickstart Example") + +@router.command(Command("hello", description="Say hello")) +def handler(response: Response): + print("Hello, world!") + \ No newline at end of file diff --git a/mock/mock_app/main.py b/mock/mock_app/main.py index bdf981b..0e07271 100644 --- a/mock/mock_app/main.py +++ b/mock/mock_app/main.py @@ -1,29 +1,16 @@ +from argenta import App, Orchestrator +from argenta.app import PredefinedMessages +from argenta.orchestrator.argparser import ArgParser, BooleanArgument +from argenta.app.dividing_line.models import DynamicDividingLine from mock.mock_app.routers import work_router -from argenta import App, Orchestrator -from argenta.app import PredefinedMessages, DynamicDividingLine, AutoCompleter -from argenta.orchestrator import ArgParser -from argenta.orchestrator.argparser import BooleanArgument, ValueArgument -from dishka import Provider, provide, Scope # type: ignore - - -class temProvider(Provider): - @provide(scope=Scope.APP) - def get_apace(self) -> int: - return 1234 - -arg_parser: ArgParser = ArgParser( - processed_args=[ - BooleanArgument(name="repeat", is_deprecated=True), - ValueArgument(name="required", is_required=True), - ] -) app: App = App( - dividing_line=DynamicDividingLine(), - autocompleter=AutoCompleter(), + dividing_line=DynamicDividingLine('^'), ) -orchestrator: Orchestrator = Orchestrator(arg_parser, custom_providers=[temProvider()]) +argparser = ArgParser([BooleanArgument('some')]) +orchestrator: Orchestrator = Orchestrator(argparser) +print(argparser.parsed_argspace.get_by_type(BooleanArgument)) def main(): app.include_router(work_router) @@ -34,6 +21,6 @@ def main(): orchestrator.start_polling(app) - if __name__ == "__main__": - main() + orchestrator.start_polling(app) + \ No newline at end of file diff --git a/mock/mock_app/routers.py b/mock/mock_app/routers.py index eed2aaf..d75a91f 100644 --- a/mock/mock_app/routers.py +++ b/mock/mock_app/routers.py @@ -1,25 +1,10 @@ -from argenta.command import Command, PredefinedFlags, Flags, Flag, PossibleValues -from argenta.response import Response -from argenta import Router +from argenta import Command, Response, Router +from argenta.command import Flag, Flags + +work_router: Router = Router(title="Base points:", disable_redirect_stdout=True) -work_router: Router = Router(title="Work points:", disable_redirect_stdout=True) - -flag = Flag("csdv", possible_values=PossibleValues.NEITHER) - - -@work_router.command( - Command( - "get", - description="Get Help", - aliases=["help", "Get_help"], - flags=Flags([PredefinedFlags.PORT, PredefinedFlags.HOST]), - ) -) +@work_router.command(Command("hello", flags=Flags(Flag("test")), description="Hello, world!")) def command_help(response: Response): - response.update_data({"data": [_ for _ in range(9999999)]}) - - -@work_router.command("run") -def command_start_solving(response: Response): - print(response.get_data()) + c = input("Enter your name: ") + print(f"Hello, {c}!") diff --git a/pyproject.toml b/pyproject.toml index 9fd14fa..be694df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "argenta" version = "1.1.2" description = "Python library for building modular CLI applications" authors = [{ name = "kolo", email = "kolo.is.main@gmail.com" }] -requires-python = ">=3.11" +requires-python = ">=3.12" readme = "README.md" license = { text = "MIT" } dependencies = [ @@ -13,6 +13,29 @@ dependencies = [ "dishka>=1.7.2", ] +[dependency-groups] +linters = [ + "isort>=7.0.0", + "ruff>=0.12.12", + "wemake-python-styleguide>=0.17.0", +] +typecheckers = [ + "mypy>=1.14.1", +] +docs = [ + "esbonio>=1.0.0", + "shibuya>=2025.9.25", + "sphinx>=8.2.3", + "sphinx-autobuild>=2025.8.25", + "sphinx-intl>=2.3.2", +] +tests = [ + "pyfakefs>=5.5.0", + "pytest>=8.3.2", + "pytest-cov>=7.0.0", + "pytest-mock>=3.15.1", +] + [tool.ruff] exclude = [ ".idea", @@ -22,6 +45,7 @@ exclude = [ ".__pycache__", "tests" ] +line-length=90 [tool.pyright] typeCheckingMode = "strict" @@ -29,14 +53,9 @@ typeCheckingMode = "strict" [tool.mypy] disable_error_code = "import-untyped" +[tool.isort] +line_length=90 + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" - -[dependency-groups] -dev = [ - "mypy>=1.14.1", - "pytest>=8.3.2", - "ruff>=0.12.12", - "wemake-python-styleguide>=0.17.0", -] diff --git a/src/argenta/__init__.py b/src/argenta/__init__.py index 6685918..6def41f 100644 --- a/src/argenta/__init__.py +++ b/src/argenta/__init__.py @@ -1,6 +1,6 @@ -__all__ = ["App", "Orchestrator", "Router"] - - -from argenta.orchestrator.entity import Orchestrator -from argenta.app.models import App -from argenta.router.entity import Router +from argenta.app.models import App as App +from argenta.command.models import Command as Command +from argenta.data_bridge.entity import DataBridge as DataBridge +from argenta.orchestrator.entity import Orchestrator as Orchestrator +from argenta.response.entity import Response as Response +from argenta.router.entity import Router as Router diff --git a/src/argenta/app/__init__.py b/src/argenta/app/__init__.py index 7803dd8..0cd6ba0 100644 --- a/src/argenta/app/__init__.py +++ b/src/argenta/app/__init__.py @@ -1,12 +1,5 @@ -__all__ = [ - "App", - "PredefinedMessages", - "DynamicDividingLine", - "StaticDividingLine", - "AutoCompleter" -] - -from argenta.app.models import App -from argenta.app.defaults import PredefinedMessages -from argenta.app.dividing_line.models import DynamicDividingLine, StaticDividingLine -from argenta.app.autocompleter.entity import AutoCompleter +from argenta.app.autocompleter.entity import AutoCompleter as AutoCompleter +from argenta.app.defaults import PredefinedMessages as PredefinedMessages +from argenta.app.dividing_line.models import DynamicDividingLine as DynamicDividingLine +from argenta.app.dividing_line.models import StaticDividingLine as StaticDividingLine +from argenta.app.models import App as App diff --git a/src/argenta/app/autocompleter/__init__.py b/src/argenta/app/autocompleter/__init__.py index 30bb9ce..f829610 100644 --- a/src/argenta/app/autocompleter/__init__.py +++ b/src/argenta/app/autocompleter/__init__.py @@ -1,4 +1 @@ -__all__ = ["AutoCompleter"] - - -from argenta.app.autocompleter.entity import AutoCompleter +from argenta.app.autocompleter.entity import AutoCompleter as AutoCompleter diff --git a/src/argenta/app/autocompleter/entity.py b/src/argenta/app/autocompleter/entity.py index e90de55..7f1479f 100644 --- a/src/argenta/app/autocompleter/entity.py +++ b/src/argenta/app/autocompleter/entity.py @@ -1,12 +1,12 @@ +__all__ = ["AutoCompleter"] + import os import readline from typing import Never class AutoCompleter: - def __init__( - self, history_filename: str | None = None, autocomplete_button: str = "tab" - ) -> None: + def __init__(self, history_filename: str | None = None, autocomplete_button: str = "tab") -> None: """ Public. Configures and implements auto-completion of input command :param history_filename: the name of the file for saving the history of the autocompleter @@ -23,22 +23,16 @@ class AutoCompleter: :param state: the current cursor position is relative to the beginning of the line :return: the desired candidate as str or None """ - matches: list[str] = sorted( - cmd for cmd in _get_history_items() if cmd.startswith(text) - ) + matches: list[str] = sorted(cmd for cmd in _get_history_items() if cmd.startswith(text)) if len(matches) > 1: common_prefix = matches[0] for match in matches[1:]: i = 0 - while ( - i < len(common_prefix) - and i < len(match) - and common_prefix[i] == match[i] - ): + while i < len(common_prefix) and i < len(match) and common_prefix[i] == match[i]: i += 1 common_prefix = common_prefix[:i] if state == 0: - readline.insert_text(common_prefix[len(text) :]) + readline.insert_text(common_prefix[len(text) :]) readline.redisplay() return None elif len(matches) == 1: @@ -54,37 +48,45 @@ class AutoCompleter: """ if self.history_filename: if os.path.exists(self.history_filename): - readline.read_history_file(self.history_filename) + readline.read_history_file(self.history_filename) else: for line in all_commands: - readline.add_history(line) + readline.add_history(line) + + if not self.history_filename: + for line in all_commands: + readline.add_history(line) readline.set_completer(self._complete) readline.set_completer_delims(readline.get_completer_delims().replace(" ", "")) readline.parse_and_bind(f"{self.autocomplete_button}: complete") - def exit_setup(self, all_commands: list[str]) -> None: + def exit_setup(self, all_commands: list[str], ignore_command_register: bool) -> None: """ Private. Exit setup function :return: None """ if self.history_filename: - readline.write_history_file(self.history_filename) + readline.write_history_file(self.history_filename) with open(self.history_filename, "r") as history_file: raw_history = history_file.read() pretty_history: list[str] = [] for line in set(raw_history.strip().split("\n")): - if line.split()[0] in all_commands: + if _is_command_exist(line.split()[0], all_commands, ignore_command_register): pretty_history.append(line) with open(self.history_filename, "w") as history_file: _ = history_file.write("\n".join(pretty_history)) + +def _is_command_exist(command: str, existing_commands: list[str], ignore_command_register: bool) -> bool: + if ignore_command_register: + return command.lower() in existing_commands + return command in existing_commands + + def _get_history_items() -> list[str] | list[Never]: """ Private. Returns a list of all commands entered by the user :return: all commands entered by the user as list[str] | list[Never] """ - return [ - readline.get_history_item(i) - for i in range(1, readline.get_current_history_length() + 1) - ] + return [readline.get_history_item(i) for i in range(1, readline.get_current_history_length() + 1)] diff --git a/src/argenta/app/defaults.py b/src/argenta/app/defaults.py index 2908cef..cf0f73d 100644 --- a/src/argenta/app/defaults.py +++ b/src/argenta/app/defaults.py @@ -1,3 +1,5 @@ +__all__ = ["PredefinedMessages"] + from enum import StrEnum @@ -5,6 +7,7 @@ class PredefinedMessages(StrEnum): """ Public. A dataclass with predetermined messages for quick use """ + USAGE = "[b dim]Usage[/b dim]: [i] <[green]flags[/green]>[/i]" HELP = "[b dim]Help[/b dim]: [i][/i] [b red]--help[/b red]" AUTOCOMPLETE = "[b dim]Autocomplete[/b dim]: [i][/i] [bold]" diff --git a/src/argenta/app/dividing_line/__init__.py b/src/argenta/app/dividing_line/__init__.py index 4b4f746..73b9894 100644 --- a/src/argenta/app/dividing_line/__init__.py +++ b/src/argenta/app/dividing_line/__init__.py @@ -1,4 +1,2 @@ -__all__ = ["StaticDividingLine", "DynamicDividingLine"] - - -from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine +from argenta.app.dividing_line.models import DynamicDividingLine as DynamicDividingLine +from argenta.app.dividing_line.models import StaticDividingLine as StaticDividingLine diff --git a/src/argenta/app/dividing_line/models.py b/src/argenta/app/dividing_line/models.py index d54279e..f64bbc1 100644 --- a/src/argenta/app/dividing_line/models.py +++ b/src/argenta/app/dividing_line/models.py @@ -1,3 +1,5 @@ +__all__ = ["StaticDividingLine", "DynamicDividingLine"] + from abc import ABC diff --git a/src/argenta/app/models.py b/src/argenta/app/models.py index 676cf15..328a87d 100644 --- a/src/argenta/app/models.py +++ b/src/argenta/app/models.py @@ -1,9 +1,11 @@ +__all__ = ["App"] + import io import re from contextlib import redirect_stdout from typing import Never, TypeAlias -from art import text2art # pyright: ignore[reportMissingTypeStubs, reportUnknownVariableType] +from art import text2art from rich.console import Console from rich.markup import escape @@ -41,7 +43,7 @@ class BaseApp: system_router_title: str | None, ignore_command_register: bool, dividing_line: StaticDividingLine | DynamicDividingLine, - repeat_command_groups: bool, + repeat_command_groups_printing: bool, override_system_messages: bool, autocompleter: AutoCompleter, print_func: Printer, @@ -52,7 +54,7 @@ class BaseApp: self._system_router_title: str | None = system_router_title self._dividing_line: StaticDividingLine | DynamicDividingLine = dividing_line self._ignore_command_register: bool = ignore_command_register - self._repeat_command_groups_description: bool = repeat_command_groups + self._repeat_command_groups_printing_description: bool = repeat_command_groups_printing self._override_system_messages: bool = override_system_messages self._autocompleter: AutoCompleter = autocompleter @@ -74,25 +76,21 @@ class BaseApp: else self._matching_default_triggers_with_routers ) - self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = ( - lambda _: print_func(f"Incorrect flag syntax: {_}") + self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = lambda _: print_func( + f"Incorrect flag syntax: {_}" ) - self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = ( - lambda _: print_func(f"Repeated input flags: {_}") + self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = lambda _: print_func( + f"Repeated input flags: {_}" ) - self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func( - "Empty input command" + self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func("Empty input command") + self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = lambda _: print_func( + f"Unknown command: {_.trigger}" ) - self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = ( - lambda _: print_func(f"Unknown command: {_.trigger}") - ) - self._exit_command_handler: NonStandardBehaviorHandler[Response] = ( - lambda _: print_func(self._farewell_message) + self._exit_command_handler: NonStandardBehaviorHandler[Response] = lambda _: print_func( + self._farewell_message ) - def set_description_message_pattern( - self, _: DescriptionMessageGenerator, / - ) -> None: + def set_description_message_pattern(self, _: DescriptionMessageGenerator, /) -> None: """ Public. Sets the output pattern of the available commands :param _: output pattern of the available commands @@ -100,9 +98,7 @@ class BaseApp: """ self._description_message_gen = _ - def set_incorrect_input_syntax_handler( - self, _: NonStandardBehaviorHandler[str], / - ) -> None: + def set_incorrect_input_syntax_handler(self, _: NonStandardBehaviorHandler[str], /) -> None: """ Public. Sets the handler for incorrect flags when entering a command :param _: handler for incorrect flags when entering a command @@ -110,9 +106,7 @@ class BaseApp: """ self._incorrect_input_syntax_handler = _ - def set_repeated_input_flags_handler( - self, _: NonStandardBehaviorHandler[str], / - ) -> None: + def set_repeated_input_flags_handler(self, _: NonStandardBehaviorHandler[str], /) -> None: """ Public. Sets the handler for repeated flags when entering a command :param _: handler for repeated flags when entering a command @@ -120,9 +114,7 @@ class BaseApp: """ self._repeated_input_flags_handler = _ - def set_unknown_command_handler( - self, _: NonStandardBehaviorHandler[InputCommand], / - ) -> None: + def set_unknown_command_handler(self, _: NonStandardBehaviorHandler[InputCommand], /) -> None: """ Public. Sets the handler for unknown commands when entering a command :param _: handler for unknown commands when entering a command @@ -138,9 +130,7 @@ class BaseApp: """ self._empty_input_command_handler = _ - def set_exit_command_handler( - self, _: NonStandardBehaviorHandler[Response], / - ) -> None: + def set_exit_command_handler(self, _: NonStandardBehaviorHandler[Response], /) -> None: """ Public. Sets the handler for exit command when entering a command :param _: handler for exit command when entering a command @@ -176,11 +166,7 @@ class BaseApp: clear_text = re.sub(r"\u001b\[[0-9;]*m", "", text) max_length_line = max([len(line) for line in clear_text.split("\n")]) max_length_line = ( - max_length_line - if 10 <= max_length_line <= 80 - else 80 - if max_length_line > 80 - else 10 + max_length_line if 10 <= max_length_line <= 80 else 80 if max_length_line > 80 else 10 ) self._print_func( @@ -197,15 +183,11 @@ class BaseApp: elif isinstance(self._dividing_line, StaticDividingLine): # pyright: ignore[reportUnnecessaryIsInstance] self._print_func( - self._dividing_line.get_full_static_line( - is_override=self._override_system_messages - ) + self._dividing_line.get_full_static_line(is_override=self._override_system_messages) ) print(text.strip("\n")) self._print_func( - self._dividing_line.get_full_static_line( - is_override=self._override_system_messages - ) + self._dividing_line.get_full_static_line(is_override=self._override_system_messages) ) else: @@ -239,14 +221,10 @@ class BaseApp: """ input_command_trigger = command.trigger if self._ignore_command_register: - if input_command_trigger.lower() in list( - self._current_matching_triggers_with_routers.keys() - ): + if input_command_trigger.lower() in list(self._current_matching_triggers_with_routers.keys()): return False else: - if input_command_trigger in list( - self._current_matching_triggers_with_routers.keys() - ): + if input_command_trigger in list(self._current_matching_triggers_with_routers.keys()): return False return True @@ -304,9 +282,7 @@ class BaseApp: :return: None """ self._prompt = f"[italic dim bold]{self._prompt}" - self._initial_message = ( - "\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n" - ) + self._initial_message = "\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n" self._farewell_message = ( "[bold red]\n\n" + str(text2art(self._farewell_message, font="chanky")) # pyright: ignore[reportUnknownArgumentType] @@ -324,20 +300,14 @@ class BaseApp: self._repeated_input_flags_handler = lambda raw_command: self._print_func( f"[red bold]Repeated input flags: {escape(raw_command)}" ) - self._empty_input_command_handler = lambda: self._print_func( - "[red bold]Empty input command" - ) + self._empty_input_command_handler = lambda: self._print_func("[red bold]Empty input command") def unknown_command_handler(command: InputCommand) -> None: cmd_trg: str = command.trigger mst_sim_cmd: str | None = self._most_similar_command(cmd_trg) - first_part_of_text = ( - f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]" - ) + first_part_of_text = f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]" second_part_of_text = ( - ("[red], most similar:[/red] " + ("[blue]" + mst_sim_cmd + "[/blue]")) - if mst_sim_cmd - else "" + ("[red], most similar:[/red] " + ("[blue]" + mst_sim_cmd + "[/blue]")) if mst_sim_cmd else "" ) self._print_func(first_part_of_text + second_part_of_text) @@ -357,13 +327,9 @@ class BaseApp: for trigger in combined: self._matching_default_triggers_with_routers[trigger] = router_entity - self._matching_lower_triggers_with_routers[trigger.lower()] = ( - router_entity - ) + self._matching_lower_triggers_with_routers[trigger.lower()] = router_entity - self._autocompleter.initial_setup( - list(self._current_matching_triggers_with_routers.keys()) - ) + self._autocompleter.initial_setup(list(self._current_matching_triggers_with_routers.keys())) seen = {} for item in list(self._current_matching_triggers_with_routers.keys()): @@ -383,7 +349,7 @@ class BaseApp: self._print_func(message) if self._messages_on_startup: print("\n") - if not self._repeat_command_groups_description: + if not self._repeat_command_groups_printing_description: self._print_command_group_description() @@ -406,7 +372,7 @@ class App(BaseApp): system_router_title: str | None = "System points:", ignore_command_register: bool = True, dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE, - repeat_command_groups: bool = True, + repeat_command_groups_printing: bool = True, override_system_messages: bool = False, autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER, print_func: Printer = DEFAULT_PRINT_FUNC, @@ -421,7 +387,7 @@ class App(BaseApp): :param system_router_title: system router title :param ignore_command_register: whether to ignore the case of the entered commands :param dividing_line: the entity of the dividing line - :param repeat_command_groups: whether to repeat the available commands and their description + :param repeat_command_groups_printing: whether to repeat the available commands and their description :param override_system_messages: whether to redefine the default formatting of system messages :param autocompleter: the entity of the autocompleter :param print_func: system messages text output function @@ -435,7 +401,7 @@ class App(BaseApp): system_router_title=system_router_title, ignore_command_register=ignore_command_register, dividing_line=dividing_line, - repeat_command_groups=repeat_command_groups, + repeat_command_groups_printing=repeat_command_groups_printing, override_system_messages=override_system_messages, autocompleter=autocompleter, print_func=print_func, @@ -448,15 +414,13 @@ class App(BaseApp): """ self._pre_cycle_setup() while True: - if self._repeat_command_groups_description: + if self._repeat_command_groups_printing_description: self._print_command_group_description() raw_command: str = Console().input(self._prompt) try: - input_command: InputCommand = InputCommand.parse( - raw_command=raw_command - ) + input_command: InputCommand = InputCommand.parse(raw_command=raw_command) except InputCommandException as error: with redirect_stdout(io.StringIO()) as stderr: self._error_handler(error, raw_command) @@ -467,7 +431,7 @@ class App(BaseApp): if self._is_exit_command(input_command): system_router.finds_appropriate_handler(input_command) self._autocompleter.exit_setup( - list(self._current_matching_triggers_with_routers.keys()) + list(self._current_matching_triggers_with_routers.keys()), self._ignore_command_register ) return @@ -478,40 +442,21 @@ class App(BaseApp): self._print_framed_text(stdout_res) continue - processing_router = self._current_matching_triggers_with_routers[ - input_command.trigger.lower() - ] + processing_router = self._current_matching_triggers_with_routers[input_command.trigger.lower()] if processing_router.disable_redirect_stdout: - if isinstance(self._dividing_line, StaticDividingLine): - self._print_func( - self._dividing_line.get_full_static_line( - is_override=self._override_system_messages - ) + dividing_line_unit_part: str = self._dividing_line.get_unit_part() + self._print_func( + StaticDividingLine(dividing_line_unit_part).get_full_static_line( + is_override=self._override_system_messages ) - processing_router.finds_appropriate_handler(input_command) - self._print_func( - self._dividing_line.get_full_static_line( - is_override=self._override_system_messages - ) - ) - else: - dividing_line_unit_part: str = self._dividing_line.get_unit_part() - self._print_func( - StaticDividingLine( - dividing_line_unit_part - ).get_full_static_line( - is_override=self._override_system_messages - ) - ) - processing_router.finds_appropriate_handler(input_command) - self._print_func( - StaticDividingLine( - dividing_line_unit_part - ).get_full_static_line( - is_override=self._override_system_messages - ) + ) + processing_router.finds_appropriate_handler(input_command) + self._print_func( + StaticDividingLine(dividing_line_unit_part).get_full_static_line( + is_override=self._override_system_messages ) + ) else: with redirect_stdout(io.StringIO()) as stdout: processing_router.finds_appropriate_handler(input_command) diff --git a/src/argenta/app/protocols.py b/src/argenta/app/protocols.py index e3f13fd..8300be5 100644 --- a/src/argenta/app/protocols.py +++ b/src/argenta/app/protocols.py @@ -1,16 +1,19 @@ +__all__ = ["NonStandardBehaviorHandler", "EmptyCommandHandler", "Printer", "DescriptionMessageGenerator"] + from typing import Protocol, TypeVar -T = TypeVar('T', contravariant=True) # noqa: WPS111 +T = TypeVar("T", contravariant=True) # noqa: WPS111 class NonStandardBehaviorHandler(Protocol[T]): def __call__(self, __param: T) -> None: raise NotImplementedError - + + class EmptyCommandHandler(Protocol): def __call__(self) -> None: raise NotImplementedError - + class Printer(Protocol): def __call__(self, __text: str) -> None: diff --git a/src/argenta/app/registered_routers/entity.py b/src/argenta/app/registered_routers/entity.py index bce0a23..2212389 100644 --- a/src/argenta/app/registered_routers/entity.py +++ b/src/argenta/app/registered_routers/entity.py @@ -1,3 +1,5 @@ +__all__ = ["RegisteredRouters"] + from typing import Iterator, Optional from argenta.router import Router diff --git a/src/argenta/command/__init__.py b/src/argenta/command/__init__.py index 98310e5..6bc02cc 100644 --- a/src/argenta/command/__init__.py +++ b/src/argenta/command/__init__.py @@ -1,12 +1,8 @@ -__all__ = [ - "Command", - "PossibleValues", - "PredefinedFlags", - "InputCommand", - "Flags", - "Flag" -] - -from argenta.command.models import Command, InputCommand -from argenta.command.flag import defaults as PredefinedFlags -from argenta.command.flag import (Flag, Flags, PossibleValues) +from argenta.command.flag import Flag as Flag +from argenta.command.flag import Flags as Flags +from argenta.command.flag import InputFlag as InputFlag +from argenta.command.flag import InputFlags as InputFlags +from argenta.command.flag import PossibleValues as PossibleValues +from argenta.command.flag.defaults import PredefinedFlags as PredefinedFlags +from argenta.command.models import Command as Command +from argenta.command.models import InputCommand as InputCommand diff --git a/src/argenta/command/exceptions.py b/src/argenta/command/exceptions.py index 8e5a2ad..6a83aaf 100644 --- a/src/argenta/command/exceptions.py +++ b/src/argenta/command/exceptions.py @@ -1,12 +1,21 @@ -from argenta.command.flag.models import Flag, InputFlag +__all__ = [ + "InputCommandException", + "UnprocessedInputFlagException", + "RepeatedInputFlagsException", + "EmptyInputCommandException", +] + from abc import ABC, abstractmethod from typing import override +from argenta.command.flag.models import Flag, InputFlag + class InputCommandException(ABC, Exception): """ Private. Base exception class for all exceptions raised when parse input command """ + @override @abstractmethod def __str__(self) -> str: @@ -17,6 +26,7 @@ class UnprocessedInputFlagException(InputCommandException): """ Private. Raised when an unprocessed input flag is detected """ + @override def __str__(self) -> str: return "Unprocessed Input Flags" @@ -34,16 +44,14 @@ class RepeatedInputFlagsException(InputCommandException): @override def __str__(self) -> str: string_entity: str = self.flag.string_entity - return ( - "Repeated Input Flags\n" - f"Duplicate flag was detected in the input: '{string_entity}'" - ) + return f"Repeated Input Flags\nDuplicate flag was detected in the input: '{string_entity}'" class EmptyInputCommandException(InputCommandException): """ Private. Raised when an empty input command is detected """ + @override def __str__(self) -> str: return "Input Command is empty" diff --git a/src/argenta/command/flag/__init__.py b/src/argenta/command/flag/__init__.py index 3095bd8..dc8e2be 100644 --- a/src/argenta/command/flag/__init__.py +++ b/src/argenta/command/flag/__init__.py @@ -1,11 +1,6 @@ -__all__ = [ - "Flag", - "InputFlag", - "Flags", - "PossibleValues", - "ValidationStatus" -] - - -from argenta.command.flag.models import Flag, InputFlag, PossibleValues, ValidationStatus -from argenta.command.flag.flags.models import Flags +from argenta.command.flag.flags.models import Flags as Flags +from argenta.command.flag.flags.models import InputFlags as InputFlags +from argenta.command.flag.models import Flag as Flag +from argenta.command.flag.models import InputFlag as InputFlag +from argenta.command.flag.models import PossibleValues as PossibleValues +from argenta.command.flag.models import ValidationStatus as ValidationStatus diff --git a/src/argenta/command/flag/defaults.py b/src/argenta/command/flag/defaults.py index 2ffb780..1fb7a97 100644 --- a/src/argenta/command/flag/defaults.py +++ b/src/argenta/command/flag/defaults.py @@ -1,27 +1,29 @@ -from typing import Literal -from argenta.command.flag.models import Flag, PossibleValues -import re +__all__ = ["PredefinedFlags"] +import re +from typing import Literal + +from argenta.command.flag.models import Flag, PossibleValues DEFAULT_PREFIX: Literal["-", "--", "---"] = "-" -HELP = Flag(name="help", possible_values=PossibleValues.NEITHER) -SHORT_HELP = Flag(name="H", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER) -INFO = Flag(name="info", possible_values=PossibleValues.NEITHER) # noqa: WPS110 -SHORT_INFO = Flag(name="I", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER) +class PredefinedFlags: + HELP = Flag(name="help", possible_values=PossibleValues.NEITHER) + SHORT_HELP = Flag(name="H", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER) -ALL = Flag(name="all", possible_values=PossibleValues.NEITHER) -SHORT_ALL = Flag(name="A", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER) + INFO = Flag(name="info", possible_values=PossibleValues.NEITHER) # noqa: WPS110 + SHORT_INFO = Flag(name="I", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER) -HOST = Flag( - name="host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") -) -SHORT_HOST = Flag( - name="H", - prefix=DEFAULT_PREFIX, - possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"), -) + ALL = Flag(name="all", possible_values=PossibleValues.NEITHER) + SHORT_ALL = Flag(name="A", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER) -PORT = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$")) -SHORT_PORT = Flag(name="P", prefix=DEFAULT_PREFIX, possible_values=re.compile(r"^\d{1,5}$")) + HOST = Flag(name="host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")) + SHORT_HOST = Flag( + name="H", + prefix=DEFAULT_PREFIX, + possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"), + ) + + PORT = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$")) + SHORT_PORT = Flag(name="P", prefix=DEFAULT_PREFIX, possible_values=re.compile(r"^\d{1,5}$")) diff --git a/src/argenta/command/flag/flags/__init__.py b/src/argenta/command/flag/flags/__init__.py index b355115..a7d3a22 100644 --- a/src/argenta/command/flag/flags/__init__.py +++ b/src/argenta/command/flag/flags/__init__.py @@ -1,10 +1,2 @@ -__all__ = [ - "Flags", - "InputFlags" -] - - -from argenta.command.flag.flags.models import ( - Flags, - InputFlags -) +from argenta.command.flag.flags.models import Flags as Flags +from argenta.command.flag.flags.models import InputFlags as InputFlags diff --git a/src/argenta/command/flag/flags/models.py b/src/argenta/command/flag/flags/models.py index c80b21b..4edb692 100644 --- a/src/argenta/command/flag/flags/models.py +++ b/src/argenta/command/flag/flags/models.py @@ -1,7 +1,9 @@ -from argenta.command.flag.models import InputFlag, Flag -from typing import Generic, TypeVar, override -from collections.abc import Iterator +__all__ = ["Flags", "InputFlags"] +from collections.abc import Iterator +from typing import Generic, TypeVar, override + +from argenta.command.flag.models import Flag, InputFlag FlagType = TypeVar("FlagType") @@ -30,6 +32,9 @@ class BaseFlags(Generic[FlagType]): :return: None """ self.flags.extend(flags) + + def __len__(self) -> int: + return len(self.flags) def __iter__(self) -> Iterator[FlagType]: return iter(self.flags) @@ -52,7 +57,7 @@ class Flags(BaseFlags[Flag]): :return: entity of the flag or None """ return next((flag for flag in self.flags if flag.name == name), None) - + @override def __eq__(self, other: object) -> bool: if not isinstance(other, Flags): @@ -82,9 +87,9 @@ class InputFlags(BaseFlags[InputFlag]): :return: entity of the flag or None """ return next((flag for flag in self.flags if flag.name == name), None) - + @override - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> bool: if not isinstance(other, InputFlags): raise NotImplementedError @@ -103,4 +108,3 @@ class InputFlags(BaseFlags[InputFlag]): return False else: raise TypeError - diff --git a/src/argenta/command/flag/models.py b/src/argenta/command/flag/models.py index 7f04992..86be447 100644 --- a/src/argenta/command/flag/models.py +++ b/src/argenta/command/flag/models.py @@ -1,23 +1,30 @@ +__all__ = ["PossibleValues", "ValidationStatus", "Flag", "InputFlag"] + from enum import Enum from re import Pattern from typing import Literal, override +PREFIX_TYPE = Literal["-", "--", "---"] + + class PossibleValues(Enum): - NEITHER = 'NEITHER' - ALL = 'ALL' + NEITHER = "NEITHER" + ALL = "ALL" class ValidationStatus(Enum): - VALID = 'VALID' - INVALID = 'INVALID' - UNDEFINED = 'UNDEFINED' + VALID = "VALID" + INVALID = "INVALID" + UNDEFINED = "UNDEFINED" class Flag: def __init__( - self, name: str, *, - prefix: Literal["-", "--", "---"] = "--", + self, + name: str, + *, + prefix: PREFIX_TYPE = "--", possible_values: list[str] | Pattern[str] | PossibleValues = PossibleValues.ALL, ) -> None: """ @@ -28,26 +35,29 @@ class Flag: :return: None """ self.name: str = name - self.prefix: Literal["-", "--", "---"] = prefix + self.prefix: PREFIX_TYPE = prefix self.possible_values: list[str] | Pattern[str] | PossibleValues = possible_values - def validate_input_flag_value(self, input_flag_value: str | None) -> bool: + def validate_input_flag_value(self, input_flag_value: str) -> bool: """ Private. Validates the input flag value :param input_flag_value: The input flag value to validate :return: whether the entered flag is valid as bool """ if self.possible_values == PossibleValues.NEITHER: - return input_flag_value is None + return input_flag_value == '' + + if self.possible_values == PossibleValues.ALL: + return input_flag_value != '' if isinstance(self.possible_values, Pattern): - return isinstance(input_flag_value, str) and bool(self.possible_values.match(input_flag_value)) + return bool(self.possible_values.match(input_flag_value)) if isinstance(self.possible_values, list): return input_flag_value in self.possible_values + + return False - return True - @property def string_entity(self) -> str: """ @@ -56,17 +66,17 @@ class Flag: """ string_entity: str = self.prefix + self.name return string_entity - + @override def __str__(self) -> str: return self.string_entity - + @override def __repr__(self) -> str: - return f'Flag' - + return f"Flag" + @override - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> bool: if isinstance(other, Flag): return self.string_entity == other.string_entity else: @@ -75,10 +85,12 @@ class Flag: class InputFlag: def __init__( - self, name: str, *, - prefix: Literal['-', '--', '---'] = '--', - input_value: str | None, - status: ValidationStatus | None + self, + name: str, + *, + prefix: PREFIX_TYPE = "--", + input_value: str, + status: ValidationStatus | None, ): """ Public. The entity of the flag of the entered command @@ -88,10 +100,10 @@ class InputFlag: :return: None """ self.name: str = name - self.prefix: Literal['-', '--', '---'] = prefix - self.input_value: str | None = input_value + self.prefix: PREFIX_TYPE = prefix + self.input_value: str = input_value self.status: ValidationStatus | None = status - + @property def string_entity(self) -> str: """ @@ -103,17 +115,15 @@ class InputFlag: @override def __str__(self) -> str: - return f'{self.string_entity} {self.input_value}' - - @override - def __repr__(self) -> str: - return f'InputFlag' + return f"{self.string_entity} {self.input_value}" @override - def __eq__(self, other: object) -> bool: + def __repr__(self) -> str: + return f"InputFlag" + + @override + def __eq__(self, other: object) -> bool: if isinstance(other, InputFlag): - return ( - self.name == other.name - ) + return self.name == other.name else: raise NotImplementedError diff --git a/src/argenta/command/models.py b/src/argenta/command/models.py index 6088a21..1553b35 100644 --- a/src/argenta/command/models.py +++ b/src/argenta/command/models.py @@ -1,18 +1,23 @@ -from argenta.command.flag.models import Flag, InputFlag, ValidationStatus -from argenta.command.flag.flags.models import InputFlags, Flags -from argenta.command.exceptions import ( - UnprocessedInputFlagException, - RepeatedInputFlagsException, - EmptyInputCommandException, -) +__all__ = ["Command", "InputCommand"] + +import shlex from typing import Never, Self, cast, Literal +from argenta.command.exceptions import ( + EmptyInputCommandException, + RepeatedInputFlagsException, + UnprocessedInputFlagException, +) +from argenta.command.flag.flags.models import Flags, InputFlags +from argenta.command.flag.models import Flag, InputFlag, ValidationStatus ParseFlagsResult = tuple[InputFlags, str | None, str | None] ParseResult = tuple[str, InputFlags] MIN_FLAG_PREFIX: str = "-" +PREFIX_TYPE = Literal["-", "--", "---"] DEFAULT_WITHOUT_FLAGS: Flags = Flags() +DEFAULT_WITHOUT_ALIASES: list[Never] = [] DEFAULT_WITHOUT_INPUT_FLAGS: InputFlags = InputFlags() @@ -20,10 +25,11 @@ DEFAULT_WITHOUT_INPUT_FLAGS: InputFlags = InputFlags() class Command: def __init__( self, - trigger: str, *, - description: str | None = None, + trigger: str, + *, + description: str = "Some useful command", flags: Flag | Flags = DEFAULT_WITHOUT_FLAGS, - aliases: list[str] | None = None, + aliases: list[str] | list[Never] = DEFAULT_WITHOUT_ALIASES, ): """ Public. The command that can and should be registered in the Router @@ -34,12 +40,10 @@ class Command: """ self.registered_flags: Flags = flags if isinstance(flags, Flags) else Flags([flags]) self.trigger: str = trigger - self.description: str = description if description else "Command without description" - self.aliases: list[str] = aliases if aliases else [] + self.description: str = description + self.aliases: list[str] | list[Never] = aliases - def validate_input_flag( - self, flag: InputFlag - ) -> ValidationStatus: + def validate_input_flag(self, flag: InputFlag) -> ValidationStatus: """ Private. Validates the input flag :param flag: input flag for validation @@ -57,8 +61,7 @@ class Command: class InputCommand: - def __init__(self, trigger: str, *, - input_flags: InputFlag | InputFlags = DEFAULT_WITHOUT_INPUT_FLAGS): + def __init__(self, trigger: str, *, input_flags: InputFlag | InputFlags = DEFAULT_WITHOUT_INPUT_FLAGS): """ Private. The model of the input command, after parsing :param trigger:the trigger of the command @@ -66,7 +69,9 @@ class InputCommand: :return: None """ self.trigger: str = trigger - self.input_flags: InputFlags = input_flags if isinstance(input_flags, InputFlags) else InputFlags([input_flags]) + self.input_flags: InputFlags = ( + input_flags if isinstance(input_flags, InputFlags) else InputFlags([input_flags]) + ) @classmethod def parse(cls, raw_command: str) -> Self: @@ -75,77 +80,50 @@ class InputCommand: :param raw_command: raw input command :return: model of the input command, after parsing as InputCommand """ - trigger, input_flags = CommandParser(raw_command).parse_raw_command() - - return cls(trigger=trigger, input_flags=input_flags) + tokens = shlex.split(raw_command) - -class CommandParser: - def __init__(self, raw_command: str) -> None: - self.raw_command: str = raw_command - self._parsed_input_flags: InputFlags = InputFlags() - - def parse_raw_command(self) -> ParseResult: - if not self.raw_command: - raise EmptyInputCommandException() - - input_flags, crnt_flag_name, crnt_flag_val = self._parse_flags(self.raw_command.split()[1:]) - - if any([crnt_flag_name, crnt_flag_val]): - raise UnprocessedInputFlagException() - else: - return (self.raw_command.split()[0], input_flags) - - def _parse_flags(self, _tokens: list[str] | list[Never]) -> ParseFlagsResult: - crnt_flg_name, crnt_flg_val = None, None - for index, token in enumerate(_tokens): - crnt_flg_name, crnt_flg_val = _parse_single_token(token, crnt_flg_name, crnt_flg_val) - - if not crnt_flg_name or self._is_next_token_value(index, _tokens): - continue - + if not tokens: + raise EmptyInputCommandException + + command = tokens[0] + flags: InputFlags = InputFlags() + + i = 1 + while i < len(tokens): + token = tokens[i] + + if token.startswith("---"): + prefix = "---" + name = token[3:] + elif token.startswith("--"): + prefix = "--" + name = token[2:] + elif token.startswith("-"): + prefix = "-" + name = token[1:] + else: + raise UnprocessedInputFlagException + + if not name: + raise UnprocessedInputFlagException + + if i + 1 < len(tokens) and not tokens[i + 1].startswith("-"): + input_value = tokens[i + 1] + i += 2 + else: + input_value = "" + i += 1 + input_flag = InputFlag( - name=crnt_flg_name[crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1:], - prefix=cast( - Literal["-", "--", "---"], - crnt_flg_name[:crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1], - ), - input_value=crnt_flg_val, + name=name, + prefix=cast(PREFIX_TYPE, prefix), # pyright: ignore[reportUnnecessaryCast] + input_value=input_value, status=None ) - - if input_flag in self._parsed_input_flags: + + if input_flag in flags: raise RepeatedInputFlagsException(input_flag) - self._parsed_input_flags.add_flag(input_flag) - crnt_flg_name, crnt_flg_val = None, None - - return (self._parsed_input_flags, crnt_flg_name, crnt_flg_val) - - def _is_next_token_value(self, current_index: int, - _tokens: list[str] | list[Never]) -> bool: - next_index = current_index + 1 - if next_index >= len(_tokens): - return False + flags.add_flag(input_flag) - next_token = _tokens[next_index] - return not next_token.startswith(MIN_FLAG_PREFIX) - -def _parse_single_token( - token: str, - crnt_flag_name: str | None, - crnt_flag_val: str | None -) -> tuple[str | None, str | None]: - if not token.startswith(MIN_FLAG_PREFIX): - if not crnt_flag_name or crnt_flag_val: - raise UnprocessedInputFlagException - return crnt_flag_name, token - - prefix = token[:token.rfind(MIN_FLAG_PREFIX)] - if len(token) < 2 or len(prefix) > 2: - raise UnprocessedInputFlagException - - new_flag_name = token - new_flag_value = None - - return new_flag_name, new_flag_value + return cls(command, input_flags=flags) diff --git a/src/argenta/data_bridge/__init__.py b/src/argenta/data_bridge/__init__.py new file mode 100644 index 0000000..95adfdc --- /dev/null +++ b/src/argenta/data_bridge/__init__.py @@ -0,0 +1,3 @@ +__all__ = ["DataBridge"] + +from .entity import DataBridge as DataBridge diff --git a/src/argenta/data_bridge/entity.py b/src/argenta/data_bridge/entity.py new file mode 100644 index 0000000..6f1f3f6 --- /dev/null +++ b/src/argenta/data_bridge/entity.py @@ -0,0 +1,23 @@ +__all__ = ["DataBridge"] + +from typing import Any + + +class DataBridge: + def __init__(self, initial_data: dict[str, Any] | None = None) -> None: + self._data: dict[str, Any] = initial_data if initial_data else {} + + def update(self, data: dict[str, Any]) -> None: + self._data.update(data) + + def get_all(self) -> dict[str, Any]: + return self._data + + def clear_all(self) -> None: + self._data.clear() + + def get_by_key(self, key: str) -> Any: + return self._data.get(key) + + def delete_by_key(self, key: str) -> None: + self._data.pop(key) diff --git a/src/argenta/di/__init__.py b/src/argenta/di/__init__.py index 04d5216..e7d5091 100644 --- a/src/argenta/di/__init__.py +++ b/src/argenta/di/__init__.py @@ -1,2 +1,2 @@ -from argenta.di.integration import inject as inject from argenta.di.integration import FromDishka as FromDishka +from argenta.di.integration import inject as inject diff --git a/src/argenta/di/integration.py b/src/argenta/di/integration.py index a4001ec..4c73a3a 100644 --- a/src/argenta/di/integration.py +++ b/src/argenta/di/integration.py @@ -3,11 +3,10 @@ __all__ = ["inject", "setup_dishka", "FromDishka"] from typing import Any, Callable, TypeVar from dishka import Container, FromDishka -from dishka.integrations.base import wrap_injection, is_dishka_injected - -from argenta.response import Response -from argenta.app import App +from dishka.integrations.base import is_dishka_injected, wrap_injection +from argenta.app.models import App +from argenta.response.entity import Response T = TypeVar("T") @@ -20,20 +19,18 @@ def inject(func: Callable[..., T]) -> Callable[..., T]: ) -def setup_dishka(app: App, *, auto_inject: bool = False) -> None: +def setup_dishka(app: App, container: Container, *, auto_inject: bool = False) -> None: if auto_inject: _auto_inject_handlers(app) + Response.patch_by_container(container) -def _get_container_from_response( - args: tuple[Any, ...], kwargs: dict[str, Any] -) -> Container: +def _get_container_from_response(args: tuple[Any, ...], kwargs: dict[str, Any]) -> Container: for arg in args: if isinstance(arg, Response): if hasattr(arg, "_dishka_container"): return arg._dishka_container # pyright: ignore[reportPrivateUsage] break - raise RuntimeError("dishka container not found in Response") diff --git a/src/argenta/di/providers.py b/src/argenta/di/providers.py index 43e404e..18191de 100644 --- a/src/argenta/di/providers.py +++ b/src/argenta/di/providers.py @@ -1,14 +1,19 @@ -from argenta.orchestrator.argparser import ArgParser -from dishka import Provider, provide, Scope +__all__ = [ + "SystemProvider", +] +from dishka import Provider, Scope, provide + +from argenta.data_bridge import DataBridge +from argenta.orchestrator.argparser import ArgParser from argenta.orchestrator.argparser.entity import ArgSpace class SystemProvider(Provider): - def __init__(self, arg_parser: ArgParser): - super().__init__() - self._arg_parser: ArgParser = arg_parser + @provide(scope=Scope.APP) + def get_argspace(self, arg_parser: ArgParser) -> ArgSpace: + return arg_parser.parsed_argspace @provide(scope=Scope.APP) - def get_argspace(self) -> ArgSpace: - return self._arg_parser.parse_args() + def get_data_bridge(self) -> DataBridge: + return DataBridge() diff --git a/src/argenta/metrics/__init__.py b/src/argenta/metrics/__init__.py index 5b98ef8..9888ab8 100644 --- a/src/argenta/metrics/__init__.py +++ b/src/argenta/metrics/__init__.py @@ -1,4 +1 @@ -__all__ = ["get_time_of_pre_cycle_setup"] - - -from argenta.metrics.main import get_time_of_pre_cycle_setup +from argenta.metrics.main import get_time_of_pre_cycle_setup as get_time_of_pre_cycle_setup diff --git a/src/argenta/metrics/main.py b/src/argenta/metrics/main.py index cc68072..d87c945 100644 --- a/src/argenta/metrics/main.py +++ b/src/argenta/metrics/main.py @@ -1,3 +1,7 @@ +__all__ = [ + "get_time_of_pre_cycle_setup", +] + import io from contextlib import redirect_stdout from time import time @@ -13,6 +17,6 @@ def get_time_of_pre_cycle_setup(app: App) -> float: """ start = time() with redirect_stdout(io.StringIO()): - app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage] + app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage] end = time() return end - start diff --git a/src/argenta/orchestrator/__init__.py b/src/argenta/orchestrator/__init__.py index b980d66..1eddaa5 100644 --- a/src/argenta/orchestrator/__init__.py +++ b/src/argenta/orchestrator/__init__.py @@ -1,4 +1,2 @@ -__all__ = ["ArgParser", "Orchestrator"] - -from argenta.orchestrator.argparser.entity import ArgParser -from argenta.orchestrator.entity import Orchestrator +from argenta.orchestrator.argparser.entity import ArgParser as ArgParser +from argenta.orchestrator.entity import Orchestrator as Orchestrator diff --git a/src/argenta/orchestrator/argparser/__init__.py b/src/argenta/orchestrator/argparser/__init__.py index 4f59581..6466f13 100644 --- a/src/argenta/orchestrator/argparser/__init__.py +++ b/src/argenta/orchestrator/argparser/__init__.py @@ -1,9 +1,4 @@ -__all__ = [ - "ArgParser", - "BooleanArgument", - "ValueArgument" -] - - -from argenta.orchestrator.argparser.entity import ArgParser -from argenta.orchestrator.argparser.arguments import BooleanArgument, ValueArgument +from argenta.orchestrator.argparser.arguments import BooleanArgument as BooleanArgument +from argenta.orchestrator.argparser.arguments import ValueArgument as ValueArgument +from argenta.orchestrator.argparser.entity import ArgParser as ArgParser +from argenta.orchestrator.argparser.entity import ArgSpace as ArgSpace diff --git a/src/argenta/orchestrator/argparser/arguments/__init__.py b/src/argenta/orchestrator/argparser/arguments/__init__.py index 0308877..d2058a3 100644 --- a/src/argenta/orchestrator/argparser/arguments/__init__.py +++ b/src/argenta/orchestrator/argparser/arguments/__init__.py @@ -1,8 +1,3 @@ -__all__ = ["BooleanArgument", "ValueArgument", "InputArgument"] - - -from argenta.orchestrator.argparser.arguments.models import ( - BooleanArgument, - ValueArgument, - InputArgument -) +from argenta.orchestrator.argparser.arguments.models import BooleanArgument as BooleanArgument +from argenta.orchestrator.argparser.arguments.models import InputArgument as InputArgument +from argenta.orchestrator.argparser.arguments.models import ValueArgument as ValueArgument diff --git a/src/argenta/orchestrator/argparser/arguments/models.py b/src/argenta/orchestrator/argparser/arguments/models.py index 32d6f02..98e9480 100644 --- a/src/argenta/orchestrator/argparser/arguments/models.py +++ b/src/argenta/orchestrator/argparser/arguments/models.py @@ -1,3 +1,5 @@ +__all__ = ["BooleanArgument", "ValueArgument", "InputArgument"] + from typing import Literal @@ -5,10 +7,8 @@ class BaseArgument: """ Private. Base class for all arguments """ - def __init__(self, name: str, *, - help: str, - is_deprecated: bool, - prefix: Literal["-", "--", "---"]): + + def __init__(self, name: str, *, help: str, is_deprecated: bool, prefix: Literal["-", "--", "---"]): """ Public. Boolean argument, does not require a value :param name: name of the argument @@ -20,20 +20,24 @@ class BaseArgument: self.help: str = help self.is_deprecated: bool = is_deprecated self.prefix: Literal["-", "--", "---"] = prefix - + @property def string_entity(self) -> str: return self.prefix + self.name class ValueArgument(BaseArgument): - def __init__(self, name: str, *, - prefix: Literal["-", "--", "---"] = "--", - help: str = "Help message for the value argument", - possible_values: list[str] | None = None, - default: str | None = None, - is_required: bool = False, - is_deprecated: bool = False): + def __init__( + self, + name: str, + *, + prefix: Literal["-", "--", "---"] = "--", + help: str = "Help message for the value argument", + possible_values: list[str] | None = None, + default: str | None = None, + is_required: bool = False, + is_deprecated: bool = False, + ): """ Public. Value argument, must have the value :param name: name of the argument @@ -52,10 +56,14 @@ class ValueArgument(BaseArgument): class BooleanArgument(BaseArgument): - def __init__(self, name: str, *, - prefix: Literal["-", "--", "---"] = "--", - help: str = "Help message for the boolean argument", - is_deprecated: bool = False): + def __init__( + self, + name: str, + *, + prefix: Literal["-", "--", "---"] = "--", + help: str = "Help message for the boolean argument", + is_deprecated: bool = False, + ): """ Public. Boolean argument, does not require a value :param name: name of the argument @@ -68,15 +76,13 @@ class BooleanArgument(BaseArgument): class InputArgument: - def __init__(self, name: str, - value: str | None, - founder_class: type[BaseArgument]) -> None: + def __init__(self, name: str, value: str | Literal[True], founder_class: type[BaseArgument]) -> None: self.name: str = name - self.value: str | None = value + self.value: str | Literal[True] = value self.founder_class: type[BaseArgument] = founder_class - + def __str__(self) -> str: return f"InputArgument({self.name}={self.value})" - + def __repr__(self) -> str: return f"InputArgument" diff --git a/src/argenta/orchestrator/argparser/entity.py b/src/argenta/orchestrator/argparser/entity.py index b49e426..42baaff 100644 --- a/src/argenta/orchestrator/argparser/entity.py +++ b/src/argenta/orchestrator/argparser/entity.py @@ -1,3 +1,9 @@ +__all__ = [ + "ArgSpace", + "ArgParser", +] + +import sys from argparse import ArgumentParser, Namespace from typing import Never, Self @@ -5,40 +11,63 @@ from argenta.orchestrator.argparser.arguments.models import ( BaseArgument, BooleanArgument, InputArgument, - ValueArgument + ValueArgument, ) - + class ArgSpace: def __init__(self, all_arguments: list[InputArgument]) -> None: self.all_arguments = all_arguments - - @classmethod - def from_namespace(cls, namespace: Namespace, - processed_args: list[ValueArgument | BooleanArgument]) -> Self: - name_type_paired_args: dict[str, type[BaseArgument]] = { - arg.name: type(arg) - for arg in processed_args + + self._name_object_paired_args: dict[str, InputArgument] = {} + self._type_object_paired_args: dict[type[BaseArgument], list[InputArgument]] = { + BooleanArgument: [], + ValueArgument: [] } - return cls([InputArgument(name=name, - value=value, - founder_class=name_type_paired_args[name]) - for name, value in vars(namespace).items()]) - + + self._setup_getters() + + @classmethod + def from_namespace( + cls, + namespace: Namespace, + processed_args: list[ValueArgument | BooleanArgument] + ) -> Self: + name_type_paired_processed_args: dict[str, type[BaseArgument]] = { + arg.name: type(arg) for arg in processed_args + } + parsed_arguments: list[InputArgument] = [] + + for name, value in vars(namespace).items(): + parsed_arguments.append( + InputArgument( + name=name, + value=value, + founder_class=name_type_paired_processed_args[name] + ) + ) + + return cls(parsed_arguments) + + def _setup_getters(self) -> None: + if not self.all_arguments: + return + for input_arg in self.all_arguments: + self._name_object_paired_args[input_arg.name] = input_arg + self._type_object_paired_args[input_arg.founder_class].append(input_arg) + def get_by_name(self, name: str) -> InputArgument | None: - for arg in self.all_arguments: - if arg.name == name: - return arg - return None - + return self._name_object_paired_args.get(name) + def get_by_type(self, arg_type: type[BaseArgument]) -> list[InputArgument] | list[Never]: - return [arg for arg in self.all_arguments if arg.founder_class is arg_type] - + return self._type_object_paired_args.get(arg_type, []) + class ArgParser: def __init__( self, - processed_args: list[ValueArgument | BooleanArgument], *, + processed_args: list[ValueArgument | BooleanArgument], + *, name: str = "Argenta", description: str = "Argenta available arguments", epilog: str = "github.com/koloideal/Argenta | made by kolo", @@ -55,24 +84,51 @@ class ArgParser: self.epilog: str = epilog self.processed_args: list[ValueArgument | BooleanArgument] = processed_args - self._core: ArgumentParser = ArgumentParser(prog=name, description=description, epilog=epilog) - - for arg in processed_args: - if isinstance(arg, BooleanArgument): - _ = self._core.add_argument(arg.string_entity, - action=arg.action, - help=arg.help, - deprecated=arg.is_deprecated) - else: - _ = self._core.add_argument(arg.string_entity, - action=arg.action, - help=arg.help, - default=arg.default, - choices=arg.possible_values, - required=arg.is_required, - deprecated=arg.is_deprecated) + self.parsed_argspace: ArgSpace = ArgSpace([]) - def parse_args(self) -> ArgSpace: - return ArgSpace.from_namespace(namespace=self._core.parse_args(), - processed_args=self.processed_args) - \ No newline at end of file + self._core: ArgumentParser = ArgumentParser(prog=name, description=description, epilog=epilog) + self._register_args(processed_args) + + def _parse_args(self) -> None: + self.parsed_argspace = ArgSpace.from_namespace( + namespace=self._core.parse_args(), processed_args=self.processed_args + ) + + def _register_args(self, processed_args: list[ValueArgument | BooleanArgument]) -> None: + if sys.version_info >= (3, 13): + for arg in processed_args: + if isinstance(arg, BooleanArgument): + _ = self._core.add_argument( + arg.string_entity, + action=arg.action, + help=arg.help, + deprecated=arg.is_deprecated + ) + else: + _ = self._core.add_argument( + arg.string_entity, + action=arg.action, + help=arg.help, + default=arg.default, + choices=arg.possible_values, + required=arg.is_required, + deprecated=arg.is_deprecated, + ) + else: + for arg in processed_args: + if isinstance(arg, BooleanArgument): + _ = self._core.add_argument( + arg.string_entity, + action=arg.action, + help=arg.help, + ) + else: + _ = self._core.add_argument( + arg.string_entity, + action=arg.action, + help=arg.help, + default=arg.default, + choices=arg.possible_values, + required=arg.is_required + ) + diff --git a/src/argenta/orchestrator/entity.py b/src/argenta/orchestrator/entity.py index 7934e56..e4646a1 100644 --- a/src/argenta/orchestrator/entity.py +++ b/src/argenta/orchestrator/entity.py @@ -1,20 +1,22 @@ -from argenta.app import App -from argenta.response import Response - -from argenta.orchestrator.argparser import ArgParser -from argenta.di.integration import setup_dishka -from argenta.di.providers import SystemProvider +__all__ = ["Orchestrator"] from dishka import Provider, make_container +from argenta.app import App +from argenta.di.integration import setup_dishka +from argenta.di.providers import SystemProvider +from argenta.orchestrator.argparser import ArgParser DEFAULT_ARGPARSER: ArgParser = ArgParser(processed_args=[]) class Orchestrator: - def __init__(self, arg_parser: ArgParser = DEFAULT_ARGPARSER, - custom_providers: list[Provider] = [], - auto_inject_handlers: bool = True): + def __init__( + self, + arg_parser: ArgParser = DEFAULT_ARGPARSER, + custom_providers: list[Provider] = [], + auto_inject_handlers: bool = True, + ): """ Public. An orchestrator and configurator that defines the behavior of an integrated system, one level higher than the App :param arg_parser: Cmd argument parser and configurator at startup @@ -24,14 +26,17 @@ class Orchestrator: self._custom_providers: list[Provider] = custom_providers self._auto_inject_handlers: bool = auto_inject_handlers + self._arg_parser._parse_args() + def start_polling(self, app: App) -> None: """ Public. Starting the user input processing cycle :param app: a running application :return: None """ - container = make_container(SystemProvider(self._arg_parser), *self._custom_providers) - Response.patch_by_container(container) - setup_dishka(app, auto_inject=self._auto_inject_handlers) + container = make_container( + SystemProvider(), *self._custom_providers, context={ArgParser: self._arg_parser} + ) + setup_dishka(app, container, auto_inject=self._auto_inject_handlers) app.run_polling() diff --git a/src/argenta/response/__init__.py b/src/argenta/response/__init__.py index d7eb4a1..905f782 100644 --- a/src/argenta/response/__init__.py +++ b/src/argenta/response/__init__.py @@ -1,5 +1,2 @@ -__all__ = ["Response", "ResponseStatus"] - - -from argenta.response.entity import Response -from argenta.response.status import ResponseStatus +from argenta.response.entity import Response as Response +from argenta.response.status import ResponseStatus as ResponseStatus diff --git a/src/argenta/response/entity.py b/src/argenta/response/entity.py index 2d072ae..516e80d 100644 --- a/src/argenta/response/entity.py +++ b/src/argenta/response/entity.py @@ -1,35 +1,14 @@ -from typing import Any +__all__ = ["Response"] from dishka import Container from argenta.command.flag.flags.models import InputFlags from argenta.response.status import ResponseStatus - EMPTY_INPUT_FLAGS: InputFlags = InputFlags() -class DataBridge: - _data: dict[str, Any] = {} - - @classmethod - def update_data(cls, data: dict[str, Any]) -> None: - cls._data.update(data) - - @classmethod - def get_data(cls) -> dict[str, Any]: - return cls._data - - @classmethod - def clear_data(cls) -> None: - cls._data.clear() - - @classmethod - def delete_from_data(cls, key: str) -> None: - cls._data.pop(key) - - -class Response(DataBridge): +class Response: _dishka_container: Container def __init__( diff --git a/src/argenta/response/status.py b/src/argenta/response/status.py index 7c860fd..c736de0 100644 --- a/src/argenta/response/status.py +++ b/src/argenta/response/status.py @@ -1,3 +1,5 @@ +__all__ = ["ResponseStatus"] + from enum import Enum @@ -8,12 +10,12 @@ class ResponseStatus(Enum): UNDEFINED_AND_INVALID_FLAGS = "UNDEFINED_AND_INVALID_FLAGS" @classmethod - def from_flags(cls, *, has_invalid_value_flags: bool, has_undefined_flags: bool) -> 'ResponseStatus': + def from_flags(cls, *, has_invalid_value_flags: bool, has_undefined_flags: bool) -> "ResponseStatus": key = (has_invalid_value_flags, has_undefined_flags) status_map: dict[tuple[bool, bool], ResponseStatus] = { - (True, True): cls.UNDEFINED_AND_INVALID_FLAGS, - (True, False): cls.INVALID_VALUE_FLAGS, - (False, True): cls.UNDEFINED_FLAGS, + (True, True): cls.UNDEFINED_AND_INVALID_FLAGS, + (True, False): cls.INVALID_VALUE_FLAGS, + (False, True): cls.UNDEFINED_FLAGS, (False, False): cls.ALL_FLAGS_VALID, } return status_map[key] diff --git a/src/argenta/router/__init__.py b/src/argenta/router/__init__.py index ecdb426..2a961de 100644 --- a/src/argenta/router/__init__.py +++ b/src/argenta/router/__init__.py @@ -1,4 +1 @@ -from argenta.router.entity import Router - - -__all__ = ["Router"] +from argenta.router.entity import Router as Router diff --git a/src/argenta/router/command_handler/entity.py b/src/argenta/router/command_handler/entity.py index ac01d71..300c458 100644 --- a/src/argenta/router/command_handler/entity.py +++ b/src/argenta/router/command_handler/entity.py @@ -1,3 +1,5 @@ +__all__ = ["CommandHandler", "CommandHandlers"] + from collections.abc import Iterator from typing import Callable @@ -30,9 +32,7 @@ class CommandHandlers: Private. The model that unites all CommandHandler of the routers :param command_handlers: list of CommandHandlers for register """ - self.command_handlers: list[CommandHandler] = ( - command_handlers if command_handlers else [] - ) + self.command_handlers: list[CommandHandler] = command_handlers if command_handlers else [] def add_handler(self, command_handler: CommandHandler) -> None: """ diff --git a/src/argenta/router/defaults.py b/src/argenta/router/defaults.py index d8e3f21..b0b96aa 100644 --- a/src/argenta/router/defaults.py +++ b/src/argenta/router/defaults.py @@ -1,4 +1,5 @@ +__all__ = ["system_router"] + from argenta.router import Router - system_router = Router(title="System points:") diff --git a/src/argenta/router/entity.py b/src/argenta/router/entity.py index 841c6d0..cb5d5dd 100644 --- a/src/argenta/router/entity.py +++ b/src/argenta/router/entity.py @@ -1,27 +1,29 @@ +__all__ = ["Router"] + +from inspect import get_annotations, getfullargspec, getsourcefile, getsourcelines from typing import Callable, TypeAlias -from inspect import getfullargspec, get_annotations, getsourcefile, getsourcelines + from rich.console import Console from argenta.command import Command, InputCommand from argenta.command.flag import ValidationStatus -from argenta.response import Response, ResponseStatus -from argenta.router.command_handler.entity import CommandHandlers, CommandHandler from argenta.command.flag.flags import Flags, InputFlags +from argenta.response import Response, ResponseStatus +from argenta.router.command_handler.entity import CommandHandler, CommandHandlers from argenta.router.exceptions import ( RepeatedFlagNameException, RequiredArgumentNotPassedException, TriggerContainSpacesException, ) - HandlerFunc: TypeAlias = Callable[..., None] class Router: def __init__( self, - *, title: str | None = "Default title", + *, disable_redirect_stdout: bool = False, ): """ @@ -76,9 +78,7 @@ class Router: if input_command_name.lower() in handle_command.aliases: self.process_input_command(input_command_flags, command_handler) - def process_input_command( - self, input_command_flags: InputFlags, command_handler: CommandHandler - ) -> None: + def process_input_command(self, input_command_flags: InputFlags, command_handler: CommandHandler) -> None: """ Private. Processes input command with the appropriate handler :param input_command_flags: input command flags as InputFlags @@ -88,9 +88,7 @@ class Router: handle_command = command_handler.handled_command if handle_command.registered_flags.flags: if input_command_flags.flags: - response: Response = _structuring_input_flags( - handle_command, input_command_flags - ) + response: Response = _structuring_input_flags(handle_command, input_command_flags) command_handler.handling(response) else: response = Response(ResponseStatus.ALL_FLAGS_VALID) @@ -101,9 +99,7 @@ class Router: for input_flag in input_command_flags: input_flag.status = ValidationStatus.UNDEFINED undefined_flags.add_flag(input_flag) - response = Response( - ResponseStatus.UNDEFINED_FLAGS, input_flags=undefined_flags - ) + response = Response(ResponseStatus.UNDEFINED_FLAGS, input_flags=undefined_flags) command_handler.handling(response) else: response = Response(ResponseStatus.ALL_FLAGS_VALID) @@ -140,15 +136,11 @@ class CommandDecorator: def __call__(self, handler_func: Callable[..., None]) -> Callable[..., None]: _validate_func_args(handler_func) - self.router.command_handlers.add_handler( - CommandHandler(handler_func, self.command) - ) + self.router.command_handlers.add_handler(CommandHandler(handler_func, self.command)) return handler_func -def _structuring_input_flags( - handled_command: Command, input_flags: InputFlags -) -> Response: +def _structuring_input_flags(handled_command: Command, input_flags: InputFlags) -> Response: """ Private. Validates flags of input command :param handled_command: entity of the handled command diff --git a/src/argenta/router/exceptions.py b/src/argenta/router/exceptions.py index 161f08f..2c40df0 100644 --- a/src/argenta/router/exceptions.py +++ b/src/argenta/router/exceptions.py @@ -1,3 +1,5 @@ +__all__ = ["RepeatedFlagNameException", "RequiredArgumentNotPassedException", "TriggerContainSpacesException"] + from typing import override diff --git a/tests/system_tests/test_system_handling_non_standard_behavior.py b/tests/system_tests/test_system_handling_non_standard_behavior.py index f711575..47375f1 100644 --- a/tests/system_tests/test_system_handling_non_standard_behavior.py +++ b/tests/system_tests/test_system_handling_non_standard_behavior.py @@ -1,14 +1,15 @@ -import _io -from unittest.mock import patch, MagicMock -from unittest import TestCase import io import re import sys +from unittest import TestCase +from unittest.mock import MagicMock, patch +import _io + +from argenta import App, Orchestrator, Router from argenta.command import Command, PredefinedFlags -from argenta.command.flag.models import ValidationStatus from argenta.command.flag.flags.models import Flags -from argenta import Orchestrator, App, Router +from argenta.command.flag.models import ValidationStatus from argenta.response import Response diff --git a/tests/system_tests/test_system_handling_normal_behavior.py b/tests/system_tests/test_system_handling_normal_behavior.py index f2f5a7b..3ebc80d 100644 --- a/tests/system_tests/test_system_handling_normal_behavior.py +++ b/tests/system_tests/test_system_handling_normal_behavior.py @@ -1,16 +1,17 @@ -import _io -from unittest.mock import patch, MagicMock -from unittest import TestCase import io import re import sys +from unittest import TestCase +from unittest.mock import MagicMock, patch +import _io + +from argenta import App, Orchestrator, Router from argenta.command import Command, PredefinedFlags -from argenta.command.flag.models import PossibleValues, ValidationStatus -from argenta.response import Response -from argenta import Orchestrator, App, Router from argenta.command.flag import Flag from argenta.command.flag.flags import Flags +from argenta.command.flag.models import PossibleValues, ValidationStatus +from argenta.response import Response class PatchedArgvTestCase(TestCase): diff --git a/tests/unit_tests/test_app.py b/tests/unit_tests/test_app.py index a0eb7bf..a30dacf 100644 --- a/tests/unit_tests/test_app.py +++ b/tests/unit_tests/test_app.py @@ -1,8 +1,7 @@ -from argenta.command.models import InputCommand, Command -from argenta.app import App - import unittest +from argenta.app import App +from argenta.command.models import Command, InputCommand from argenta.router import Router diff --git a/tests/unit_tests/test_argparser.py b/tests/unit_tests/test_argparser.py index da86049..f02beb2 100644 --- a/tests/unit_tests/test_argparser.py +++ b/tests/unit_tests/test_argparser.py @@ -1,18 +1,21 @@ -import unittest -from unittest.mock import MagicMock, patch from argparse import Namespace +import sys +from unittest.mock import call +import pytest + +from argenta.orchestrator.argparser.arguments.models import (BaseArgument, + BooleanArgument, + InputArgument, + ValueArgument) from argenta.orchestrator.argparser.entity import ArgParser, ArgSpace -from argenta.orchestrator.argparser.arguments.models import ( - ValueArgument, - BooleanArgument, - InputArgument, - BaseArgument -) -class TestArgumentClasses(unittest.TestCase): +class TestArgumentCreation: + """Tests for the creation and attribute validation of argument model classes.""" + def test_value_argument_creation(self): + """Ensures ValueArgument instances are created with correct attributes.""" arg = ValueArgument( name="test_arg", prefix="--", @@ -20,145 +23,227 @@ class TestArgumentClasses(unittest.TestCase): possible_values=["one", "two"], default="one", is_required=True, - is_deprecated=False + is_deprecated=False, ) - self.assertEqual(arg.name, "test_arg") - self.assertEqual(arg.prefix, "--") - self.assertEqual(arg.help, "A test argument.") - self.assertEqual(arg.possible_values, ["one", "two"]) - self.assertEqual(arg.default, "one") - self.assertTrue(arg.is_required) - self.assertFalse(arg.is_deprecated) - self.assertEqual(arg.action, "store") - self.assertEqual(arg.string_entity, "--test_arg") + assert arg.name == "test_arg" + assert arg.prefix == "--" + assert arg.help == "A test argument." + assert arg.possible_values == ["one", "two"] + assert arg.default == "one" + assert arg.is_required is True + assert arg.is_deprecated is False + assert arg.action == "store" + assert arg.string_entity == "--test_arg" def test_boolean_argument_creation(self): + """Ensures BooleanArgument instances are created with correct attributes.""" arg = BooleanArgument( - name="verbose", - prefix="-", - help="Enable verbose mode.", - is_deprecated=True + name="verbose", prefix="-", help="Enable verbose mode.", is_deprecated=True ) - self.assertEqual(arg.name, "verbose") - self.assertEqual(arg.prefix, "-") - self.assertEqual(arg.help, "Enable verbose mode.") - self.assertTrue(arg.is_deprecated) - self.assertEqual(arg.action, "store_true") - self.assertEqual(arg.string_entity, "-verbose") + assert arg.name == "verbose" + assert arg.prefix == "-" + assert arg.help == "Enable verbose mode." + assert arg.is_deprecated is True + assert arg.action == "store_true" + assert arg.string_entity == "-verbose" def test_input_argument_creation(self): + """Ensures InputArgument instances are created with correct attributes.""" arg = InputArgument( - name="file", - value="/path/to/file", - founder_class=ValueArgument + name="file", value="/path/to/file", founder_class=ValueArgument ) - self.assertEqual(arg.name, "file") - self.assertEqual(arg.value, "/path/to/file") - self.assertEqual(arg.founder_class, ValueArgument) + assert arg.name == "file" + assert arg.value == "/path/to/file" + assert arg.founder_class is ValueArgument -class TestArgParser(unittest.TestCase): - def setUp(self): - self.value_arg = ValueArgument(name="config", help="Path to config file") - self.bool_arg = BooleanArgument(name="debug", help="Enable debug mode") - self.processed_args = [self.value_arg, self.bool_arg] +class TestArgSpace: + """Tests for the ArgSpace class, which holds parsed argument values.""" - def test_argparser_initialization(self): - parser = ArgParser( - processed_args=self.processed_args, - name="TestApp", - description="A test application.", - epilog="Test epilog." - ) - self.assertEqual(parser.name, "TestApp") - self.assertEqual(parser.description, "A test application.") - self.assertEqual(parser.epilog, "Test epilog.") - self.assertEqual(parser.processed_args, self.processed_args) + @pytest.fixture + def mock_arguments(self) -> list[InputArgument]: + """Provides a list of mock InputArgument objects for testing.""" + return [ + InputArgument(name="arg1", value="val1", founder_class=ValueArgument), + InputArgument(name="arg2", value=True, founder_class=BooleanArgument), + InputArgument(name="arg3", value="val3", founder_class=ValueArgument), + ] - @patch('argenta.orchestrator.argparser.entity.ArgumentParser.parse_args') - def test_parse_args(self, mock_parse_args: MagicMock): - mock_namespace = Namespace(config='config.json', debug=True) - mock_parse_args.return_value = mock_namespace + @pytest.fixture + def arg_space(self, mock_arguments: list[InputArgument]) -> ArgSpace: + """Provides a pre-populated ArgSpace instance.""" + return ArgSpace(all_arguments=mock_arguments) - parser = ArgParser(processed_args=self.processed_args) - arg_space = parser.parse_args() + def test_initialization(self, arg_space: ArgSpace, mock_arguments: list[InputArgument]): + """Tests if ArgSpace is initialized correctly with a list of arguments.""" + assert len(arg_space.all_arguments) == 3 + assert arg_space.all_arguments == mock_arguments - self.assertIsInstance(arg_space, ArgSpace) - self.assertEqual(len(arg_space.all_arguments), 2) + def test_get_by_name(self, arg_space: ArgSpace, mock_arguments: list[InputArgument]): + """Tests retrieving an argument by its name.""" + found_arg = arg_space.get_by_name("arg1") + assert found_arg is not None + assert found_arg == mock_arguments[0] + + def test_get_by_name_not_found(self, arg_space: ArgSpace): + """Tests that get_by_name returns None for a non-existent argument.""" + found_arg = arg_space.get_by_name("non_existent_arg") + assert found_arg is None + + def test_get_by_type(self, arg_space: ArgSpace, mock_arguments: list[InputArgument]): + """Tests retrieving arguments based on their founder class type.""" + value_args = arg_space.get_by_type(ValueArgument) + assert len(value_args) == 2 + assert mock_arguments[0] in value_args + assert mock_arguments[2] in value_args + + bool_args = arg_space.get_by_type(BooleanArgument) + assert len(bool_args) == 1 + assert mock_arguments[1] in bool_args + + def test_get_by_type_not_found(self, arg_space: ArgSpace): + """Tests that get_by_type returns an empty list for an unused argument type.""" + class OtherArgument(BaseArgument): + pass + other_args = arg_space.get_by_type(OtherArgument) + assert other_args == [] + + def test_from_namespace(self): + """Tests the class method for creating an ArgSpace from an argparse.Namespace.""" + namespace = Namespace(config="config.json", debug=True, verbose=False) + processed_args = [ + ValueArgument(name="config", prefix="--"), + BooleanArgument(name="debug", prefix="-"), + BooleanArgument(name="verbose", prefix="-"), + ] + + arg_space = ArgSpace.from_namespace(namespace, processed_args) + assert len(arg_space.all_arguments) == 3 config_arg = arg_space.get_by_name('config') debug_arg = arg_space.get_by_name('debug') - self.assertIsNotNone(config_arg) - if config_arg: - self.assertEqual(config_arg.value, 'config.json') - self.assertEqual(config_arg.founder_class, ValueArgument) + assert config_arg is not None + assert config_arg.value == "config.json" + assert config_arg.founder_class is ValueArgument - self.assertIsNotNone(debug_arg) - if debug_arg: - self.assertTrue(debug_arg.value) - self.assertEqual(debug_arg.founder_class, BooleanArgument) + assert debug_arg is not None + assert debug_arg.value is True + assert debug_arg.founder_class is BooleanArgument -class TestArgSpace(unittest.TestCase): - def setUp(self): - self.input_arg1 = InputArgument(name="arg1", value="val1", founder_class=ValueArgument) - self.input_arg2 = InputArgument(name="arg2", value="val2", founder_class=BooleanArgument) - self.input_arg3 = InputArgument(name="arg3", value="val3", founder_class=ValueArgument) - self.arg_space = ArgSpace(all_arguments=[self.input_arg1, self.input_arg2, self.input_arg3]) +class TestArgParser: + """Tests for the ArgParser class, which orchestrates argument parsing.""" - def test_argspace_initialization(self): - self.assertEqual(len(self.arg_space.all_arguments), 3) - self.assertIn(self.input_arg1, self.arg_space.all_arguments) - self.assertIn(self.input_arg2, self.arg_space.all_arguments) - self.assertIn(self.input_arg3, self.arg_space.all_arguments) + @pytest.fixture + def value_arg(self) -> ValueArgument: + """Provides a sample ValueArgument.""" + return ValueArgument(name="config", help="Path to config file", default="dev.json", is_required=False, possible_values=["dev.json", "prod.json"]) - def test_get_by_name(self): - found_arg = self.arg_space.get_by_name("arg1") - self.assertIsNotNone(found_arg) - if found_arg: - self.assertEqual(found_arg, self.input_arg1) + @pytest.fixture + def bool_arg(self) -> BooleanArgument: + """Provides a sample BooleanArgument.""" + return BooleanArgument(name="debug", help="Enable debug mode") - def test_get_by_name_not_found(self): - found_arg = self.arg_space.get_by_name("non_existent_arg") - self.assertIsNone(found_arg) + @pytest.fixture + def processed_args(self, value_arg: ValueArgument, bool_arg: BooleanArgument) -> list: + """Provides a list of processed arguments.""" + return [value_arg, bool_arg] - def test_get_by_type(self): - value_args = self.arg_space.get_by_type(ValueArgument) - self.assertEqual(len(value_args), 2) - self.assertIn(self.input_arg1, value_args) - self.assertIn(self.input_arg3, value_args) + def test_initialization(self, processed_args: list): + """Tests that the ArgParser constructor correctly assigns attributes.""" + parser = ArgParser( + processed_args=processed_args, + name="TestApp", + description="A test application.", + epilog="Test epilog.", + ) + assert parser.name == "TestApp" + assert parser.description == "A test application." + assert parser.epilog == "Test epilog." + assert parser.processed_args == processed_args + assert isinstance(parser.parsed_argspace, ArgSpace) + assert parser.parsed_argspace.all_arguments == [] - bool_args = self.arg_space.get_by_type(BooleanArgument) - self.assertEqual(len(bool_args), 1) - self.assertIn(self.input_arg2, bool_args) + @pytest.mark.skipif(sys.version_info < (3, 13), reason="requires python3.13 or higher") + def test_register_args(self, mocker, value_arg: ValueArgument, bool_arg: BooleanArgument): + """Tests that arguments are correctly registered with the underlying ArgumentParser.""" + mock_add_argument = mocker.patch("argparse.ArgumentParser.add_argument") - def test_get_by_type_not_found(self): - class OtherArgument(BaseArgument): - pass + parser = ArgParser(processed_args=[value_arg, bool_arg]) - other_args = self.arg_space.get_by_type(OtherArgument) - self.assertEqual(len(other_args), 0) - - def test_from_namespace(self): - namespace = Namespace(arg1="val1", debug=True) - processed_args = [ - ValueArgument(name="arg1", prefix="--"), - BooleanArgument(name="debug", prefix="-") + expected_calls = [ + # Call for the ValueArgument + call( + value_arg.string_entity, + action=value_arg.action, + help=value_arg.help, + default=value_arg.default, + choices=value_arg.possible_values, + required=value_arg.is_required, + deprecated=value_arg.is_deprecated + ), + # Call for the BooleanArgument + call( + bool_arg.string_entity, + action=bool_arg.action, + help=bool_arg.help, + deprecated=bool_arg.is_deprecated + ) ] + mock_add_argument.assert_has_calls(expected_calls, any_order=True) + + @pytest.mark.skipif(sys.version_info > (3, 12), reason='for more latest python version has been other test') + def test_register_args(self, mocker, value_arg: ValueArgument, bool_arg: BooleanArgument): + """Tests that arguments are correctly registered with the underlying ArgumentParser.""" + mock_add_argument = mocker.patch("argparse.ArgumentParser.add_argument") - arg_space = ArgSpace.from_namespace(namespace, processed_args) - self.assertEqual(len(arg_space.all_arguments), 2) + parser = ArgParser(processed_args=[value_arg, bool_arg]) - arg1 = arg_space.get_by_name('arg1') + expected_calls = [ + # Call for the ValueArgument + call( + value_arg.string_entity, + action=value_arg.action, + help=value_arg.help, + default=value_arg.default, + choices=value_arg.possible_values, + required=value_arg.is_required + ), + # Call for the BooleanArgument + call( + bool_arg.string_entity, + action=bool_arg.action, + help=bool_arg.help + ) + ] + mock_add_argument.assert_has_calls(expected_calls, any_order=True) + + def test_parse_args_populates_argspace(self, mocker, processed_args: list[ValueArgument | BooleanArgument]): + """Tests that _parse_args correctly calls the parser and populates the ArgSpace.""" + # 1. Mock the return value of the internal argparse instance + mock_namespace = Namespace(config='config.json', debug=True) + mocker.patch( + 'argparse.ArgumentParser.parse_args', + return_value=mock_namespace + ) + + # 2. Initialize the parser and call the method under test + parser = ArgParser(processed_args=processed_args) + parser._parse_args() # Test the private method that contains the logic + + # 3. Assert the results + arg_space = parser.parsed_argspace + assert isinstance(arg_space, ArgSpace) + assert len(arg_space.all_arguments) == 2 + + config_arg = arg_space.get_by_name('config') debug_arg = arg_space.get_by_name('debug') - self.assertIsNotNone(arg1) - if arg1: - self.assertEqual(arg1.value, "val1") - self.assertEqual(arg1.founder_class, ValueArgument) + assert config_arg is not None + assert config_arg.value == 'config.json' + assert config_arg.founder_class is ValueArgument - self.assertIsNotNone(debug_arg) - if debug_arg: - self.assertTrue(debug_arg.value) - self.assertEqual(debug_arg.founder_class, BooleanArgument) + assert debug_arg is not None + assert debug_arg.value is True + assert debug_arg.founder_class is BooleanArgument diff --git a/tests/unit_tests/test_autocompleter.py b/tests/unit_tests/test_autocompleter.py new file mode 100644 index 0000000..aab1a66 --- /dev/null +++ b/tests/unit_tests/test_autocompleter.py @@ -0,0 +1,198 @@ +import os +from unittest.mock import MagicMock, call, patch + +import pytest + +# Since readline is not available on all platforms (e.g., Windows) for testing, +# it is mocked for all tests. +readline_mock = MagicMock() + +# We patch the module where it's imported, not where it's defined. +@pytest.fixture +def mock_readline(): + """Fixture to provide a mock of the `readline` module.""" + with patch('argenta.app.autocompleter.entity.readline', readline_mock) as mock: + # This nested state simulates readline's internal history list. + _history = [] + + def add_history(item: str) -> None: + _history.append(item) + + def get_history_item(index: int) -> str | None: + # readline history is 1-based. + if 1 <= index <= len(_history): + return _history[index - 1] + return None + + def get_current_history_length() -> int: + return len(_history) + + def clear_history() -> None: + _history.clear() + + # Reset all mocks and the internal history before each test. + mock.reset_mock() + clear_history() + + # Apply side effects to mock functions to simulate real behavior. + mock.add_history.side_effect = add_history + mock.get_history_item.side_effect = get_history_item + mock.get_current_history_length.side_effect = get_current_history_length + + # Provide a default return value for functions that are read from. + mock.get_completer_delims.return_value = " " + + yield mock + +# We import the class under test after setting up the patch context if needed, +# or ensure patches target the correct import location. +from argenta.app.autocompleter.entity import (AutoCompleter, + _get_history_items, + _is_command_exist) + + +class TestAutoCompleter: + """Test suite for the AutoCompleter class.""" + HISTORY_FILE = "test_history.txt" + COMMANDS = ["start", "stop", "status"] + + def test_initialization(self): + """Tests that the constructor correctly assigns attributes.""" + completer = AutoCompleter(history_filename=self.HISTORY_FILE, autocomplete_button="tab") + assert completer.history_filename == self.HISTORY_FILE + assert completer.autocomplete_button == "tab" + + def test_initial_setup_if_history_file_does_not_exist(self, fs, mock_readline): + """Tests initial setup creates history from commands when the history file is absent.""" + # Ensure the file does not exist in the fake filesystem. + if os.path.exists(self.HISTORY_FILE): + os.remove(self.HISTORY_FILE) + + completer = AutoCompleter(history_filename=self.HISTORY_FILE) + completer.initial_setup(self.COMMANDS) + + mock_readline.read_history_file.assert_not_called() + expected_calls = [call(cmd) for cmd in self.COMMANDS] + mock_readline.add_history.assert_has_calls(expected_calls, any_order=True) + assert mock_readline.add_history.call_count == len(self.COMMANDS) + + mock_readline.set_completer.assert_called_with(completer._complete) + mock_readline.parse_and_bind.assert_called_with("tab: complete") + + def test_initial_setup_if_history_file_exists(self, fs, mock_readline): + """Tests initial setup reads from an existing history file.""" + fs.create_file(self.HISTORY_FILE, contents="previous_command\n") + + completer = AutoCompleter(history_filename=self.HISTORY_FILE) + completer.initial_setup(self.COMMANDS) + + mock_readline.read_history_file.assert_called_once_with(self.HISTORY_FILE) + mock_readline.add_history.assert_not_called() + mock_readline.set_completer.assert_called_once() + mock_readline.parse_and_bind.assert_called_once() + + def test_initial_setup_with_no_history_filename(self, mock_readline): + """Tests initial setup when no history filename is provided.""" + completer = AutoCompleter(history_filename=None) + completer.initial_setup(self.COMMANDS) + + mock_readline.read_history_file.assert_not_called() + expected_calls = [call(cmd) for cmd in self.COMMANDS] + mock_readline.add_history.assert_has_calls(expected_calls, any_order=True) + + def test_exit_setup_writes_and_filters_history(self, fs, mock_readline): + """Tests that exit_setup writes a filtered and unique history to the file.""" + # 1. Populate the mock readline history. + mock_readline.add_history.side_effect(None) # Temporarily disable side effect to just record calls + mock_readline.add_history("start server") + mock_readline.add_history("stop client") + mock_readline.add_history("invalid command") + mock_readline.add_history("start server") # Add a duplicate. + + # 2. Simulate the state of the history file after readline.write_history_file would have run. + raw_history_content = "\n".join(["start server", "stop client", "invalid command", "start server"]) + fs.create_file(self.HISTORY_FILE, contents=raw_history_content) + + # 3. Call the method under test. + completer = AutoCompleter(history_filename=self.HISTORY_FILE) + completer.exit_setup(all_commands=["start", "stop"], ignore_command_register=False) + + # 4. Assert that readline's write function was called. + mock_readline.write_history_file.assert_called_once_with(self.HISTORY_FILE) + + # 5. Assert the file was correctly re-written with filtered and unique content. + with open(self.HISTORY_FILE, "r") as f: + content = f.read() + lines = sorted(content.strip().split("\n")) + assert lines == ["start server", "stop client"] + + def test_exit_setup_with_no_history_filename(self, mock_readline): + """Tests that exit_setup does nothing if no filename is provided.""" + completer = AutoCompleter(history_filename=None) + completer.exit_setup(all_commands=self.COMMANDS, ignore_command_register=False) + mock_readline.write_history_file.assert_not_called() + + def test_complete_with_no_matches(self, mock_readline): + """Tests the _complete method when there are no matching history items.""" + for cmd in ["start", "stop"]: + mock_readline.add_history(cmd) + + completer = AutoCompleter() + assert completer._complete("run", 0) is None + assert completer._complete("run", 1) is None + + def test_complete_with_one_match(self, mock_readline): + """Tests the _complete method when there is exactly one match.""" + mock_readline.add_history("start server") + mock_readline.add_history("stop server") + + completer = AutoCompleter() + assert completer._complete("start", 0) == "start server" + assert completer._complete("start", 1) is None # Subsequent states yield no matches + + def test_complete_with_multiple_matches(self, mock_readline): + """Tests _complete with multiple matches that share a common prefix.""" + mock_readline.add_history("status client") + mock_readline.add_history("status server") + mock_readline.add_history("stop") + + completer = AutoCompleter() + + # On state 0, it should insert the common prefix via readline and return None. + result = completer._complete("stat", 0) + assert result is None + mock_readline.insert_text.assert_called_once_with("us ") # Completes "stat" to "status " + mock_readline.redisplay.assert_called_once() + + # On subsequent states, it should do nothing. + mock_readline.reset_mock() + result_state_1 = completer._complete("stat", 1) + assert result_state_1 is None + mock_readline.insert_text.assert_not_called() + + +class TestHelperFunctions: + """Test suite for helper functions in the autocompleter module.""" + + def test_is_command_exist(self): + """Tests the _is_command_exist helper function.""" + existing = ["start", "stop", "status"] + + # Case-sensitive check + assert _is_command_exist("start", existing, ignore_command_register=False) is True + assert _is_command_exist("START", existing, ignore_command_register=False) is False + assert _is_command_exist("unknown", existing, ignore_command_register=False) is False + + # Case-insensitive check + assert _is_command_exist("start", existing, ignore_command_register=True) is True + assert _is_command_exist("START", existing, ignore_command_register=True) is True + assert _is_command_exist("unknown", existing, ignore_command_register=True) is False + + def test_get_history_items(self, mock_readline): + """Tests the _get_history_items helper function.""" + assert _get_history_items() == [] + + mock_readline.add_history("first item") + mock_readline.add_history("second item") + + assert _get_history_items() == ["first item", "second item"] diff --git a/tests/unit_tests/test_command.py b/tests/unit_tests/test_command.py index 4577f77..fbd5bab 100644 --- a/tests/unit_tests/test_command.py +++ b/tests/unit_tests/test_command.py @@ -1,13 +1,13 @@ +import re +import unittest + +from argenta.command.exceptions import (EmptyInputCommandException, + RepeatedInputFlagsException, + UnprocessedInputFlagException) from argenta.command.flag import Flag, InputFlag from argenta.command.flag.flags import Flags -from argenta.command.flag.models import PossibleValues -from argenta.command.models import InputCommand, Command, ValidationStatus -from argenta.command.exceptions import (UnprocessedInputFlagException, - RepeatedInputFlagsException, - EmptyInputCommandException) - -import unittest -import re +from argenta.command.flag.models import PossibleValues, ValidationStatus +from argenta.command.models import Command, InputCommand class TestInputCommand(unittest.TestCase): @@ -26,25 +26,25 @@ class TestInputCommand(unittest.TestCase): with self.assertRaises(EmptyInputCommandException): InputCommand.parse('') - def test_validate_valid_input_flag1(self): + def test_validate_invalid_input_flag1(self): command = Command('some', flags=Flag('test')) - self.assertEqual(command.validate_input_flag(InputFlag('test', input_value=None, status=None)), ValidationStatus.VALID) + self.assertEqual(command.validate_input_flag(InputFlag('test', input_value='', status=None)), ValidationStatus.INVALID) def test_validate_valid_input_flag2(self): command = Command('some', flags=Flags([Flag('test'), Flag('more')])) - self.assertEqual(command.validate_input_flag(InputFlag('more', input_value=None, status=None)), ValidationStatus.VALID) + self.assertEqual(command.validate_input_flag(InputFlag('more', input_value='random-value', status=None)), ValidationStatus.VALID) def test_validate_undefined_input_flag1(self): command = Command('some', flags=Flag('test')) - self.assertEqual(command.validate_input_flag(InputFlag('more', input_value=None, status=None)), ValidationStatus.UNDEFINED) + self.assertEqual(command.validate_input_flag(InputFlag('more', input_value='', status=None)), ValidationStatus.UNDEFINED) def test_validate_undefined_input_flag2(self): command = Command('some', flags=Flags([Flag('test'), Flag('more')])) - self.assertEqual(command.validate_input_flag(InputFlag('case', input_value=None, status=None)), ValidationStatus.UNDEFINED) + self.assertEqual(command.validate_input_flag(InputFlag('case', input_value='', status=None)), ValidationStatus.UNDEFINED) def test_validate_undefined_input_flag3(self): command = Command('some') - self.assertEqual(command.validate_input_flag(InputFlag('case', input_value=None, status=None)), ValidationStatus.UNDEFINED) + self.assertEqual(command.validate_input_flag(InputFlag('case', input_value='', status=None)), ValidationStatus.UNDEFINED) def test_invalid_input_flag1(self): command = Command('some', flags=Flag('test', possible_values=PossibleValues.NEITHER)) @@ -61,4 +61,3 @@ class TestInputCommand(unittest.TestCase): def test_isinstance_parse_correct_raw_command(self): cmd = InputCommand.parse('ssh --host 192.168.0.3') self.assertIsInstance(cmd, InputCommand) - diff --git a/tests/unit_tests/test_dividing_line.py b/tests/unit_tests/test_dividing_line.py index edd353d..2496d8d 100644 --- a/tests/unit_tests/test_dividing_line.py +++ b/tests/unit_tests/test_dividing_line.py @@ -1,7 +1,7 @@ -from argenta.app.dividing_line import DynamicDividingLine, StaticDividingLine - import unittest +from argenta.app.dividing_line import DynamicDividingLine, StaticDividingLine + class TestDividingLine(unittest.TestCase): def test_get_static_dividing_line_full_line(self): diff --git a/tests/unit_tests/test_flag.py b/tests/unit_tests/test_flag.py index 5203201..d85a446 100644 --- a/tests/unit_tests/test_flag.py +++ b/tests/unit_tests/test_flag.py @@ -1,8 +1,8 @@ -from argenta.command.flag import Flag, InputFlag, PossibleValues -from argenta.command.flag.flags import InputFlags, Flags - -import unittest import re +import unittest + +from argenta.command.flag import Flag, InputFlag, PossibleValues +from argenta.command.flag.flags import Flags, InputFlags class TestFlag(unittest.TestCase): @@ -29,8 +29,8 @@ class TestFlag(unittest.TestCase): '--') def test_get_flag_value_without_set(self): - self.assertEqual(InputFlag(name='test', input_value=None, status=None).input_value, - None) + self.assertEqual(InputFlag(name='test', input_value='', status=None).input_value, + '') def test_get_flag_value_with_set(self): flag = InputFlag(name='test', input_value='example', status=None) @@ -54,11 +54,11 @@ class TestFlag(unittest.TestCase): def test_validate_correct_empty_flag_value_without_possible_flag_values(self): flag = Flag(name='test', possible_values=PossibleValues.NEITHER) - self.assertEqual(flag.validate_input_flag_value(None), True) + self.assertEqual(flag.validate_input_flag_value(''), True) def test_validate_correct_empty_flag_value_with_possible_flag_values(self): flag = Flag(name='test', possible_values=PossibleValues.NEITHER) - self.assertEqual(flag.validate_input_flag_value(None), True) + self.assertEqual(flag.validate_input_flag_value(''), True) def test_validate_incorrect_random_flag_value_without_possible_flag_values(self): flag = Flag(name='test', possible_values=PossibleValues.NEITHER) @@ -69,19 +69,19 @@ class TestFlag(unittest.TestCase): self.assertEqual(flag.validate_input_flag_value('random value'), True) def test_get_input_flag1(self): - flag = InputFlag(name='test', input_value=None, status=None) + flag = InputFlag(name='test', input_value='', status=None) input_flags = InputFlags([flag]) self.assertEqual(input_flags.get_flag_by_name('test'), flag) def test_get_input_flag2(self): - flag = InputFlag(name='test', input_value=None, status=None) - flag2 = InputFlag(name='some', input_value=None, status=None) + flag = InputFlag(name='test', input_value='', status=None) + flag2 = InputFlag(name='some', input_value='', status=None) input_flags = InputFlags([flag, flag2]) self.assertEqual(input_flags.get_flag_by_name('some'), flag2) def test_get_undefined_input_flag(self): - flag = InputFlag(name='test', input_value=None, status=None) - flag2 = InputFlag(name='some', input_value=None, status=None) + flag = InputFlag(name='test', input_value='', status=None) + flag2 = InputFlag(name='some', input_value='', status=None) input_flags = InputFlags([flag, flag2]) self.assertEqual(input_flags.get_flag_by_name('case'), None) diff --git a/tests/unit_tests/test_response.py b/tests/unit_tests/test_response.py index 6e01960..6a1d020 100644 --- a/tests/unit_tests/test_response.py +++ b/tests/unit_tests/test_response.py @@ -1,131 +1,80 @@ import unittest -from datetime import datetime, date +from datetime import date, datetime -from argenta.response.entity import Response, DataBridge, EMPTY_INPUT_FLAGS -from argenta.response.status import ResponseStatus +from argenta.data_bridge import DataBridge +from argenta.command.flag.models import InputFlag from argenta.command.flag.flags.models import InputFlags -from argenta.command.flag import InputFlag +from argenta.response.entity import EMPTY_INPUT_FLAGS, Response +from argenta.response.status import ResponseStatus class TestDataBridge(unittest.TestCase): def setUp(self): - """Clear data before each test""" - DataBridge.clear_data() - - def tearDown(self): - """Clear data after each test""" - DataBridge.clear_data() + """Create a new DataBridge instance for each test""" + self.data_bridge = DataBridge() def test_update_data_basic(self): """Test basic data update functionality""" test_data = {"key1": "value1", "key2": "value2"} - DataBridge.update_data(test_data) - self.assertEqual(DataBridge.get_data(), test_data) + self.data_bridge.update(test_data) + self.assertEqual(self.data_bridge.get_all(), test_data) def test_update_data_with_datetime(self): """Test updating data with datetime objects""" test_datetime = datetime(2024, 1, 15, 10, 30, 45) test_data = {"created_at": test_datetime, "name": "test"} - DataBridge.update_data(test_data) - - result = DataBridge.get_data() + self.data_bridge.update(test_data) + + result = self.data_bridge.get_all() self.assertEqual(result["created_at"], test_datetime) self.assertEqual(result["name"], "test") - def test_update_data_with_date(self): - """Test updating data with date objects""" - test_date = date(2024, 1, 15) - test_data = {"birth_date": test_date, "active": True} - DataBridge.update_data(test_data) - - result = DataBridge.get_data() - self.assertEqual(result["birth_date"], test_date) - self.assertEqual(result["active"], True) - def test_update_data_multiple_calls(self): - """Test multiple update_data calls merge data""" - first_data = {"key1": "value1", "date1": date(2024, 1, 1)} - second_data = {"key2": "value2", "date2": datetime(2024, 2, 1, 12, 0)} - - DataBridge.update_data(first_data) - DataBridge.update_data(second_data) - - result = DataBridge.get_data() - self.assertEqual(len(result), 4) - self.assertEqual(result["key1"], "value1") - self.assertEqual(result["key2"], "value2") - self.assertEqual(result["date1"], date(2024, 1, 1)) - self.assertEqual(result["date2"], datetime(2024, 2, 1, 12, 0)) - - def test_update_data_overwrites_existing_keys(self): - """Test that update_data overwrites existing keys""" - initial_data = {"key": "old_value", "date": date(2024, 1, 1)} - updated_data = {"key": "new_value", "date": date(2024, 2, 1)} - - DataBridge.update_data(initial_data) - DataBridge.update_data(updated_data) - - result = DataBridge.get_data() - self.assertEqual(result["key"], "new_value") - self.assertEqual(result["date"], date(2024, 2, 1)) + """Test multiple update calls merge data""" + first_data = {"key1": "value1"} + second_data = {"key2": "value2"} + self.data_bridge.update(first_data) + self.data_bridge.update(second_data) + self.assertEqual(len(self.data_bridge.get_all()), 2) def test_get_data_empty(self): - """Test get_data returns empty dict when no data""" - result = DataBridge.get_data() - self.assertEqual(result, {}) + """Test get_all returns empty dict when no data""" + self.assertEqual(self.data_bridge.get_all(), {}) def test_clear_data(self): - """Test clear_data removes all data""" - test_data = {"key": "value", "timestamp": datetime.now()} - DataBridge.update_data(test_data) - - # Verify data exists - self.assertNotEqual(DataBridge.get_data(), {}) - - # Clear and verify - DataBridge.clear_data() - self.assertEqual(DataBridge.get_data(), {}) + """Test clear_all removes all data""" + self.data_bridge.update({"key": "value"}) + self.assertNotEqual(self.data_bridge.get_all(), {}) + self.data_bridge.clear_all() + self.assertEqual(self.data_bridge.get_all(), {}) def test_delete_from_data(self): - """Test delete_from_data removes specific key""" - test_data = { - "key1": "value1", - "key2": "value2", - "created_at": datetime(2024, 1, 1, 10, 0) - } - DataBridge.update_data(test_data) - - # Delete one key - DataBridge.delete_from_data("key1") - - result = DataBridge.get_data() - self.assertEqual(len(result), 2) + """Test delete_by_key removes specific key""" + test_data = {"key1": "value1", "key2": "value2"} + self.data_bridge.update(test_data) + self.data_bridge.delete_by_key("key1") + result = self.data_bridge.get_all() self.assertNotIn("key1", result) self.assertIn("key2", result) - self.assertIn("created_at", result) def test_delete_from_data_nonexistent_key(self): - """Test delete_from_data with nonexistent key raises KeyError""" - test_data = {"existing_key": "value"} - DataBridge.update_data(test_data) - + """Test delete_by_key with nonexistent key raises KeyError""" with self.assertRaises(KeyError): - DataBridge.delete_from_data("nonexistent_key") + self.data_bridge.delete_by_key("nonexistent_key") + + def test_get_by_key(self): + """Test get_by_key retrieves correct value""" + test_data = {"key1": "value1", "key2": date(2024, 1, 1)} + self.data_bridge.update(test_data) + self.assertEqual(self.data_bridge.get_by_key("key1"), "value1") + self.assertEqual(self.data_bridge.get_by_key("key2"), date(2024, 1, 1)) + self.assertIsNone(self.data_bridge.get_by_key("nonexistent")) class TestResponse(unittest.TestCase): - def setUp(self): - """Clear data before each test""" - DataBridge.clear_data() - - def tearDown(self): - """Clear data after each test""" - DataBridge.clear_data() - def test_response_initialization_basic(self): """Test basic Response initialization""" response = Response(ResponseStatus.ALL_FLAGS_VALID) - self.assertEqual(response.status, ResponseStatus.ALL_FLAGS_VALID) self.assertEqual(response.input_flags, EMPTY_INPUT_FLAGS) @@ -133,151 +82,9 @@ class TestResponse(unittest.TestCase): """Test Response initialization with input flags""" input_flags = InputFlags([InputFlag('test', input_value='value', status=None)]) response = Response(ResponseStatus.INVALID_VALUE_FLAGS, input_flags) - self.assertEqual(response.status, ResponseStatus.INVALID_VALUE_FLAGS) self.assertEqual(response.input_flags, input_flags) - def test_response_inherits_databridge_functionality(self): - """Test that Response inherits DataBridge methods""" - response = Response(ResponseStatus.ALL_FLAGS_VALID) - test_data = {"message": "hello", "timestamp": datetime.now()} - - # Test update_data - response.update_data(test_data) - result = response.get_data() - self.assertEqual(result["message"], "hello") - self.assertIsInstance(result["timestamp"], datetime) - - def test_response_data_passing_with_dates(self): - """Test passing date and datetime objects through Response""" - response = Response(ResponseStatus.ALL_FLAGS_VALID) - - current_time = datetime.now() - today = date.today() - - date_data = { - "current_datetime": current_time, - "current_date": today, - "custom_datetime": datetime(2024, 3, 15, 14, 30, 0), - "custom_date": date(2023, 12, 25), - "metadata": {"created": current_time, "updated": today} - } - - response.update_data(date_data) - retrieved_data = response.get_data() - - # Verify datetime objects are preserved - self.assertEqual(retrieved_data["current_datetime"], current_time) - self.assertEqual(retrieved_data["current_date"], today) - self.assertEqual(retrieved_data["custom_datetime"], datetime(2024, 3, 15, 14, 30, 0)) - self.assertEqual(retrieved_data["custom_date"], date(2023, 12, 25)) - - # Verify nested datetime objects - self.assertEqual(retrieved_data["metadata"]["created"], current_time) - self.assertEqual(retrieved_data["metadata"]["updated"], today) - - def test_response_data_persistence_across_instances(self): - """Test that data persists across different Response instances""" - # First response instance - response1 = Response(ResponseStatus.ALL_FLAGS_VALID) - test_datetime = datetime(2024, 1, 1, 12, 0, 0) - response1.update_data({"session_start": test_datetime}) - - # Second response instance - response2 = Response(ResponseStatus.UNDEFINED_FLAGS) - retrieved_data = response2.get_data() - - # Data should persist - self.assertEqual(retrieved_data["session_start"], test_datetime) - - def test_response_data_complex_date_scenarios(self): - """Test complex scenarios with date/datetime handling""" - response = Response(ResponseStatus.ALL_FLAGS_VALID) - - # Create complex data structure with various date formats - complex_data = { - "user": { - "name": "John Doe", - "birth_date": date(1990, 5, 15), - "last_login": datetime(2024, 1, 15, 10, 30, 45), - "preferences": { - "timezone": "UTC", - "date_format": "%Y-%m-%d", - "created_at": datetime(2023, 1, 1, 0, 0, 0) - } - }, - "events": [ - {"name": "login", "timestamp": datetime(2024, 1, 15, 10, 30, 45)}, - {"name": "logout", "timestamp": datetime(2024, 1, 15, 18, 45, 30)}, - ], - "dates_list": [ - date(2024, 1, 1), - date(2024, 1, 2), - date(2024, 1, 3) - ] - } - - response.update_data(complex_data) - retrieved_data = response.get_data() - - # Verify all date/datetime objects are correctly preserved - self.assertEqual(retrieved_data["user"]["birth_date"], date(1990, 5, 15)) - self.assertEqual(retrieved_data["user"]["last_login"], datetime(2024, 1, 15, 10, 30, 45)) - self.assertEqual(retrieved_data["user"]["preferences"]["created_at"], datetime(2023, 1, 1, 0, 0, 0)) - - # Verify dates in lists - self.assertEqual(len(retrieved_data["events"]), 2) - self.assertEqual(retrieved_data["events"][0]["timestamp"], datetime(2024, 1, 15, 10, 30, 45)) - self.assertEqual(retrieved_data["events"][1]["timestamp"], datetime(2024, 1, 15, 18, 45, 30)) - - # Verify date list - self.assertEqual(len(retrieved_data["dates_list"]), 3) - self.assertEqual(retrieved_data["dates_list"][0], date(2024, 1, 1)) - self.assertEqual(retrieved_data["dates_list"][1], date(2024, 1, 2)) - self.assertEqual(retrieved_data["dates_list"][2], date(2024, 1, 3)) - - def test_response_clear_data_functionality(self): - """Test clearing data functionality through Response""" - response = Response(ResponseStatus.ALL_FLAGS_VALID) - - # Add some data with dates - response.update_data({ - "timestamp": datetime.now(), - "date": date.today(), - "message": "test" - }) - - # Verify data exists - self.assertNotEqual(response.get_data(), {}) - - # Clear data - response.clear_data() - - # Verify data is cleared - self.assertEqual(response.get_data(), {}) - - def test_response_delete_specific_date_data(self): - """Test deleting specific date-related data""" - response = Response(ResponseStatus.ALL_FLAGS_VALID) - - # Add mixed data - test_data = { - "start_date": date(2024, 1, 1), - "end_date": date(2024, 12, 31), - "created_at": datetime.now(), - "name": "test_session" - } - response.update_data(test_data) - - # Delete specific date field - response.delete_from_data("start_date") - - result = response.get_data() - self.assertNotIn("start_date", result) - self.assertIn("end_date", result) - self.assertIn("created_at", result) - self.assertIn("name", result) - def test_response_status_types(self): """Test Response with different status types""" statuses = [ @@ -286,14 +93,10 @@ class TestResponse(unittest.TestCase): ResponseStatus.INVALID_VALUE_FLAGS, ResponseStatus.UNDEFINED_AND_INVALID_FLAGS ] - for status in statuses: - response = Response(status) - response.update_data({"timestamp": datetime.now(), "status_test": True}) - - self.assertEqual(response.status, status) - self.assertIn("timestamp", response.get_data()) - self.assertIn("status_test", response.get_data()) + with self.subTest(status=status): + response = Response(status) + self.assertEqual(response.status, status) if __name__ == '__main__': diff --git a/tests/unit_tests/test_router.py b/tests/unit_tests/test_router.py index 54593de..95ea8e9 100644 --- a/tests/unit_tests/test_router.py +++ b/tests/unit_tests/test_router.py @@ -1,16 +1,16 @@ -from argenta.command.flag import InputFlag, Flag +import re +import unittest + +from argenta.command import Command +from argenta.command.flag import Flag, InputFlag from argenta.command.flag.flags import Flags, InputFlags from argenta.command.flag.models import PossibleValues, ValidationStatus from argenta.response.entity import Response from argenta.router import Router -from argenta.command import Command -from argenta.router.entity import _structuring_input_flags, _validate_command, _validate_func_args # pyright: ignore[reportPrivateUsage] -from argenta.router.exceptions import (TriggerContainSpacesException, - RepeatedFlagNameException, - RequiredArgumentNotPassedException) - -import unittest -import re +from argenta.router.entity import _structuring_input_flags, _validate_command, _validate_func_args # pyright: ignore[reportPrivateUsage] +from argenta.router.exceptions import (RepeatedFlagNameException, + RequiredArgumentNotPassedException, + TriggerContainSpacesException) class TestRouter(unittest.TestCase): diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..d5fb31f --- /dev/null +++ b/uv.lock @@ -0,0 +1,1069 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "aiosqlite" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "argenta" +version = "1.1.2" +source = { editable = "." } +dependencies = [ + { name = "art" }, + { name = "dishka" }, + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, + { name = "rich" }, +] + +[package.dev-dependencies] +docs = [ + { name = "esbonio" }, + { name = "shibuya" }, + { name = "sphinx" }, + { name = "sphinx-autobuild" }, + { name = "sphinx-intl" }, +] +linters = [ + { name = "isort" }, + { name = "ruff" }, + { name = "wemake-python-styleguide" }, +] +tests = [ + { name = "pyfakefs" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, +] +typecheckers = [ + { name = "mypy" }, +] + +[package.metadata] +requires-dist = [ + { name = "art", specifier = ">=6.4,<7.0" }, + { name = "dishka", specifier = ">=1.7.2" }, + { name = "pyreadline3", marker = "sys_platform == 'win32'", specifier = ">=3.5.4" }, + { name = "rich", specifier = ">=14.0.0,<15.0.0" }, +] + +[package.metadata.requires-dev] +docs = [ + { name = "esbonio", specifier = ">=1.0.0" }, + { name = "shibuya", specifier = ">=2025.9.25" }, + { name = "sphinx", specifier = ">=8.2.3" }, + { name = "sphinx-autobuild", specifier = ">=2025.8.25" }, + { name = "sphinx-intl", specifier = ">=2.3.2" }, +] +linters = [ + { name = "isort", specifier = ">=7.0.0" }, + { name = "ruff", specifier = ">=0.12.12" }, + { name = "wemake-python-styleguide", specifier = ">=0.17.0" }, +] +tests = [ + { name = "pyfakefs", specifier = ">=5.5.0" }, + { name = "pytest", specifier = ">=8.3.2" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-mock", specifier = ">=3.15.1" }, +] +typecheckers = [{ name = "mypy", specifier = ">=1.14.1" }] + +[[package]] +name = "art" +version = "6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/6e/dc8dc5c2d99a171cb97a02b817c2dd8db3b33c53bdb29852ba2802676fd1/art-6.4.tar.gz", hash = "sha256:417fea674bff8cea7ed058291ad1b81a6032dfce5152f28e629fa4a798a2c14c", size = 670900, upload-time = "2024-11-26T14:46:40.088Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/15/727410b4f105e7e4e84632b8c0802beb93911b9a218838606050fa7ba15c/art-6.4-py3-none-any.whl", hash = "sha256:4e58b6f0a0bb8574efb311eff24bdd28bf889c0c526ccbbb5410c644340a301c", size = 608628, upload-time = "2024-11-26T14:46:42.444Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "cattrs" +version = "25.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/42/988b3a667967e9d2d32346e7ed7edee540ef1cee829b53ef80aa8d4a0222/cattrs-25.2.0.tar.gz", hash = "sha256:f46c918e955db0177be6aa559068390f71988e877c603ae2e56c71827165cc06", size = 506531, upload-time = "2025-08-31T20:41:59.301Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/a5/b3771ac30b590026b9d721187110194ade05bfbea3d98b423a9cafd80959/cattrs-25.2.0-py3-none-any.whl", hash = "sha256:539d7eedee7d2f0706e4e109182ad096d608ba84633c32c75ef3458f1d11e8f1", size = 70040, upload-time = "2025-08-31T20:41:57.543Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098, upload-time = "2025-10-15T15:13:02.916Z" }, + { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331, upload-time = "2025-10-15T15:13:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825, upload-time = "2025-10-15T15:13:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573, upload-time = "2025-10-15T15:13:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706, upload-time = "2025-10-15T15:13:09.4Z" }, + { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221, upload-time = "2025-10-15T15:13:10.964Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624, upload-time = "2025-10-15T15:13:12.5Z" }, + { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744, upload-time = "2025-10-15T15:13:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325, upload-time = "2025-10-15T15:13:16.438Z" }, + { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180, upload-time = "2025-10-15T15:13:17.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479, upload-time = "2025-10-15T15:13:19.608Z" }, + { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290, upload-time = "2025-10-15T15:13:21.593Z" }, + { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924, upload-time = "2025-10-15T15:13:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", size = 216129, upload-time = "2025-10-15T15:13:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", size = 216380, upload-time = "2025-10-15T15:13:26.976Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", size = 247375, upload-time = "2025-10-15T15:13:28.923Z" }, + { url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", size = 249978, upload-time = "2025-10-15T15:13:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", size = 251253, upload-time = "2025-10-15T15:13:32.174Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", size = 247591, upload-time = "2025-10-15T15:13:34.147Z" }, + { url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", size = 249411, upload-time = "2025-10-15T15:13:38.425Z" }, + { url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", size = 247303, upload-time = "2025-10-15T15:13:40.464Z" }, + { url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", size = 247157, upload-time = "2025-10-15T15:13:42.087Z" }, + { url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", size = 248921, upload-time = "2025-10-15T15:13:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", size = 218526, upload-time = "2025-10-15T15:13:45.336Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", size = 219317, upload-time = "2025-10-15T15:13:47.401Z" }, + { url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", size = 217948, upload-time = "2025-10-15T15:13:49.096Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", size = 216837, upload-time = "2025-10-15T15:13:51.09Z" }, + { url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", size = 217061, upload-time = "2025-10-15T15:13:52.747Z" }, + { url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", size = 258398, upload-time = "2025-10-15T15:13:54.45Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", size = 260574, upload-time = "2025-10-15T15:13:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", size = 262797, upload-time = "2025-10-15T15:13:58.635Z" }, + { url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", size = 257361, upload-time = "2025-10-15T15:14:00.409Z" }, + { url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", size = 260349, upload-time = "2025-10-15T15:14:02.188Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", size = 258114, upload-time = "2025-10-15T15:14:03.907Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", size = 256723, upload-time = "2025-10-15T15:14:06.324Z" }, + { url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", size = 259238, upload-time = "2025-10-15T15:14:08.002Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", size = 219180, upload-time = "2025-10-15T15:14:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", size = 220241, upload-time = "2025-10-15T15:14:11.471Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", size = 218510, upload-time = "2025-10-15T15:14:13.46Z" }, + { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", size = 216110, upload-time = "2025-10-15T15:14:15.157Z" }, + { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", size = 216395, upload-time = "2025-10-15T15:14:16.863Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", size = 247433, upload-time = "2025-10-15T15:14:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", size = 249970, upload-time = "2025-10-15T15:14:20.307Z" }, + { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", size = 251324, upload-time = "2025-10-15T15:14:22.419Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", size = 247445, upload-time = "2025-10-15T15:14:24.205Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", size = 249324, upload-time = "2025-10-15T15:14:26.234Z" }, + { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", size = 247261, upload-time = "2025-10-15T15:14:28.42Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", size = 247092, upload-time = "2025-10-15T15:14:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", size = 248755, upload-time = "2025-10-15T15:14:32.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", size = 218793, upload-time = "2025-10-15T15:14:34.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", size = 219587, upload-time = "2025-10-15T15:14:37.047Z" }, + { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", size = 218168, upload-time = "2025-10-15T15:14:38.861Z" }, + { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", size = 216850, upload-time = "2025-10-15T15:14:40.651Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", size = 217071, upload-time = "2025-10-15T15:14:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", size = 258570, upload-time = "2025-10-15T15:14:44.542Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", size = 260738, upload-time = "2025-10-15T15:14:46.436Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", size = 262994, upload-time = "2025-10-15T15:14:48.3Z" }, + { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", size = 257282, upload-time = "2025-10-15T15:14:50.236Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", size = 260430, upload-time = "2025-10-15T15:14:52.413Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", size = 258190, upload-time = "2025-10-15T15:14:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", size = 256658, upload-time = "2025-10-15T15:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", size = 259342, upload-time = "2025-10-15T15:14:58.538Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", size = 219568, upload-time = "2025-10-15T15:15:00.382Z" }, + { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", size = 220687, upload-time = "2025-10-15T15:15:02.322Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", size = 218711, upload-time = "2025-10-15T15:15:04.575Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" }, +] + +[[package]] +name = "dishka" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/d7/1be31f5ef32387059190353f9fa493ff4d07a1c75fa856c7566ca45e0800/dishka-1.7.2.tar.gz", hash = "sha256:47d4cb5162b28c61bf5541860e605ed5eaf5c667122299c7ef657c86fc8d5a49", size = 68132, upload-time = "2025-09-24T21:23:05.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/89381173b4f336e986d72471198614806cd313e0f85c143ccb677c310223/dishka-1.7.2-py3-none-any.whl", hash = "sha256:f6faa6ab321903926b825b3337d77172ee693450279b314434864978d01fbad3", size = 94774, upload-time = "2025-09-24T21:23:03.246Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +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/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]] +name = "esbonio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiosqlite" }, + { name = "docutils" }, + { name = "platformdirs" }, + { name = "pygls" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/85/03b0944176b7eca5174959dc6f6606ddd87e054a91e8174a593eb8723e06/esbonio-1.0.0.tar.gz", hash = "sha256:9d1c3d3e074b3f7fe285147cd713fbda0290472864756772020aebf19f5935c0", size = 123653, upload-time = "2025-10-28T20:03:15.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/a6/e1368fc807e7abe7ca7ef224e9c13f96be7c943be364e6ff869e96d7e633/esbonio-1.0.0-py3-none-any.whl", hash = "sha256:049aa76d5d637f0f79c86d690de4b74bc2be302c68425c4fe6c83b241ac0634c", size = 118424, upload-time = "2025-10-28T20:03:13.855Z" }, +] + +[[package]] +name = "flake8" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "isort" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +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 = "lsprotocol" +version = "2025.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/26/67b84e6ec1402f0e6764ef3d2a0aaf9a79522cc1d37738f4e5bb0b21521a/lsprotocol-2025.0.0.tar.gz", hash = "sha256:e879da2b9301e82cfc3e60d805630487ac2f7ab17492f4f5ba5aaba94fe56c29", size = 74896, upload-time = "2025-06-17T21:30:18.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/f0/92f2d609d6642b5f30cb50a885d2bf1483301c69d5786286500d15651ef2/lsprotocol-2025.0.0-py3-none-any.whl", hash = "sha256:f9d78f25221f2a60eaa4a96d3b4ffae011b107537facee61d3da3313880995c7", size = 76250, upload-time = "2025-06-17T21:30:19.455Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +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/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]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +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 = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +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 = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +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 = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +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 = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, +] + +[[package]] +name = "pyfakefs" +version = "5.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/a1/17910b4bb06b5cbb7157c9cdf88323e17014fdbf1a66839c68b851317bd2/pyfakefs-5.10.0.tar.gz", hash = "sha256:68b33b8d9338ed332ad0c809417b875559c2e8ac10972fce248cb19b89d325fa", size = 231032, upload-time = "2025-10-11T09:19:07.461Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/56/a419a4168dd2c0876d548986541ac6012f36e560e19c807373ddfd90a64c/pyfakefs-5.10.0-py3-none-any.whl", hash = "sha256:105a97076aed079589546a8c2bea68e771a19e7a8ee335a30936aa03d98ab4fe", size = 246057, upload-time = "2025-10-11T09:19:04.939Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, +] + +[[package]] +name = "pygls" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, + { name = "lsprotocol" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/50/2bfc32f3acbc8941042919b59c9f592291127b55d7331b72e67ce7b62f08/pygls-2.0.0.tar.gz", hash = "sha256:99accd03de1ca76fe1e7e317f0968ebccf7b9955afed6e2e3e188606a20b4f07", size = 55796, upload-time = "2025-10-17T19:22:47.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/09/14feafc13bebb9c85b29b374889c1549d3700cb572f2d43a1bb940d70315/pygls-2.0.0-py3-none-any.whl", hash = "sha256:b4e54bba806f76781017ded8fd07463b98670f959042c44170cd362088b200cc", size = 69533, upload-time = "2025-10-17T19:22:46.63Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, +] + +[[package]] +name = "pygments-styles" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/78/091f2760489400d8f42a231b9b7331ffff63c2c7f99c0fce6f09bbf1ff69/pygments_styles-0.2.0.tar.gz", hash = "sha256:49727d051518ea46cc2d78c329c6985311bf27e1826f524c229a40b32cf00cff", size = 14266, upload-time = "2025-09-26T08:39:03.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/3d/8888e7ca0c6b093b52aa5c6693b0022e66d5958adcc685ed7a6a8ae615e8/pygments_styles-0.2.0-py3-none-any.whl", hash = "sha256:40fb7f1d34ce2b2792aecabc8d3877ca364eb04bb3b7f7747cfc9a7f0569bae9", size = 34200, upload-time = "2025-09-26T08:39:02.262Z" }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/b9/9bd84453ed6dd04688de9b3f3a4146a1698e8faae2ceeccce4e14c67ae17/ruff-0.14.0.tar.gz", hash = "sha256:62ec8969b7510f77945df916de15da55311fade8d6050995ff7f680afe582c57", size = 5452071, upload-time = "2025-10-07T18:21:55.763Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/4e/79d463a5f80654e93fa653ebfb98e0becc3f0e7cf6219c9ddedf1e197072/ruff-0.14.0-py3-none-linux_armv6l.whl", hash = "sha256:58e15bffa7054299becf4bab8a1187062c6f8cafbe9f6e39e0d5aface455d6b3", size = 12494532, upload-time = "2025-10-07T18:21:00.373Z" }, + { url = "https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8", size = 13160768, upload-time = "2025-10-07T18:21:04.73Z" }, + { url = "https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8", size = 12363376, upload-time = "2025-10-07T18:21:07.833Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/1ffef5a1875add82416ff388fcb7ea8b22a53be67a638487937aea81af27/ruff-0.14.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba9a8925e90f861502f7d974cc60e18ca29c72bb0ee8bfeabb6ade35a3abde7", size = 12608055, upload-time = "2025-10-07T18:21:10.72Z" }, + { url = "https://files.pythonhosted.org/packages/4a/32/986725199d7cee510d9f1dfdf95bf1efc5fa9dd714d0d85c1fb1f6be3bc3/ruff-0.14.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e41f785498bd200ffc276eb9e1570c019c1d907b07cfb081092c8ad51975bbe7", size = 12318544, upload-time = "2025-10-07T18:21:13.741Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ed/4969cefd53315164c94eaf4da7cfba1f267dc275b0abdd593d11c90829a3/ruff-0.14.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30a58c087aef4584c193aebf2700f0fbcfc1e77b89c7385e3139956fa90434e2", size = 14001280, upload-time = "2025-10-07T18:21:16.411Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ad/96c1fc9f8854c37681c9613d825925c7f24ca1acfc62a4eb3896b50bacd2/ruff-0.14.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f8d07350bc7af0a5ce8812b7d5c1a7293cf02476752f23fdfc500d24b79b783c", size = 15027286, upload-time = "2025-10-07T18:21:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/b3/00/1426978f97df4fe331074baf69615f579dc4e7c37bb4c6f57c2aad80c87f/ruff-0.14.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eec3bbbf3a7d5482b5c1f42d5fc972774d71d107d447919fca620b0be3e3b75e", size = 14451506, upload-time = "2025-10-07T18:21:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/9c1cea6e493c0cf0647674cca26b579ea9d2a213b74b5c195fbeb9678e15/ruff-0.14.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16b68e183a0e28e5c176d51004aaa40559e8f90065a10a559176713fcf435206", size = 13437384, upload-time = "2025-10-07T18:21:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e", size = 13447976, upload-time = "2025-10-07T18:21:28.83Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c0/ac42f546d07e4f49f62332576cb845d45c67cf5610d1851254e341d563b6/ruff-0.14.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c958f66ab884b7873e72df38dcabee03d556a8f2ee1b8538ee1c2bbd619883dd", size = 13682850, upload-time = "2025-10-07T18:21:31.842Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c4/4b0c9bcadd45b4c29fe1af9c5d1dc0ca87b4021665dfbe1c4688d407aa20/ruff-0.14.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7eb0499a2e01f6e0c285afc5bac43ab380cbfc17cd43a2e1dd10ec97d6f2c42d", size = 12449825, upload-time = "2025-10-07T18:21:35.074Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a8/e2e76288e6c16540fa820d148d83e55f15e994d852485f221b9524514730/ruff-0.14.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c63b2d99fafa05efca0ab198fd48fa6030d57e4423df3f18e03aa62518c565f", size = 12272599, upload-time = "2025-10-07T18:21:38.08Z" }, + { url = "https://files.pythonhosted.org/packages/18/14/e2815d8eff847391af632b22422b8207704222ff575dec8d044f9ab779b2/ruff-0.14.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:668fce701b7a222f3f5327f86909db2bbe99c30877c8001ff934c5413812ac02", size = 13193828, upload-time = "2025-10-07T18:21:41.216Z" }, + { url = "https://files.pythonhosted.org/packages/44/c6/61ccc2987cf0aecc588ff8f3212dea64840770e60d78f5606cd7dc34de32/ruff-0.14.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a86bf575e05cb68dcb34e4c7dfe1064d44d3f0c04bbc0491949092192b515296", size = 13628617, upload-time = "2025-10-07T18:21:44.04Z" }, + { url = "https://files.pythonhosted.org/packages/73/e6/03b882225a1b0627e75339b420883dc3c90707a8917d2284abef7a58d317/ruff-0.14.0-py3-none-win32.whl", hash = "sha256:7450a243d7125d1c032cb4b93d9625dea46c8c42b4f06c6b709baac168e10543", size = 12367872, upload-time = "2025-10-07T18:21:46.67Z" }, + { url = "https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl", hash = "sha256:ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2", size = 13464628, upload-time = "2025-10-07T18:21:50.318Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730", size = 12565142, upload-time = "2025-10-07T18:21:53.577Z" }, +] + +[[package]] +name = "shibuya" +version = "2025.9.25" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments-styles" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/f3/36435b8e126b0a2cd7efa346cb366f25769094425fa0b145ab6991d20829/shibuya-2025.9.25.tar.gz", hash = "sha256:03131beb0f8e31cf4f501a4b744cd9c3f387cf23696259476d141b9ca976156b", size = 81798, upload-time = "2025-09-25T02:54:18.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/d7/e4831122c1cdaa66cf432d50cf24b38dd161aa69187eebfa41295c3a84ec/shibuya-2025.9.25-py3-none-any.whl", hash = "sha256:93e439181167936dd1950874880c23920d551f90ff475888ce278af1d2efa4eb", size = 97695, upload-time = "2025-09-25T02:54:17.351Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +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/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]] +name = "sphinx-autobuild" +version = "2025.8.25" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "sphinx" }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "watchfiles" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, +] + +[[package]] +name = "sphinx-intl" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "click" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/21/eb12016ecb0b52861762b0d227dff75622988f238776a5ee4c75bade507e/sphinx_intl-2.3.2.tar.gz", hash = "sha256:04b0d8ea04d111a7ba278b17b7b3fe9625c58b6f8ffb78bb8a1dd1288d88c1c7", size = 27921, upload-time = "2025-08-02T04:53:01.891Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3b/156032fa29a87e9eba9182b8e893a7e88c1d98907a078a371d69be432e52/sphinx_intl-2.3.2-py3-none-any.whl", hash = "sha256:f0082f9383066bab8406129a2ed531d21c38706d08467bf5ca3714e8914bb2be", size = 12899, upload-time = "2025-08-02T04:53:00.353Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +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-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +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-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "starlette" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367, upload-time = "2025-09-23T13:33:47.486Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "wemake-python-styleguide" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "flake8" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/08/c0776aa654dc43cb390e8ee13f597e7241495f30b454b7534ee482ece5b5/wemake_python_styleguide-1.4.0.tar.gz", hash = "sha256:0964cf40ac4d3f1c89dd79aee4b6edba9a1806fb395836c73e746fe287dbae3e", size = 153955, upload-time = "2025-08-25T10:15:08.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/58/98c4aa00e3de8e45726029799d8facbdcd75347b2f48b285857577e8efd8/wemake_python_styleguide-1.4.0-py3-none-any.whl", hash = "sha256:c0727475a20a1b7d59f1d806040e84768bdb0935d1147023453aa44c14b65c95", size = 215985, upload-time = "2025-08-25T10:15:06.713Z" }, +]