diff --git a/.gitignore b/.gitignore index 76c967d..518e1e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,7 @@ *#*# -*.egg-info *~ *pyc -.pytest_cache/ -.mypy_cache/ -.ruff_cache/ -.venv/ -__pycache__/ +*.egg-info build dist docs/build -workshop diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index f5c5c79..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# run the command "pre-commit install" when you clone the repository -# the command installs a git-hook in .git/hooks/pre-commit -repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.6 - hooks: - - id: ruff - args: [--fix] - - id: ruff-format - - - repo: local - hooks: - - id: pytest - name: pytest - entry: uv run pytest . - language: system - pass_filenames: false - always_run: true diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index ba88efe..0000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,16 +0,0 @@ -version: 2 - -build: - os: ubuntu-24.04 - tools: - python: "3.13" - -python: - install: - - method: uv - command: sync - groups: - - dev - -sphinx: - configuration: docs/source/conf.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b85c4..ca2931f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,122 +2,25 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.5.3] - 2026-04-24 -### 📚 Documentation -- Add API reference with autodoc for the three classes +## [Unreleased] -## [1.5.2] - 2026-04-24 -### 🐛 Bug Fixes -- Enable sphinx docs build locally and on Read the Docs +## [0.0.4] - 2020-06-14 -### 📚 Documentation -- Add Step 11 on migration of dev dependencies to pyproject.toml -- Extend Step 11 with sphinx dev-dep and Read the Docs configuration +### Added +- the interface class +- the abstract class +- a class that it extends an abstract class and it implements an interface -### ⚡ Performance -- Declare dev-deps in pyproject.toml and prefix uv run in Makefile +## [0.0.1] - 2020-06-04 -### ⚙️ Miscellaneous Tasks -- Remove tests/requirements-test.txt (replaced by [dependency-groups].dev) - -## [1.5.1] - 2026-04-24 -### 🚀 Features -- Added ruff for linting, format e isort - -### 📚 Documentation -- Close documentation before release - -### ⚡ Performance -- Changed docstring format -- Added type hints and all others ruff controls -- Added pyright -- Added pre-commit -- Fix release workflow and refine pre-commit hooks - -### ⚙️ Miscellaneous Tasks -- Update .gitignore and add uv.lock - -## [1.5.0] - 2026-03-02 -### 🚀 Features -- Changed packaging method -- Changed naming convention test files for pytest -- Added requirements for testing and building - -### 🚜 Refactor -- Changed all disallowed variable and method names - -### 📚 Documentation -- Added CHANGELOG.md management and updated stepbystep - -### ⚡ Performance -- Changed MyClassInterface unittests in pytest -- Changed MyClassAbstract unittests in pytest -- Changed MyClass unittests in pytest -- Changed the format by black -- Changed the syntax by pylint suggestions - -## [1.4.0] - 2026-02-22 -### 🚀 Features -- Added pyproject.toml and versioning management, updated license's year - -### 🐛 Bug Fixes -- Fixed some links and docs syntax - -### 📚 Documentation -- Updated license's year -- Updated license's year - -## [1.3.1] - 2026-02-22 -### 📚 Documentation -- Updated documentation -- Updated documentation - -### ⚡ Performance -- Updated changelog and version of the simple-sample package - -## [1.3.0] - 2026-02-22 -### 🚀 Features -- Added the unit test for MyClass -- Added MyClass and unit tests works properly - -### 📚 Documentation -- Updated documentation -- Updated documentation - -## [1.2.0] - 2026-02-22 -### 🚀 Features -- Added the unit test for MyClassAbstract -- Added MyClassAbstract and unit tests works properly - -### 📚 Documentation -- Updated documentation -- Updated documentation - -## [1.1.0] - 2026-02-22 -### 🚀 Features -- Added the unit test for MyClassInterface -- Added MyClassInterface and unit tests works properly - -### 📚 Documentation -- Updated documentation -- Updated documentation - -## [1.0.0] - 2026-02-22 -### 🚀 Features -- Added the outline files -- Added the empty package version -- Added documentation by sphinx - -[1.5.3]: https://github.com/bilardi/python-prototype/compare/v1.5.2...v1.5.3 -[1.5.2]: https://github.com/bilardi/python-prototype/compare/v1.5.1...v1.5.2 -[1.5.1]: https://github.com/bilardi/python-prototype/compare/v1.5.0...v1.5.1 -[1.5.0]: https://github.com/bilardi/python-prototype/compare/v1.4.0...v1.5.0 -[1.4.0]: https://github.com/bilardi/python-prototype/compare/v1.3.1...v1.4.0 -[1.3.1]: https://github.com/bilardi/python-prototype/compare/v1.3.0...v1.3.1 -[1.3.0]: https://github.com/bilardi/python-prototype/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/bilardi/python-prototype/compare/v1.1.0...v1.2.0 -[1.1.0]: https://github.com/bilardi/python-prototype/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/bilardi/python-prototype/compare/...v1.0.0 +### Added +- the outline files +- the init files of package and tests +- the documentation by sphinx +[Unreleased]: https://github.com/bilardi/python-prototype/compare/v0.0.4...HEAD +[0.0.4]: https://github.com/bilardi/python-prototype/releases/tag/v0.0.1...v0.0.4 +[0.0.1]: https://github.com/bilardi/python-prototype/releases/tag/v0.0.1 diff --git a/LICENSE b/LICENSE index a3bdc5e..3e55e37 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2026 Alessandra Bilardi +Copyright (c) 2020-2022 Alessandra Bilardi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index c3b7bcb..6bb8e98 100644 --- a/Makefile +++ b/Makefile @@ -1,83 +1,36 @@ # Python prototype makefile PACKAGE_NAME = "simple-sample" -LIBRARY_NAME = "simple_sample" +YOUR_USERNAME = "bilardi" .PHONY: help # print this help list help: grep PHONY Makefile | sed 's/.PHONY: /make /' | grep -v grep +.PHONY: unittest # run unit tests +unittest: + python3 -m unittest discover -v + .PHONY: clean # remove packaging files clean: rm -rf build dist *.egg-info; rm -rf */*pyc; rm -rf */*/*pyc; rm -rf */__pycache__ -.PHONY: sync # install/refresh dev dependencies via uv sync -sync: - uv sync - -.PHONY: test # run unit tests -test: - uv run pytest - -.PHONY: lint # run ruff check -lint: - uv run ruff check --no-fix . - -.PHONY: format # run ruff format -format: - uv run ruff format . - -.PHONY: typecheck # run pyright -typecheck: - uv run pyright - .PHONY: doc # build documentation -doc: - cd docs; make html SPHINXBUILD="uv run sphinx-build"; cd - +doc: + cd docs; make html; cd - -.PHONY: buildtest # build package and upload on testpypi +.PHONY: buildtest # build package on testpypi buildtest: clean - uv build && uv run python -m twine upload --repository testpypi dist/* + python3 setup.py sdist bdist_wheel; python3 -m twine upload --repository testpypi dist/* -.PHONY: installtest # install package from testpypi in a sandbox venv and verify +.PHONY: installtest # install package from testpypi installtest: - uv venv --clear .installtest - uv pip install --python .installtest/bin/python --upgrade --index-url https://test.pypi.org/simple/ --no-deps $(PACKAGE_NAME) - .installtest/bin/python -c "import $(LIBRARY_NAME); print($(LIBRARY_NAME).__version__)" - rm -rf .installtest + mkdir -p test; cd test; python3 -m pip install --upgrade --index-url https://test.pypi.org/simple/ --no-deps $(PACKAGE_NAME)-$(YOUR_USERNAME); cd - -.PHONY: build # build package and upload on pypi +.PHONY: build # build package on pypi build: clean - uv build && uv run python -m twine upload dist/* + python3 setup.py sdist bdist_wheel; python3 -m twine upload dist/* -.PHONY: install # install package from pypi in a sandbox venv and verify +.PHONY: install # install package from pypi install: - uv venv --clear .install - uv pip install --python .install/bin/python --upgrade $(PACKAGE_NAME) - .install/bin/python -c "import $(LIBRARY_NAME); print($(LIBRARY_NAME).__version__)" - rm -rf .install - -.PHONY: major minor patch # update version, CHANGELOG.md and push with also tags -major: - $(MAKE) release PART=major - -minor: - $(MAKE) release PART=minor - -patch: - $(MAKE) release PART=patch - -release: - uv run bump-my-version bump $(PART) - $(MAKE) changelog - @echo "To publish the release to remote, run:" - @echo " git push && git push --tags --force" - -.PHONY: changelog # update CHANGELOG.md and amend it on the commit -changelog: - uv run git-cliff --config pyproject.toml --output CHANGELOG.md - sed -i 's///g' CHANGELOG.md - git add CHANGELOG.md uv.lock - TAG=$$(git tag --points-at HEAD); \ - git commit --amend --no-edit; \ - [ -n "$$TAG" ] && git tag -f $$TAG $$(git rev-parse HEAD) || true + python3 -m pip install --upgrade $(PACKAGE_NAME) diff --git a/POST.en.md b/POST.en.md deleted file mode 100644 index 39cc7fe..0000000 --- a/POST.en.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -title: "The lazy developer's code quality" -date: 2026-04-30 -categories: [python] -tags: [pytest, ruff, pyright, uv] -repo: bilardi/python-prototype -social_summary: "#CodeQuality of the lazy developer 🛠️\n\nUpdating an educational #Python repo, I looked into what to automate for code quality.\n\n🔮 Spoiler: I landed on uv, ruff, pyright and pre-commit.\n\nIn the article: the picks, why, and the make patch that broke on the first try 😄" ---- - -![Flow](docs/images/code.quality.vertical.flowchart.png) - -## A repo to refresh, several rabbit holes to dive into - -A while ago, at PyCon IT, I attended a talk that opened my eyes on [pytest](https://pypi.org/project/pytest/): - -- simpler test management, especially for mocks -- parametrizable fixtures instead of the `setUp` / `tearDown` ritual -- bare `assert` instead of a thousand `self.assertEqual` - -I'd like my repo [python-prototype](https://github.com/bilardi/python-prototype/), born for educational purposes, to also be a bit of a template I can pull off the shelf for the next projects. - -So, with the excuse of refreshing the testing system with pytest and the packaging with [pyproject](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/), I started thinking about adding more. - -I had been using [black](https://pypi.org/project/black/) and [pylint](https://pypi.org/project/pylint/) for a long time, so my first thought was: ok, let's bring in formatting and linting too. But I asked myself: isn't there something better that maintains style ([PEP 8](https://peps.python.org/pep-0008/)), docstrings ([PEP 257](https://peps.python.org/pep-0257/)) and type hints ([PEP 484](https://peps.python.org/pep-0484/)) automatically ? - -And the environment, can it be modernized too ? With what ? Well, just like there are two schools, emacs and vi, there are also two schools, [poetry](https://python-poetry.org/) and [uv](https://docs.astral.sh/uv/) .. without even mentioning all the others. - -What I needed was something to cover code quality, formatting, packaging and beyond: fewer tasks left to memory or to reading the holy README, more chances they actually get done. - -Since there's no "all-inclusive package", the plan was to test what was maintained and maintainable, and find the one most suited to my needs. - -## Today's chosen stack - -Four tools, not ten: - -- **uv**: the env manager. One Rust binary in place of `pip`, `venv`, `pyenv` and `pipx`. With poetry, the last two aren't covered and need to be installed separately: fewer satellite tools around. -- **[ruff](https://docs.astral.sh/ruff/)**: formatting and linting. Replaces `black`, `isort`, `flake8` and most of `pylint`. Another Rust binary. -- **[pyright](https://microsoft.github.io/pyright/)**: the type checker. Skipping [mypy](https://mypy-lang.org/), [pyrefly](https://pyrefly.org/) and [ty](https://github.com/astral-sh/ty). For now. -- **[pre-commit](https://pre-commit.com/)**: a git-hook that runs ruff and pytest automatically before every commit. Just .. remember to set it up at the start of the project ! - -The single criterion that drove all these choices is **least total effort**. Fewer tools = less config = less maintenance. The lazy developer wants the toolchain to break before the commit, in case some step gets forgotten. But without overdoing it: just enough to produce quality code. - -## Stories from the field - -### Pylint and the 4.35/10 grade - -The first run of pylint on simple-sample stings: 4.35/10. A high school grade, not a teaching repo's. I sit down to fix my JavaScript hangover: `myClass` becomes `my_class` (PEP 8 naming), `foo` and `bar` and `foobar` become `get_param_processing`, `get_boolean`, `get_reverse_protected_param` (names that say what they do). Up to 9.41/10. - -But before claiming victory, three warnings need a decision: - -- **W0223**: abstract method not implemented in a subclass. Pylint flags it as a bug to fix. In my case it MUST fail: it's part of the educational example. I keep it. -- **C0301**: line too long. I look: it's an HTTP link in a docstring, can't be broken. I ignore it. -- **C0104**: names like "foo" and "bar" are disallowed. I could disable the rule globally, but here I prefer having spent the hour of restructuring: variables and methods should be expressive. - -Each of these decisions is a "the tool is right about the code but not about the context". And here is where pylint's limit shows up: it tells you what it found, not whether it really needs fixing. The case-by-case judgement stays with you: it doesn't change anything by itself. - -### Pylint doesn't understand pytest - -I go looking for trouble, and run pylint on the test suite: a new warning shows up, W0621 `redefining-outer-name`, on the fixtures: - -```python -@pytest.fixture -def mci(): - return MyClassInterface() - -def test_mci_creation(mci): - assert isinstance(mci, MyClassInterface) -``` - -Pylint says "you're redefining `mci` from the outer scope". But this pattern is the way fixtures work: it's not redefinition, it's parameter injection. Pylint reads the code as if it were running it, but it doesn't know how pytest runs it. - -False positive. The workaround exists: - -```python -@pytest.fixture(name="mci") -def mci_fixture(): - return MyClassInterface() - -def test_mci_creation(mci): - assert isinstance(mci, MyClassInterface) -``` - -But it's there to silence pylint, not to improve the code. I don't add it. And here I start thinking that pylint is old for pytest, and it's time to switch tool. - -### Ruff arrives and takes black's place - -I try `ruff check` and `ruff format`. It covers practically everything black did for formatting, and a good chunk of what pylint did for linting. One binary. Config in `pyproject.toml`: a single section instead of two. Execution time: milliseconds. - -Ruff openly states the trade-off: it's AST-based and works on a single file at a time, it doesn't "read" the class hierarchy across files. So the abstract method not overridden, which I do need to see, doesn't get flagged. Ruff is a fast surface linter, not a deep analyst. - -Ok. Ruff takes black's place and covers most of pylint. For what's missing (abstract method, type consistency across files) I need another tool: a type checker. - -### The type checker tour - -Pylint flagged both typing and scoping errors (W0621 is a style check, not a type one). Choosing a type checker, I focus on the typing front: the scoping front stays out of this tour. - -I add type hints everywhere, otherwise the type checkers would throw a sea of red (with nothing to check): the signature `def get_param_processing(self, param):` becomes `def get_param_processing(self, param: bool) -> bool:`. - -Then I run mypy, pyrefly, ty, pyright on the same code to see who flags what. - -| Tool | Abstract method not implemented | Return None where type hint says bool | Other | -|------|---------------------------------|--------------------------------------|-------| -| mypy | yes | yes | historical, slow | -| pyrefly | in a different form | yes | lightning fast, young | -| ty | yes (interface only) | yes | lightning fast, young | -| pyright | yes | yes | also flags a third error: the method is used in MyClass | - -Pyright finds more and has a mature ecosystem: Microsoft maintains it actively, and Pylance (the Python extension for VS Code) is built on top of pyright. Pyright wins. Pyrefly and ty are under active development: I'll come back to them later. - -### The workflow breaking at the first `make patch` - -Setup done. Ruff passes clean. Pyright passes clean. Pre-commit stops me if I forget something. I run `make patch` for the first "real" release .. and: - -``` -make[1]: bump-my-version: No such file or directory -``` - -The Makefile was calling `bump-my-version` directly, and the project's dev-deps were in `tests/requirements-test.txt`, not in `pyproject.toml`. So whoever cloned the repo had to know to do a `pip install -r tests/requirements-test.txt` on top of `uv sync`, and the release workflow assumed the venv was activated. Too much implicit knowledge, too much hassle. - -I'm so used to using `uv run` that I don't run `source .venv/bin/activate` anymore, so I tripped over something that "the old-fashioned way" would never have happened. - -What did it take to truly hand the environment over to uv ? Well, all I needed was to add every dependency in `pyproject.toml` with: - -```bash -uv add --dev -r tests/requirements-test.txt -``` - -A single command. uv reads the requirements file, writes everything in `[dependency-groups].dev` of `pyproject.toml` (the standard introduced by [PEP 735](https://peps.python.org/pep-0735/) for dev-deps), updates `uv.lock`, and installs. The `tests/requirements-test.txt` file becomes redundant: one less file to handle. - -And then in the Makefile I added `uv run` in front of every Python command: - -```make -release: - uv run bump-my-version bump $(PART) - $(MAKE) changelog - git tag -f v$$(uv run python -c "from simple_sample import __version__; print(__version__)") - git push && git push --tags --force -``` - -Now `make patch` works even from a fresh shell, no activation needed. The venv is no longer tribal knowledge, it's implicit in every command. - -### Seven sections in `pyproject.toml`, one per tool - -`pyproject.toml` was born for packaging, and from there it picked up the config sections of the project's tools: seven in total. - -**ruff** starts from `select = ["ALL"]`: I enable every available rule and use `ignore` for the ones I find too much. Philosophy "everything by default, exclude by name": as ruff adds new rules, I get them automatically. And the "ALL" bundle isn't just style + lint: it includes naming (PEP 8), docstring (PEP 257), type annotations (PEP 484, with `flake8-annotations`), cyclomatic complexity (`mccabe`), basic security (`bandit-base`), import order (`isort`). Ruff isn't "just" a formatter + linter, it's the umbrella under which black + isort + flake8 + parts of pylint, pydocstyle and bandit live. - -**pyright** in `typeCheckingMode = "strict"`: the default `basic` lets a lot slide, `strict` requires complete type hints and explicit returns. It's the mode that surfaces those errors the type checker tour had revealed (and that mypy / pyrefly / ty in default config would have missed). - -**pytest**: minimal config, `asyncio_mode = "auto"` and `testpaths = ["tests"]`. The rest lives in the tests themselves. - -**[dependency-groups].dev**: the list of dev-deps with version constraints (PEP 735). uv reads this section for `uv sync --group dev`. - -**packaging** (`[build-system]`, `[project]`, `[tool.setuptools]`), **bumpversion**, **git-cliff**: handle the release pipeline (metadata + runtime dependencies + wheel and sdist build + versioning + CHANGELOG from conventional commits). A different topic from code quality, but necessary for the modernization and automation goal. - -**pre-commit** lives in `.pre-commit-config.yaml` (outside `pyproject.toml`): it points to the official `astral-sh/ruff-pre-commit` repo for the two ruff hooks (check + format) and keeps a local hook running `uv run pytest` for the tests. So pre-commit also leans on uv to access the project's venv, just like the Makefile targets. - -## Plus - -The lazy developer adds tools when they're really needed, when it's time to handle some other aspect automatically. - -Still on the code quality front, what could be added and when ? - -- **[vulture](https://pypi.org/project/vulture/) and [radon](https://pypi.org/project/radon/)**: project-level dead code and complexity reports. When a map of the codebase is needed, for instance before a major refactor: ruff sees the single file, vulture and radon see the whole. -- **[bandit](https://pypi.org/project/bandit/) (SAST), [pip-audit](https://pypi.org/project/pip-audit/) (SCA) and [detect-secrets](https://pypi.org/project/detect-secrets/)**: if the package becomes an API or handles sensitive data, but here a whole new world opens up .. -- **mypy in strict mode**: a second pass on top of pyright. Today I don't have an example that would push me to add it, pyright strict covers well. -- **pyrefly and ty**: worth re-evaluating especially for projects with many files. They're fast but young. -- **[pre-commit.ci](https://pre-commit.ci/)**: a hook that runs in CI on every PR too. For a personal one-maintainer project it's overhead, for a shared repo it would make sense. diff --git a/POST.it.md b/POST.it.md deleted file mode 100644 index 57c78d3..0000000 --- a/POST.it.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: "La code quality dell'informatico pigro" -date: 2026-04-30 -categories: [python] -tags: [pytest, ruff, pyright, uv] -repo: bilardi/python-prototype ---- - -![Flow](docs/images/code.quality.vertical.flowchart.png) - -## Un repo da rinnovare, tante ricerche da fare - -Tempo fa, alla PyCon IT, ho partecipato a un talk che mi ha illuminato su [pytest](https://pypi.org/project/pytest/): - -- gestione più semplice dei test, specialmente per i mock -- fixture parametrizzabili al posto del rituale di `setUp` / `tearDown` -- l'`assert` nudo invece dei mille `self.assertEqual` - -Vorrei che il mio repo [python-prototype](https://github.com/bilardi/python-prototype/), nato a scopo didattico, fosse anche un po' un template da sfruttare per i prossimi progetti. - -Quindi, con la scusa di rimodernare il sistema di testing con pytest e di packaging con [pyproject](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/), stavo pensando di aggiungere dell'altro. - -Era da parecchio che sfruttavo [black](https://pypi.org/project/black/) e [pylint](https://pypi.org/project/pylint/), quindi il primo pensiero è stato: ok, integriamo anche formatting e linting. Ma mi sono chiesta: non c'è di meglio che mantenga in automatico lo stile ([PEP 8](https://peps.python.org/pep-0008/)), le docstring ([PEP 257](https://peps.python.org/pep-0257/)) e i type hints ([PEP 484](https://peps.python.org/pep-0484/)) ? - -E l'environment, si può modernizzare anche quello ? Con cosa ? Beh, come ci sono le due scuole, emacs e vi, ci sono anche le due scuole, [poetry](https://python-poetry.org/) e [uv](https://docs.astral.sh/uv/) .. senza nemmeno nominare tutti gli altri. - -C'era da scegliere qualcosa che coprisse code quality, formatting, packaging e oltre: meno attività lasciate al ricordo o alla lettura del santo README, più probabilità che vengano fatte davvero. - -Visto che non esiste un "pacchetto tutto incluso", c'era da testare ciò che era mantenuto e mantenibile, e trovare quello più adatto alle necessità. - -## Lo stack scelto oggi - -Quattro tool, non dieci: - -- **uv**: l'env manager. Un binario in Rust al posto di `pip`, `venv`, `pyenv` e `pipx`. Con poetry, gli ultimi due non sono coperti e vanno installati a parte: meno strumenti satellite intorno. -- **[ruff](https://docs.astral.sh/ruff/)**: formatting e linting. Rimpiazza `black`, `isort`, `flake8` e la gran parte di `pylint`. Un altro binario in Rust. -- **[pyright](https://microsoft.github.io/pyright/)**: il type checker. Scartando [mypy](https://mypy-lang.org/), [pyrefly](https://pyrefly.org/) e [ty](https://github.com/astral-sh/ty). Per il momento. -- **[pre-commit](https://pre-commit.com/)**: git-hook che fa girare ruff e pytest automaticamente prima di ogni commit. Basta .. ricordarsi di impostarlo a inizio progetto ! - -Il criterio che ha guidato tutte queste scelte è uno solo: **meno sforzo totale**. Meno tool = meno config = meno manutenzione. L'informatico pigro vuole che la toolchain si spacchi prima del commit, se ci si dimentica qualche passaggio. Ma senza esagerare: quanto basta a produrre codice di qualità. - -## Storie dal campo - -### Pylint e il voto 4.35/10 - -Il primo giro di pylint su simple-sample fa male: 4.35/10. Un rate da superiori, non da repo didattico. Si scende a correggere il mio strascico JavaScript: `myClass` diventa `my_class` (naming PEP 8), `foo` e `bar` e `foobar` diventano `get_param_processing`, `get_boolean`, `get_reverse_protected_param` (nomi che dicono cosa fanno). Si risale a 9.41/10. - -Ma prima di quotare il voto come vittoria, vanno decisi tre casi di warning: - -- **W0223**: metodo astratto non implementato in una sottoclasse. Pylint lo segnala come bug da sistemare. Nel mio caso invece DEVE fallire: è parte dell'esempio didattico. Lo tengo. -- **C0301**: linea troppo lunga. Guardo: è un link HTTP in una docstring, non si taglia. Lo ignoro. -- **C0104**: nomi come "foo" e "bar" sono disallowed. Potrei disabilitare la regola globalmente, ma qui preferisco aver speso l'ora di ristrutturazione: le variabili e i metodi devono essere parlanti. - -Ognuna di queste decisioni è un "il tool ha ragione sul codice ma non sul contesto". Ed è qui che emerge il limite di pylint: ti dice cosa ha trovato, ma non se serve davvero sistemarlo. La decisione caso per caso resta tua: lui non modifica nulla. - -### Pylint non capisce pytest - -Mi invento di farmi male, e faccio girare pylint sulla suite di test: arriva un warning nuovo, W0621 `redefining-outer-name`, sulle fixture: - -```python -@pytest.fixture -def mci(): - return MyClassInterface() - -def test_mci_creation(mci): - assert isinstance(mci, MyClassInterface) -``` - -Pylint dice "stai ridefinendo `mci` dello scope esterno". Però il pattern è la base del funzionamento delle fixture: non è una ridefinizione, è l'iniezione del parametro. Pylint legge il codice come se lo eseguisse, ma non sa come pytest lo esegue. - -Falso positivo. La fix di circostanza esiste: - -```python -@pytest.fixture(name="mci") -def mci_fixture(): - return MyClassInterface() - -def test_mci_creation(mci): - assert isinstance(mci, MyClassInterface) -``` - -Ma è per far stare zitto pylint, non per migliorare il codice. Non la metto. E qui inizio a pensare che pylint è vecchio per pytest e che bisogna cambiare tool. - -### Ruff arriva e prende il posto di black - -Provo `ruff check` e `ruff format`. Copre praticamente tutto quello che faceva black per il formatting, e una buona parte di quello che faceva pylint per il linting. Un binario. Config in `pyproject.toml`: una sola sezione al posto di due. Tempo di esecuzione: millisecondi. - -Ruff dichiara apertamente il trade-off: è AST-based e lavora sul singolo file, non "legge" la gerarchia delle classi tra file. Quindi l'abstract method non overridden, che a me serve vedere, non lo vede. Ruff è un linter di superficie fatto veloce, non un analista profondo. - -Ok. Ruff prende il posto di black e copre buona parte di pylint. Per quello che mi manca (abstract method, consistenza di tipo tra file) mi serve un altro strumento: un type checker. - -### Il giro dei type checker - -Pylint trovava errori di typing e di scoping (W0621 è un check di stile, non di tipo). Scegliendo un type checker mi concentro sul fronte tipi: il fronte scoping resta fuori da questo giro. - -Aggiungo type hints ovunque, altrimenti i type checker darebbero un mare di rossi (non avrebbero niente da controllare): la firma `def get_param_processing(self, param):` diventa `def get_param_processing(self, param: bool) -> bool:`. - -Poi lancio mypy, pyrefly, ty, pyright sullo stesso codice per vedere chi identifica cosa. - -| Tool | Metodo astratto non implementato | Return None dove type hint dice bool | Altro | -|------|----------------------------------|--------------------------------------|-------| -| mypy | sì | sì | storico, lento | -| pyrefly | in forma diversa | sì | fulmineo, giovane | -| ty | sì (solo interfaccia) | sì | fulmineo, giovane | -| pyright | sì | sì | segnala anche un terzo errore: il metodo viene usato in MyClass | - -Pyright trova più cose e ha un ecosistema maturo: Microsoft lo mantiene attivamente, e Pylance (l'estensione Python di VS Code) è costruita sopra pyright. Vince pyright. Pyrefly e ty sono in fase di sviluppo attivo: li rivaluterò più avanti. - -### Il workflow che si rompe al primo `make patch` - -Setup completato. Ruff passa clean. Pyright passa clean. Pre-commit mi ferma se dimentico qualcosa. Lancio `make patch` per il primo release "vero" .. e: - -``` -make[1]: bump-my-version: No such file or directory -``` - -Il Makefile chiamava `bump-my-version` nudo, e le dev-deps del progetto stavano in `tests/requirements-test.txt`, non in `pyproject.toml`. Così chi clonava il repo doveva sapere di fare un `pip install -r tests/requirements-test.txt` oltre a `uv sync`, e il workflow di release assumeva che il venv fosse attivato. Troppa conoscenza implicita, troppo sbatti. - -Oramai sono abituata a usare `uv run` e non lancio più `source .venv/bin/activate`, e così mi sono ritrovata a fare qualcosa che "alla vecchia maniera" non mi sarebbe mai successo. - -Cosa è servito fare per avere l'ambiente gestito davvero da uv ? Beh, è bastato aggiungere in `pyproject.toml` tutte le dipendenze con: - -```bash -uv add --dev -r tests/requirements-test.txt -``` - -Un comando solo. uv legge il requirements file, scrive tutto in `[dependency-groups].dev` di `pyproject.toml` (lo standard introdotto da [PEP 735](https://peps.python.org/pep-0735/) per le dev-deps), aggiorna `uv.lock`, e installa. Il file `tests/requirements-test.txt` diventa ridondante: un file in meno da gestire. - -E poi nel Makefile ho aggiunto `uv run` davanti a ogni comando Python: - -```make -release: - uv run bump-my-version bump $(PART) - $(MAKE) changelog - git tag -f v$$(uv run python -c "from simple_sample import __version__; print(__version__)") - git push && git push --tags --force -``` - -Ora `make patch` funziona anche da una shell vergine, senza attivare niente. Il venv non è più una convenzione tribale, è implicito in ogni comando. - -### Sette sezioni in `pyproject.toml`, una per tool - -`pyproject.toml` nasce per il packaging e da lì ha raccolto le sezioni di config dei tool del progetto: sette in totale. - -**ruff** parte da `select = ["ALL"]`: abilito tutte le regole disponibili e uso `ignore` per quelle che considero troppo. Filosofia "tutto in default, escludo per nome": man mano che ruff aggiunge regole nuove, le adotto in automatico. E il pacchetto "ALL" non è solo style + lint: include anche regole di naming (PEP 8), docstring (PEP 257), type annotations (PEP 484, con `flake8-annotations`), complessità ciclomatica (`mccabe`), security elementare (`bandit-base`), import order (`isort`). Ruff non è "solo" un formatter + linter, è l'ombrello sotto il quale stanno black + isort + flake8 + pezzi di pylint, pydocstyle e bandit. - -**pyright** in `typeCheckingMode = "strict"`: il default `basic` chiude un occhio su molte cose, `strict` pretende type hints completi e ritorni espliciti. È la modalità che fa emergere quegli errori che il giro dei type checker aveva rilevato (e che mypy / pyrefly / ty con la configurazione di default avrebbero perso). - -**pytest**: configurazione minimale, `asyncio_mode = "auto"` e `testpaths = ["tests"]`. Il resto sta nei test stessi. - -**[dependency-groups].dev**: la lista delle dev-deps con i version constraint (PEP 735). uv legge questa sezione per `uv sync --group dev`. - -**packaging** (`[build-system]`, `[project]`, `[tool.setuptools]`), **bumpversion**, **git-cliff**: gestiscono la pipeline di release (metadata + dipendenze runtime + build di wheel e sdist + versioning + CHANGELOG dai conventional commits). Argomento diverso dal code quality, ma necessario allo scopo della modernizzazione e dell'automazione. - -**pre-commit** sta in `.pre-commit-config.yaml` (fuori da `pyproject.toml`): punta al repo ufficiale `astral-sh/ruff-pre-commit` per i due hook ruff (check + format) e tiene un local hook che lancia `uv run pytest` per i test. Così anche pre-commit si appoggia a uv per accedere al venv del progetto, esattamente come i target del Makefile. - -## Plus - -L'informatico pigro aggiunge tool quando realmente servono, quando è il momento di gestire in automatico qualche altro aspetto. - -Sempre sulla code quality, cosa si potrebbe aggiungere e quando ? - -- **[vulture](https://pypi.org/project/vulture/) e [radon](https://pypi.org/project/radon/)**: dead code a livello di progetto e report di complessità. Quando serve una mappa del codebase, per esempio prima di un refactor importante: ruff vede il singolo file, vulture e radon vedono l'insieme della codebase. -- **[bandit](https://pypi.org/project/bandit/) (SAST), [pip-audit](https://pypi.org/project/pip-audit/) (SCA) e [detect-secrets](https://pypi.org/project/detect-secrets/)**: se il pacchetto diventa un'API o gestisce dati sensibili, ma qui si apre un altro mondo .. -- **mypy in strict mode**: doppio check di pyright. Oggi non conosco l'esempio per il quale dovrei integrarlo, pyright strict copre bene. -- **pyrefly e ty**: da rivalutare specialmente per progetti con molti file. Sono veloci ma giovani. -- **[pre-commit.ci](https://pre-commit.ci/)**: un hook che gira anche in CI su ogni PR. Per un progetto personale con un solo maintainer è overhead, per un repo condiviso avrebbe senso. diff --git a/README.rst b/README.rst index 264858c..203c543 100644 --- a/README.rst +++ b/README.rst @@ -1,89 +1,68 @@ Python prototype ================ -A simple sample of a Python package prototype. Part of the `educational repositories `_, used as reference for TDD, packaging and code quality best practices. +This package contains a simple sample of a Python package prototype. +It is part of the `educational repositories `_ to learn how to write stardard code and common uses of the TDD. -Full documentation on `readthedocs `_. +See the documentation and how to do it on `readthedocs `_. +And see the development of this code step by step -Two reading paths through the development history: - -* `step by step `_: built from scratch with the **legacy stack** (``unittest`` + ``setup.py``), uses `see-git-steps `_ to walk through the commits. -* `refactoring `_: migration to the **modern stack** (``pytest`` + ``pyproject.toml``, plus ``uv``, ``ruff``, ``pyright``, ``pre-commit``). - -A visual overview of the daily code quality cycle and the TDD sequence is on the `code quality `_ page. +* with `see-git-steps `_ +* on `readthedocs / step by step `_ Installation -############ +############### -The package is self-contained. You can download it from GitHub: +The package is self-consistent. So you can download the package by github: .. code-block:: bash $ git clone https://github.com/bilardi/python-prototype -Or install it with pip: +Or you can install by python3-pip: .. code-block:: bash - $ pip3 install simple-sample + $ pip3 install simple_sample Usage ##### -Read `tests/test_my_class.py `_ to see how to use the package, or read the docstrings from the REPL: +Read the unit tests in `tests/testMyClass.py `_ file to use it. This is a best practice. +You can read also the documentation by command line, .. code-block:: bash $ python3 - >>> from simple_sample.my_class import MyClass + >>> from simple_sample.myClass import MyClass + >>> print(MyClass.__doc__) >>> help(MyClass) >>> quit() -For more, see `readthedocs / howtouse `_. +If you want to see the local documentation, that you have downloaded by github, you can use the same steps but before you must to change the directory + +.. code-block:: bash + + $ cd python-prototype Development ########### -See `readthedocs / howtomake `_ for environment setup, testing, pre-commit hooks, conventional commits, versioning and packaging. - -See `readthedocs / refactoring `_ for how this project moved from ``unittest`` / ``setup.py`` to ``pytest`` / ``pyproject.toml``. +It is common use to test the code step by step and unittest module is a good beginning for unit test and functional test. -Project structure -################# +Test with unittest module -:: +.. code-block:: bash - simple_sample/ # the package source - __init__.py # package metadata and version - my_class.py # example class: inherits from interface and abstract - my_class_abstract.py # abstract class - my_class_interface.py # interface class - tests/ # pytest test files - docs/source/ # sphinx documentation (published on readthedocs) - pyproject.toml # deps, ruff, pyright, bumpversion, git-cliff - Makefile # targets for test, build, release - .pre-commit-config.yaml # ruff + pytest hooks - CHANGELOG.md # generated by git-cliff + $ cd python-prototype + $ python3 -m unittest discover -v Change Log ########## See `CHANGELOG.md `_ for details. -This file is updated by a `Makefile `_ target: - -.. code-block:: bash - - $ make changelog - -See the `Versioning management `_ section in howtomake for how to bump the version and release: the same page also shows how to install the dependencies first. - -Blog post -######### - -* Italian: `POST.it.md `_ -* English: `POST.en.md `_ License ####### -This package is released under the MIT license. See `LICENSE `_ for details. +This package is released under the MIT license. See `LICENSE `_ for details. diff --git a/docs/images/code.quality.horizontal.flowchart.mermaid b/docs/images/code.quality.horizontal.flowchart.mermaid deleted file mode 100644 index 95c0b1b..0000000 --- a/docs/images/code.quality.horizontal.flowchart.mermaid +++ /dev/null @@ -1,12 +0,0 @@ -flowchart LR - Setup([uv sync]) -.-> Code - Code[write code] -->|save| IDE[pyright in IDE] - IDE --> Code - Code -->|git commit| PC[pre-commit hook] - PC --> Ruff[ruff check + format] - PC --> Pytest[pytest] - Ruff --> Check{all green?} - Pytest --> Check - Check -->|pass| Commit[commit created] - Check -->|fail| Fix[fix] - Fix --> Code diff --git a/docs/images/code.quality.horizontal.flowchart.png b/docs/images/code.quality.horizontal.flowchart.png deleted file mode 100644 index d6d7884..0000000 Binary files a/docs/images/code.quality.horizontal.flowchart.png and /dev/null differ diff --git a/docs/images/code.quality.sequence.mermaid b/docs/images/code.quality.sequence.mermaid deleted file mode 100644 index d6d1a2b..0000000 --- a/docs/images/code.quality.sequence.mermaid +++ /dev/null @@ -1,23 +0,0 @@ -sequenceDiagram - actor Dev as Developer - participant Test as tests/ - participant Code as src/ - participant PC as pre-commit - participant Py as pytest - participant R as ruff - - Note over Dev,Code: TDD: tests first, then the code - Dev->>Test: write a test - Dev->>Py: uv run pytest - Py-->>Dev: red (test fails) - Dev->>Code: write the minimum to pass - Dev->>Py: uv run pytest - Py-->>Dev: green - - Note over Dev,PC: Commit: pre-commit verifies - Dev->>PC: git commit - PC->>R: ruff check + format - R-->>PC: ok - PC->>Py: pytest - Py-->>PC: green - PC-->>Dev: commit created diff --git a/docs/images/code.quality.sequence.png b/docs/images/code.quality.sequence.png deleted file mode 100644 index 3e71e29..0000000 Binary files a/docs/images/code.quality.sequence.png and /dev/null differ diff --git a/docs/images/code.quality.vertical.flowchart.mermaid b/docs/images/code.quality.vertical.flowchart.mermaid deleted file mode 100644 index a436c63..0000000 --- a/docs/images/code.quality.vertical.flowchart.mermaid +++ /dev/null @@ -1,12 +0,0 @@ -flowchart TD - Setup([uv sync]) -.-> Code - Code[write code] -->|save| IDE[pyright in IDE] - IDE --> Code - Code -->|git commit| PC[pre-commit hook] - PC --> Ruff[ruff check + format] - PC --> Pytest[pytest] - Ruff --> Check{all green?} - Pytest --> Check - Check -->|pass| Commit[commit created] - Check -->|fail| Fix[fix] - Fix --> Code diff --git a/docs/images/code.quality.vertical.flowchart.png b/docs/images/code.quality.vertical.flowchart.png deleted file mode 100644 index e3e040b..0000000 Binary files a/docs/images/code.quality.vertical.flowchart.png and /dev/null differ diff --git a/docs/source/api.rst b/docs/source/api.rst deleted file mode 100644 index d2509f2..0000000 --- a/docs/source/api.rst +++ /dev/null @@ -1,9 +0,0 @@ -API -=== - -.. toctree:: - :maxdepth: 1 - - api/my_class_interface - api/my_class_abstract - api/my_class diff --git a/docs/source/api/my_class.rst b/docs/source/api/my_class.rst deleted file mode 100644 index 5964841..0000000 --- a/docs/source/api/my_class.rst +++ /dev/null @@ -1,8 +0,0 @@ -MyClass -======= - -.. automodule:: simple_sample.my_class - :no-members: - -.. autoclass:: MyClass - :members: diff --git a/docs/source/api/my_class_abstract.rst b/docs/source/api/my_class_abstract.rst deleted file mode 100644 index 5c88aa6..0000000 --- a/docs/source/api/my_class_abstract.rst +++ /dev/null @@ -1,8 +0,0 @@ -MyClassAbstract -=============== - -.. automodule:: simple_sample.my_class_abstract - :no-members: - -.. autoclass:: MyClassAbstract - :members: diff --git a/docs/source/api/my_class_interface.rst b/docs/source/api/my_class_interface.rst deleted file mode 100644 index 1c4165b..0000000 --- a/docs/source/api/my_class_interface.rst +++ /dev/null @@ -1,8 +0,0 @@ -MyClassInterface -================ - -.. automodule:: simple_sample.my_class_interface - :no-members: - -.. autoclass:: MyClassInterface - :members: diff --git a/docs/source/codequality.rst b/docs/source/codequality.rst deleted file mode 100644 index 3db8503..0000000 --- a/docs/source/codequality.rst +++ /dev/null @@ -1,28 +0,0 @@ -Code quality -============ - -This page describes the day-to-day code quality cycle of the project: how the four tools chosen during the refactoring (``uv``, ``ruff``, ``pyright``, ``pre-commit``) work together while you write code, until the commit is accepted. - -Two views from two angles. The first one is a flowchart that shows the cycle from the perspective of the **tools**: who runs when, where each one fits, and what happens at the *all green ?* fork. The second one is a sequence diagram that shows the cycle from the perspective of **time**: tests are written first, then the code, and pre-commit verifies again at ``git commit``. - -The cycle of the four tools -########################### - -The flowchart below shows the daily cycle of the four tools. ``uv sync`` (dotted) is the initial setup, not part of the daily loop. While you write code, ``pyright`` runs in real time inside the IDE (typically via Pylance on VS Code). When you ``git commit``, the ``pre-commit`` hook fires ``ruff check + format`` and ``pytest``: if all green, the commit is accepted; if not, you fix and the loop starts again. - -For the configuration of each tool see `howtomake / Test tools `_; for the story of why these tools were chosen see `refactoring `_ (Step 6 to Step 10). - -.. image:: ../images/code.quality.vertical.flowchart.png - :alt: Code quality cycle with uv, pyright in IDE, ruff and pytest at pre-commit - :align: center - -The TDD sequence -################ - -The sequence diagram below shows the same cycle from a temporal angle, focused on TDD. The order matters: ``pytest`` works only if the tests exist, and tests are written before the code. The first part of the sequence (``Dev``, ``tests/``, ``src/``, ``pytest``) is the TDD loop in local: write a test, see it red, write the code, see it green. The second part (``Dev``, ``pre-commit``, ``ruff``, ``pytest``) is the verification at ``git commit``: pre-commit runs the same tools again as a safety net before the commit is accepted. - -For examples of pytest tests in this repo see `howtouse / PyTest `_; for the conversion of unit tests from ``unittest`` to ``pytest`` see Step 5 of the `refactoring `_ page. - -.. image:: ../images/code.quality.sequence.png - :alt: TDD sequence: red test, green test, pre-commit verification at git commit - :align: center diff --git a/docs/source/conf.py b/docs/source/conf.py index ecedf9e..c0eb1d8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,16 +17,16 @@ # -- Project information ----------------------------------------------------- -project = "simple-sample" -copyright = "2026, Alessandra Bilardi" -author = "Alessandra Bilardi" +project = 'simple-sample' +copyright = '2021, Alessandra Bilardi' +author = 'Alessandra Bilardi' # The full version, including alpha/beta/rc tags -version = "1.5.3" -release = "1.5.3" +version = '0.0.4' +release = '0.0.4' # specify the master doc, otherwise the build at read the docs fails -master_doc = "index" +master_doc = 'index' # -- General configuration --------------------------------------------------- @@ -34,13 +34,10 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinx.ext.intersphinx", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -53,9 +50,9 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = "sphinx_rtd_theme" +html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +html_static_path = ['_static'] diff --git a/docs/source/howtomake.rst b/docs/source/howtomake.rst index 2da0286..8d73d50 100644 --- a/docs/source/howtomake.rst +++ b/docs/source/howtomake.rst @@ -6,130 +6,47 @@ In this section you can find how to generate or publish that helps you with mana Virtual environment ################### -When you have to install specific requirements, you can use a `virtual environment `_: this method is important to test different package versions without installing them on your local. +When you have to install specific requirements.txt, you can use a `virtual environment `_: this method is important to test different packages versions without to install them on your local. The main commands are .. code-block:: bash $ cd python-prototype - $ python3 -m venv .env # create virtual environment - $ source .env/bin/activate # enter in the virtual environment - $ pip install --upgrade -r requirements.txt # install your dependencies - $ deactivate # exit when you have finished the job - $ rm -rf .env # remove the virtual environment: it is a best practice - -Now there are many packages that can help in these steps, one is `uv `_ - -The main commands become - -.. code-block:: bash - - $ cd python-prototype - $ pip install uv - # you can manage a specific python version - $ uv python install 3.13 - # create the environment and install all the dependencies (including dev) - $ uv sync - # it is no longer necessary to enter the virtual environment if you use `uv run` - $ # source .venv/bin/activate - -After `uv sync`, if you use `uv run` and the command you need (ie: `uv run pytest`, to test unit tests), -you don't need to use `source .venv/bin/activate` because `uv` package is the one who takes care of it. - -TDD -### - -Before writing code, it is important to verbalize the concepts by documentation and to create a Test Driven Development (TDD) for your code. Then, it is important to use unit tests for finding the issues and before updating the change log file and the package version. - -See the development of this code step by step on `readthedocs / step by step `_ for learning how to make a unit test. - -The ``step by step`` page uses ``unittest`` (on the ``unittest`` branch). For the ``pytest`` equivalent, see Step 5 of the `refactoring `_ page, where each ``unittest`` test file is converted to ``pytest`` file by file. - -Test tools -########## - -Code quality is checked by three tools, all configured in the ``pyproject.toml``: - -* `pytest `_, to run the tests -* `ruff `_, as linter and formatter (it replaces older tools like ``black``, ``flake8`` and ``isort`` in a single binary) -* `pyright `_, as static type checker (it validates type hints without running the code) - -The main commands are - -.. code-block:: bash - - $ cd python-prototype - $ uv run pytest # run all tests - $ uv run ruff check --no-fix . # lint (do not auto-fix) - $ uv run ruff format --check . # check formatting (do not rewrite) - $ uv run pyright # type check - -See the `refactoring `_ page for how these tools were chosen and configured. - -Pre-commit hooks -################ - -To run ruff and pytest automatically before every commit, this project uses `pre-commit `_: a framework that installs a git hook in ``.git/hooks/pre-commit`` and runs a chain of checks against your changes. - -The chain is declared in ``.pre-commit-config.yaml``. To install the git hook on your clone: - -.. code-block:: bash - - $ cd python-prototype - $ uv run pre-commit install - -From now on, every ``git commit`` will first run ruff (with auto-fix), ruff-format and pytest. If any of them fails, the commit is aborted. - -You can also trigger the chain manually, without committing: - -.. code-block:: bash - - $ uv run pre-commit run --all-files - -Conventional Commits -#################### - -The commit messages follow the `conventional commits `_ syntax. Each message starts with a type, a colon, and your description: - -.. code-block:: bash - - $ git commit -m "feat: add MyClass and its unit tests" - -The types used in this project are ``feat``, ``fix``, ``refactor``, ``perf``, ``docs``, ``test`` and ``chore``. The type is not cosmetic: `git-cliff `_ reads it to group the commits in ``CHANGELOG.md`` under "Features", "Bug Fixes", "Performance" and so on, so the changelog is generated automatically from the git history. - -Versioning management -##################### - -The project version lives in three files: ``simple_sample/__init__.py``, ``pyproject.toml`` and ``docs/source/conf.py``. Keeping them aligned by hand is error-prone, so there is a single command for each release type (see `semver `_ for the difference between patch, minor and major): - -.. code-block:: bash - - $ cd python-prototype - $ make patch # bump the patch version (e.g. 1.5.0 -> 1.5.1) - $ make minor # bump the minor version (e.g. 1.5.0 -> 1.6.0) - $ make major # bump the major version (e.g. 1.5.0 -> 2.0.0) - -Under the hood, the ``release`` target in the ``Makefile`` runs ``bump-my-version`` (which updates the three files above), regenerates ``CHANGELOG.md`` with ``git-cliff``, amends the last commit, force-moves the tag and pushes both. + $ python3 -m venv .env # create virtual environment + $ source .env/bin/activate # enter in the virtual environment + $ pip install --upgrade -r requirements.txt # install your dependences + $ deactivate # exit when you will have finished the job + $ rm -rf .env # remove the virtual environment it is a best practice Documentation ############# -The Python language is permissive, but if you use a basic documentation like `Python Enhancement Proposals 8 `_ (PEP-8) and unit / functional test, everyone can easily read your code. There are many styles to write `Python documentation `_. +The Python language is permissive, but if you use a basic documentation like `Python Enhancement Proposals 8 `_ (PEP-8) and unit / functional test, everyone can easily read your code. There are many style to write `Python documentation `_. -If you load your package on `GitHub `_, `GitLab `_, or `BitBucket `_, you can also use `sphinx `_ for creating a docs folder like in this package. It can help you to organize concepts in a single place without duplicates: the README.rst is the homepage on Github and Pypi repositories and it is also one of these pages (see `overview.rst `_). +If you load your package on `GitHub `_, `GitLab `_, or `BitBucket `_, you can also use `sphinx `_ for creating docs folder like in this package. It can help you to organize concepts in an unique place without duplicates: the README.rst is the homepage on Github and Pypi repositories and it is also one of these pages (see `overview.rst `_). The main commands for building documentation are .. code-block:: bash - $ cd python-prototype - $ make doc + $ pip3 install sphinx + $ pip3 install sphinx_rtd_theme # it is necessary only if you want the theme sphinx_rtd_theme + $ cd python-prototype/docs/ + $ sphinx-quickstart + $ make html -And you can open ``docs/build/html/index.html`` in your web browser to see your docs. +And you can open build/html/index.html in your web browser to see your docs. Instead, for uploading documentation on readthedocs, you have to follow `this guide `_. +TDD +### + +Before write code, it is important to verbalize the concepts by documentation and to create Test Driven Development (TDD) for your code. Then, it is important to use unit test for finding the issues and before to update change log file and package version. + +See the development of this code step by step on `readthedocs / step by step `_ for learning how to make a unit test. + Packaging ######### @@ -139,12 +56,17 @@ The main commands for testing are .. code-block:: bash - $ make buildtest # build and upload on testpypi - $ make installtest # install from testpypi + $ rm -rf build dist *.egg-info + $ python3 setup.py sdist bdist_wheel # create source archive + $ python3 -m twine upload --repository testpypi dist/* # upload source archive on testpypi + $ python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps simple-sample-bilardi # install package from testpypi The main commands for the production environment are .. code-block:: bash - $ make build # build and upload on pypi - $ make install # install from pypi + $ rm -rf build dist *.egg-info + $ python3 setup.py sdist bdist_wheel # create source archive + $ python3 -m twine upload dist/* # upload source archive on pypi + $ python3 -m pip install simple-sample # install package from pypi + $ pip3 install simple-sample # slim command for installing the package diff --git a/docs/source/howtouse.rst b/docs/source/howtouse.rst index 3ac8ce1..5250a23 100644 --- a/docs/source/howtouse.rst +++ b/docs/source/howtouse.rst @@ -3,97 +3,73 @@ How to use In this section you can find some tips & tricks for learning to use any code. -For the full story of the refactoring from ``unittest`` / ``setup.py`` to ``pytest`` / ``pyproject.toml``, see the `refactoring `_ page. - Unit tests ########## -The branch ``unittest`` keeps the package state before the refactoring, so you can run the original tests with the Python standard library. - -When you change something on your code, you can run one unit test about the class you changed: +When you change something on your code, you can run one unit test about that class changed .. code-block:: bash $ cd python-prototype - $ git fetch origin - $ git checkout unittest $ python3 -m unittest -v tests/testMyClass.py -And when you are ready for the commit, you can run all unit tests: +And when you are ready for the commit, you can use a command for running all unit tests .. code-block:: bash $ cd python-prototype - $ git checkout unittest $ python3 -m unittest discover -v -To learn how to use a class in your code, read its unit test file: you find the import, the initialization, and the main public methods. - -PyTest -###### - -On the branch ``master``, the tests use `pytest `_. -Unlike ``unittest``, pytest is a third-party library, so you need to install the dependencies first: - -.. code-block:: bash - - $ cd python-prototype - $ git checkout master - $ uv sync - $ uv run pytest tests/test_my_class.py - -And for running all tests: - -.. code-block:: bash - - $ uv run pytest +But for learning how to use an class in your code, you need to read its unit test file. +You can find the import class, the initialization class, and the main public methods. Documentation ############# -Another approach is to read the documentation in the class file. When you install a package, you can also read its documentation by shell: +Another approach is to read the documentation in the class file. When you install a package, you can also read its documentation by shell .. code-block:: bash $ python3 >>> import simple_sample - >>> print(simple_sample.__doc__) # description like overview of the package - >>> help(simple_sample) # description of the package contents, and if there are, classes and functions + >>> print(simple_sample.__doc__) # description like overview of the package + >>> help(simple_sample) # description of the package contents, and if there are, classes and functions >>> quit() -In the description, you can find an example for a command that you can use for each package element: +In the description, you can find an example for a command that you can use for each package element .. code-block:: bash $ python3 - >>> import simple_sample.my_class - >>> print(simple_sample.my_class.__doc__) # description like overview of the element - >>> help(simple_sample.my_class) # description of the element contents: classes and functions + >>> import simple_sample.myClass + >>> print(simple_sample.myClass.__doc__) # description like overview of the element + >>> help(simple_sample.myClass) # description of the element contents: classes and functions >>> quit() -If there are more classes in a package element, you can import a specific class to read all its methods: +If there are more classes in an package element, you can import a specific class where to read all methods .. code-block:: bash $ python3 - >>> from simple_sample.my_class import MyClass - >>> print(MyClass.__doc__) # description like overview of the class - >>> help(MyClass) # description of the class contents: methods, and if there are, functions + >>> from simple_sample.myClass import MyClass + >>> print(MyClass.__doc__) # description like overview of the class + >>> help(MyClass) # description of the class contents: methods, and if there are, functions >>> quit() MyClass ####### -The previous approaches are the best practice for learning something about a specific package. +The precedent approches are the best practice for learning something about a specific package. -Sometimes, the package is so complex, that it is also necessary a "Quick start" where a developer can learn the main classes or methods to start from: +Sometimes, the package is so complex, that it is also necessary a "Quick start" where a developer can learn the main classes or methods to start from .. code-block:: bash $ python3 - >>> from simple_sample.my_class import MyClass - >>> mc = MyClass() # initialization with or without boolean argument - >>> mc.get_param_processing(True) # returns the reverse value of the argument - >>> mc.get_boolean() # returns the boolean initialized - >>> mc.get_reverse_protected_param() # returns the reverse value of the boolean initialized + >>> from simple_sample.myClass import MyClass + >>> mc = MyClass() # initialization with or without boolean argument + >>> mc.foo(True); # returns the reverse value + >>> mc.bar(); # returns the boolean initialized + >>> mc.foobar(); # returns the reverse value of the boolean initialized >>> quit() + diff --git a/docs/source/index.rst b/docs/source/index.rst index d56fe7b..bdeb05f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,10 +14,7 @@ This package contains a simple sample of a Python package prototype. overview howtouse - codequality howtomake - refactoring - api stepbystep Indices and tables diff --git a/docs/source/refactoring.rst b/docs/source/refactoring.rst deleted file mode 100644 index b7bdbdf..0000000 --- a/docs/source/refactoring.rst +++ /dev/null @@ -1,612 +0,0 @@ -Refactoring -=========== - -This page is for learning all steps that are necessary for refactoring a simple package like simple-sample, - -* from unittest and setup.py -* to pytest and pyproject.toml - -Before following the guide, it is important to follow the guide `how to make `_, -because you need to install dependencies for testing, improving and checking your code. - -pyproject.toml -############## - -This is the core of configuration of a project where you can add all your custom modules configuration. - -In this project you can find, - -* `bumpversion `_, to manage the versioning update in the files that need it (see details in Makefile) - -.. code-block:: bash - - # use one of the following commands according to the guide https://semver.org/ - make patch - make minor - make major - -* `git-cliff `_, to update the CHANGELOG.md (see details in Makefile) - -.. code-block:: bash - - # it also adds the file in the commit - make changelog - -* `pytest `_, to run the tests. -* `ruff `_, it is the linter and formatter to automate code styling. -* `pyright `_, to analyze the code and suggest how it could be refactored. - -Getting started -############### - -The goal of the refactoring of the package simple-sample is to update some concepts to manage a Python package. -So you can use this simple package or this guide to refactor your package. - -Step 1 -****** - -The first step is to change the commits that have an old style described on `readthedocs / step by step `_. - -The `conventional commits `_ uses a specific syntax: - -* one of the types described in the guide -* colon -* your message, for me, with `keepachangelog style `_ - -To change all commits, we need to use `git-rebase `_ - -.. code-block:: bash - - # save the status in a specific branch - git checkout -b unittest - - # work on another branch - git checkout -b conventional-commits - - # replace pick with r to rewrite the commit marked - git rebase -i --root - -Step 2 -****** - -The second step is to add new tags: - -* now, the commits are different -* so the tags are linked only on the unittest branch commits - -To add the tags, it is important to define - -* which commits to use -* if you want to use the same versions or new ones - -In this case, only to keep the page `readthedocs / step by step `_ working, -we start from a major release. - -.. code-block:: bash - - git tag -a v1.0.0 -f -m "Empty package and documentation by sphinx" 714213d7614c2df9007948d51a0c8b5f5789145f - git tag -a v1.1.0 -f -m "MyClassInterface, unit tests and documentation" b8ac778148da489d654b44e5964258996736dcac - git tag -a v1.2.0 -f -m "MyClassAbstract, unit tests and documentation" 5f4b830e17798ba71a6d512175c0b41a4d30f9ab - git tag -a v1.3.0 -f -m "MyClass, unit tests and documentation" b4b4d745857893efde51c0c3d235778f8f127de6 - git tag -a v1.3.1 -f -m "The first full version of the simple-sample package" bb6029414975a84043fd7b8692318269e26511b0 - -After that, you can merge on the branch master and keep only the branch unittest. - -Step 3 -****** - -Before rewriting the code, it is important to test the new approach to package: - -* instead of using `setup.py `_ file that is a legacy method -* you can use pyproject.toml to save all that information - -You can see the files added or modified in this step in the commit `f131f15 `_. - -And you can try the build by - -.. code-block:: bash - - $ cd python-prototype - $ git checkout master - $ python -m build - -If it works, you can try to load the package on the repository testpypi - -.. code-block:: bash - - $ cd python-prototype - $ git checkout master - $ make buildtest - -If it works, and you can load the `test page `_, you can try to install it on a new folder - -.. code-block:: bash - - $ cd python-prototype - $ git checkout master - $ make installtest - -Step 4 -****** - -Before loading the package on the official repository, it is important to commit and push all: - -* not only the code -* but also the documentation -* and the version - -So, after the commit with code or documentation, you can version the status of the package: according to the guide `https://semver.org/ `_ - -.. code-block:: bash - - $ cd python-prototype - $ git checkout master - $ make patch # or - $ make minor # or - $ make major # or - -You can see the files added or modified in this step in the commit `545b1b66 `_: -it is very useful to create a version because, you can see the commits and the files involved easily. - -Only when you have a new version of your package, you can push it on GitHub and you can load it on the official repository - -.. code-block:: bash - - $ cd python-prototype - $ git checkout master - $ make build - -Step 5 -****** - -In our refactoring, we want to move from `unittest `_ to `pytest `_ package because it is more modern and easier. - -pytest can run unittest methods, but we want to simplify the tests with its syntax. - -So, before we can try to run pytest - -.. code-block:: bash - - $ cd python-prototype - $ uv run pytest - -It doesn't work - -.. code-block:: bash - - ======================================================== test session starts ======================================================== - platform linux -- Python 3.13.3, pytest-9.0.2, pluggy-1.6.0 - rootdir: /home/bilardi/python-prototype - configfile: pyproject.toml - testpaths: tests - plugins: asyncio-1.3.0, anyio-4.12.1 - asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function - collected 0 items - - ======================================================= no tests ran in 0.00s ======================================================= - -Because the files don't have the standard naming convention and they are not identified: - -* **tests/testMyClass.py** has to be renamed to **tests/test_my_class.py** (or at least ``test_``) -* **tests/testMyClasAbstract.py** has to be renamed to **tests/test_my_class_abstract.py** (or at least ``test_``) -* **tests/testMyClassInterface.py** has to be renamed to **tests/test_my_class_interface.py** (or at least ``test_``) - -Now the command ``uv run pytest`` works - -.. code-block:: bash - - ======================================================== test session starts ======================================================== - platform linux -- Python 3.13.3, pytest-9.0.2, pluggy-1.6.0 - rootdir: /home/bilardi/python-prototype - configfile: pyproject.toml - testpaths: tests - plugins: asyncio-1.3.0, anyio-4.12.1 - asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function - collected 10 items - - tests/test_my_class.py ...... [ 60%] - tests/test_my_class_abstract.py . [ 70%] - tests/test_my_class_interface.py ... [100%] - - ======================================================== 10 passed in 0.01s ========================================================= - -So, we can start to convert the syntax: - -* in a big project, it is not necessary to convert -* this is only for educational purpose - -We can change one file at a time, running ``uv run pytest`` each time to confirm that it works: - -* tests/test_my_class_interface.py -* tests/test_my_class_abstract.py -* tests/test_my_class.py - -Step 6 -****** - -Now we can try `black `_ - -.. code-block:: bash - - $ cd python-prototype - $ uv run black . - reformatted /home/bilardi/python-prototype/simple_sample/myClassInterface.py - reformatted /home/bilardi/python-prototype/simple_sample/__init__.py - reformatted /home/bilardi/python-prototype/tests/test_my_class_abstract.py - reformatted /home/bilardi/python-prototype/docs/source/conf.py - reformatted /home/bilardi/python-prototype/simple_sample/myClassAbstract.py - reformatted /home/bilardi/python-prototype/simple_sample/myClass.py - - All done! ✨ 🍰 ✨ - 6 files reformatted, 3 files left unchanged. - -The formatter changed - -* single quotes with double quotes -* removing spaces at the end of lines -* one line break after a docstring and methods -* double line breaks after imports - -You can see the files added or modified in this step in the commit `e3dd1e3 `_. - -It is a best practise to run black after a change and before a commit, or using the black extension on your IDE. - -Step 7 -****** - -Now we can try `pylint `_ - -.. code-block:: bash - - $ cd python-prototype - $ uv run pylint . - Built simple-sample @ file:///home/bilardi/python-prototype - Uninstalled 1 package in 0.23ms - Installed 1 package in 0.36ms - ************* Module simple_sample.myClassInterface - simple_sample/myClassInterface.py:4:0: C0301: Line too long (104/100) (line-too-long) - simple_sample/myClassInterface.py:9:0: C0301: Line too long (106/100) (line-too-long) - simple_sample/myClassInterface.py:20:0: C0301: Line too long (108/100) (line-too-long) - simple_sample/myClassInterface.py:1:0: C0103: Module name "myClassInterface" doesn't conform to snake_case naming style (invalid-name) - simple_sample/myClassInterface.py:23:4: C0104: Disallowed name "bar" (disallowed-name) - simple_sample/myClassInterface.py:29:8: W0107: Unnecessary pass statement (unnecessary-pass) - ************* Module simple_sample.myClassAbstract - simple_sample/myClassAbstract.py:29:0: C0301: Line too long (103/100) (line-too-long) - simple_sample/myClassAbstract.py:1:0: C0103: Module name "myClassAbstract" doesn't conform to snake_case naming style (invalid-name) - simple_sample/myClassAbstract.py:32:4: C0104: Disallowed name "baz" (disallowed-name) - simple_sample/myClassAbstract.py:41:4: C0104: Disallowed name "foo" (disallowed-name) - simple_sample/myClassAbstract.py:41:18: C0104: Disallowed name "foo" (disallowed-name) - ************* Module simple_sample.myClass - simple_sample/myClass.py:11:0: C0301: Line too long (119/100) (line-too-long) - simple_sample/myClass.py:19:0: C0301: Line too long (131/100) (line-too-long) - simple_sample/myClass.py:21:0: C0301: Line too long (121/100) (line-too-long) - simple_sample/myClass.py:1:0: C0103: Module name "myClass" doesn't conform to snake_case naming style (invalid-name) - simple_sample/myClass.py:29:0: W0223: Method 'qux' is abstract in class 'MyClassInterface' but is not overridden in child class 'MyClass' (abstract-method) - simple_sample/myClass.py:41:23: C0104: Disallowed name "bar" (disallowed-name) - ************* Module conf - docs/source/conf.py:1:0: C0114: Missing module docstring (missing-module-docstring) - docs/source/conf.py:21:0: W0622: Redefining built-in 'copyright' (redefined-builtin) - docs/source/conf.py:20:0: C0103: Constant name "project" doesn't conform to UPPER_CASE naming style (invalid-name) - docs/source/conf.py:21:0: C0103: Constant name "copyright" doesn't conform to UPPER_CASE naming style (invalid-name) - docs/source/conf.py:22:0: C0103: Constant name "author" doesn't conform to UPPER_CASE naming style (invalid-name) - docs/source/conf.py:25:0: C0103: Constant name "version" doesn't conform to UPPER_CASE naming style (invalid-name) - docs/source/conf.py:26:0: C0103: Constant name "release" doesn't conform to UPPER_CASE naming style (invalid-name) - docs/source/conf.py:29:0: C0103: Constant name "master_doc" doesn't conform to UPPER_CASE naming style (invalid-name) - docs/source/conf.py:52:0: C0103: Constant name "html_theme" doesn't conform to UPPER_CASE naming style (invalid-name) - - ----------------------------------- - Your code has been rated at 4.35/10 - -You can see the files added or modified in this step in the commit `b252557 `_. - -After the changes the rate improved: - -.. code-block:: bash - - $ uv run pylint . - ************* Module simple_sample.my_class - my_class.py:32:0: W0223: Method 'qux' is abstract in class 'MyClassInterface' but is not overridden in child class 'MyClass' (abstract-method) - my_class.py:44:23: C0104: Disallowed name "bar" (disallowed-name) - ************* Module simple_sample.my_class_abstract - my_class_abstract.py:33:4: C0104: Disallowed name "baz" (disallowed-name) - my_class_abstract.py:42:4: C0104: Disallowed name "foo" (disallowed-name) - my_class_abstract.py:42:18: C0104: Disallowed name "foo" (disallowed-name) - ************* Module simple_sample.my_class_interface - my_class_interface.py:9:0: C0301: Line too long (106/100) (line-too-long) - my_class_interface.py:24:4: C0104: Disallowed name "bar" (disallowed-name) - - ------------------------------------------------------------------ - Your code has been rated at 7.94/10 - -It's not a given that we want to change everything pylint tells us: - -* W0223, we want it to fail when ``qux`` is used in ``MyClass`` -* C0301, it is a link: we can't split it -* C0104, the original classes use disallowed names: we can - * disable **disallowed-name** control adding in pyproject.toml **disallowed-name** in the disable array - * rename all variables with an intention-revealing name - -This must be an example, so we will also change the variable names. - -You can see the files added or modified in this step in the commit `e4a831e `_. - -After the changes the rate improved: - -.. code-block:: bash - - $ uv run pylint . - ************* Module simple_sample.my_class - simple_sample/my_class.py:33:0: W0223: Method 'method_with_not_implemented_error' is abstract in class 'MyClassInterface' but is not overridden in child class 'MyClass' (abstract-method) - ************* Module simple_sample.my_class_interface - simple_sample/my_class_interface.py:10:0: C0301: Line too long (106/100) (line-too-long) - - ------------------------------------------------------------------ - Your code has been rated at 9.41/10 - -If you remove from pyproject.toml "tests" from "ignore" - -.. code-block:: bash - - [tool.pylint.main] - py-version = "3.13" - recursive = true - ignore = [".venv", "docs"] - -And you try again pylint, - -.. code-block:: bash - - $ uv run pylint . - ************* Module simple_sample.my_class - simple_sample/my_class.py:33:0: W0223: Method 'method_with_not_implemented_error' is abstract in class 'MyClassInterface' but is not overridden in child class 'MyClass' (abstract-method) - ************* Module simple_sample.my_class_interface - simple_sample/my_class_interface.py:10:0: C0301: Line too long (106/100) (line-too-long) - ************* Module tests.test_my_class_abstract - tests/test_my_class_abstract.py:23:8: E0110: Abstract class 'MyClassAbstract' with abstract methods instantiated (abstract-class-instantiated) - ************* Module tests.test_my_class_interface - tests/test_my_class_interface.py:25:43: W0621: Redefining name 'mci' from outer scope (line 20) (redefined-outer-name) - tests/test_my_class_interface.py:30:51: W0621: Redefining name 'mci' from outer scope (line 20) (redefined-outer-name) - tests/test_my_class_interface.py:35:73: W0621: Redefining name 'mci' from outer scope (line 20) (redefined-outer-name) - ************* Module tests.test_my_class - tests/test_my_class.py:25:33: W0621: Redefining name 'mc' from outer scope (line 20) (redefined-outer-name) - tests/test_my_class.py:30:48: W0621: Redefining name 'mc' from outer scope (line 20) (redefined-outer-name) - tests/test_my_class.py:38:48: W0621: Redefining name 'mc' from outer scope (line 20) (redefined-outer-name) - tests/test_my_class.py:44:50: W0621: Redefining name 'mc' from outer scope (line 20) (redefined-outer-name) - tests/test_my_class.py:50:63: W0621: Redefining name 'mc' from outer scope (line 20) (redefined-outer-name) - tests/test_my_class.py:56:49: W0621: Redefining name 'mc' from outer scope (line 20) (redefined-outer-name) - - ------------------------------------------------------------------ - Your code has been rated at 7.78/10 - -The warning code W0621 is because pylint and pytest are not aligned .. but it is possible to improve without disable **redefined-outer-name** `rule `_. - -.. code-block:: python - - @pytest.fixture(name="my_class") - def mc(): - return MyClass() - - def test_my_class_can_be_created(my_class): - assert isinstance(my_class, MyClass) - -And the execution improve the rate: - -.. code-block:: bash - - $ uv run pylint . - ************* Module simple_sample.my_class - simple_sample/my_class.py:33:0: W0223: Method 'method_with_not_implemented_error' is abstract in class 'MyClassInterface' but is not overridden in child class 'MyClass' (abstract-method) - ************* Module simple_sample.my_class_interface - simple_sample/my_class_interface.py:10:0: C0301: Line too long (106/100) (line-too-long) - ************* Module tests.test_my_class_abstract - tests/test_my_class_abstract.py:23:8: E0110: Abstract class 'MyClassAbstract' with abstract methods instantiated (abstract-class-instantiated) - - ------------------------------------------------------------------ - Your code has been rated at 9.03/10 - -But this change would only be to avoid the pylint warning because it doesn't recognise pytest behaviour. So it would be wrong to please it. - -Is there something else better on pylint ? Well, I thought not, but I was wrong. - -I started my analysis with `ruff `_ and `mypy `_. -Then I added to the research `pyright `_, `pyrefly `_ and `ty `_. - -Step 8 -****** - -`pylint `_ is a “deep analysis” linter (static type inference). It tries to understand the class hierarchy and how objects interact with each other. - -`ruff `_ is a “fast analysis” linter (based on the AST - Abstract Syntax Tree). It's incredibly fast because it analyzes individual files almost in isolation, but for this reason it struggles to "connect the dots" between a parent class in one file and a child class in another. - -Currently, ruff doesn't have data flow analysis or class hierarchy analysis deep enough to know that MyClass inherits from MyClassInterface and that you forgot to implement a method. - -* pylint "reads" the code almost as if it were executing it, -* while ruff reads it like structured text. - -But, **ruff** replace very well **black** and it can modify the code also for *a lot of lint* cases. - -You can try it with **check** as linter - -.. code-block:: bash - - uv run ruff check . - # or making explicit --fix, but in the configuration there is fix = true - uv run ruff check --fix . - -The lint issues can be resolved by - -.. code-block:: bash - - uv run ruff check --unsafe-fixes . - -The format issue can be resolved by - -.. code-block:: bash - - uv run ruff format . - -You can see the files added or modified in this step in the commits `c77f81c...babe6b3 `_. - -ruff changed - -* alphabetical order of the package imports -* new line between third package imports and yours -* one point after the first line of the comment -* comments spaces management - -Step 9 -****** - -If ruff can modify the code, mypy, pyright, pyrefly and ty can't do it like pylint. - -I think that all these tools are very interesting - -* `mypy `_, it recognizes that - * the empty method is not implemented - * and the abstract class is not possible to initialize in the test_my_class_abstract.py -* `pyrefly `_, it recognizes that - * the empty method has a bad return, because it should return a bool instead it is missing - * and the abstract class is not possible to initialize in the test_my_class_abstract.py -* `ty `_, it recognizes that - * the empty method is not implemented on the interface class - * and this method returns None though the type hinting is bool -* `pyright `_, it recognizes that - * the same points of ty - * and also another point where the empty method is used in the MyClass - -So pyright finds the most useful information and it is also a mature tool. -Instead pyrefly and ty are very fast, so they could be already useful for big projects, although they are young tools. - -You can see the files added or modified in this step in the commit `1374bcd `_. - -After the changes the rate improved: - -.. code-block:: bash - - uv run pyright - simple_sample/my_class.py:88:16 - error: Condition will always evaluate to False since the types "bool" - and "None" have no overlap (reportUnnecessaryComparison) - simple_sample/my_class_interface.py:27:30 - error: Function with declared return type "bool" must return value on all code paths - "None" is not assignable to "bool" (reportReturnType) - 2 errors, 0 warnings, 0 informations - -Step 10 -******* - -If we want to run ruff and pytest always before a commit, it could be useful to configure a `pre-commit `_ action. - -You can configure a git-hook in ``.git/hooks/pre-commit`` with the command ``pre-commit install`` because - -* you have already installed the dependencies with `how to make `_, -* you have the file ``.pre-commit-config.yaml`` - -When you try to do a ``git-commit``, git will run for you ruff and pytest. - -If you want to try it before, you can run the command, - -.. code-block:: bash - - pre-commit run - ruff-check...............................................................Passed - ruff-format..............................................................Passed - pytest...................................................................Passed - -If the step fails, the pre-commit package tells you where and why: for example, if you have not committed as in this example - -.. code-block:: bash - - pre-commit run - [WARNING] Unstaged files detected. - ruff-check...............................................................Passed - ruff-format..............................................................Failed - - hook id: ruff-format - - files were modified by this hook - - 2 files reformatted, 6 files left unchanged - - pytest...................................................................Passed - -Step 11 -******* - -Before the refactoring, the dev dependencies were listed in ``tests/requirements-test.txt``: - -.. code-block:: bash - - build - bump-my-version - git-cliff - httpx - pre-commit - pyright - pytest - pytest-asyncio - ruff - twine - -With `uv `_ and the new `dependency groups `_ (PEP 735), the same list lives directly in ``pyproject.toml``. You can migrate the requirements file in one command: - -.. code-block:: bash - - $ uv add --dev -r tests/requirements-test.txt - -``uv`` reads the requirements file, resolves the versions, writes a ``[dependency-groups]`` section in ``pyproject.toml``, updates ``uv.lock`` and installs everything in ``.venv/``. After this, ``tests/requirements-test.txt`` is redundant and can be removed. - -.. note:: - - The command fails if ``requires-python`` in ``pyproject.toml`` is lower than what any dev tool requires. In this project, ``git-cliff`` needs Python >= 3.7, while the original ``requires-python = ">=3.6"`` was a pre-refactoring leftover. Align ``requires-python`` to the target version (``>=3.13`` here, matching `target-version `_ for ruff and `pythonVersion `_ for pyright) before running ``uv add``. - -The same pattern applies to `sphinx `_ and its theme, needed to build this documentation. They are added as dev deps in the same way: - -.. code-block:: bash - - $ uv add --dev sphinx sphinx_rtd_theme - -The Sphinx-generated ``docs/Makefile`` expects a ``sphinx-build`` in the ``PATH``. To make ``make doc`` work without activating the venv, the ``doc`` target in the project ``Makefile`` overrides the ``SPHINXBUILD`` variable with ``uv run sphinx-build``: - -.. code-block:: make - - doc: - cd docs; make html SPHINXBUILD="uv run sphinx-build"; cd - - -This way the binary is resolved through ``uv run`` without touching the auto-generated ``docs/Makefile``. - -The story is not finished at local ``make doc``: `Read the Docs `_ builds the documentation on its own infrastructure, and needs to know how to install the dev dependencies before running Sphinx. Without a configuration file, the build on Read the Docs fails (release 1.5.1 failed for this reason). - -The file ``.readthedocs.yaml`` in the project root tells Read the Docs which Python version to use, how to install dependencies with ``uv`` and where ``conf.py`` lives. The key entry is ``python.install.method: uv`` with ``command: sync`` and ``groups: [dev]``: this uses the native Read the Docs integration (`docs `_) to run ``uv sync --group dev`` on their build machine, the same command that works locally, without ``pip install uv`` workarounds in ``build.jobs``. - -Step 12 -******* - -After tagging a release on GitHub (`Step 4`_), the next step is to publish the package on `PyPI `_. Before uploading to the production index, it is a good practice to test the upload on `TestPyPI `_: same API and flow, but a separate index for experiments. - -The Makefile has four targets for this workflow: - -.. code-block:: bash - - $ make buildtest # build the wheel and upload to TestPyPI - $ make installtest # install the package from TestPyPI in a sandbox venv and verify the import - $ make build # build the wheel and upload to PyPI - $ make install # install the package from PyPI in a sandbox venv and verify the import - -All four targets prefix ``uv run`` for the build and twine commands, so they work without activating the project venv. - -The ``installtest`` and ``install`` targets use a throwaway venv (``.installtest/`` or ``.install/``) so that the install is tested end-to-end: - -* create a clean venv with ``uv venv --clear`` -* install the package in that venv with ``uv pip install --python ...``, downloading the wheel from (Test)PyPI -* verify the import by printing the version with ``python -c "import ..."`` -* delete the venv - -Without the sandbox venv, ``uv pip install`` would just audit the already-installed editable version in the project ``.venv/`` and download nothing from (Test)PyPI. - -Conclusion -########## - -You have completed the refactoring of the package: the codebase now uses ``pyproject.toml`` (with `dependency-groups `_), ``pytest``, ``ruff``, ``pyright`` and ``pre-commit``. - -Every time you finish a class with its unit test, you can: - -* release with ``make patch`` / ``make minor`` / ``make major`` (see `Step 4`_): updates ``CHANGELOG.md``, bumps the version, pushes commit and tag in one step -* publish on TestPyPI with ``make buildtest`` and verify with ``make installtest`` (see `Step 12`_) -* publish on PyPI with ``make build`` when the release is stable diff --git a/docs/source/stepbystep.rst b/docs/source/stepbystep.rst index 5f74589..8b4df19 100644 --- a/docs/source/stepbystep.rst +++ b/docs/source/stepbystep.rst @@ -1,12 +1,7 @@ Step by step ============ -.. note:: - - This page describes the **pre-refactoring** state of the project, on the ``unittest`` branch. - For the modern equivalent (``pytest``, ``pyproject.toml``, ``uv``, ``ruff``, ``pyright``, ``pre-commit``), see the `refactoring `_ page. - -This page is for learning all steps that are necessary for writing a simple package like simple-sample. +This page is for learning all steps that they are necessary for writing a simple package like simple-sample. see-git-steps ############# @@ -21,15 +16,6 @@ Getting started The goal of the package simple-sample is to create a Python package prototype. So you can use this simple package for downloading a base for your package. -This package has been refactored with new tools and best practises. -So you can follow this guide only after changing the branch. - -.. code-block:: bash - - $ cd python-prototype - $ git fetch origin - $ git checkout unittest - Step 1 ****** @@ -71,7 +57,7 @@ When you have modified, you can commit your first change .. code-block:: bash $ cd python-prototype - $ git init # for initializing the repository + $ git init # for initializing the repository $ git add .gitignore CHANGELOG.md LICENSE MANIFEST.in Makefile README.rst setup.py $ git commit -m "step 1 - add the outline files" @@ -113,7 +99,7 @@ When you have modified **setup.py** and added the new files, you can commit your Step 3 ****** -Before writing code, it is important to verbalize the concepts by documentation: +Before write code, it is important to verbalize the concepts by documentation: so the documentation is important to learn a package as to plan how to write the code. You can write your documentation as you want: you can create docs folder like in this package, by `sphinx `_. @@ -132,7 +118,7 @@ The standard behaviour is to add changes in a CHANGELOG file: see the changes of .. code-block:: bash $ cd python-prototype - $ see-git-steps -c 89c4563c7bdcbe5f50fd153c078aa813846cfd09 -v | head -n 21 + $ see-git-steps -c 20b91ae691f29c96059dc3d3b355ab7c91eb9928 -v | head -n 21 So you can add CHANGELOG.md on your last commit, or you can create one commit for changelog, and then you can add the tag. @@ -140,16 +126,16 @@ So you can add CHANGELOG.md on your last commit, or you can create one commit fo $ cd python-prototype $ git add CHANGELOG.md - $ git commit --amend # add file on your last commit - $ git tag v0.0.1 -m "Empty package and documentation by sphinx" # create a tag with that version name - $ git tag -n # show the tag list with description - $ git push origin --tags # load the tag on repository + $ git commit --amend # add file on your last commit + $ git tag v0.0.1 -m "Empty package and documentation by sphinx" # create a tag with that version name + $ git tag -n # show the tag list with description + $ git push origin --tags # load the tag on repository Step 4 ****** -Before writing code, it is important to verbalize the methods by creating a Test Driven Development (TDD) for your code. -Then, it is important to use unit tests for finding the issues and before updating the change log file and the package version. +Before write code, it is important to verbalize the methods by create Test Driven Development (TDD) for your code. +Then, it is important to use unit test for finding the issues and before to update change log file and package version. In Python, a standard TDD is offered by unittest module: see the unit tests of MyClassInterface by `GitHub `_ or by command line with see-git-steps @@ -332,7 +318,7 @@ So you can add the files updated, you can create a commit dedicated, and then yo $ cd python-prototype $ git add CHANGELOG.md docs/source/conf.py simple_sample/__init__.py $ git commit -m "step 10 - update changelog and version of the simple-sample package" - $ git push origin master # load the commit on remote repository - $ git tag v0.0.4 -m "The first full version of the simple-sample package" # create a tag with that version name - $ git tag -n # show the tag list with description - $ git push origin --tags # load the tag on repository + $ git push origin master # load the commit on remote repository + $ git tag v0.0.4 -m "The first full version of the simple-sample package" # create a tag with that version name + $ git tag -n # show the tag list with description + $ git push origin --tags # load the tag on repository diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 3a0f6f5..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,210 +0,0 @@ -[build-system] -requires = ["setuptools>=69", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "simple-sample" -dynamic = ["version"] -requires-python = ">=3.13" -classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", -] -description = "A simple sample of a Python package prototype" -readme = "README.rst" -authors = [ - { name="Alessandra Bilardi", email="alessandra.bilardi@gmail.com" } -] - -[project.urls] -Homepage = "https://simple-sample.readthedocs.io/" -Source = "https://github.com/bilardi/python-prototype" -"Bug Reports" = "https://github.com/bilardi/python-prototype/issues" -Funding = "https://donate.pypi.org" - -[tool.setuptools] -packages = { find = {} } - -[tool.setuptools.dynamic] -version = {attr = "simple_sample.__version__"} - -[dependency-groups] -dev = [ - "build>=1.4.4", - "bump-my-version>=1.3.0", - "git-cliff>=2.12.0", - "httpx>=0.28.1", - "pre-commit>=4.6.0", - "pyright>=1.1.409", - "pytest>=9.0.3", - "pytest-asyncio>=1.3.0", - "ruff>=0.15.11", - "sphinx>=9.1.0", - "sphinx-rtd-theme>=3.1.0", - "twine>=6.2.0", -] - -# --------------------------------------------------------------------------- -# bumpversion -# --------------------------------------------------------------------------- -[tool.bumpversion] -current_version = "1.5.3" -commit = true -tag = true -tag_name = "v{new_version}" -message = "build: release {new_version}" -tag_message = "simple-sample {new_version}" - -[[tool.bumpversion.files]] -filename = "simple_sample/__init__.py" -search = '__version__ = "{current_version}"' -replace = '__version__ = "{new_version}"' - -[[tool.bumpversion.files]] -filename = "pyproject.toml" -search = 'current_version = "{current_version}"' -replace = 'current_version = "{new_version}"' - -[[tool.bumpversion.files]] -filename = "docs/source/conf.py" -search = 'version = "{current_version}"' -replace = 'version = "{new_version}"' - -[[tool.bumpversion.files]] -filename = "docs/source/conf.py" -search = 'release = "{current_version}"' -replace = 'release = "{new_version}"' - -# --------------------------------------------------------------------------- -# git-cliff -# --------------------------------------------------------------------------- -[tool.git-cliff.remote.github] -owner = "bilardi" -repo = "python-prototype" - -[tool.git-cliff.changelog] -header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n" -body = """ -{% if version %}\ -## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} -{% else %}\ -## [Unreleased] -{% endif %}\ -{% for group, commits in commits | group_by(attribute="group") %}\ -### {{ group }} -{% for commit in commits %}\ -- {{ commit.message | upper_first }} -{% endfor %} -{% endfor %}\ -""" -footer = """ -{% for release in releases %}\ -{% if release.version %}\ -{% if release.previous is defined and release.previous.version is defined %}\ -[{{ release.version | trim_start_matches(pat="v") }}]: https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/compare/{{ release.previous.version }}...{{ release.version }} -{% else %}\ -[{{ release.version | trim_start_matches(pat="v") }}]: https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/releases/tag/{{ release.version }} -{% endif %}\ -{% else %}\ -{% if release.previous is defined and release.previous.version is defined %}\ -[Unreleased]: https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/compare/{{ release.previous.version }}...HEAD -{% endif %}\ -{% endif %}\ -{% endfor %} -""" -trim = true -render_always = false -skip_if_empty = true - -[tool.git-cliff.git] -conventional_commits = true -filter_unconventional = true -tag_pattern = "v[0-9].*" -sort_commits = "oldest" -split_commits = false - -[[tool.git-cliff.git.commit_parsers]] -message = "^feat" -group = "Features" - -[[tool.git-cliff.git.commit_parsers]] -message = "^fix" -group = "Bug Fixes" - -[[tool.git-cliff.git.commit_parsers]] -message = "^refactor|^perf" -group = "Performance" - -[[tool.git-cliff.git.commit_parsers]] -message = "^docs" -group = "Documentation" - -[[tool.git-cliff.git.commit_parsers]] -message = "^test" -group = "Test" - -[[tool.git-cliff.git.commit_parsers]] -message = "^chore" -group = "Chores" - -# --------------------------------------------------------------------------- -# pytest -# --------------------------------------------------------------------------- -[tool.pytest.ini_options] -asyncio_mode = "auto" -testpaths = ["tests"] -# markers = [ -# "slow: tests longer than ~Xs; run with -m slow to include them", -# ] -# addopts = ["-m", "not slow"] - -# --------------------------------------------------------------------------- -# ruff -# --------------------------------------------------------------------------- -[tool.ruff] -exclude = [".venv", "docs"] -fix = true -line-length = 100 -output-format = "grouped" -target-version = "py313" - -[tool.ruff.lint] -select = ["ALL"] -ignore = [ - "COM812", # missing trailing comma # conflict with formatter - "D203", # incorrect-blank-line-before-class - "D213", # multi-line-summary-second-line - "D413", # Missing blank line after last section ("Args", "Returns") - "D415", # First line should end with a period, question mark, or exclamation point - "EXE002", # The file is executable but no shebang is present - "FBT", # positional argument in function definition or call - "ISC001", # implicit string concatenation # conflict with formatter -] - -[tool.ruff.lint.per-file-ignores] -"__init__.py" = ["D104"] # Missing docstring in public package -"tests/**" = [ - "ANN", # Missing return / type annotation for public function - "S101" # Use of `assert` detected -] - -[tool.ruff.format] -docstring-code-format = true -docstring-code-line-length = 100 -indent-style = "space" -line-ending = "auto" -quote-style = "double" - -[tool.ruff.lint.mccabe] -max-complexity = 10 - -# --------------------------------------------------------------------------- -# pyright -# --------------------------------------------------------------------------- -[tool.pyright] -exclude = ["**/__pycache__"] -include = ["simple_sample"] -pythonVersion = "3.13" -reportMissingTypeStubs = false -typeCheckingMode = "strict" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ad0f529 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +import setuptools +import simple_sample + +setuptools.setup( + name="simple-sample", + version=simple_sample.__version__, + author=simple_sample.__author__, + author_email="alessandra.bilardi@gmail.com", + description="A simple sample of a Python package prototype", +# long_description=open('README.md').read(), +# long_description_content_type="text/markdown", + long_description=open('README.rst').read(), + long_description_content_type="text/x-rst", + url="https://simple-sample.readthedocs.io/", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.6', + project_urls={ + "Source":"https://github.com/bilardi/python-prototype", + "Bug Reports":"https://github.com/bilardi/python-prototype/issues", + "Funding":"https://donate.pypi.org", + }, +) diff --git a/simple_sample/__init__.py b/simple_sample/__init__.py index 61182c2..fa96bbd 100644 --- a/simple_sample/__init__.py +++ b/simple_sample/__init__.py @@ -1,4 +1,4 @@ -"""An example of package. +"""An example of package This package contains an example of module like a prototype that you can follow for yours. The package is self-consistent: it contains three classes and some unit tests. @@ -7,12 +7,11 @@ Package contents three files, and each file contains one class with the same name. - >>> import simple_sample.my_class - >>> help(simple_sample.my_class) + >>> import simple_sample.myClass + >>> help(simple_sample.myClass) # license MIT # support https://github.com/bilardi/python-prototype/issues """ - -__version__ = "1.5.3" -__author__ = "Alessandra Bilardi" +__version__ = '0.0.4' +__author__ = 'Alessandra Bilardi' diff --git a/simple_sample/myClass.py b/simple_sample/myClass.py new file mode 100755 index 0000000..ccafd9d --- /dev/null +++ b/simple_sample/myClass.py @@ -0,0 +1,92 @@ +"""An example of class + +An example of class that it extends an abstract class and it implements an interface. +There is a boolean pun by foo function of abstract class, bar function of interface class, +and foobar function of this class. + +# license MIT +# author Alessandra Bilardi +# see https://github.com/bilardi/python-prototype for details + +Note that the _quux method is not present in the documentation like the other methods because it is a method protected. + + >>> from simple_sample.myClass import MyClass + >>> help(MyClass) + +# cite https://stackoverflow.com/questions/11483366/protected-method-in-python/11483397#11483397 + +Python does not support access protection as C++/Java/C# does. Everything is public. +The motto is, "We're all adults here." Document your classes, and insist that your collaborators read and follow the documentation. +The culture in Python is that names starting with underscores mean, +"don't use these unless you really know you should." You might choose to begin your "protected" methods with underscores. +But keep in mind, this is just a convention, it doesn't change how the method can be accessed. +""" + +from simple_sample.myClassInterface import MyClassInterface +from simple_sample.myClassAbstract import MyClassAbstract + +class MyClass(MyClassInterface, MyClassAbstract): + """ + An example of class that it extends an abstract class and it implements an interface. + There is a boolean pun by foo function of abstract class, bar function of interface class, + and foobar function of this class. + Args: + bar(bool): a boolean value + """ + # bar(bool): a class boolean variable with default True + _bar = True + + def __init__(self, bar = True): + """ + Initialization of variables + Args: + bar(bool): a boolean value + """ + self._bar = bar + + def foo(self, foo): + """ + Foo gets reverse value of foo + Args: + foo(bool): a boolean value + Returns: + The reverse value of foo + """ + return not foo + + def bar(self): + """ + Bar + Returns: + The boolean value of _bar + """ + return self._bar + + def foobar(self): + """ + Foobar gets reverse value of _bar + Returns: + The reverse value of _bar + """ + return self.foo(self._bar) + + def _quux(self): + """ + Quux recalls some methods + Returns: + The boolean value + """ + try: + if MyClassInterface.bar(self) is None: + MyClassInterface.qux(self) + except NotImplementedError: + return self.baz() + return True + + def fooquux(self): + """ + Fooquux gets reverse value of protected method _quux + Returns: + The boolean value + """ + return self.foo(self._quux()) diff --git a/simple_sample/my_class_abstract.py b/simple_sample/myClassAbstract.py similarity index 64% rename from simple_sample/my_class_abstract.py rename to simple_sample/myClassAbstract.py index 4ceeb8d..d6b057c 100755 --- a/simple_sample/my_class_abstract.py +++ b/simple_sample/myClassAbstract.py @@ -1,4 +1,4 @@ -"""An example of abstract class. +"""An example of abstract class An example of abstract class. If you need to use a framework, abstract class is a good method. There is a random boolean by baz function and an abstract method named foo function. @@ -17,40 +17,33 @@ such as distinguishing between mappings and sequences. """ -import random from abc import ABCMeta, abstractmethod - +import random class MyClassAbstract(metaclass=ABCMeta): - """An example of abstract class. - - If you need to use a framework, abstract class is a good method. - There is a random boolean by implemented_method function and - an abstract method named get_param_processing function. + """ + An example of abstract class. If you need to use a framework, abstract class is a good method. + There is a random boolean by baz function and an abstract method named foo function. It is not possible to instantiate a class with an abstract method. - And it is not possible to have an abstract method protected: - it will be changed in a pubilc method. + And it is not possible to have an abstract method protected: it will be changed in a pubilc method. """ - - def get_random_boolean(self) -> bool: - """Get a random boolean. - - Returns: - A random boolean value + def baz(self): + """ + Baz gets a random boolean + Returns: + A random boolean value """ return bool(random.getrandbits(1)) @abstractmethod - def get_param_processing(self, param: bool) -> bool: - """Get boolean by param processing. - - Not implemented and abstract method - - Args: - param(bool): a boolean value - Returns: - A boolean value - Raises: - NotImplementedError + def foo(self, foo) -> bool: + """ + Foo + Args: + foo(bool): a boolean value + Returns: + A boolean value + Raises: + NotImplementedError """ raise NotImplementedError diff --git a/simple_sample/myClassInterface.py b/simple_sample/myClassInterface.py new file mode 100755 index 0000000..6277668 --- /dev/null +++ b/simple_sample/myClassInterface.py @@ -0,0 +1,35 @@ +"""An example of interface class + +An example of interface class, but interfaces are not necessary in Python. +There are two examples of methods without implementation: bar returns None and qux returns an exception. + +# license MIT +# author Alessandra Bilardi +# see https://github.com/bilardi/python-prototype for details +# cite https://stackoverflow.com/questions/2124190/how-do-i-implement-interfaces-in-python/2124415#2124415 + +Interfaces are not necessary in Python. This is because Python has proper multiple inheritance, +and also ducktyping, which means that the places where you must have interfaces in Java, +you don't have to have them in Python. +""" + +class MyClassInterface(): + """ + An example of interface class. Interfaces are not necessary in Python. + There are two examples of methods without implementation: bar returns None and qux returns an exception. + """ + def bar(self) -> bool: + """ + Bar + Returns: + A boolean value + """ + pass + + def qux(self) -> bool: + """ + Qux + Returns: + A boolean value + """ + raise NotImplementedError diff --git a/simple_sample/my_class.py b/simple_sample/my_class.py deleted file mode 100755 index 3958d0f..0000000 --- a/simple_sample/my_class.py +++ /dev/null @@ -1,100 +0,0 @@ -"""An example of class. - -An example of class that it extends an abstract class and it implements an interface. -There is a boolean pun by not_implemented_and_abstract_method function of abstract class, -get_boolean function of interface class, -and foobar function of this class. - -# license MIT -# author Alessandra Bilardi -# see https://github.com/bilardi/python-prototype for details - -Note that the _quux method is not present in the documentation like the other methods because -it is a method protected. - - >>> from simple_sample.my_class import MyClass - >>> help(MyClass) - -# cite https://stackoverflow.com/questions/11483366/protected-method-in-python/11483397#11483397 - -Python does not support access protection as C++/Java/C# does. Everything is public. -The motto is, "We're all adults here." Document your classes, and insist that your collaborators -read and follow the documentation. -The culture in Python is that names starting with underscores mean, -"don't use these unless you really know you should." -You might choose to begin your "protected" methods with underscores. -But keep in mind, this is just a convention, it doesn't change how the method can be accessed. -""" - -from simple_sample.my_class_abstract import MyClassAbstract -from simple_sample.my_class_interface import MyClassInterface - - -class MyClass(MyClassInterface, MyClassAbstract): - """An example of class that it extends an abstract class and it implements an interface. - - There is a boolean pun by not_implemented_and_abstract_method function of abstract class, - get_boolean function of interface class, - and foobar function of this class. - - Args: - param(bool): a boolean value - """ - - # param(bool): a class boolean variable with default True - _protected_param = True - - def __init__(self, param: bool = True) -> None: - """Initialize of variables. - - Args: - param(bool): a boolean value - """ - self._protected_param = param - - def get_param_processing(self, param: bool) -> bool: - """Override of the abstract method gets reverse value of param. - - Args: - param(bool): a boolean value - Returns: - The reverse value of param - """ - return not param - - def get_boolean(self) -> bool: - """Override of the class MyClassInterface. - - Returns: - The boolean value of _protected_param - """ - return self._protected_param - - def get_reverse_protected_param(self) -> bool: - """Get reverse value of _protected_param. - - Returns: - The reverse value of _protected_param - """ - return self.get_param_processing(self._protected_param) - - def _protected_method(self) -> bool: - """Protected method recalls some methods. - - Returns: - The boolean value - """ - try: - if MyClassInterface.get_boolean(self) is None: - MyClassInterface.method_with_not_implemented_error(self) - except NotImplementedError: - return self.get_random_boolean() - return True - - def get_reverse_boolean(self) -> bool: - """Get reverse value of the protected method _protected_method. - - Returns: - The boolean value - """ - return self.get_param_processing(self._protected_method()) diff --git a/simple_sample/my_class_interface.py b/simple_sample/my_class_interface.py deleted file mode 100755 index 917cdb6..0000000 --- a/simple_sample/my_class_interface.py +++ /dev/null @@ -1,42 +0,0 @@ -"""An example of interface class. - -An example of interface class, but interfaces are not necessary in Python. -There are two examples of methods without implementation: -bar returns None and qux returns an exception. - -# license MIT -# author Alessandra Bilardi -# see https://github.com/bilardi/python-prototype for details -# cite https://stackoverflow.com/questions/2124190/how-do-i-implement-interfaces-in-python/2124415#2124415 - -Interfaces are not necessary in Python. This is because Python has proper multiple inheritance, -and also ducktyping, which means that the places where you must have interfaces in Java, -you don't have to have them in Python. -""" - - -class MyClassInterface: - """An example of interface class. - - Interfaces are not necessary in Python. - There are two examples of methods without implementation: - get_boolean returns None - and method_with_not_implemented_error returns an exception. - """ - - def get_boolean(self) -> bool: - """Get boolean. - - Method without implementation - - Returns: - A boolean value - """ - - def method_with_not_implemented_error(self) -> bool: - """Not implemented method. - - Returns: - A boolean value - """ - raise NotImplementedError diff --git a/tests/testMyClass.py b/tests/testMyClass.py new file mode 100755 index 0000000..1deb46d --- /dev/null +++ b/tests/testMyClass.py @@ -0,0 +1,75 @@ +"""Test class of MyClass + +This is a basic unit test class. There is a test for each public function. +If the functions contained conditions, there would be more tests for each public function. + +# license MIT +# author Alessandra Bilardi +# see https://github.com/bilardi/python-prototype for details +""" + +import unittest +from simple_sample.myClass import MyClass + +class TestMyClass(unittest.TestCase, MyClass): + """ + This is a basic unit test class. There is a test for each public function. + If the functions contained conditions, there would be more tests for each public function. + """ + # mc(MyClass): a class variable with default None + mc = None + def __init__(self, *args, **kwargs): + """ + Initialization of variables + """ + self.mc = MyClass() + unittest.TestCase.__init__(self, *args, **kwargs) + + def test_my_class_can_be_created(self): + """ + Verifies if the class MyClass can be created + """ + self.assertTrue(isinstance(self.mc, MyClass)) + + def test_my_class_gets_bar_value(self): + """ + Verifies if the class MyClass gets the bar value correctly + """ + self.assertTrue(self.mc.bar()) + + mc = MyClass(True) + self.assertTrue(mc.bar()) + + mc = MyClass(False) + self.assertFalse(mc.bar()) + + def test_my_class_gets_baz_value(self): + """ + Verifies if the class MyClass gets the baz value correctly + """ + for _ in range(10): + self.assertTrue(self.mc.baz() in [True, False]) + + def test_my_class_gets_foo_value(self): + """ + Verifies if the class MyClass gets the foo value correctly + """ + self.assertFalse(self.mc.foo(True)) + self.assertTrue(self.mc.foo(False)) + + def test_my_class_gets_qux_value(self): + """ + Verifies if the class MyClass qux method raises an exception + """ + with self.assertRaises(NotImplementedError): + self.mc.qux() + + def test_my_class_gets_fooquux_value(self): + """ + Verifies if the class MyClass gets the fooquux value correctly + """ + for _ in range(10): + self.assertTrue(self.mc.fooquux() in [True, False]) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testMyClassAbstract.py b/tests/testMyClassAbstract.py new file mode 100755 index 0000000..a5660e2 --- /dev/null +++ b/tests/testMyClassAbstract.py @@ -0,0 +1,35 @@ +"""Test class of MyClassAbstract + +This is a basic unit test class. +It is not possible to instantiate a class with an abstract method, +so missing the method for testing the other method. + +# license MIT +# author Alessandra Bilardi +# see https://github.com/bilardi/python-prototype for details +""" + +import unittest +from simple_sample.myClassAbstract import MyClassAbstract + +class TestMyClassAbstract(unittest.TestCase): + """ + This is a basic unit test class. + It is not possible to instantiate a class with an abstract method, + so missing the method for testing the other method. + """ + def __init__(self, *args, **kwargs): + """ + Initialization of variables + """ + unittest.TestCase.__init__(self, *args, **kwargs) + + def test_my_class_abstract_can_be_created(self): + """ + Verifies if the class MyClassAbstract raises an exception + """ + with self.assertRaises(TypeError): + MyClassAbstract() + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testMyClassInterface.py b/tests/testMyClassInterface.py new file mode 100755 index 0000000..fae8c2f --- /dev/null +++ b/tests/testMyClassInterface.py @@ -0,0 +1,48 @@ +"""Test class of MyClassInterface + +This is a basic unit test class. There is a test for each public function. +If the functions contained conditions, there would be more tests for each public function. + +# license MIT +# author Alessandra Bilardi +# see https://github.com/bilardi/python-prototype for details +""" + +import unittest +from simple_sample.myClassInterface import MyClassInterface + +class TestMyClassInterface(unittest.TestCase, MyClassInterface): + """ + This is a basic unit test class. There is a test for each public function. + If the functions contained conditions, there would be more tests for each public function. + """ + # mci(MyClassInterface): a class variable with default None + mci = None + def __init__(self, *args, **kwargs): + """ + Initialization of variables + """ + self.mci = MyClassInterface() + unittest.TestCase.__init__(self, *args, **kwargs) + + def test_my_class_interface_can_be_created(self): + """ + Verifies if the class MyClassInterface can be created + """ + self.assertTrue(isinstance(self.mci, MyClassInterface)) + + def test_my_class_interface_gets_bar_value(self): + """ + Verifies if the class MyClassInterface bar method return None + """ + self.assertIsNone(self.mci.bar()) + + def test_my_class_interface_gets_qux_value(self): + """ + Verifies if the class MyClassInterface qux method raises an exception + """ + with self.assertRaises(NotImplementedError): + self.mci.qux() + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_my_class.py b/tests/test_my_class.py deleted file mode 100755 index 1c4f759..0000000 --- a/tests/test_my_class.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Test class of MyClass. - -There are basic unit tests. There is a test for each public function. -If the functions contained conditions, there would be more tests for each public function. - -# license MIT -# author Alessandra Bilardi -# see https://github.com/bilardi/python-prototype for details - -Only with unittest we need a class: pytest works with methods. - -$ uv run pytest -""" - -import pytest - -from simple_sample.my_class import MyClass - - -@pytest.fixture -def mc(): - """Instantiate the class.""" - return MyClass() - - -def test_my_class_can_be_created(mc): - """Verifies if the class MyClass can be created.""" - assert isinstance(mc, MyClass) - - -def test_my_class_gets_method_get_boolean_value(mc): - """Verifies if the class MyClass gets the get_boolean value correctly.""" - assert mc.get_boolean() - - assert MyClass(True).get_boolean() - assert not MyClass(False).get_boolean() - - -def test_my_class_gets_get_random_boolean_value(mc): - """Verifies if the class MyClass gets the get_random_boolean value correctly.""" - for _ in range(10): - assert mc.get_random_boolean() in [True, False] - - -def test_my_class_gets_get_param_processing_value(mc): - """Verifies if the class MyClass gets the get_param_processing value correctly.""" - assert not mc.get_param_processing(True) - assert mc.get_param_processing(False) - - -def test_my_class_gets_method_with_not_implemented_error_value(mc): - """Verifies if the method_with_not_implemented_error method raises an exception.""" - with pytest.raises(NotImplementedError): - mc.method_with_not_implemented_error() - - -def test_my_class_gets_get_reverse_boolean_value(mc): - """Verifies if the class MyClass gets the get_reverse_boolean value correctly.""" - for _ in range(10): - assert mc.get_reverse_boolean() in [True, False] diff --git a/tests/test_my_class_abstract.py b/tests/test_my_class_abstract.py deleted file mode 100755 index 97226a2..0000000 --- a/tests/test_my_class_abstract.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Test class of MyClassAbstract. - -There are basic unit tests. -It is not possible to instantiate a class with an abstract method, -so missing the method for testing the other method. - -# license MIT -# author Alessandra Bilardi -# see https://github.com/bilardi/python-prototype for details - -Only with unittest we need a class: pytest works with methods. - -$ uv run pytest -""" - -import pytest - -from simple_sample.my_class_abstract import MyClassAbstract - - -def test_my_class_abstract_can_be_created(): - """Verifies if the class MyClassAbstract raises an exception.""" - with pytest.raises(TypeError): - MyClassAbstract() diff --git a/tests/test_my_class_interface.py b/tests/test_my_class_interface.py deleted file mode 100755 index eb78069..0000000 --- a/tests/test_my_class_interface.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Test class of MyClassInterface. - -There are basic unit tests. There is a test for each public function. -If the functions contained conditions, there would be more tests for each public function. - -# license MIT -# author Alessandra Bilardi -# see https://github.com/bilardi/python-prototype for details - -Only with unittest we need a class: pytest works with methods. - -$ uv run pytest -""" - -import pytest - -from simple_sample.my_class_interface import MyClassInterface - - -@pytest.fixture -def mci(): - """Instantiate the class.""" - return MyClassInterface() - - -def test_my_class_interface_can_be_created(mci): - """Verifies if the class MyClassInterface can be created.""" - assert isinstance(mci, MyClassInterface) - - -def test_my_class_interface_gets_get_boolean_value(mci): - """Verifies if the get_boolean method returns None.""" - assert mci.get_boolean() is None - - -def test_my_class_interface_gets_method_with_not_implemented_error_value(mci): - """Verifies if the method_with_not_implemented_error method raises an exception.""" - with pytest.raises(NotImplementedError): - mci.method_with_not_implemented_error() diff --git a/uv.lock b/uv.lock deleted file mode 100644 index 6164630..0000000 --- a/uv.lock +++ /dev/null @@ -1,1241 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.13" - -[[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 = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anyio" -version = "4.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, -] - -[[package]] -name = "babel" -version = "2.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, -] - -[[package]] -name = "bracex" -version = "2.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, -] - -[[package]] -name = "build" -version = "1.4.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/02/ec/bf5ae0a7e5ab57abe8aabdd0759c971883895d1a20c49ae99f8146840c3c/build-1.4.4.tar.gz", hash = "sha256:f832ae053061f3fb524af812dc94b8b84bac6880cd587630e3b5d91a6a9c1703", size = 89220, upload-time = "2026-04-22T20:53:44.807Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/88/6764e7a109dd84294850741501145da90d13cdeac9d4e614929464a37420/build-1.4.4-py3-none-any.whl", hash = "sha256:8c3f48a6090b39edec1a273d2d57949aaf13723b01e02f9d518396887519f64d", size = 25921, upload-time = "2026-04-22T20:53:43.251Z" }, -] - -[[package]] -name = "bump-my-version" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "httpx" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "questionary" }, - { name = "rich" }, - { name = "rich-click" }, - { name = "tomlkit" }, - { name = "wcmatch" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/61/07b90027091a4192b4a0290dc3da1aeea6b9e7b6b4c0f7fd30dab36070c1/bump_my_version-1.3.0.tar.gz", hash = "sha256:5780137a8d93378af3839798fcba01c7e6cb28dcc5aa5a7ab4d8507787f1995c", size = 1142429, upload-time = "2026-03-22T13:27:34.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/01/b168791bfbfb0322ef6d38d236f6f17a02e41fb7753e23e4cdb0f19ac969/bump_my_version-1.3.0-py3-none-any.whl", hash = "sha256:3cdaa54588d2443a29303b77e7539417187952c3d22f87bfdd32c0fe6af2f570", size = 64878, upload-time = "2026-03-22T13:27:33.006Z" }, -] - -[[package]] -name = "certifi" -version = "2026.4.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, -] - -[[package]] -name = "cfgv" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, -] - -[[package]] -name = "click" -version = "8.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, -] - -[[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 = "cryptography" -version = "46.0.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, - { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, - { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, - { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, - { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, - { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, - { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, - { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, - { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, - { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, - { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, - { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, - { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, - { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, - { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, -] - -[[package]] -name = "distlib" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, -] - -[[package]] -name = "docutils" -version = "0.22.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, -] - -[[package]] -name = "filelock" -version = "3.29.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, -] - -[[package]] -name = "git-cliff" -version = "2.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/cf/dff8cd706d2e30e264cb3b9880235607188fb3ad596bfe6282147165bdcd/git_cliff-2.12.0.tar.gz", hash = "sha256:57b96b1f61167f85395353d6f47a89944b4882c03880312d53c09dacecb7ff86", size = 102106, upload-time = "2026-01-20T17:46:12.602Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/a5/dc5f800f6a6dc175faa0787653119754dbbe81a9db1274e041443690287b/git_cliff-2.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e9ee9aa29e9435211712fdab4b5ec9fb432c4bc9d244e39351b2be57aeba7999", size = 6879200, upload-time = "2026-01-20T17:45:55.964Z" }, - { url = "https://files.pythonhosted.org/packages/d7/b6/0e251bd49700e767c47d8d524a690ad713a3aed4318074278438042b8f25/git_cliff-2.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e18512138db5ef57302155b1163c0a2cf43c3d79071a5e083883b65bb990218c", size = 6456349, upload-time = "2026-01-20T17:45:58.202Z" }, - { url = "https://files.pythonhosted.org/packages/5e/63/4e8780f60ad28e8c26ae2b2b365daff9ffa84cb441a5d5bf62c42a75e75a/git_cliff-2.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d24c3e334fdf309c59802ea1a9cd3828e92c8c7cacdd619bcabdc638e00e2ade", size = 6916209, upload-time = "2026-01-20T17:45:59.931Z" }, - { url = "https://files.pythonhosted.org/packages/71/83/0bfab93065e10bcbe97e6136ccf6c1e8552715ef61c11eb678c397ff5fb0/git_cliff-2.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1aa25b05a0315d0f58fc2ac21503538ca749fc3dd7476ee5d6bdf380d9f26ab", size = 7305605, upload-time = "2026-01-20T17:46:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/30/eb/78f624e387c1d9084ca7bcec3a8f28fda9fbbfbeb18c71465a727ee677b5/git_cliff-2.12.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:91eafd2f3ecf226b9a9c2a6c54d96df6042479927b48a97fcf46b728e8744bf1", size = 6927694, upload-time = "2026-01-20T17:46:03.798Z" }, - { url = "https://files.pythonhosted.org/packages/49/3f/735ddcb426c9f77498a039e9398162345c59f29c7990fbf22a530a15fb97/git_cliff-2.12.0-py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:26c9771a50a039252c67803f4c7f187f2ce9c5eea336b8cef890e94483af7a9d", size = 7118983, upload-time = "2026-01-20T17:46:05.535Z" }, - { url = "https://files.pythonhosted.org/packages/f0/97/68a5bd8063904fc43df7811e713483ccd831a877751283c6514dfb5b079e/git_cliff-2.12.0-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:168f48b82f81ab8e1625d42adb739471623e25bd0a7e25b8c70490bad9e90e2b", size = 7541855, upload-time = "2026-01-20T17:46:07.348Z" }, - { url = "https://files.pythonhosted.org/packages/f7/00/2ed0bf7d71340c20906c1317db50cd6c14bdf0c90fa68a62885c9daf40a9/git_cliff-2.12.0-py3-none-win32.whl", hash = "sha256:4bc609a748c1c3493fe3e00a48305d343255ddff80e564fbf8eb954aac387784", size = 6354818, upload-time = "2026-01-20T17:46:09.117Z" }, - { url = "https://files.pythonhosted.org/packages/c0/fd/679d54e4ed37fdbadb58080219af8f35b5f659dd25e47ab1951b6349d1d0/git_cliff-2.12.0-py3-none-win_amd64.whl", hash = "sha256:c992b5756298251ecdd4db8abe087e90d00327f9eaf0c2470a44dbff64377d07", size = 7303564, upload-time = "2026-01-20T17:46:11.154Z" }, -] - -[[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 = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "id" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" }, -] - -[[package]] -name = "identify" -version = "2.6.19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, -] - -[[package]] -name = "idna" -version = "3.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, -] - -[[package]] -name = "imagesize" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "jaraco-classes" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "more-itertools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, -] - -[[package]] -name = "jaraco-context" -version = "6.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, -] - -[[package]] -name = "jaraco-functools" -version = "4.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "more-itertools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, -] - -[[package]] -name = "jeepney" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, -] - -[[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 = "keyring" -version = "25.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jaraco-classes" }, - { name = "jaraco-context" }, - { name = "jaraco-functools" }, - { name = "jeepney", marker = "sys_platform == 'linux'" }, - { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, - { name = "secretstorage", marker = "sys_platform == 'linux'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, -] - -[[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/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 = "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 = "more-itertools" -version = "11.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, -] - -[[package]] -name = "nh3" -version = "0.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/86/f8d3a7c9bd1bbaa181f6312c757e0b74d25f71ecf84ea3c0dc5e0f01840d/nh3-0.3.4.tar.gz", hash = "sha256:96709a379997c1b28c8974146ca660b0dcd3794f4f6d50c1ea549bab39ac6ade", size = 19520, upload-time = "2026-03-25T10:57:30.789Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/5e/c400663d14be2216bc084ed2befc871b7b12563f85d40904f2a4bf0dd2b7/nh3-0.3.4-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8b61058f34c2105d44d2a4d4241bacf603a1ef5c143b08766bbd0cf23830118f", size = 1417991, upload-time = "2026-03-25T10:56:59.13Z" }, - { url = "https://files.pythonhosted.org/packages/36/f5/109526f5002ec41322ac8cafd50f0f154bae0c26b9607c0fcb708bdca8ec/nh3-0.3.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:554cc2bab281758e94d770c3fb0bf2d8be5fb403ef6b2e8841dd7c1615df7a0f", size = 790566, upload-time = "2026-03-25T10:57:00.445Z" }, - { url = "https://files.pythonhosted.org/packages/7b/66/38950f2b4b316ffd82ee51ed8f9143d1f56fdd620312cacc91613b77b3e7/nh3-0.3.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbe76feaa44e2ef9436f345016012a591550e77818876a8de5c8bc2a248e08df", size = 837538, upload-time = "2026-03-25T10:57:01.848Z" }, - { url = "https://files.pythonhosted.org/packages/d8/9f/9d6da970e9524fe360ea02a2082856390c2c8ba540409d1be6e5851887b3/nh3-0.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:87dac8d611b4a478400e0821a13b35770e88c266582f065e7249d6a37b0f86e8", size = 1012154, upload-time = "2026-03-25T10:57:03.592Z" }, - { url = "https://files.pythonhosted.org/packages/54/92/7c85c33c241e9dd51dda115bd3f765e940446588cdaaca62ef8edffe675f/nh3-0.3.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8d697e19f2995b337f648204848ac3a528eaafffc39e7ce4ac6b7a2fbe6c84af", size = 1092516, upload-time = "2026-03-25T10:57:04.726Z" }, - { url = "https://files.pythonhosted.org/packages/16/0f/597842bdb2890999a3faa2f3fcb02db8aa6ad09320d3d843ff6d0a1f737b/nh3-0.3.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:7cae217f031809321db962cd7e092bda8d4e95a87f78c0226628fa6c2ea8ebc5", size = 1053793, upload-time = "2026-03-25T10:57:06.171Z" }, - { url = "https://files.pythonhosted.org/packages/7d/32/669da65147bc10746d2e1d7a8a3dbfbffe0315f419e74b559e2ee3471a01/nh3-0.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:07999b998bf89692738f15c0eac76a416382932f855709e0b7488b595c30ec89", size = 1035975, upload-time = "2026-03-25T10:57:07.292Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/9e97a8b3c5161c79b4bf21cc54e9334860a52cc54ede15bf2239ef494b73/nh3-0.3.4-cp314-cp314t-win32.whl", hash = "sha256:ca90397c8d36c1535bf1988b2bed006597337843a164c7ec269dc8813f37536b", size = 600419, upload-time = "2026-03-25T10:57:08.342Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c7/6849d8d4295d3997d148eacb2d4b1c9faada4895ee3c1b1e12e72f4611e2/nh3-0.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:41e46b3499918ab6128b6421677b316e79869d0c140da24069d220a94f4e72d1", size = 613342, upload-time = "2026-03-25T10:57:09.593Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0e/14a3f510f36c20b922c123a2730f071f938d006fb513aacfd46d6cbc03a7/nh3-0.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:80b955d802bf365bd42e09f6c3d64567dce777d20e97968d94b3e9d9e99b265e", size = 607025, upload-time = "2026-03-25T10:57:10.959Z" }, - { url = "https://files.pythonhosted.org/packages/4a/57/a97955bc95960cfb1f0517043d60a121f4ba93fde252d4d9ffd3c2a9eead/nh3-0.3.4-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d8bebcb20ab4b91858385cd98fe58046ec4a624275b45ef9b976475604f45b49", size = 1439519, upload-time = "2026-03-25T10:57:12.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/c9a33361da8cde7c7760f091cd10467bc470634e4eea31c8bb70935b00a4/nh3-0.3.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d825722a1e8cbc87d7ca1e47ffb1d2a6cf343ad4c1b8465becf7cadcabcdfd0", size = 833798, upload-time = "2026-03-25T10:57:13.264Z" }, - { url = "https://files.pythonhosted.org/packages/6b/19/9487790780b8c94eacca37866c1270b747a4af8e244d43b3b550fddbbf62/nh3-0.3.4-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa8b43e68c26b68069a3b6cef09de166d1d7fa140cf8d77e409a46cbf742e44", size = 820414, upload-time = "2026-03-25T10:57:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b4/c6a340dd321d20b1e4a663307032741da045685c87403926c43656f6f5ec/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f5f214618ad5eff4f2a6b13a8d4da4d9e7f37c569d90a13fb9f0caaf7d04fe21", size = 1061531, upload-time = "2026-03-25T10:57:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/c4/49/f6b4b474e0032e4bcbb7174b44e4cf6915670e09c62421deb06ccfcb88b8/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3390e4333883673a684ce16c1716b481e91782d6f56dec5c85fed9feedb23382", size = 1021889, upload-time = "2026-03-25T10:57:16.454Z" }, - { url = "https://files.pythonhosted.org/packages/43/da/e52a6941746d1f974752af3fc8591f1dbcdcf7fd8c726c7d99f444ba820e/nh3-0.3.4-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a2e44ccb29cbb45071b8f3f2dab9ebfb41a6516f328f91f1f1fd18196239a4", size = 912965, upload-time = "2026-03-25T10:57:17.624Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304", size = 804975, upload-time = "2026-03-25T10:57:18.994Z" }, - { url = "https://files.pythonhosted.org/packages/a9/56/b1275aa2c6510191eed76178da4626b0900402439cb9f27d6b9bf7c6d5e9/nh3-0.3.4-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:9337517edb7c10228252cce2898e20fb3d77e32ffaccbb3c66897927d74215a0", size = 833400, upload-time = "2026-03-25T10:57:20.086Z" }, - { url = "https://files.pythonhosted.org/packages/7c/a5/5d574ffa3c6e49a5364d1b25ebad165501c055340056671493beb467a15e/nh3-0.3.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d866701affe67a5171b916b5c076e767a74c6a9efb7fb2006eb8d3c5f9a293d5", size = 854277, upload-time = "2026-03-25T10:57:21.433Z" }, - { url = "https://files.pythonhosted.org/packages/79/36/8aeb2ab21517cefa212db109e41024e02650716cb42bf293d0a88437a92d/nh3-0.3.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:47d749d99ae005ab19517224140b280dd56e77b33afb82f9b600e106d0458003", size = 1022021, upload-time = "2026-03-25T10:57:22.433Z" }, - { url = "https://files.pythonhosted.org/packages/9c/95/9fd860997685e64abe2d5a995ca2eb5004c0fb6d6585429612a7871548b9/nh3-0.3.4-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f987cb56458323405e8e5ea827e1befcf141ffa0c0ac797d6d02e6b646056d9a", size = 1103526, upload-time = "2026-03-25T10:57:23.487Z" }, - { url = "https://files.pythonhosted.org/packages/7d/0d/df545070614c1007f0109bb004230226c9000e7857c9785583ec25cda9d7/nh3-0.3.4-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:883d5a6d6ee8078c4afc8e96e022fe579c4c265775ff6ee21e39b8c542cabab3", size = 1068050, upload-time = "2026-03-25T10:57:24.624Z" }, - { url = "https://files.pythonhosted.org/packages/94/d5/17b016df52df052f714c53be71df26a1943551d9931e9383b92c998b88f8/nh3-0.3.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:75643c22f5092d8e209f766ee8108c400bc1e44760fc94d2d638eb138d18f853", size = 1046037, upload-time = "2026-03-25T10:57:25.799Z" }, - { url = "https://files.pythonhosted.org/packages/51/39/49f737907e6ab2b4ca71855d3bd63dd7958862e9c8b94fb4e5b18ccf6988/nh3-0.3.4-cp38-abi3-win32.whl", hash = "sha256:72e4e9ca1c4bd41b4a28b0190edc2e21e3f71496acd36a0162858e1a28db3d7e", size = 609542, upload-time = "2026-03-25T10:57:27.112Z" }, - { url = "https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl", hash = "sha256:c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75", size = 624244, upload-time = "2026-03-25T10:57:28.302Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/37695d6b0168f6714b5c492331636a9e6123d6ec22d25876c68d06eab1b8/nh3-0.3.4-cp38-abi3-win_arm64.whl", hash = "sha256:43ad4eedee7e049b9069bc015b7b095d320ed6d167ecec111f877de1540656e9", size = 616649, upload-time = "2026-03-25T10:57:29.623Z" }, -] - -[[package]] -name = "nodeenv" -version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, -] - -[[package]] -name = "packaging" -version = "26.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, -] - -[[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 = "pre-commit" -version = "4.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, -] - -[[package]] -name = "prompt-toolkit" -version = "3.0.52" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - -[[package]] -name = "pydantic" -version = "2.13.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.46.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, - { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, - { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, - { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, - { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, - { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, - { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, - { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, - { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, - { url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" }, - { url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" }, - { url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" }, - { url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" }, - { url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" }, - { url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" }, - { url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" }, - { url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" }, - { url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" }, - { url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" }, - { url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" }, - { url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" }, - { url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" }, - { url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" }, - { url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" }, - { url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" }, - { url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" }, - { url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" }, - { url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" }, - { url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, -] - -[[package]] -name = "pyright" -version = "1.1.409" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/51/4e/3aa27f74211522dba7e9cbc3e74de779c6d4b654c54e50a4840623be8014/pyright-1.1.409.tar.gz", hash = "sha256:986ee05beca9e077c165758ad123667c679e050059a2546aa02473930394bc93", size = 4430434, upload-time = "2026-04-23T11:02:03.799Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/6b/330d8ebae582b30c2959a1ef4c3bc344ebde48c2ff0c3f113c4710735e11/pyright-1.1.409-py3-none-any.whl", hash = "sha256:aa3ea228cab90c845c7a60d28db7a844c04315356392aa09fafcee98c8c22fb3", size = 6438161, upload-time = "2026-04-23T11:02:01.309Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.3" -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/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, -] - -[[package]] -name = "python-discovery" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, -] - -[[package]] -name = "pywin32-ctypes" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - -[[package]] -name = "questionary" -version = "2.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "prompt-toolkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" }, -] - -[[package]] -name = "readme-renderer" -version = "44.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "nh3" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056, upload-time = "2024-07-08T15:00:57.805Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310, upload-time = "2024-07-08T15:00:56.577Z" }, -] - -[[package]] -name = "requests" -version = "2.33.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, -] - -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, -] - -[[package]] -name = "rfc3986" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, -] - -[[package]] -name = "rich" -version = "15.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, -] - -[[package]] -name = "rich-click" -version = "1.9.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/27/091e140ea834272188e63f8dd6faac1f5c687582b687197b3e0ec3c78ebf/rich_click-1.9.7.tar.gz", hash = "sha256:022997c1e30731995bdbc8ec2f82819340d42543237f033a003c7b1f843fc5dc", size = 74838, upload-time = "2026-01-31T04:29:27.707Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/e5/d708d262b600a352abe01c2ae360d8ff75b0af819b78e9af293191d928e6/rich_click-1.9.7-py3-none-any.whl", hash = "sha256:2f99120fca78f536e07b114d3b60333bc4bb2a0969053b1250869bcdc1b5351b", size = 71491, upload-time = "2026-01-31T04:29:26.777Z" }, -] - -[[package]] -name = "roman-numerals" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, -] - -[[package]] -name = "ruff" -version = "0.15.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, - { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, - { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, - { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, - { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, - { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, -] - -[[package]] -name = "secretstorage" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "jeepney" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, -] - -[[package]] -name = "simple-sample" -source = { editable = "." } - -[package.dev-dependencies] -dev = [ - { name = "build" }, - { name = "bump-my-version" }, - { name = "git-cliff" }, - { name = "httpx" }, - { name = "pre-commit" }, - { name = "pyright" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "ruff" }, - { name = "sphinx" }, - { name = "sphinx-rtd-theme" }, - { name = "twine" }, -] - -[package.metadata] - -[package.metadata.requires-dev] -dev = [ - { name = "build", specifier = ">=1.4.4" }, - { name = "bump-my-version", specifier = ">=1.3.0" }, - { name = "git-cliff", specifier = ">=2.12.0" }, - { name = "httpx", specifier = ">=0.28.1" }, - { name = "pre-commit", specifier = ">=4.6.0" }, - { name = "pyright", specifier = ">=1.1.409" }, - { name = "pytest", specifier = ">=9.0.3" }, - { name = "pytest-asyncio", specifier = ">=1.3.0" }, - { name = "ruff", specifier = ">=0.15.11" }, - { name = "sphinx", specifier = ">=9.1.0" }, - { name = "sphinx-rtd-theme", specifier = ">=3.1.0" }, - { name = "twine", specifier = ">=6.2.0" }, -] - -[[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 = "9.1.0" -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" }, - { 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/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, -] - -[[package]] -name = "sphinx-rtd-theme" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "sphinx" }, - { name = "sphinxcontrib-jquery" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/84/68/a1bfbf38c0f7bccc9b10bbf76b94606f64acb1552ae394f0b8285bfaea25/sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c", size = 7620915, upload-time = "2026-01-12T16:03:31.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89", size = 7655617, upload-time = "2026-01-12T16:03:28.101Z" }, -] - -[[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-jquery" -version = "4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, -] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -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 = "tomlkit" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, -] - -[[package]] -name = "twine" -version = "6.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "id" }, - { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, - { name = "packaging" }, - { name = "readme-renderer" }, - { name = "requests" }, - { name = "requests-toolbelt" }, - { name = "rfc3986" }, - { name = "rich" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727, upload-time = "2025-09-04T15:43:15.994Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] - -[[package]] -name = "virtualenv" -version = "21.2.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, - { name = "python-discovery" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742, upload-time = "2026-04-14T22:15:31.438Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232, upload-time = "2026-04-14T22:15:29.342Z" }, -] - -[[package]] -name = "wcmatch" -version = "10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "bracex" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, -] - -[[package]] -name = "wcwidth" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, -]