@@ -56,15 +62,23 @@
From 33da7da2b7216ce51a9866ad63a41a2962dee4cc Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sun, 22 Mar 2026 19:53:11 -0500
Subject: [PATCH 58/89] docs(contributing): Update for project tooling
why: Template referenced flake8 and make test; project uses ruff, mypy, and uv.
what:
- Rewrite PR process for ruff, mypy, pytest, uv workflow
- Reference AGENTS.md and developing docs
---
.github/contributing.md | 24 ++++++++----------------
1 file changed, 8 insertions(+), 16 deletions(-)
diff --git a/.github/contributing.md b/.github/contributing.md
index 5712dcf03a..c0eddac2c2 100644
--- a/.github/contributing.md
+++ b/.github/contributing.md
@@ -3,25 +3,17 @@
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the maintainers of this repository before making a change.
-Please note we have a code of conduct, please follow it in all your interactions with the project.
+See [developing](../docs/developing.md) for environment setup and [AGENTS.md](../AGENTS.md) for
+detailed coding standards.
## Pull Request Process
-1. Ensure any install or build dependencies are removed before the end of the layer when doing a
- build.
-2. This project uses flake8 to conform with common Python standards. Make sure
- to run your code through linter using latest version of flake8, before pull request.
-3. Bad documnentation is a Bug. If your change demands documentation update, please do so. If you
- find an issue with documentation, take the time to improve or fix it.
-4. pytest is used for automated testing. Please make sure to update tests that are needed, and to run
- `make test` before submitting your pull request. This should prevent issues with TravisCI and
- make the review and merging process easier and faster.
-5. Update the README.md with details of changes to the interface, this includes new environment
- variables, exposed ports, useful file locations and container parameters.
-6. Increase the version numbers in any examples files and the README.md to the new version that this
- Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
-7. You may merge the Pull Request in once you have the sign-off of one other developer. If you
- do not have permission to do that, you may request reviewer to merge it for you.
+1. **Format and lint**: `uv run ruff format .` then `uv run ruff check . --fix --show-fixes`
+2. **Type check**: `uv run mypy`
+3. **Test**: `uv run pytest` — all tests must pass before submitting
+4. **Document**: Update docs if your change affects the public interface
+5. You may merge the Pull Request once you have the sign-off of one other developer. If you
+ do not have permission to do that, you may request a reviewer to merge it for you.
## Decorum
From 3869c11fe476a0d4ec4d210854ed12d8ba7fd0d5 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 17:50:05 -0500
Subject: [PATCH 59/89] py(deps[docs]): add sphinx-design, annotate doc
dependencies with site URLs
why: sphinx-design is needed for grid cards in the documentation
landing pages and section indexes. Doc-site URLs added as inline
comments for quick reference when managing dependencies.
what:
- Add sphinx-design to docs and dev dependency groups
- Annotate all doc dependencies with their documentation URLs
---
pyproject.toml | 54 ++++++++++++++++++++++++++------------------------
uv.lock | 37 ++++++++++++++++++++++++++++++++++
2 files changed, 65 insertions(+), 26 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index a5c8826633..02fc0e28f6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -55,19 +55,20 @@ tmuxp = 'tmuxp:cli.cli'
[dependency-groups]
dev = [
# Docs
- "aafigure",
- "pillow",
- "sphinx<9",
- "furo",
- "gp-libs",
- "sphinx-autobuild",
- "sphinx-autodoc-typehints",
- "sphinx-inline-tabs",
- "sphinxext-opengraph",
- "sphinx-copybutton",
- "sphinxext-rediraffe",
- "myst-parser",
- "linkify-it-py",
+ "aafigure", # https://launchpad.net/aafigure
+ "pillow", # https://pillow.readthedocs.io/
+ "sphinx<9", # https://www.sphinx-doc.org/
+ "furo", # https://pradyunsg.me/furo/
+ "gp-libs", # https://gp-libs.git-pull.com/
+ "sphinx-autobuild", # https://sphinx-extensions.readthedocs.io/en/latest/sphinx-autobuild.html
+ "sphinx-autodoc-typehints", # https://sphinx-autodoc-typehints.readthedocs.io/
+ "sphinx-inline-tabs", # https://sphinx-inline-tabs.readthedocs.io/
+ "sphinxext-opengraph", # https://sphinxext-opengraph.readthedocs.io/
+ "sphinx-copybutton", # https://sphinx-copybutton.readthedocs.io/
+ "sphinxext-rediraffe", # https://sphinxext-rediraffe.readthedocs.io/
+ "sphinx-design", # https://sphinx-design.readthedocs.io/
+ "myst-parser", # https://myst-parser.readthedocs.io/
+ "linkify-it-py", # https://github.com/tsutsu3/linkify-it-py
# Testing
"gp-libs",
"pytest",
@@ -87,19 +88,20 @@ dev = [
]
docs = [
- "aafigure",
- "pillow",
- "sphinx<9",
- "furo",
- "gp-libs",
- "sphinx-autobuild",
- "sphinx-autodoc-typehints",
- "sphinx-inline-tabs",
- "sphinxext-opengraph",
- "sphinx-copybutton",
- "sphinxext-rediraffe",
- "myst-parser",
- "linkify-it-py",
+ "aafigure", # https://launchpad.net/aafigure
+ "pillow", # https://pillow.readthedocs.io/
+ "sphinx<9", # https://www.sphinx-doc.org/
+ "furo", # https://pradyunsg.me/furo/
+ "gp-libs", # https://gp-libs.git-pull.com/
+ "sphinx-autobuild", # https://sphinx-extensions.readthedocs.io/en/latest/sphinx-autobuild.html
+ "sphinx-autodoc-typehints", # https://sphinx-autodoc-typehints.readthedocs.io/
+ "sphinx-inline-tabs", # https://sphinx-inline-tabs.readthedocs.io/
+ "sphinxext-opengraph", # https://sphinxext-opengraph.readthedocs.io/
+ "sphinx-copybutton", # https://sphinx-copybutton.readthedocs.io/
+ "sphinxext-rediraffe", # https://sphinxext-rediraffe.readthedocs.io/
+ "sphinx-design", # https://sphinx-design.readthedocs.io/
+ "myst-parser", # https://myst-parser.readthedocs.io/
+ "linkify-it-py", # https://github.com/tsutsu3/linkify-it-py
]
testing = [
"gp-libs",
diff --git a/uv.lock b/uv.lock
index 379131738e..d937343d61 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1287,6 +1287,37 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" },
]
+[[package]]
+name = "sphinx-design"
+version = "0.6.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.11'",
+]
+dependencies = [
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" },
+]
+
+[[package]]
+name = "sphinx-design"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.12'",
+ "python_full_version == '3.11.*'",
+]
+dependencies = [
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" },
+]
+
[[package]]
name = "sphinx-inline-tabs"
version = "2025.12.21.14"
@@ -1432,6 +1463,8 @@ dev = [
{ name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "sphinx-copybutton" },
+ { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "sphinx-inline-tabs" },
{ name = "sphinxext-opengraph" },
{ name = "sphinxext-rediraffe" },
@@ -1454,6 +1487,8 @@ docs = [
{ name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "sphinx-copybutton" },
+ { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "sphinx-inline-tabs" },
{ name = "sphinxext-opengraph" },
{ name = "sphinxext-rediraffe" },
@@ -1505,6 +1540,7 @@ dev = [
{ name = "sphinx-autobuild" },
{ name = "sphinx-autodoc-typehints" },
{ name = "sphinx-copybutton" },
+ { name = "sphinx-design" },
{ name = "sphinx-inline-tabs" },
{ name = "sphinxext-opengraph" },
{ name = "sphinxext-rediraffe" },
@@ -1523,6 +1559,7 @@ docs = [
{ name = "sphinx-autobuild" },
{ name = "sphinx-autodoc-typehints" },
{ name = "sphinx-copybutton" },
+ { name = "sphinx-design" },
{ name = "sphinx-inline-tabs" },
{ name = "sphinxext-opengraph" },
{ name = "sphinxext-rediraffe" },
From 07c0ff95056e70080d1d395832e7ca1d749ee1e2 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 17:50:05 -0500
Subject: [PATCH 60/89] docs(redesign): restructure documentation to CLI
Frontend Skeleton pattern
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
why: The documentation mixed user-facing CLI docs with internal Python
API reference at the same sidebar level, the landing page dumped the
entire README (12 H1 headings), and contributor docs were scattered.
This restructure follows the Python Documentation Skeletons spec where
cli/ is the primary reference surface for a CLI package and the Python
API is explicitly internal.
what:
Structure:
- Move entire docs/api/ to docs/internals/api/ (Python API is internal
for a CLI package)
- Rename inner api/internals/ to api/_internal/ to avoid path stutter
- Create topics/ directory with workflows, troubleshooting,
library-vs-cli, and plugins (moved from plugins/)
- Create project/ directory (contributing, code-style, releasing)
- Move developing.md to project/contributing.md
- Fold about.md (stale 2016 content) into topics/index.md as a brief
tmuxinator/teamocil comparison note
- Delete about.md
New pages:
- cli/exit-codes.md — exit codes for scripting and automation
- cli/recipes.md — copy-pasteable command invocations
- internals/index.md — explicit "not for end users" warning
- internals/architecture.md — CLI dispatch flow diagram
- topics/workflows.md — CI integration, scripting patterns
- topics/troubleshooting.md — common shell/PATH/tmux issues
- topics/library-vs-cli.md — when to use tmuxp CLI vs libtmux,
concept mapping table, what the CLI can't express
- project/code-style.md — ruff, mypy, NumPy docstrings
- project/releasing.md — git tags, OIDC trusted publishing
Landing page:
- Compose standalone homepage (no README.md includes)
- One-sentence intro, 3+2 grid cards, 3-command install,
inline YAML example + tmuxp load command, demo GIF
Section indexes:
- cli/index.md: heading "CLI Reference", 2x3 card grid for key
commands + exit-codes and recipes
- topics/index.md: 2x2 card grid with comparison note
- project/index.md: 2x2 card grid for contributor pages (3 items)
- configuration/index.md: 1x3 card grid for reference subpages
Navigation:
- Sidebar primary: Quickstart, CLI Reference, Workspace files, Topics,
Internals, Project, Changelog
- Sidebar "More" caption: The Tao of tmux, Migration, Glossary
- 35 redirects for all moved files (every individual api/ file covered)
- README.md URLs updated to new doc structure paths, http → https
Dependencies:
- Add sphinx-design to docs and dev dependency groups
- Annotate all doc dependencies with documentation site URLs
conf.py:
- Add sphinx_design extension
- Add myst_heading_anchors = 4
---
README.md | 16 +--
docs/about.md | 104 ------------------
docs/cli/exit-codes.md | 30 +++++
docs/cli/index.md | 51 ++++++++-
docs/cli/recipes.md | 77 +++++++++++++
docs/conf.py | 3 +
docs/configuration/index.md | 25 ++++-
docs/index.md | 100 ++++++++++++++---
.../api/_internal}/colors.md | 0
.../api/_internal}/config_reader.md | 0
.../api/_internal}/index.md | 4 +-
.../api/_internal}/private_path.md | 0
.../api/_internal}/types.md | 0
docs/{ => internals}/api/cli/convert.md | 0
docs/{ => internals}/api/cli/debug_info.md | 0
docs/{ => internals}/api/cli/edit.md | 0
docs/{ => internals}/api/cli/freeze.md | 0
docs/{ => internals}/api/cli/import_config.md | 0
docs/{ => internals}/api/cli/index.md | 0
docs/{ => internals}/api/cli/load.md | 0
docs/{ => internals}/api/cli/ls.md | 0
docs/{ => internals}/api/cli/progress.md | 0
docs/{ => internals}/api/cli/search.md | 0
docs/{ => internals}/api/cli/shell.md | 0
docs/{ => internals}/api/cli/utils.md | 0
docs/{ => internals}/api/exc.md | 0
docs/{ => internals}/api/index.md | 2 +-
docs/{ => internals}/api/log.md | 0
docs/{ => internals}/api/plugin.md | 0
docs/{ => internals}/api/shell.md | 0
docs/{ => internals}/api/types.md | 0
docs/{ => internals}/api/util.md | 0
docs/{ => internals}/api/workspace/builder.md | 0
.../api/workspace/constants.md | 0
docs/{ => internals}/api/workspace/finders.md | 0
docs/{ => internals}/api/workspace/freezer.md | 0
.../api/workspace/importers.md | 0
docs/{ => internals}/api/workspace/index.md | 0
docs/{ => internals}/api/workspace/loader.md | 0
.../api/workspace/validation.md | 0
docs/internals/architecture.md | 39 +++++++
docs/internals/index.md | 19 ++++
docs/project/code-style.md | 32 ++++++
.../contributing.md} | 0
docs/project/index.md | 36 ++++++
docs/project/releasing.md | 50 +++++++++
docs/redirects.txt | 35 ++++++
docs/topics/index.md | 52 +++++++++
docs/topics/library-vs-cli.md | 62 +++++++++++
docs/{plugins/index.md => topics/plugins.md} | 0
docs/topics/troubleshooting.md | 40 +++++++
docs/topics/workflows.md | 32 ++++++
52 files changed, 674 insertions(+), 135 deletions(-)
delete mode 100644 docs/about.md
create mode 100644 docs/cli/exit-codes.md
create mode 100644 docs/cli/recipes.md
rename docs/{api/internals => internals/api/_internal}/colors.md (100%)
rename docs/{api/internals => internals/api/_internal}/config_reader.md (100%)
rename docs/{api/internals => internals/api/_internal}/index.md (90%)
rename docs/{api/internals => internals/api/_internal}/private_path.md (100%)
rename docs/{api/internals => internals/api/_internal}/types.md (100%)
rename docs/{ => internals}/api/cli/convert.md (100%)
rename docs/{ => internals}/api/cli/debug_info.md (100%)
rename docs/{ => internals}/api/cli/edit.md (100%)
rename docs/{ => internals}/api/cli/freeze.md (100%)
rename docs/{ => internals}/api/cli/import_config.md (100%)
rename docs/{ => internals}/api/cli/index.md (100%)
rename docs/{ => internals}/api/cli/load.md (100%)
rename docs/{ => internals}/api/cli/ls.md (100%)
rename docs/{ => internals}/api/cli/progress.md (100%)
rename docs/{ => internals}/api/cli/search.md (100%)
rename docs/{ => internals}/api/cli/shell.md (100%)
rename docs/{ => internals}/api/cli/utils.md (100%)
rename docs/{ => internals}/api/exc.md (100%)
rename docs/{ => internals}/api/index.md (94%)
rename docs/{ => internals}/api/log.md (100%)
rename docs/{ => internals}/api/plugin.md (100%)
rename docs/{ => internals}/api/shell.md (100%)
rename docs/{ => internals}/api/types.md (100%)
rename docs/{ => internals}/api/util.md (100%)
rename docs/{ => internals}/api/workspace/builder.md (100%)
rename docs/{ => internals}/api/workspace/constants.md (100%)
rename docs/{ => internals}/api/workspace/finders.md (100%)
rename docs/{ => internals}/api/workspace/freezer.md (100%)
rename docs/{ => internals}/api/workspace/importers.md (100%)
rename docs/{ => internals}/api/workspace/index.md (100%)
rename docs/{ => internals}/api/workspace/loader.md (100%)
rename docs/{ => internals}/api/workspace/validation.md (100%)
create mode 100644 docs/internals/architecture.md
create mode 100644 docs/internals/index.md
create mode 100644 docs/project/code-style.md
rename docs/{developing.md => project/contributing.md} (100%)
create mode 100644 docs/project/index.md
create mode 100644 docs/project/releasing.md
create mode 100644 docs/topics/index.md
create mode 100644 docs/topics/library-vs-cli.md
rename docs/{plugins/index.md => topics/plugins.md} (100%)
create mode 100644 docs/topics/troubleshooting.md
create mode 100644 docs/topics/workflows.md
diff --git a/README.md b/README.md
index c1a97d75a0..b53d3e100a 100644
--- a/README.md
+++ b/README.md
@@ -132,9 +132,9 @@ Name a session:
tmuxp load -s session_name ./mysession.yaml
```
-[simple](http://tmuxp.git-pull.com/examples.html#short-hand-inline) and
+[simple](https://tmuxp.git-pull.com/configuration/examples.html#short-hand-inline-style) and
[very
-elaborate](http://tmuxp.git-pull.com/examples.html#super-advanced-dev-environment)
+elaborate](https://tmuxp.git-pull.com/configuration/examples.html#super-advanced-dev-environment)
config examples
# User-level configurations
@@ -204,7 +204,7 @@ the CLI docs.
Run custom startup scripts (such as installing project dependencies)
before loading tmux. See the
-[before_script](http://tmuxp.git-pull.com/examples.html#bootstrap-project-before-launch)
+[before_script](https://tmuxp.git-pull.com/configuration/examples.html#bootstrap-project-before-launch)
example
# Load in detached state
@@ -247,7 +247,7 @@ $ tmuxp convert --yes filename
# Plugin System
tmuxp has a plugin system to allow for custom behavior. See more about
-the [Plugin System](http://tmuxp.git-pull.com/plugin_system.html).
+the [Plugin System](https://tmuxp.git-pull.com/topics/plugins.html).
# Debugging Helpers
@@ -272,13 +272,13 @@ environment:
# Docs / Reading material
-See the [Quickstart](http://tmuxp.git-pull.com/quickstart.html).
+See the [Quickstart](https://tmuxp.git-pull.com/quickstart.html).
-[Documentation](http://tmuxp.git-pull.com) homepage (also in
+[Documentation](https://tmuxp.git-pull.com) homepage (also in
[中文](http://tmuxp-zh.rtfd.org/))
Want to learn more about tmux itself? [Read The Tao of Tmux
-online](http://tmuxp.git-pull.com/about_tmux.html).
+online](https://tmuxp.git-pull.com/about_tmux.html).
# Donations
@@ -295,7 +295,7 @@ See donation options at .
- python support: >= 3.10, pypy, pypy3
- Source:
- Docs:
-- API:
+- API:
- Changelog:
- Issues:
- Test Coverage:
diff --git a/docs/about.md b/docs/about.md
deleted file mode 100644
index 0d380728db..0000000000
--- a/docs/about.md
+++ /dev/null
@@ -1,104 +0,0 @@
-```{module} tmuxp
-
-```
-
-(about)=
-
-# About
-
-tmuxp helps you manage tmux workspaces.
-
-Built on an object relational mapper for tmux. tmux users can reload common
-workspaces from YAML, JSON and {py:obj}`dict` workspace files like
-[tmuxinator] and [teamocil].
-
-tmuxp is used by developers for tmux automation at great companies like
-[Bugsnag], [Pragmatic Coders] and many others.
-
-To jump right in, see {ref}`quickstart` and {ref}`examples`.
-
-Interested in some kung-fu or joining the effort? {ref}`api` and
-{ref}`developing`.
-
-[MIT-licensed]. Code on [github](http://github.com/tmux-python/tmuxp).
-
-[bugsnag]: https://blog.bugsnag.com/benefits-of-using-tmux/
-[pragmatic coders]: http://pragmaticcoders.com/blog/tmuxp-preconfigured-sessions/
-
-## Compared to tmuxinator / teamocil
-
-### Similarities
-
-**Load sessions** Loads tmux sessions from config
-
-**YAML** Supports YAML format
-
-**Inlining / shorthand configuration** All three support short-hand and
-simplified markup for panes that have one command.
-
-**Maturity and stability** As of 2016, all three are considered stable,
-well tested and adopted.
-
-### Missing
-
-**Version support** tmuxp only supports `tmux >= 3.2`. Teamocil and
-tmuxinator may have support for earlier versions.
-
-### Differences
-
-**Programming Language** python. teamocil and tmuxinator use ruby.
-
-**Workspace building process** teamocil and tmuxinator process configs
-directly shell commands. tmuxp processes configuration via ORM layer.
-
-## Additional Features
-
-**CLI** tmuxp's CLI can attach and kill sessions with tab-completion
-support. See {ref}`commands`.
-
-**Import config** import configs from Teamocil / Tmuxinator [^id4]. See
-{ref}`cli-import`.
-
-**Session freezing** Supports session freezing into YAML and JSON
-format [^id4]. See {ref}`cli-freeze`.
-
-**JSON config** JSON config support. See {ref}`Examples`.
-
-**ORM-based API** via [libtmux] - Utilizes tmux's unique IDs for
-panes, windows and sessions to create an object relational view of the tmux
-{class}`~libtmux.Server`, its {class}`~libtmux.Session`,
-{class}`~libtmux.Window`, and {class}`~libtmux.Pane`.
-See {ref}`libtmux's internals `.
-
-**Conversion** `$ tmuxp convert ` can convert files to and
-from JSON and YAML.
-
-[^id4]: On freezing
-
- While freezing and importing sessions is a great way to save time,
- tweaking will probably be required - There is no substitute to a
- config made with love.
-
-[libtmux]: https://libtmux.git-pull.com
-
-## Minor tweaks
-
-- Unit tests against live tmux version to test statefulness of tmux
- sessions, windows and panes. See {ref}`gh-actions`.
-- Load + switch to new session from inside tmux.
-- Resume session if config loaded.
-- Pre-commands virtualenv / rvm / any other commands.
-- Load config from anywhere `$ tmuxp load /full/file/path.json`.
-- Load config `.tmuxp.yaml` or `.tmuxp.json` from current working
- directory with `$ tmuxp load .`.
-- `$ tmuxp -2`, `$ tmuxp -8` for forcing term colors a la
- {term}`tmux(1)`.
-- `$ tmuxp -L`, `$ tmuxp -S` for sockets and
- `$ tmuxp -f ` for config file.
-
-[attempt at 1.7 test]: https://travis-ci.org/tmux-python/tmuxp/jobs/12348263
-[mit-licensed]: http://opensource.org/licenses/MIT
-[tmuxinator]: https://github.com/aziz/tmuxinator
-[teamocil]: https://github.com/remiprev/teamocil
-[erb]: http://ruby-doc.org/stdlib-2.0.0/libdoc/erb/rdoc/ERB.html
-[edit this page]: https://github.com/tmux-python/tmuxp/edit/master/doc/about.rst
diff --git a/docs/cli/exit-codes.md b/docs/cli/exit-codes.md
new file mode 100644
index 0000000000..e8a80e892d
--- /dev/null
+++ b/docs/cli/exit-codes.md
@@ -0,0 +1,30 @@
+(cli-exit-codes)=
+
+# Exit Codes
+
+tmuxp uses standard exit codes for scripting and automation.
+
+| Code | Meaning |
+|------|---------|
+| `0` | Success |
+| `1` | General error (config validation, tmux command failure) |
+| `2` | Usage error (invalid arguments, missing required options) |
+
+## Usage in Scripts
+
+```bash
+#!/bin/bash
+tmuxp load my-workspace.yaml
+if [ $? -ne 0 ]; then
+ echo "Failed to load workspace"
+ exit 1
+fi
+```
+
+```bash
+#!/bin/bash
+tmuxp load -d my-workspace.yaml || {
+ echo "tmuxp failed with exit code $?"
+ exit 1
+}
+```
diff --git a/docs/cli/index.md b/docs/cli/index.md
index cb8bf94b3b..c1c6b605fc 100644
--- a/docs/cli/index.md
+++ b/docs/cli/index.md
@@ -2,7 +2,48 @@
(commands)=
-# Commands
+# CLI Reference
+
+::::{grid} 2
+:gutter: 3
+
+:::{grid-item-card} tmuxp load
+:link: load
+:link-type: doc
+Load tmux sessions from workspace configs.
+:::
+
+:::{grid-item-card} tmuxp shell
+:link: shell
+:link-type: doc
+Interactive Python shell with tmux context.
+:::
+
+:::{grid-item-card} tmuxp freeze
+:link: freeze
+:link-type: doc
+Export running sessions to config files.
+:::
+
+:::{grid-item-card} tmuxp convert
+:link: convert
+:link-type: doc
+Convert between YAML and JSON formats.
+:::
+
+:::{grid-item-card} Exit Codes
+:link: exit-codes
+:link-type: doc
+Exit codes for scripting and automation.
+:::
+
+:::{grid-item-card} Recipes
+:link: recipes
+:link-type: doc
+Copy-pasteable command invocations.
+:::
+
+::::
```{toctree}
:caption: General commands
@@ -38,6 +79,14 @@ debug-info
completion
```
+```{toctree}
+:caption: Reference
+:maxdepth: 1
+
+exit-codes
+recipes
+```
+
(cli-main)=
(tmuxp-main)=
diff --git a/docs/cli/recipes.md b/docs/cli/recipes.md
new file mode 100644
index 0000000000..fdbdd7dad9
--- /dev/null
+++ b/docs/cli/recipes.md
@@ -0,0 +1,77 @@
+(cli-recipes)=
+
+# Recipes
+
+Copy-pasteable command invocations for common tasks.
+
+## Load a workspace
+
+```console
+$ tmuxp load my-workspace.yaml
+```
+
+## Load in detached mode
+
+```console
+$ tmuxp load -d my-workspace.yaml
+```
+
+## Load from a project directory
+
+```console
+$ tmuxp load .
+```
+
+## Freeze a running session
+
+```console
+$ tmuxp freeze my-session
+```
+
+## Convert YAML to JSON
+
+```console
+$ tmuxp convert my-workspace.yaml
+```
+
+## Convert JSON to YAML
+
+```console
+$ tmuxp convert my-workspace.json
+```
+
+## List available workspaces
+
+```console
+$ tmuxp ls
+```
+
+## Search workspaces
+
+```console
+$ tmuxp search my-project
+```
+
+## Edit a workspace config
+
+```console
+$ tmuxp edit my-workspace
+```
+
+## Collect debug info
+
+```console
+$ tmuxp debug-info
+```
+
+## Shell with tmux context
+
+```console
+$ tmuxp shell
+```
+
+Access libtmux objects directly:
+
+```console
+$ tmuxp shell --best --command 'print(server.sessions)'
+```
diff --git a/docs/conf.py b/docs/conf.py
index 478e5c1f01..3d77fc8993 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -44,6 +44,7 @@
"sphinxext.rediraffe",
"myst_parser",
"linkify_issues",
+ "sphinx_design",
]
myst_enable_extensions = [
@@ -54,6 +55,8 @@
"linkify",
]
+myst_heading_anchors = 4
+
templates_path = ["_templates"]
source_suffix = {".rst": "restructuredtext", ".md": "markdown"}
diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index aa59b017ee..89b37b2a34 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -167,10 +167,33 @@ $ tmuxp load /opt/myapp
## Reference and usage
+::::{grid} 3
+:gutter: 3
+
+:::{grid-item-card} Top-level Options
+:link: top-level
+:link-type: doc
+Session and window configuration keys.
+:::
+
+:::{grid-item-card} Environment Variables
+:link: environmental-variables
+:link-type: doc
+TMUXP_CONFIGDIR and other env vars.
+:::
+
+:::{grid-item-card} Examples
+:link: examples
+:link-type: doc
+Sample workspace configurations.
+:::
+
+::::
+
```{toctree}
+:hidden:
top-level
environmental-variables
examples
-
```
diff --git a/docs/index.md b/docs/index.md
index fd8a21575f..9d8d12db2b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,7 +1,81 @@
(index)=
-```{include} ../README.md
-:end-before:
```
diff --git a/docs/api/internals/colors.md b/docs/internals/api/_internal/colors.md
similarity index 100%
rename from docs/api/internals/colors.md
rename to docs/internals/api/_internal/colors.md
diff --git a/docs/api/internals/config_reader.md b/docs/internals/api/_internal/config_reader.md
similarity index 100%
rename from docs/api/internals/config_reader.md
rename to docs/internals/api/_internal/config_reader.md
diff --git a/docs/api/internals/index.md b/docs/internals/api/_internal/index.md
similarity index 90%
rename from docs/api/internals/index.md
rename to docs/internals/api/_internal/index.md
index b96fc8657b..391a80b60f 100644
--- a/docs/api/internals/index.md
+++ b/docs/internals/api/_internal/index.md
@@ -1,6 +1,6 @@
-(internals)=
+(api-internal)=
-# Internals
+# Internal Modules
:::{warning}
Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions!
diff --git a/docs/api/internals/private_path.md b/docs/internals/api/_internal/private_path.md
similarity index 100%
rename from docs/api/internals/private_path.md
rename to docs/internals/api/_internal/private_path.md
diff --git a/docs/api/internals/types.md b/docs/internals/api/_internal/types.md
similarity index 100%
rename from docs/api/internals/types.md
rename to docs/internals/api/_internal/types.md
diff --git a/docs/api/cli/convert.md b/docs/internals/api/cli/convert.md
similarity index 100%
rename from docs/api/cli/convert.md
rename to docs/internals/api/cli/convert.md
diff --git a/docs/api/cli/debug_info.md b/docs/internals/api/cli/debug_info.md
similarity index 100%
rename from docs/api/cli/debug_info.md
rename to docs/internals/api/cli/debug_info.md
diff --git a/docs/api/cli/edit.md b/docs/internals/api/cli/edit.md
similarity index 100%
rename from docs/api/cli/edit.md
rename to docs/internals/api/cli/edit.md
diff --git a/docs/api/cli/freeze.md b/docs/internals/api/cli/freeze.md
similarity index 100%
rename from docs/api/cli/freeze.md
rename to docs/internals/api/cli/freeze.md
diff --git a/docs/api/cli/import_config.md b/docs/internals/api/cli/import_config.md
similarity index 100%
rename from docs/api/cli/import_config.md
rename to docs/internals/api/cli/import_config.md
diff --git a/docs/api/cli/index.md b/docs/internals/api/cli/index.md
similarity index 100%
rename from docs/api/cli/index.md
rename to docs/internals/api/cli/index.md
diff --git a/docs/api/cli/load.md b/docs/internals/api/cli/load.md
similarity index 100%
rename from docs/api/cli/load.md
rename to docs/internals/api/cli/load.md
diff --git a/docs/api/cli/ls.md b/docs/internals/api/cli/ls.md
similarity index 100%
rename from docs/api/cli/ls.md
rename to docs/internals/api/cli/ls.md
diff --git a/docs/api/cli/progress.md b/docs/internals/api/cli/progress.md
similarity index 100%
rename from docs/api/cli/progress.md
rename to docs/internals/api/cli/progress.md
diff --git a/docs/api/cli/search.md b/docs/internals/api/cli/search.md
similarity index 100%
rename from docs/api/cli/search.md
rename to docs/internals/api/cli/search.md
diff --git a/docs/api/cli/shell.md b/docs/internals/api/cli/shell.md
similarity index 100%
rename from docs/api/cli/shell.md
rename to docs/internals/api/cli/shell.md
diff --git a/docs/api/cli/utils.md b/docs/internals/api/cli/utils.md
similarity index 100%
rename from docs/api/cli/utils.md
rename to docs/internals/api/cli/utils.md
diff --git a/docs/api/exc.md b/docs/internals/api/exc.md
similarity index 100%
rename from docs/api/exc.md
rename to docs/internals/api/exc.md
diff --git a/docs/api/index.md b/docs/internals/api/index.md
similarity index 94%
rename from docs/api/index.md
rename to docs/internals/api/index.md
index 89ec3d508b..2debbe3f26 100644
--- a/docs/api/index.md
+++ b/docs/internals/api/index.md
@@ -8,7 +8,7 @@ tmux via python API calls.
:::
```{toctree}
-internals/index
+_internal/index
cli/index
workspace/index
exc
diff --git a/docs/api/log.md b/docs/internals/api/log.md
similarity index 100%
rename from docs/api/log.md
rename to docs/internals/api/log.md
diff --git a/docs/api/plugin.md b/docs/internals/api/plugin.md
similarity index 100%
rename from docs/api/plugin.md
rename to docs/internals/api/plugin.md
diff --git a/docs/api/shell.md b/docs/internals/api/shell.md
similarity index 100%
rename from docs/api/shell.md
rename to docs/internals/api/shell.md
diff --git a/docs/api/types.md b/docs/internals/api/types.md
similarity index 100%
rename from docs/api/types.md
rename to docs/internals/api/types.md
diff --git a/docs/api/util.md b/docs/internals/api/util.md
similarity index 100%
rename from docs/api/util.md
rename to docs/internals/api/util.md
diff --git a/docs/api/workspace/builder.md b/docs/internals/api/workspace/builder.md
similarity index 100%
rename from docs/api/workspace/builder.md
rename to docs/internals/api/workspace/builder.md
diff --git a/docs/api/workspace/constants.md b/docs/internals/api/workspace/constants.md
similarity index 100%
rename from docs/api/workspace/constants.md
rename to docs/internals/api/workspace/constants.md
diff --git a/docs/api/workspace/finders.md b/docs/internals/api/workspace/finders.md
similarity index 100%
rename from docs/api/workspace/finders.md
rename to docs/internals/api/workspace/finders.md
diff --git a/docs/api/workspace/freezer.md b/docs/internals/api/workspace/freezer.md
similarity index 100%
rename from docs/api/workspace/freezer.md
rename to docs/internals/api/workspace/freezer.md
diff --git a/docs/api/workspace/importers.md b/docs/internals/api/workspace/importers.md
similarity index 100%
rename from docs/api/workspace/importers.md
rename to docs/internals/api/workspace/importers.md
diff --git a/docs/api/workspace/index.md b/docs/internals/api/workspace/index.md
similarity index 100%
rename from docs/api/workspace/index.md
rename to docs/internals/api/workspace/index.md
diff --git a/docs/api/workspace/loader.md b/docs/internals/api/workspace/loader.md
similarity index 100%
rename from docs/api/workspace/loader.md
rename to docs/internals/api/workspace/loader.md
diff --git a/docs/api/workspace/validation.md b/docs/internals/api/workspace/validation.md
similarity index 100%
rename from docs/api/workspace/validation.md
rename to docs/internals/api/workspace/validation.md
diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md
new file mode 100644
index 0000000000..7a104dbfea
--- /dev/null
+++ b/docs/internals/architecture.md
@@ -0,0 +1,39 @@
+# Architecture
+
+How the tmuxp CLI dispatches commands to the underlying library.
+
+## Request Flow
+
+```
+tmuxp CLI (argparse)
+ │
+ ├── tmuxp load ──→ workspace.loader ──→ workspace.builder ──→ libtmux
+ ├── tmuxp freeze ──→ workspace.freezer ──→ libtmux
+ ├── tmuxp convert ──→ _internal.config_reader
+ ├── tmuxp shell ──→ libtmux (interactive)
+ └── tmuxp ls/search ──→ workspace.finders
+```
+
+## Key Components
+
+### CLI Layer (`tmuxp.cli`)
+
+The CLI uses Python's `argparse` with a custom formatter ({mod}`tmuxp.cli._formatter`).
+Each subcommand lives in its own module under `tmuxp.cli`.
+
+The entry point is `tmuxp.cli.cli()`, registered as a console script in `pyproject.toml`.
+
+### Workspace Layer (`tmuxp.workspace`)
+
+The workspace layer handles configuration lifecycle:
+
+1. **Finding**: {mod}`tmuxp.workspace.finders` locates config files
+2. **Loading**: {mod}`tmuxp.workspace.loader` reads and validates configs
+3. **Building**: {mod}`tmuxp.workspace.builder` creates tmux sessions via libtmux
+4. **Freezing**: {mod}`tmuxp.workspace.freezer` exports running sessions
+
+### Library Layer (libtmux)
+
+tmuxp delegates all tmux operations to [libtmux](https://libtmux.git-pull.com/).
+The `WorkspaceBuilder` creates libtmux `Server`, `Session`, `Window`, and `Pane`
+objects to construct the requested workspace.
diff --git a/docs/internals/index.md b/docs/internals/index.md
new file mode 100644
index 0000000000..9db3cf96a3
--- /dev/null
+++ b/docs/internals/index.md
@@ -0,0 +1,19 @@
+(internals)=
+
+# Internals
+
+```{warning}
+Everything in this section is **internal implementation detail**. There is
+no stability guarantee. Interfaces may change or be removed without notice
+between any release.
+
+If you are building an application with tmuxp, use the [CLI](../cli/index.md)
+or refer to the [libtmux API](https://libtmux.git-pull.com/api/).
+```
+
+```{toctree}
+:maxdepth: 2
+
+architecture
+api/index
+```
diff --git a/docs/project/code-style.md b/docs/project/code-style.md
new file mode 100644
index 0000000000..1ea71dc8c3
--- /dev/null
+++ b/docs/project/code-style.md
@@ -0,0 +1,32 @@
+# Code Style
+
+## Formatting
+
+tmuxp uses [ruff](https://github.com/astral-sh/ruff) for both linting and formatting.
+
+```console
+$ uv run ruff format .
+```
+
+```console
+$ uv run ruff check . --fix --show-fixes
+```
+
+## Type Checking
+
+Strict [mypy](https://mypy-lang.org/) is enforced.
+
+```console
+$ uv run mypy
+```
+
+## Docstrings
+
+All public functions and methods use NumPy-style docstrings.
+
+## Imports
+
+- Standard library: namespace imports (`import pathlib`, not `from pathlib import Path`)
+ - Exception: `from dataclasses import dataclass, field`
+- Typing: `import typing as t`, access via `t.Optional`, `t.NamedTuple`, etc.
+- All files: `from __future__ import annotations`
diff --git a/docs/developing.md b/docs/project/contributing.md
similarity index 100%
rename from docs/developing.md
rename to docs/project/contributing.md
diff --git a/docs/project/index.md b/docs/project/index.md
new file mode 100644
index 0000000000..8c37a15d84
--- /dev/null
+++ b/docs/project/index.md
@@ -0,0 +1,36 @@
+(project)=
+
+# Project
+
+Information for contributors and maintainers.
+
+::::{grid} 2
+:gutter: 3
+
+:::{grid-item-card} Contributing
+:link: contributing
+:link-type: doc
+Development setup, running tests, submitting PRs.
+:::
+
+:::{grid-item-card} Code Style
+:link: code-style
+:link-type: doc
+Ruff, mypy, NumPy docstrings, import conventions.
+:::
+
+:::{grid-item-card} Releasing
+:link: releasing
+:link-type: doc
+Release checklist and version policy.
+:::
+
+::::
+
+```{toctree}
+:hidden:
+
+contributing
+code-style
+releasing
+```
diff --git a/docs/project/releasing.md b/docs/project/releasing.md
new file mode 100644
index 0000000000..37b31aa3a7
--- /dev/null
+++ b/docs/project/releasing.md
@@ -0,0 +1,50 @@
+# Releasing
+
+## Release Process
+
+Releases are triggered by git tags and published to PyPI via OIDC trusted publishing.
+
+1. Update `CHANGES` with the release notes
+
+2. Bump version in `src/tmuxp/__about__.py`
+
+3. Commit:
+
+ ```console
+ $ git commit -m "tmuxp "
+ ```
+
+4. Tag:
+
+ ```console
+ $ git tag v
+ ```
+
+5. Push:
+
+ ```console
+ $ git push && git push --tags
+ ```
+
+6. CI builds and publishes to PyPI automatically via trusted publishing
+
+## Changelog Format
+
+The `CHANGES` file uses this format:
+
+```text
+tmuxp ()
+------------------------
+
+### What's new
+
+- Description of feature (#issue)
+
+### Bug fixes
+
+- Description of fix (#issue)
+
+### Breaking changes
+
+- Description of break, migration path (#issue)
+```
diff --git a/docs/redirects.txt b/docs/redirects.txt
index 66d2cb860b..b22b99cfd9 100644
--- a/docs/redirects.txt
+++ b/docs/redirects.txt
@@ -3,3 +3,38 @@
"examples.md" "configuration/examples.md"
"plugin_system.md" "plugins/index.md"
"commands/index.md" "cli/index.md"
+"api/index.md" "internals/api/index.md"
+"api/exc.md" "internals/api/exc.md"
+"api/log.md" "internals/api/log.md"
+"api/plugin.md" "internals/api/plugin.md"
+"api/shell.md" "internals/api/shell.md"
+"api/types.md" "internals/api/types.md"
+"api/util.md" "internals/api/util.md"
+"api/cli/index.md" "internals/api/cli/index.md"
+"api/cli/convert.md" "internals/api/cli/convert.md"
+"api/cli/debug_info.md" "internals/api/cli/debug_info.md"
+"api/cli/edit.md" "internals/api/cli/edit.md"
+"api/cli/freeze.md" "internals/api/cli/freeze.md"
+"api/cli/import_config.md" "internals/api/cli/import_config.md"
+"api/cli/load.md" "internals/api/cli/load.md"
+"api/cli/ls.md" "internals/api/cli/ls.md"
+"api/cli/progress.md" "internals/api/cli/progress.md"
+"api/cli/search.md" "internals/api/cli/search.md"
+"api/cli/shell.md" "internals/api/cli/shell.md"
+"api/cli/utils.md" "internals/api/cli/utils.md"
+"api/workspace/index.md" "internals/api/workspace/index.md"
+"api/workspace/builder.md" "internals/api/workspace/builder.md"
+"api/workspace/constants.md" "internals/api/workspace/constants.md"
+"api/workspace/finders.md" "internals/api/workspace/finders.md"
+"api/workspace/freezer.md" "internals/api/workspace/freezer.md"
+"api/workspace/importers.md" "internals/api/workspace/importers.md"
+"api/workspace/loader.md" "internals/api/workspace/loader.md"
+"api/workspace/validation.md" "internals/api/workspace/validation.md"
+"api/internals/index.md" "internals/api/_internal/index.md"
+"api/internals/colors.md" "internals/api/_internal/colors.md"
+"api/internals/config_reader.md" "internals/api/_internal/config_reader.md"
+"api/internals/private_path.md" "internals/api/_internal/private_path.md"
+"api/internals/types.md" "internals/api/_internal/types.md"
+"plugins/index.md" "topics/plugins.md"
+"developing.md" "project/contributing.md"
+"about.md" "topics/index.md"
diff --git a/docs/topics/index.md b/docs/topics/index.md
new file mode 100644
index 0000000000..a7be5c5792
--- /dev/null
+++ b/docs/topics/index.md
@@ -0,0 +1,52 @@
+(topics)=
+
+# Topics
+
+Conceptual guides and workflow documentation.
+
+::::{grid} 2
+:gutter: 3
+
+:::{grid-item-card} Workflows
+:link: workflows
+:link-type: doc
+CI integration, scripting, and automation patterns.
+:::
+
+:::{grid-item-card} Plugins
+:link: plugins
+:link-type: doc
+Plugin system for custom behavior.
+:::
+
+:::{grid-item-card} Library vs CLI
+:link: library-vs-cli
+:link-type: doc
+When to use tmuxp CLI vs libtmux directly.
+:::
+
+:::{grid-item-card} Troubleshooting
+:link: troubleshooting
+:link-type: doc
+Common shell, PATH, and tmux issues.
+:::
+
+::::
+
+## Compared to tmuxinator / teamocil
+
+tmuxp, [tmuxinator](https://github.com/aziz/tmuxinator), and
+[teamocil](https://github.com/remiprev/teamocil) all load tmux sessions
+from config files. Key differences: tmuxp is Python (not Ruby), builds
+sessions through [libtmux](https://libtmux.git-pull.com/)'s ORM layer
+instead of raw shell commands, supports JSON and YAML, and can
+[freeze](../cli/freeze.md) running sessions back to config.
+
+```{toctree}
+:hidden:
+
+workflows
+plugins
+library-vs-cli
+troubleshooting
+```
diff --git a/docs/topics/library-vs-cli.md b/docs/topics/library-vs-cli.md
new file mode 100644
index 0000000000..8c431caa3d
--- /dev/null
+++ b/docs/topics/library-vs-cli.md
@@ -0,0 +1,62 @@
+# Library vs CLI
+
+tmuxp is a CLI tool. [libtmux](https://libtmux.git-pull.com/) is the Python library it's built on. Both control tmux, but they serve different needs.
+
+## When to Use the CLI
+
+Use `tmuxp` when:
+
+- You want **declarative workspace configs** — define your layout in YAML, load it with one command
+- You're setting up **daily development environments** — same windows, same panes, every time
+- You need **CI/CD tmux sessions** — `tmuxp load -d` in a script
+- You prefer **configuration over code** — no Python needed
+
+```console
+$ tmuxp load my-workspace.yaml
+```
+
+## When to Use libtmux
+
+Use [libtmux](https://libtmux.git-pull.com/) directly when:
+
+- You need **dynamic logic** — conditionals, loops, branching based on state
+- You want to **read pane output** — capture what's on screen and react to it
+- You're **testing** tmux interactions — libtmux provides pytest fixtures
+- You need **multi-server orchestration** — manage multiple tmux servers programmatically
+- The CLI's config format **can't express** what you need
+
+```python
+import libtmux
+
+server = libtmux.Server()
+session = server.new_session("my-project")
+window = session.new_window("editor")
+pane = window.split()
+pane.send_keys("vim .")
+```
+
+## Concept Mapping
+
+How tmuxp config keys map to libtmux API calls:
+
+| tmuxp YAML | libtmux equivalent |
+|------------|-------------------|
+| `session_name: foo` | `server.new_session(session_name="foo")` |
+| `windows:` | `session.new_window(...)` |
+| `panes:` | `window.split(...)` |
+| `shell_command:` | `pane.send_keys(...)` |
+| `layout: main-vertical` | `window.select_layout("main-vertical")` |
+| `start_directory: ~/project` | `session.new_window(start_directory="~/project")` |
+| `before_script:` | Run via `subprocess` before building |
+
+## What the CLI Can't Express
+
+tmuxp configs are static declarations. They can't:
+
+- **Branch on conditions** — "only create this pane if a file exists"
+- **Read pane output** — "wait until the server is ready, then open the browser"
+- **React to state** — "if this session already has 3 windows, add a 4th"
+- **Orchestrate across servers** — "connect to both local and remote tmux"
+- **Build layouts dynamically** — "create N panes based on a list of services"
+
+For these, use libtmux directly. See the [libtmux quickstart](https://libtmux.git-pull.com/quickstart.html).
diff --git a/docs/plugins/index.md b/docs/topics/plugins.md
similarity index 100%
rename from docs/plugins/index.md
rename to docs/topics/plugins.md
diff --git a/docs/topics/troubleshooting.md b/docs/topics/troubleshooting.md
new file mode 100644
index 0000000000..bfacad2c34
--- /dev/null
+++ b/docs/topics/troubleshooting.md
@@ -0,0 +1,40 @@
+# Troubleshooting
+
+## tmuxp command not found
+
+Ensure tmuxp is installed and on your `PATH`:
+
+```console
+$ which tmuxp
+```
+
+If installed with `pip install --user`, ensure `~/.local/bin` is in your `PATH`.
+
+## tmux server not found
+
+tmuxp requires a running tmux server or will start one automatically.
+Ensure tmux is installed:
+
+```console
+$ tmux -V
+```
+
+Minimum required version: tmux 3.2a.
+
+## Configuration errors
+
+Use `tmuxp debug-info` to collect system information for bug reports:
+
+```console
+$ tmuxp debug-info
+```
+
+## Session already exists
+
+If a session with the same name already exists, tmuxp will prompt you.
+Use `tmuxp load -d` to load in detached mode alongside existing sessions.
+
+## Shell completion not working
+
+See [Shell Completion](../cli/completion.md) for setup instructions
+for bash, zsh, and fish.
diff --git a/docs/topics/workflows.md b/docs/topics/workflows.md
new file mode 100644
index 0000000000..5965653c0d
--- /dev/null
+++ b/docs/topics/workflows.md
@@ -0,0 +1,32 @@
+# Workflows
+
+## CI Integration
+
+tmuxp can set up tmux sessions in CI pipelines for integration testing:
+
+```console
+$ tmuxp load -d my-workspace.yaml
+```
+
+The `-d` flag loads the session in detached mode, useful for headless environments.
+
+## Scripting
+
+tmuxp's exit codes enable scripting and error handling. See
+[Exit Codes](../cli/exit-codes.md) for the complete list.
+
+## Automating Development Environments
+
+Use tmuxp to codify your development environment:
+
+1. Set up your ideal tmux layout manually
+2. Freeze it: `tmuxp freeze my-session`
+3. Edit the generated YAML to add commands
+4. Load it on any machine: `tmuxp load my-workspace.yaml`
+
+## User-Level Configuration
+
+Workspace configs can be stored in:
+- `~/.tmuxp/` (legacy)
+- `~/.config/tmuxp/` (XDG default)
+- Project-local `.tmuxp.yaml` or `.tmuxp/` directory
From 475d049b76ff18e269600d16e7089a74d6034a6f Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 18:09:22 -0500
Subject: [PATCH 61/89] fix(docs[spa-nav]): always navigate on popstate to fix
back-button content update
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
why: The popstate handler guarded on `e.state && e.state.spa`, but
browsers can deliver null state for the initial history entry even
after replaceState({spa: true}). This caused three symptoms:
- Back button changed URL but didn't update page content
- Sidebar links resolved wrong after back-navigating across depths
- Brand logo href went stale after SPA navigation
what:
- Remove state guard from popstate handler — always call navigate()
- Safe: navigate() falls back to full page load on error, and
isPop=true prevents pushing duplicate history entries
---
docs/_static/js/spa-nav.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/_static/js/spa-nav.js b/docs/_static/js/spa-nav.js
index e00e521ab8..c26ba23171 100644
--- a/docs/_static/js/spa-nav.js
+++ b/docs/_static/js/spa-nav.js
@@ -203,8 +203,8 @@
history.replaceState({ spa: true }, "");
- window.addEventListener("popstate", function (e) {
- if (e.state && e.state.spa) navigate(location.href, true);
+ window.addEventListener("popstate", function () {
+ navigate(location.href, true);
});
// --- Hover prefetch ---
From 877c9aed2983e908b0f5da87f57f9a249adffd6d Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 19:06:38 -0500
Subject: [PATCH 62/89] fix(docs[spa-nav]): update brand links and logo srcs
after SPA navigation
why: The sidebar brand link, mobile header brand link, and logo image
srcs use relative paths that go stale after SPA navigation across
directory depths. They live outside the swap regions (.article-container,
.sidebar-tree, .toc-drawer) so swap() never updates them.
what:
- After swapping content regions, copy brand href and logo src from
the fetched document (which has correct relative paths for the target)
- Covers: .sidebar-brand href, .header-center a href, .sidebar-logo src
---
docs/_static/js/spa-nav.js | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/docs/_static/js/spa-nav.js b/docs/_static/js/spa-nav.js
index c26ba23171..cd99233fb7 100644
--- a/docs/_static/js/spa-nav.js
+++ b/docs/_static/js/spa-nav.js
@@ -132,6 +132,24 @@
);
var title = doc.querySelector("title");
if (title) document.title = title.textContent || "";
+
+ // Brand links and logo images live outside swapped regions.
+ // Their relative hrefs/srcs go stale after cross-depth navigation.
+ // Copy the correct values from the fetched document.
+ [".sidebar-brand", ".header-center a"].forEach(function (sel) {
+ var fresh = doc.querySelector(sel);
+ if (!fresh) return;
+ document.querySelectorAll(sel).forEach(function (el) {
+ el.setAttribute("href", fresh.getAttribute("href"));
+ });
+ });
+ var freshLogos = doc.querySelectorAll(".sidebar-logo");
+ var staleLogos = document.querySelectorAll(".sidebar-logo");
+ freshLogos.forEach(function (fresh, i) {
+ if (staleLogos[i]) {
+ staleLogos[i].setAttribute("src", fresh.getAttribute("src"));
+ }
+ });
}
function reinit() {
From 03a559bf0963b39fe59150914276a06c9e4b32a7 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 19:23:08 -0500
Subject: [PATCH 63/89] docs(build): switch from html to dirhtml builder for
clean URLs
why: The html builder generates relative paths like ../index.html and #
that go stale after SPA navigation across directory depths. The dirhtml
builder generates clean URLs natively (/api/ instead of /api/index.html).
what:
- Change html, start, and design recipes to use -b dirhtml
- sphinx-autobuild serves dirhtml correctly (StaticFiles html=True)
- SPA nav already handles / endings (shouldIntercept checks endsWith("/"))
- rediraffe supports dirhtml natively (DirectoryHTMLBuilder detection)
- No changes needed to redirects.txt, conf.py, or documentation source
---
docs/justfile | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/justfile b/docs/justfile
index 171766555a..afeec6b509 100644
--- a/docs/justfile
+++ b/docs/justfile
@@ -27,7 +27,7 @@ default:
# Build HTML documentation
[group: 'build']
html:
- {{ sphinxbuild }} -b html {{ allsphinxopts }} {{ builddir }}/html
+ {{ sphinxbuild }} -b dirhtml {{ allsphinxopts }} {{ builddir }}/html
@echo ""
@echo "Build finished. The HTML pages are in {{ builddir }}/html."
@@ -202,9 +202,9 @@ dev:
# Start sphinx-autobuild server
[group: 'dev']
start:
- uv run sphinx-autobuild "{{ sourcedir }}" "{{ builddir }}" {{ sphinxopts }} --port {{ http_port }}
+ uv run sphinx-autobuild -b dirhtml "{{ sourcedir }}" "{{ builddir }}" {{ sphinxopts }} --port {{ http_port }}
# Design mode: watch static files and disable incremental builds
[group: 'dev']
design:
- uv run sphinx-autobuild "{{ sourcedir }}" "{{ builddir }}" {{ sphinxopts }} --port {{ http_port }} --watch "." -a
+ uv run sphinx-autobuild -b dirhtml "{{ sourcedir }}" "{{ builddir }}" {{ sphinxopts }} --port {{ http_port }} --watch "." -a
From 6745bd6b69ed245f6d6d9b9a60bb31e7c3ffc298 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 19:28:25 -0500
Subject: [PATCH 64/89] docs(readme): update links to clean URLs for dirhtml
builder
why: Switched to dirhtml builder which generates clean URLs
(/quickstart/ instead of /quickstart.html). README links need to
match since they render on GitHub/PyPI where redirects don't apply.
what:
- Convert all .html doc links to trailing-slash format
- Covers: cli/, configuration/, topics/, quickstart, history, about_tmux
---
README.md | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index b53d3e100a..359ae17202 100644
--- a/README.md
+++ b/README.md
@@ -132,9 +132,9 @@ Name a session:
tmuxp load -s session_name ./mysession.yaml
```
-[simple](https://tmuxp.git-pull.com/configuration/examples.html#short-hand-inline-style) and
+[simple](https://tmuxp.git-pull.com/configuration/examples/#short-hand-inline-style) and
[very
-elaborate](https://tmuxp.git-pull.com/configuration/examples.html#super-advanced-dev-environment)
+elaborate](https://tmuxp.git-pull.com/configuration/examples/#super-advanced-dev-environment)
config examples
# User-level configurations
@@ -197,14 +197,14 @@ $ tmuxp shell -c 'print(window.name.upper())'
MY_WINDOW
```
-Read more on [tmuxp shell](https://tmuxp.git-pull.com/cli/shell.html) in
+Read more on [tmuxp shell](https://tmuxp.git-pull.com/cli/shell/) in
the CLI docs.
# Pre-load hook
Run custom startup scripts (such as installing project dependencies)
before loading tmux. See the
-[before_script](https://tmuxp.git-pull.com/configuration/examples.html#bootstrap-project-before-launch)
+[before_script](https://tmuxp.git-pull.com/configuration/examples/#bootstrap-project-before-launch)
example
# Load in detached state
@@ -224,7 +224,7 @@ $ tmuxp freeze session-name
```
See more about [freezing
-tmux](https://tmuxp.git-pull.com/cli/freeze.html) sessions.
+tmux](https://tmuxp.git-pull.com/cli/freeze/) sessions.
# Convert a session file
@@ -247,7 +247,7 @@ $ tmuxp convert --yes filename
# Plugin System
tmuxp has a plugin system to allow for custom behavior. See more about
-the [Plugin System](https://tmuxp.git-pull.com/topics/plugins.html).
+the [Plugin System](https://tmuxp.git-pull.com/topics/plugins/).
# Debugging Helpers
@@ -272,13 +272,13 @@ environment:
# Docs / Reading material
-See the [Quickstart](https://tmuxp.git-pull.com/quickstart.html).
+See the [Quickstart](https://tmuxp.git-pull.com/quickstart/).
[Documentation](https://tmuxp.git-pull.com) homepage (also in
[中文](http://tmuxp-zh.rtfd.org/))
Want to learn more about tmux itself? [Read The Tao of Tmux
-online](https://tmuxp.git-pull.com/about_tmux.html).
+online](https://tmuxp.git-pull.com/about_tmux/).
# Donations
@@ -295,8 +295,8 @@ See donation options at .
- python support: >= 3.10, pypy, pypy3
- Source:
- Docs:
-- API:
-- Changelog:
+- API:
+- Changelog:
- Issues:
- Test Coverage:
- pypi:
From 63edc1fad7b7b69924ff264945e65890ea2fab65 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 19:32:34 -0500
Subject: [PATCH 65/89] fix(docs[redirects]): remove dirhtml-conflicting
redirect entry
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
why: With dirhtml builder, api.md and api/index.md both produce
api/index.html. The redirect chain api.md → api/index.md →
internals/api/index.md causes rediraffe to write two redirects
at the same output path, failing the build.
what:
- Comment out api.md redirect (api/index.md redirect handles it)
---
docs/redirects.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/redirects.txt b/docs/redirects.txt
index b22b99cfd9..ba67fdc915 100644
--- a/docs/redirects.txt
+++ b/docs/redirects.txt
@@ -1,5 +1,5 @@
"cli.md" "commands/index.md"
-"api.md" "api/index.md"
+# "api.md" → api/index.md: not needed with dirhtml (same output path as api/index.md redirect)
"examples.md" "configuration/examples.md"
"plugin_system.md" "plugins/index.md"
"commands/index.md" "cli/index.md"
From b5b434c38c6eb0b9c13fb5428d62e0504f0d82b0 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 19:38:45 -0500
Subject: [PATCH 66/89] fix(docs[conf]): suppress docutils duplicate label
warnings for dirhtml
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
why: The dirhtml builder generates duplicate implicit labels across CLI
pages — each argparse-generated page has "Usage", "Options", "Examples"
headings that create colliding labels. These warnings cause the build
to exit with code 1.
what:
- Add "docutils" to suppress_warnings in conf.py
---
docs/conf.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/conf.py b/docs/conf.py
index 3d77fc8993..4385b3323d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -128,6 +128,9 @@
# (types in TYPE_CHECKING blocks used for circular import avoidance)
suppress_warnings = [
"sphinx_autodoc_typehints.forward_reference",
+ # dirhtml builder creates duplicate implicit labels across CLI pages
+ # (each argparse page generates "Usage", "Options", "Examples" headings)
+ "docutils",
]
# sphinxext.opengraph
From d288c8b2ea8592b8993c19f039bccda8c2426e30 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 19:51:32 -0500
Subject: [PATCH 67/89] fix(docs[redirects]): remove cli.md self-redirect for
dirhtml, revert suppress_warnings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
why: cli.md → commands/index.md → cli/index.md chain produces same
dirhtml output path (cli/index.html) as the real cli/index.md file.
Rediraffe reports "already exists" and exits with code 1.
what:
- Comment out cli.md redirect (same dirhtml output as cli/index.md)
- Revert docutils suppress_warnings (didn't help, wrong approach)
---
docs/conf.py | 3 ---
docs/redirects.txt | 2 +-
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index 4385b3323d..3d77fc8993 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -128,9 +128,6 @@
# (types in TYPE_CHECKING blocks used for circular import avoidance)
suppress_warnings = [
"sphinx_autodoc_typehints.forward_reference",
- # dirhtml builder creates duplicate implicit labels across CLI pages
- # (each argparse page generates "Usage", "Options", "Examples" headings)
- "docutils",
]
# sphinxext.opengraph
diff --git a/docs/redirects.txt b/docs/redirects.txt
index ba67fdc915..be9e1bdd10 100644
--- a/docs/redirects.txt
+++ b/docs/redirects.txt
@@ -1,4 +1,4 @@
-"cli.md" "commands/index.md"
+# "cli.md" → commands/index.md → cli/index.md: not needed with dirhtml (same output path)
# "api.md" → api/index.md: not needed with dirhtml (same output path as api/index.md redirect)
"examples.md" "configuration/examples.md"
"plugin_system.md" "plugins/index.md"
From c956490fbd8753c6f48e678e6dd4a1e326330660 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Mon, 23 Mar 2026 20:40:41 -0500
Subject: [PATCH 68/89] docs(grid): make grid cards responsive for phone and
tablet
why: Grid cards used single-value column counts ({grid} 2, {grid} 3)
which don't collapse on small screens, squeezing content uncomfortably.
what:
- 2-col grids: {grid} 1 1 2 2 (stack on phone/tablet, 2-col on desktop)
- 3-col grids: {grid} 1 2 3 3 (1-col phone, 2-col tablet, 3-col desktop)
- Gutters: 2 2 3 3 (tighter spacing on mobile)
- Uses sphinx-design's responsive breakpoint syntax (xs sm md lg)
---
docs/cli/index.md | 4 ++--
docs/configuration/index.md | 4 ++--
docs/index.md | 8 ++++----
docs/project/index.md | 4 ++--
docs/topics/index.md | 4 ++--
5 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/docs/cli/index.md b/docs/cli/index.md
index c1c6b605fc..fd38b681ea 100644
--- a/docs/cli/index.md
+++ b/docs/cli/index.md
@@ -4,8 +4,8 @@
# CLI Reference
-::::{grid} 2
-:gutter: 3
+::::{grid} 1 1 2 2
+:gutter: 2 2 3 3
:::{grid-item-card} tmuxp load
:link: load
diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index 89b37b2a34..166017226a 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -167,8 +167,8 @@ $ tmuxp load /opt/myapp
## Reference and usage
-::::{grid} 3
-:gutter: 3
+::::{grid} 1 2 3 3
+:gutter: 2 2 3 3
:::{grid-item-card} Top-level Options
:link: top-level
diff --git a/docs/index.md b/docs/index.md
index 9d8d12db2b..64308bd782 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -5,8 +5,8 @@
Session manager for tmux. Load, freeze, and convert tmux sessions through
YAML/JSON configuration files. Powered by [libtmux](https://libtmux.git-pull.com/).
-::::{grid} 3
-:gutter: 3
+::::{grid} 1 2 3 3
+:gutter: 2 2 3 3
:::{grid-item-card} Quickstart
:link: quickstart
@@ -28,8 +28,8 @@ Config format, examples, and environment variables.
::::
-::::{grid} 2
-:gutter: 3
+::::{grid} 1 1 2 2
+:gutter: 2 2 3 3
:::{grid-item-card} Topics
:link: topics/index
diff --git a/docs/project/index.md b/docs/project/index.md
index 8c37a15d84..2b60711f5c 100644
--- a/docs/project/index.md
+++ b/docs/project/index.md
@@ -4,8 +4,8 @@
Information for contributors and maintainers.
-::::{grid} 2
-:gutter: 3
+::::{grid} 1 1 2 2
+:gutter: 2 2 3 3
:::{grid-item-card} Contributing
:link: contributing
diff --git a/docs/topics/index.md b/docs/topics/index.md
index a7be5c5792..050ddb9d58 100644
--- a/docs/topics/index.md
+++ b/docs/topics/index.md
@@ -4,8 +4,8 @@
Conceptual guides and workflow documentation.
-::::{grid} 2
-:gutter: 3
+::::{grid} 1 1 2 2
+:gutter: 2 2 3 3
:::{grid-item-card} Workflows
:link: workflows
From 6dd0024f370d2e9ab5aff179b87f3e027c828ab0 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Tue, 24 Mar 2026 03:57:46 -0500
Subject: [PATCH 69/89] docs(sections): add grid cards to configuration and
internals index pages
why: Configuration and Internals were the only section indexes without
grid cards at the top. Configuration had cards at the bottom but not
above the fold. All other sections (CLI, Topics, Project) have cards
immediately after the heading.
what:
- configuration/index.md: add 1x3 responsive card grid at top linking
to Top-level Options, Environment Variables, and Examples
- internals/index.md: add 1x2 responsive card grid for Architecture
and Python API, hide toctree for sidebar-only nav
---
docs/configuration/index.md | 23 +++++++++++++++++++++++
docs/internals/index.md | 19 ++++++++++++++++++-
2 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index 166017226a..619cb62710 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -6,6 +6,29 @@
# Workspace files
+::::{grid} 1 2 3 3
+:gutter: 2 2 3 3
+
+:::{grid-item-card} Top-level Options
+:link: top-level
+:link-type: doc
+Session and window configuration keys.
+:::
+
+:::{grid-item-card} Environment Variables
+:link: environmental-variables
+:link-type: doc
+TMUXP_CONFIGDIR and other env vars.
+:::
+
+:::{grid-item-card} Examples
+:link: examples
+:link-type: doc
+Sample workspace configurations.
+:::
+
+::::
+
tmuxp loads your terminal workspace into tmux using workspace files.
The workspace file can be JSON or YAML. It's declarative style resembles tmux's object hierarchy: session, window and panes.
diff --git a/docs/internals/index.md b/docs/internals/index.md
index 9db3cf96a3..82a8c121c0 100644
--- a/docs/internals/index.md
+++ b/docs/internals/index.md
@@ -11,8 +11,25 @@ If you are building an application with tmuxp, use the [CLI](../cli/index.md)
or refer to the [libtmux API](https://libtmux.git-pull.com/api/).
```
+::::{grid} 1 1 2 2
+:gutter: 2 2 3 3
+
+:::{grid-item-card} Architecture
+:link: architecture
+:link-type: doc
+How the CLI dispatches to the workspace builder and libtmux.
+:::
+
+:::{grid-item-card} Python API
+:link: api/index
+:link-type: doc
+Internal module reference for contributors and plugin authors.
+:::
+
+::::
+
```{toctree}
-:maxdepth: 2
+:hidden:
architecture
api/index
From 520d4605f3a5192d93b139b3540e9ac63acaa1dd Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Tue, 24 Mar 2026 04:03:04 -0500
Subject: [PATCH 70/89] fix(docs[aafig]): use builder target URI for image
paths (dirhtml compat)
why: The aafig extension computed image paths using the raw docname
instead of the builder's target URI. With dirhtml, this produces
../_images/ instead of ../../_images/ because dirhtml outputs files
one directory deeper (page/index.html vs page.html).
what:
- Change relative_uri(docname, "_images") to
relative_uri(get_target_uri(docname), "_images")
- Matches how Sphinx itself computes image paths
(sphinx/builders/html/__init__.py:655)
---
docs/_ext/aafig.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/_ext/aafig.py b/docs/_ext/aafig.py
index 418dbbc028..6f90d51378 100644
--- a/docs/_ext/aafig.py
+++ b/docs/_ext/aafig.py
@@ -176,7 +176,8 @@ def render_aafigure(
fname = "{}.{}".format(get_basename(text, options), options["format"])
if app.builder.format == "html":
# HTML
- imgpath = relative_uri(app.builder.env.docname, "_images")
+ target_uri = app.builder.get_target_uri(app.builder.env.docname)
+ imgpath = relative_uri(target_uri, "_images")
relfn = posixpath.join(imgpath, fname)
outfn = path.join(app.builder.outdir, "_images", fname)
else:
From 8b1e11e0835f19f0984b81190a4a95cd0a28d8b9 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sat, 28 Mar 2026 04:40:02 -0500
Subject: [PATCH 71/89] py(deps[dev]) Bump dev packages
---
uv.lock | 172 ++++++++++++++++++++++++++++----------------------------
1 file changed, 86 insertions(+), 86 deletions(-)
diff --git a/uv.lock b/uv.lock
index d937343d61..2c031d824e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -39,16 +39,16 @@ wheels = [
[[package]]
name = "anyio"
-version = "4.12.1"
+version = "4.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "idna" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
+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/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
+ { 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]]
@@ -933,16 +933,16 @@ wheels = [
[[package]]
name = "pytest-cov"
-version = "7.0.0"
+version = "7.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage", extra = ["toml"] },
{ name = "pluggy" },
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
]
[[package]]
@@ -1049,7 +1049,7 @@ wheels = [
[[package]]
name = "requests"
-version = "2.32.5"
+version = "2.33.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -1057,9 +1057,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
+ { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" },
]
[[package]]
@@ -1085,27 +1085,27 @@ wheels = [
[[package]]
name = "ruff"
-version = "0.15.7"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" },
- { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" },
- { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" },
- { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" },
- { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" },
- { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" },
- { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" },
- { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" },
- { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" },
- { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" },
- { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" },
- { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" },
- { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" },
- { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" },
- { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" },
- { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" },
- { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" },
+version = "0.15.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" },
+ { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" },
+ { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" },
+ { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" },
+ { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" },
+ { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" },
+ { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" },
+ { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" },
+ { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" },
+ { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" },
]
[[package]]
@@ -1413,15 +1413,15 @@ wheels = [
[[package]]
name = "starlette"
-version = "0.52.1"
+version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
]
[[package]]
@@ -1581,65 +1581,65 @@ testing = [
[[package]]
name = "tomli"
-version = "2.4.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" },
- { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" },
- { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" },
- { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" },
- { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" },
- { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" },
- { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" },
- { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" },
- { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" },
- { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" },
- { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" },
- { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" },
- { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" },
- { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" },
- { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" },
- { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" },
- { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" },
- { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" },
- { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" },
- { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" },
- { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" },
- { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" },
- { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" },
- { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" },
- { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" },
- { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" },
- { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" },
- { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" },
- { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" },
- { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" },
- { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" },
- { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" },
- { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" },
- { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" },
- { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" },
- { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" },
- { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" },
- { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" },
- { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" },
- { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" },
- { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" },
- { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" },
- { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" },
- { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" },
- { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" },
- { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
+ { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
+ { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
+ { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
+ { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
+ { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
+ { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
+ { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
+ { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
+ { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
+ { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
+ { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
+ { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
+ { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
+ { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
+ { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
+ { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
+ { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
+ { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
+ { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
+ { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
+ { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
+ { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
]
[[package]]
name = "types-docutils"
-version = "0.22.3.20260316"
+version = "0.22.3.20260322"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/9f/27/a7f16b3a2fad0a4ddd85a668319f9a1d0311c4bd9578894f6471c7e6c788/types_docutils-0.22.3.20260316.tar.gz", hash = "sha256:8ef27d565b9831ff094fe2eac75337a74151013e2d21ecabd445c2955f891564", size = 57263, upload-time = "2026-03-16T04:29:12.211Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/44/bb/243a87fc1605a4a94c2c343d6dbddbf0d7ef7c0b9550f360b8cda8e82c39/types_docutils-0.22.3.20260322.tar.gz", hash = "sha256:e2450bb997283c3141ec5db3e436b91f0aa26efe35eb9165178ca976ccb4930b", size = 57311, upload-time = "2026-03-22T04:08:44.064Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/70/60/c1f22b7cfc4837d5419e5a2d8702c7d65f03343f866364b71cccd8a73b79/types_docutils-0.22.3.20260316-py3-none-any.whl", hash = "sha256:083c7091b8072c242998ec51da1bf1492f0332387da81c3b085efbf5ca754c7d", size = 91968, upload-time = "2026-03-16T04:29:11.114Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/4a/22c090cd4615a16917dff817cbe7c5956da376c961e024c241cd962d2c3d/types_docutils-0.22.3.20260322-py3-none-any.whl", hash = "sha256:681d4510ce9b80a0c6a593f0f9843d81f8caa786db7b39ba04d9fd5480ac4442", size = 91978, upload-time = "2026-03-22T04:08:43.117Z" },
]
[[package]]
From 0413e08d564c6be5543e0621b603d124e98c59d5 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sat, 28 Mar 2026 04:44:21 -0500
Subject: [PATCH 72/89] .tool-versions(uv) uv 0.10.12 -> 0.11.2
See also:
- uv:
- https://github.com/astral-sh/uv/releases/tag/0.11.2
- https://github.com/astral-sh/uv/blob/0.11.2/CHANGELOG.md
---
.tool-versions | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.tool-versions b/.tool-versions
index 9c32c2514e..1ced26b3e9 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,3 +1,3 @@
just 1.47
-uv 0.10.12
+uv 0.11.2
python 3.14 3.13 3.12 3.11 3.10
From 1ee42b0e232874df2eaac1fb43e94fca6faedc65 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sun, 29 Mar 2026 06:02:02 -0500
Subject: [PATCH 73/89] AGENTS(ai[rules]): Add git commit standards
why: This repo documented testing and coding workflow but did not spell out the commit message structure used across recent history. Adding the commit guidance from libtmux gives agents an explicit what/why format to follow instead of inferring it from past commits.
what:
- Add a Git Commit Standards section to AGENTS.md
- Document the Scope(type[detail]) subject pattern and common commit types
- Include a what/why body template and heredoc example for multi-line commits
---
AGENTS.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/AGENTS.md b/AGENTS.md
index 7e230283db..0f87f4ce20 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -116,6 +116,55 @@ windows:
- **Type imports**: Use `import typing as t` and access via namespace (e.g., `t.Optional`)
- **Development workflow**: Format → Test → Commit → Lint/Type Check → Test → Final Commit
+## Git Commit Standards
+
+Format commit messages as:
+```
+Scope(type[detail]): concise description
+
+why: Explanation of necessity or impact.
+what:
+- Specific technical changes made
+- Focused on a single topic
+```
+
+Common commit types:
+- **feat**: New features or enhancements
+- **fix**: Bug fixes
+- **refactor**: Code restructuring without functional change
+- **docs**: Documentation updates
+- **chore**: Maintenance (dependencies, tooling, config)
+- **test**: Test-related updates
+- **style**: Code style and formatting
+- **py(deps)**: Dependencies
+- **py(deps[dev])**: Dev Dependencies
+- **ai(rules[AGENTS])**: AI rule updates
+- **ai(claude[rules])**: Claude Code rules (CLAUDE.md)
+- **ai(claude[command])**: Claude Code command changes
+
+Example:
+```
+Pane(feat[send_keys]): Add support for literal flag
+
+why: Enable sending literal characters without tmux interpretation
+what:
+- Add literal parameter to send_keys method
+- Update send_keys to pass -l flag when literal=True
+- Add tests for literal key sending
+```
+For multi-line commits, use heredoc to preserve formatting:
+```bash
+git commit -m "$(cat <<'EOF'
+feat(Component[method]) add feature description
+
+why: Explanation of the change.
+what:
+- First change
+- Second change
+EOF
+)"
+```
+
## Logging Standards
These rules guide future logging changes; existing code may not yet conform.
From e1d43b7d1bf5396dacd5b32c18e745abae4c951d Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sun, 29 Mar 2026 09:10:28 -0500
Subject: [PATCH 74/89] py(deps[dev]) Bump dev packages
---
uv.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/uv.lock b/uv.lock
index 2c031d824e..90e0fc9b54 100644
--- a/uv.lock
+++ b/uv.lock
@@ -906,11 +906,11 @@ wheels = [
[[package]]
name = "pygments"
-version = "2.19.2"
+version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+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/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+ { 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]]
From 83265512bced6ef24732becd9b4682c99d319697 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Tue, 31 Mar 2026 17:23:14 -0500
Subject: [PATCH 75/89] docs(conf): add IBM Plex Mono weights 500/600/700
why: Only weight 400 was downloaded. Bold monospace text (badge labels at
font-weight 650, in code) used browser faux-bolding which looks
noticeably worse than the real typeface weights.
what:
- Match IBM Plex Sans weight range: [400, 500, 600, 700]
---
docs/conf.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/conf.py b/docs/conf.py
index 3d77fc8993..9539c52438 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -165,7 +165,7 @@
"family": "IBM Plex Mono",
"package": "@fontsource/ibm-plex-mono",
"version": "5.2.7",
- "weights": [400],
+ "weights": [400, 500, 600, 700],
"styles": ["normal", "italic"],
"subset": "latin",
},
From 598e4e5abaa8e06bd2ccf12620660e8b2ad94c70 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Tue, 31 Mar 2026 17:23:33 -0500
Subject: [PATCH 76/89] docs(sphinx_fonts): multi-subset support with
unicode-range descriptors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
why: Only the "latin" subset was downloaded. Characters outside basic
Latin (accented letters like ñ, ř, ž common in contributor names and
code comments) fell back to system fonts, breaking the typographic
consistency of IBM Plex.
what:
- Add _UNICODE_RANGES dict mapping Fontsource subset names to CSS
unicode-range descriptors for latin, latin-ext, cyrillic,
cyrillic-ext, greek, and vietnamese
- Add _unicode_range() lookup function with empty-string fallback
for unknown subsets (omitting the descriptor = all codepoints)
- Support "subsets" (list) config key alongside legacy "subset" (str)
in _on_builder_inited — iterates all subsets in the download loop
- Emit unicode-range in font_faces dict; template renders it in
@font-face blocks so browsers only download subset files when
characters from that range appear on the page (zero cost for pages
that only use ASCII)
- Preload uses the first (primary) subset only — typically "latin"
- Update page.html template: conditional unicode-range line in
@font-face blocks
- Add 8 tests: _unicode_range (4), multi-subset builder-inited (2),
legacy subset backward compat (1), preload primary subset (1)
---
docs/_ext/sphinx_fonts.py | 92 +++++++++++++++----
docs/_templates/page.html | 3 +
tests/docs/_ext/test_sphinx_fonts.py | 129 +++++++++++++++++++++++++++
3 files changed, 206 insertions(+), 18 deletions(-)
diff --git a/docs/_ext/sphinx_fonts.py b/docs/_ext/sphinx_fonts.py
index e8d2a692ae..7a7e1a2780 100644
--- a/docs/_ext/sphinx_fonts.py
+++ b/docs/_ext/sphinx_fonts.py
@@ -54,6 +54,57 @@ def _cdn_url(
)
+# Unicode range descriptors per subset — tells the browser to only download
+# the file when characters from this range appear on the page. Ranges are
+# from Fontsource / Google Fonts CSS (CSS unicode-range values).
+_UNICODE_RANGES: dict[str, str] = {
+ "latin": (
+ "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,"
+ " U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,"
+ " U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,"
+ " U+FEFF, U+FFFD"
+ ),
+ "latin-ext": (
+ "U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,"
+ " U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,"
+ " U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,"
+ " U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF"
+ ),
+ "cyrillic": ("U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116"),
+ "cyrillic-ext": (
+ "U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F"
+ ),
+ "greek": (
+ "U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF"
+ ),
+ "vietnamese": (
+ "U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169,"
+ " U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304,"
+ " U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB"
+ ),
+}
+
+
+def _unicode_range(subset: str) -> str:
+ """Return the CSS ``unicode-range`` descriptor for *subset*.
+
+ Falls back to an empty string for unknown subsets (omitting the
+ descriptor causes the browser to treat the face as covering all
+ codepoints, which is the correct fallback).
+
+ Parameters
+ ----------
+ subset : str
+ Fontsource subset name (e.g. ``"latin"``, ``"latin-ext"``).
+
+ Returns
+ -------
+ str
+ CSS ``unicode-range`` value, or ``""`` if unknown.
+ """
+ return _UNICODE_RANGES.get(subset, "")
+
+
def _download_font(url: str, dest: pathlib.Path) -> bool:
if dest.exists():
logger.debug("font cached: %s", dest.name)
@@ -89,22 +140,25 @@ def _on_builder_inited(app: Sphinx) -> None:
font_id = font["package"].split("/")[-1]
version = font["version"]
package = font["package"]
- subset = font.get("subset", "latin")
- for weight in font["weights"]:
- for style in font["styles"]:
- filename = f"{font_id}-{subset}-{weight}-{style}.woff2"
- cached = cache / filename
- url = _cdn_url(package, version, font_id, subset, weight, style)
- if _download_font(url, cached):
- shutil.copy2(cached, fonts_dir / filename)
- font_faces.append(
- {
- "family": font["family"],
- "style": style,
- "weight": str(weight),
- "filename": filename,
- }
- )
+ # Accept "subsets" (list) or legacy "subset" (str).
+ subsets: list[str] = font.get("subsets", [font.get("subset", "latin")])
+ for subset in subsets:
+ for weight in font["weights"]:
+ for style in font["styles"]:
+ filename = f"{font_id}-{subset}-{weight}-{style}.woff2"
+ cached = cache / filename
+ url = _cdn_url(package, version, font_id, subset, weight, style)
+ if _download_font(url, cached):
+ shutil.copy2(cached, fonts_dir / filename)
+ font_faces.append(
+ {
+ "family": font["family"],
+ "style": style,
+ "weight": str(weight),
+ "filename": filename,
+ "unicode_range": _unicode_range(subset),
+ }
+ )
preload_hrefs: list[str] = []
preload_specs: list[tuple[str, int, str]] = app.config.sphinx_font_preload
@@ -112,8 +166,10 @@ def _on_builder_inited(app: Sphinx) -> None:
for font in fonts:
if font["family"] == family_name:
font_id = font["package"].split("/")[-1]
- subset = font.get("subset", "latin")
- filename = f"{font_id}-{subset}-{weight}-{style}.woff2"
+ # Preload the first (primary) subset only — typically "latin".
+ subsets = font.get("subsets", [font.get("subset", "latin")])
+ primary = subsets[0] if subsets else "latin"
+ filename = f"{font_id}-{primary}-{weight}-{style}.woff2"
preload_hrefs.append(filename)
break
diff --git a/docs/_templates/page.html b/docs/_templates/page.html
index 7c0b561789..0c1ad27ea1 100644
--- a/docs/_templates/page.html
+++ b/docs/_templates/page.html
@@ -13,6 +13,9 @@
font-weight: {{ face.weight }};
font-display: block;
src: url("{{ pathto('_static/fonts/' + face.filename, 1) }}") format("woff2");
+ {%- if face.unicode_range %}
+ unicode-range: {{ face.unicode_range }};
+ {%- endif %}
}
{%- endfor %}
{%- for fb in font_fallbacks|default([]) %}
diff --git a/tests/docs/_ext/test_sphinx_fonts.py b/tests/docs/_ext/test_sphinx_fonts.py
index 22f546a2e1..e96a879be3 100644
--- a/tests/docs/_ext/test_sphinx_fonts.py
+++ b/tests/docs/_ext/test_sphinx_fonts.py
@@ -96,6 +96,36 @@ def test_cdn_url_matches_template() -> None:
assert url.endswith(".woff2")
+# --- _unicode_range tests ---
+
+
+def test_unicode_range_latin() -> None:
+ """_unicode_range returns a non-empty range for 'latin'."""
+ result = sphinx_fonts._unicode_range("latin")
+ assert result.startswith("U+")
+ assert "U+0000" in result
+
+
+def test_unicode_range_latin_ext() -> None:
+ """_unicode_range returns a non-empty range for 'latin-ext'."""
+ result = sphinx_fonts._unicode_range("latin-ext")
+ assert result.startswith("U+")
+ assert result != sphinx_fonts._unicode_range("latin")
+
+
+def test_unicode_range_unknown_subset() -> None:
+ """_unicode_range returns empty string for unknown subsets."""
+ result = sphinx_fonts._unicode_range("klingon")
+ assert result == ""
+
+
+def test_unicode_range_all_known_subsets_non_empty() -> None:
+ """Every subset in _UNICODE_RANGES produces a non-empty range."""
+ for subset, urange in sphinx_fonts._UNICODE_RANGES.items():
+ assert urange.startswith("U+"), f"subset {subset!r} has invalid range"
+ assert sphinx_fonts._unicode_range(subset) == urange
+
+
# --- _download_font tests ---
@@ -337,6 +367,105 @@ def test_on_builder_inited_explicit_subset(
assert app._font_faces[0]["filename"] == "noto-sans-latin-ext-400-normal.woff2"
+def test_on_builder_inited_multiple_subsets(
+ tmp_path: pathlib.Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """_on_builder_inited downloads files for each subset and includes unicode_range."""
+ monkeypatch.setattr("sphinx_fonts._cache_dir", lambda: tmp_path / "cache")
+
+ fonts = [
+ {
+ "package": "@fontsource/ibm-plex-sans",
+ "version": "5.2.8",
+ "family": "IBM Plex Sans",
+ "subsets": ["latin", "latin-ext"],
+ "weights": [400],
+ "styles": ["normal"],
+ },
+ ]
+ app = _make_app(tmp_path, fonts=fonts)
+
+ cache = tmp_path / "cache"
+ cache.mkdir(parents=True)
+ (cache / "ibm-plex-sans-latin-400-normal.woff2").write_bytes(b"data")
+ (cache / "ibm-plex-sans-latin-ext-400-normal.woff2").write_bytes(b"data")
+
+ sphinx_fonts._on_builder_inited(app)
+
+ assert len(app._font_faces) == 2
+ filenames = [f["filename"] for f in app._font_faces]
+ assert "ibm-plex-sans-latin-400-normal.woff2" in filenames
+ assert "ibm-plex-sans-latin-ext-400-normal.woff2" in filenames
+
+ # unicode_range should be populated for known subsets
+ latin_face = next(f for f in app._font_faces if "latin-400" in f["filename"])
+ assert latin_face["unicode_range"].startswith("U+")
+ latin_ext_face = next(f for f in app._font_faces if "latin-ext" in f["filename"])
+ assert latin_ext_face["unicode_range"].startswith("U+")
+ assert latin_face["unicode_range"] != latin_ext_face["unicode_range"]
+
+
+def test_on_builder_inited_legacy_subset_gets_unicode_range(
+ tmp_path: pathlib.Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """Legacy single 'subset' config still produces unicode_range in font_faces."""
+ monkeypatch.setattr("sphinx_fonts._cache_dir", lambda: tmp_path / "cache")
+
+ fonts = [
+ {
+ "package": "@fontsource/noto-sans",
+ "version": "5.0.0",
+ "family": "Noto Sans",
+ "subset": "latin",
+ "weights": [400],
+ "styles": ["normal"],
+ },
+ ]
+ app = _make_app(tmp_path, fonts=fonts)
+
+ cache = tmp_path / "cache"
+ cache.mkdir(parents=True)
+ (cache / "noto-sans-latin-400-normal.woff2").write_bytes(b"data")
+
+ sphinx_fonts._on_builder_inited(app)
+
+ assert len(app._font_faces) == 1
+ assert app._font_faces[0]["unicode_range"].startswith("U+")
+
+
+def test_on_builder_inited_preload_uses_primary_subset(
+ tmp_path: pathlib.Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """Preload uses the first (primary) subset when multiple are configured."""
+ monkeypatch.setattr("sphinx_fonts._cache_dir", lambda: tmp_path / "cache")
+
+ fonts = [
+ {
+ "package": "@fontsource/ibm-plex-sans",
+ "version": "5.2.8",
+ "family": "IBM Plex Sans",
+ "subsets": ["latin", "latin-ext"],
+ "weights": [400],
+ "styles": ["normal"],
+ },
+ ]
+ preload = [("IBM Plex Sans", 400, "normal")]
+ app = _make_app(tmp_path, fonts=fonts, preload=preload)
+
+ cache = tmp_path / "cache"
+ cache.mkdir(parents=True)
+ (cache / "ibm-plex-sans-latin-400-normal.woff2").write_bytes(b"data")
+ (cache / "ibm-plex-sans-latin-ext-400-normal.woff2").write_bytes(b"data")
+
+ sphinx_fonts._on_builder_inited(app)
+
+ # Preload should only include the primary (first) subset
+ assert app._font_preload_hrefs == ["ibm-plex-sans-latin-400-normal.woff2"]
+
+
def test_on_builder_inited_preload_match(
tmp_path: pathlib.Path,
monkeypatch: pytest.MonkeyPatch,
From 4760f29e9934ed8e8308219a6a012d6fa1370467 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Tue, 31 Mar 2026 17:23:45 -0500
Subject: [PATCH 77/89] docs(conf): add latin-ext subset for IBM Plex Sans and
Mono
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
why: Accented characters (ñ, ř, ž, ö) in contributor names, docstrings,
and code comments fell back to system fonts. latin-ext covers U+0100-02FF
and extended Latin ranges used across European languages.
what:
- Change "subset": "latin" to "subsets": ["latin", "latin-ext"] for both
IBM Plex Sans and IBM Plex Mono
- Total font files: 32 (2 fonts × 2 subsets × 4 weights × 2 styles)
- Zero performance cost for ASCII-only pages: unicode-range descriptors
tell the browser to skip latin-ext downloads when no extended chars
appear on the page
---
docs/conf.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index 9539c52438..0e393d32e3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -159,7 +159,7 @@
"version": "5.2.8",
"weights": [400, 500, 600, 700],
"styles": ["normal", "italic"],
- "subset": "latin",
+ "subsets": ["latin", "latin-ext"],
},
{
"family": "IBM Plex Mono",
@@ -167,7 +167,7 @@
"version": "5.2.7",
"weights": [400, 500, 600, 700],
"styles": ["normal", "italic"],
- "subset": "latin",
+ "subsets": ["latin", "latin-ext"],
},
]
From 097f6178f9968e96d14ca2d8dbdded26147d309b Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sat, 4 Apr 2026 06:09:52 -0500
Subject: [PATCH 78/89] py(deps[dev]) Bump dev packages
---
uv.lock | 536 +++++++++++++++++++++++++++++---------------------------
1 file changed, 274 insertions(+), 262 deletions(-)
diff --git a/uv.lock b/uv.lock
index 90e0fc9b54..0a708b6f9c 100644
--- a/uv.lock
+++ b/uv.lock
@@ -84,119 +84,119 @@ wheels = [
[[package]]
name = "charset-normalizer"
-version = "3.4.6"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e6/8c/2c56124c6dc53a774d435f985b5973bc592f42d437be58c0c92d65ae7296/charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", size = 298751, upload-time = "2026-03-15T18:50:00.003Z" },
- { url = "https://files.pythonhosted.org/packages/86/2a/2a7db6b314b966a3bcad8c731c0719c60b931b931de7ae9f34b2839289ee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", size = 200027, upload-time = "2026-03-15T18:50:01.702Z" },
- { url = "https://files.pythonhosted.org/packages/68/f2/0fe775c74ae25e2a3b07b01538fc162737b3e3f795bada3bc26f4d4d495c/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", size = 220741, upload-time = "2026-03-15T18:50:03.194Z" },
- { url = "https://files.pythonhosted.org/packages/10/98/8085596e41f00b27dd6aa1e68413d1ddda7e605f34dd546833c61fddd709/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", size = 215802, upload-time = "2026-03-15T18:50:05.859Z" },
- { url = "https://files.pythonhosted.org/packages/fd/ce/865e4e09b041bad659d682bbd98b47fb490b8e124f9398c9448065f64fee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", size = 207908, upload-time = "2026-03-15T18:50:07.676Z" },
- { url = "https://files.pythonhosted.org/packages/a8/54/8c757f1f7349262898c2f169e0d562b39dcb977503f18fdf0814e923db78/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", size = 194357, upload-time = "2026-03-15T18:50:09.327Z" },
- { url = "https://files.pythonhosted.org/packages/6f/29/e88f2fac9218907fc7a70722b393d1bbe8334c61fe9c46640dba349b6e66/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", size = 205610, upload-time = "2026-03-15T18:50:10.732Z" },
- { url = "https://files.pythonhosted.org/packages/4c/c5/21d7bb0cb415287178450171d130bed9d664211fdd59731ed2c34267b07d/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", size = 203512, upload-time = "2026-03-15T18:50:12.535Z" },
- { url = "https://files.pythonhosted.org/packages/a4/be/ce52f3c7fdb35cc987ad38a53ebcef52eec498f4fb6c66ecfe62cfe57ba2/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", size = 195398, upload-time = "2026-03-15T18:50:14.236Z" },
- { url = "https://files.pythonhosted.org/packages/81/a0/3ab5dd39d4859a3555e5dadfc8a9fa7f8352f8c183d1a65c90264517da0e/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", size = 221772, upload-time = "2026-03-15T18:50:15.581Z" },
- { url = "https://files.pythonhosted.org/packages/04/6e/6a4e41a97ba6b2fa87f849c41e4d229449a586be85053c4d90135fe82d26/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", size = 205759, upload-time = "2026-03-15T18:50:17.047Z" },
- { url = "https://files.pythonhosted.org/packages/db/3b/34a712a5ee64a6957bf355b01dc17b12de457638d436fdb05d01e463cd1c/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", size = 216938, upload-time = "2026-03-15T18:50:18.44Z" },
- { url = "https://files.pythonhosted.org/packages/cb/05/5bd1e12da9ab18790af05c61aafd01a60f489778179b621ac2a305243c62/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", size = 210138, upload-time = "2026-03-15T18:50:19.852Z" },
- { url = "https://files.pythonhosted.org/packages/bd/8e/3cb9e2d998ff6b21c0a1860343cb7b83eba9cdb66b91410e18fc4969d6ab/charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", size = 144137, upload-time = "2026-03-15T18:50:21.505Z" },
- { url = "https://files.pythonhosted.org/packages/d8/8f/78f5489ffadb0db3eb7aff53d31c24531d33eb545f0c6f6567c25f49a5ff/charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", size = 154244, upload-time = "2026-03-15T18:50:22.81Z" },
- { url = "https://files.pythonhosted.org/packages/e4/74/e472659dffb0cadb2f411282d2d76c60da1fc94076d7fffed4ae8a93ec01/charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", size = 143312, upload-time = "2026-03-15T18:50:24.074Z" },
- { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" },
- { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" },
- { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" },
- { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" },
- { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" },
- { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" },
- { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" },
- { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" },
- { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" },
- { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" },
- { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" },
- { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" },
- { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" },
- { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" },
- { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" },
- { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" },
- { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" },
- { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" },
- { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" },
- { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" },
- { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" },
- { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" },
- { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" },
- { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" },
- { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" },
- { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" },
- { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" },
- { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" },
- { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" },
- { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" },
- { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" },
- { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" },
- { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" },
- { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" },
- { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" },
- { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" },
- { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" },
- { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" },
- { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" },
- { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" },
- { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" },
- { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" },
- { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" },
- { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" },
- { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" },
- { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" },
- { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" },
- { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" },
- { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" },
- { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" },
- { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" },
- { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" },
- { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" },
- { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" },
- { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" },
- { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" },
- { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" },
- { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" },
- { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" },
- { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" },
- { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" },
- { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" },
- { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" },
- { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" },
- { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" },
- { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" },
- { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" },
- { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" },
- { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" },
- { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" },
- { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" },
- { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" },
- { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" },
- { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" },
- { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" },
- { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" },
- { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" },
- { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" },
- { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" },
- { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" },
- { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" },
+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/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" },
+ { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" },
+ { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" },
+ { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" },
+ { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" },
+ { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" },
+ { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" },
+ { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" },
+ { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" },
+ { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" },
+ { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" },
+ { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" },
+ { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" },
+ { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" },
+ { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" },
+ { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" },
+ { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
+ { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
+ { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
+ { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
+ { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
+ { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
+ { 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.1"
+version = "8.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" },
]
[[package]]
@@ -685,7 +685,7 @@ wheels = [
[[package]]
name = "mypy"
-version = "1.19.1"
+version = "1.20.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
@@ -694,39 +694,51 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" },
- { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" },
- { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" },
- { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" },
- { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" },
- { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" },
- { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" },
- { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" },
- { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" },
- { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" },
- { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" },
- { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" },
- { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" },
- { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" },
- { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" },
- { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" },
- { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" },
- { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" },
- { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" },
- { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" },
- { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" },
- { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" },
- { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" },
- { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" },
- { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" },
- { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" },
- { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" },
- { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" },
- { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" },
- { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" },
- { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4d/a2/a965c8c3fcd4fa8b84ba0d46606181b0d0a1d50f274c67877f3e9ed4882c/mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8", size = 14430138, upload-time = "2026-03-31T16:52:37.843Z" },
+ { url = "https://files.pythonhosted.org/packages/53/6e/043477501deeb8eabbab7f1a2f6cac62cfb631806dc1d6862a04a7f5011b/mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a", size = 13311282, upload-time = "2026-03-31T16:55:11.021Z" },
+ { url = "https://files.pythonhosted.org/packages/65/aa/bd89b247b83128197a214f29f0632ff3c14f54d4cd70d144d157bd7d7d6e/mypy-1.20.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8426d4d75d68714abc17a4292d922f6ba2cfb984b72c2278c437f6dae797865", size = 13750889, upload-time = "2026-03-31T16:52:02.909Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/9d/2860be7355c45247ccc0be1501c91176318964c2a137bd4743f58ce6200e/mypy-1.20.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02cca0761c75b42a20a2757ae58713276605eb29a08dd8a6e092aa347c4115ca", size = 14619788, upload-time = "2026-03-31T16:50:48.928Z" },
+ { url = "https://files.pythonhosted.org/packages/75/7f/3ef3e360c91f3de120f205c8ce405e9caf9fc52ef14b65d37073e322c114/mypy-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3a49064504be59e59da664c5e149edc1f26c67c4f8e8456f6ba6aba55033018", size = 14918849, upload-time = "2026-03-31T16:51:10.478Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/72/af970dfe167ef788df7c5e6109d2ed0229f164432ce828bc9741a4250e64/mypy-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebea00201737ad4391142808ed16e875add5c17f676e0912b387739f84991e13", size = 10822007, upload-time = "2026-03-31T16:50:25.268Z" },
+ { url = "https://files.pythonhosted.org/packages/93/94/ba9065c2ebe5421619aff684b793d953e438a8bfe31a320dd6d1e0706e81/mypy-1.20.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80cf77847d0d3e6e3111b7b25db32a7f8762fd4b9a3a72ce53fe16a2863b281", size = 9756158, upload-time = "2026-03-31T16:48:36.213Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/1c/74cb1d9993236910286865679d1c616b136b2eae468493aa939431eda410/mypy-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4525e7010b1b38334516181c5b81e16180b8e149e6684cee5a727c78186b4e3b", size = 14343972, upload-time = "2026-03-31T16:49:04.887Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/0d/01399515eca280386e308cf57901e68d3a52af18691941b773b3380c1df8/mypy-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a17c5d0bdcca61ce24a35beb828a2d0d323d3fcf387d7512206888c900193367", size = 13225007, upload-time = "2026-03-31T16:50:08.151Z" },
+ { url = "https://files.pythonhosted.org/packages/56/ac/b4ba5094fb2d7fe9d2037cd8d18bbe02bcf68fd22ab9ff013f55e57ba095/mypy-1.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75ff57defcd0f1d6e006d721ccdec6c88d4f6a7816eb92f1c4890d979d9ee62", size = 13663752, upload-time = "2026-03-31T16:49:26.064Z" },
+ { url = "https://files.pythonhosted.org/packages/db/a7/460678d3cf7da252d2288dad0c602294b6ec22a91932ec368cc11e44bb6e/mypy-1.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b503ab55a836136b619b5fc21c8803d810c5b87551af8600b72eecafb0059cb0", size = 14532265, upload-time = "2026-03-31T16:53:55.077Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/3e/051cca8166cf0438ae3ea80e0e7c030d7a8ab98dffc93f80a1aa3f23c1a2/mypy-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1973868d2adbb4584a3835780b27436f06d1dc606af5be09f187aaa25be1070f", size = 14768476, upload-time = "2026-03-31T16:50:34.587Z" },
+ { url = "https://files.pythonhosted.org/packages/be/66/8e02ec184f852ed5c4abb805583305db475930854e09964b55e107cdcbc4/mypy-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:2fcedb16d456106e545b2bfd7ef9d24e70b38ec252d2a629823a4d07ebcdb69e", size = 10818226, upload-time = "2026-03-31T16:53:15.624Z" },
+ { url = "https://files.pythonhosted.org/packages/13/4b/383ad1924b28f41e4879a74151e7a5451123330d45652da359f9183bcd45/mypy-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:379edf079ce44ac8d2805bcf9b3dd7340d4f97aad3a5e0ebabbf9d125b84b442", size = 9750091, upload-time = "2026-03-31T16:54:12.162Z" },
+ { url = "https://files.pythonhosted.org/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" },
+ { url = "https://files.pythonhosted.org/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" },
+ { url = "https://files.pythonhosted.org/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" },
+ { url = "https://files.pythonhosted.org/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" },
+ { url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" },
+ { url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" },
+ { url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" },
+ { url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" },
+ { url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" },
+ { url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" },
+ { url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" },
+ { url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" },
]
[[package]]
@@ -799,100 +811,100 @@ wheels = [
[[package]]
name = "pillow"
-version = "12.1.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/1d/30/5bd3d794762481f8c8ae9c80e7b76ecea73b916959eb587521358ef0b2f9/pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0", size = 5304099, upload-time = "2026-02-11T04:20:06.13Z" },
- { url = "https://files.pythonhosted.org/packages/bd/c1/aab9e8f3eeb4490180e357955e15c2ef74b31f64790ff356c06fb6cf6d84/pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713", size = 4657880, upload-time = "2026-02-11T04:20:09.291Z" },
- { url = "https://files.pythonhosted.org/packages/f1/0a/9879e30d56815ad529d3985aeff5af4964202425c27261a6ada10f7cbf53/pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b", size = 6222587, upload-time = "2026-02-11T04:20:10.82Z" },
- { url = "https://files.pythonhosted.org/packages/5a/5f/a1b72ff7139e4f89014e8d451442c74a774d5c43cd938fb0a9f878576b37/pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b", size = 8027678, upload-time = "2026-02-11T04:20:12.455Z" },
- { url = "https://files.pythonhosted.org/packages/e2/c2/c7cb187dac79a3d22c3ebeae727abee01e077c8c7d930791dc592f335153/pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4", size = 6335777, upload-time = "2026-02-11T04:20:14.441Z" },
- { url = "https://files.pythonhosted.org/packages/0c/7b/f9b09a7804ec7336effb96c26d37c29d27225783dc1501b7d62dcef6ae25/pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4", size = 7027140, upload-time = "2026-02-11T04:20:16.387Z" },
- { url = "https://files.pythonhosted.org/packages/98/b2/2fa3c391550bd421b10849d1a2144c44abcd966daadd2f7c12e19ea988c4/pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e", size = 6449855, upload-time = "2026-02-11T04:20:18.554Z" },
- { url = "https://files.pythonhosted.org/packages/96/ff/9caf4b5b950c669263c39e96c78c0d74a342c71c4f43fd031bb5cb7ceac9/pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff", size = 7151329, upload-time = "2026-02-11T04:20:20.646Z" },
- { url = "https://files.pythonhosted.org/packages/7b/f8/4b24841f582704da675ca535935bccb32b00a6da1226820845fac4a71136/pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40", size = 6325574, upload-time = "2026-02-11T04:20:22.43Z" },
- { url = "https://files.pythonhosted.org/packages/f8/f9/9f6b01c0881d7036063aa6612ef04c0e2cad96be21325a1e92d0203f8e91/pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23", size = 7032347, upload-time = "2026-02-11T04:20:23.932Z" },
- { url = "https://files.pythonhosted.org/packages/79/13/c7922edded3dcdaf10c59297540b72785620abc0538872c819915746757d/pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9", size = 2453457, upload-time = "2026-02-11T04:20:25.392Z" },
- { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" },
- { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" },
- { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" },
- { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" },
- { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" },
- { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" },
- { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" },
- { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" },
- { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" },
- { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" },
- { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" },
- { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" },
- { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" },
- { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" },
- { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" },
- { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" },
- { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" },
- { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" },
- { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" },
- { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" },
- { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" },
- { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" },
- { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" },
- { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" },
- { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" },
- { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" },
- { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" },
- { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" },
- { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" },
- { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" },
- { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" },
- { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" },
- { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" },
- { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" },
- { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" },
- { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" },
- { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" },
- { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" },
- { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" },
- { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" },
- { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" },
- { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" },
- { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" },
- { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" },
- { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" },
- { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" },
- { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" },
- { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
- { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
- { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
- { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
- { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
- { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
- { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
- { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
- { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
- { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
- { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
- { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
- { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
- { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
- { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
- { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
- { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
- { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
- { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
- { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
- { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
- { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
- { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
- { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
- { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
- { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" },
- { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" },
- { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" },
- { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" },
- { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" },
- { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" },
- { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" },
+version = "12.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/aa/d0b28e1c811cd4d5f5c2bfe2e022292bd255ae5744a3b9ac7d6c8f72dd75/pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f", size = 5354355, upload-time = "2026-04-01T14:42:15.402Z" },
+ { url = "https://files.pythonhosted.org/packages/27/8e/1d5b39b8ae2bd7650d0c7b6abb9602d16043ead9ebbfef4bc4047454da2a/pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97", size = 4695871, upload-time = "2026-04-01T14:42:18.234Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/c5/dcb7a6ca6b7d3be41a76958e90018d56c8462166b3ef223150360850c8da/pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff", size = 6269734, upload-time = "2026-04-01T14:42:20.608Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/f1/aa1bb13b2f4eba914e9637893c73f2af8e48d7d4023b9d3750d4c5eb2d0c/pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec", size = 8076080, upload-time = "2026-04-01T14:42:23.095Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/2a/8c79d6a53169937784604a8ae8d77e45888c41537f7f6f65ed1f407fe66d/pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136", size = 6382236, upload-time = "2026-04-01T14:42:25.82Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/42/bbcb6051030e1e421d103ce7a8ecadf837aa2f39b8f82ef1a8d37c3d4ebc/pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c", size = 7070220, upload-time = "2026-04-01T14:42:28.68Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/e1/c2a7d6dd8cfa6b231227da096fd2d58754bab3603b9d73bf609d3c18b64f/pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3", size = 6493124, upload-time = "2026-04-01T14:42:31.579Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/41/7c8617da5d32e1d2f026e509484fdb6f3ad7efaef1749a0c1928adbb099e/pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa", size = 7194324, upload-time = "2026-04-01T14:42:34.615Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/de/a777627e19fd6d62f84070ee1521adde5eeda4855b5cf60fe0b149118bca/pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032", size = 6376363, upload-time = "2026-04-01T14:42:37.19Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/34/fc4cb5204896465842767b96d250c08410f01f2f28afc43b257de842eed5/pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5", size = 7083523, upload-time = "2026-04-01T14:42:39.62Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/a0/32852d36bc7709f14dc3f64f929a275e958ad8c19a6deba9610d458e28b3/pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024", size = 2463318, upload-time = "2026-04-01T14:42:42.063Z" },
+ { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" },
+ { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" },
+ { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" },
+ { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" },
+ { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" },
+ { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" },
+ { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" },
+ { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" },
+ { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" },
+ { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" },
+ { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" },
+ { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" },
+ { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" },
+ { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" },
+ { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" },
+ { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" },
+ { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" },
+ { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" },
+ { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
+ { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
+ { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
+ { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
+ { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
+ { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
+ { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
+ { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" },
+ { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" },
]
[[package]]
@@ -1049,7 +1061,7 @@ wheels = [
[[package]]
name = "requests"
-version = "2.33.0"
+version = "2.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -1057,9 +1069,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" }
+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/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" },
+ { 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]]
@@ -1085,27 +1097,27 @@ wheels = [
[[package]]
name = "ruff"
-version = "0.15.8"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" },
- { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" },
- { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" },
- { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" },
- { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" },
- { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" },
- { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" },
- { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" },
- { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" },
- { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" },
- { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" },
- { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" },
- { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" },
- { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" },
- { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" },
- { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" },
- { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" },
+version = "0.15.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" },
+ { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" },
+ { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" },
+ { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" },
+ { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" },
+ { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" },
+ { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" },
+ { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" },
+ { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" },
+ { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" },
]
[[package]]
@@ -1644,14 +1656,14 @@ wheels = [
[[package]]
name = "types-pygments"
-version = "2.19.0.20251121"
+version = "2.19.0.20260402"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "types-docutils" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/90/3b/cd650700ce9e26b56bd1a6aa4af397bbbc1784e22a03971cb633cdb0b601/types_pygments-2.19.0.20251121.tar.gz", hash = "sha256:eef114fde2ef6265365522045eac0f8354978a566852f69e75c531f0553822b1", size = 18590, upload-time = "2025-11-21T03:03:46.623Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/a8/5834c55d900ce31b31367eedb82e664347dfa551a957b74a0ce0cd9f4f9a/types_pygments-2.19.0.20260402.tar.gz", hash = "sha256:bd26e1f662c9a3b8ea56668ddc099b809ffd54931bee97b15853bc4a8e5d4250", size = 18808, upload-time = "2026-04-02T04:21:13.058Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/99/8a/9244b21f1d60dcc62e261435d76b02f1853b4771663d7ec7d287e47a9ba9/types_pygments-2.19.0.20251121-py3-none-any.whl", hash = "sha256:cb3bfde34eb75b984c98fb733ce4f795213bd3378f855c32e75b49318371bb25", size = 25674, upload-time = "2025-11-21T03:03:45.72Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/f2/b23659df4219fc4f39a45ede527cc209d961c7ff957678536a7a9d9ac9c5/types_pygments-2.19.0.20260402-py3-none-any.whl", hash = "sha256:5b0d863cec1c43ba38c946fb6e89d389c1cf287806f72360336fba4482f8daeb", size = 25672, upload-time = "2026-04-02T04:21:11.916Z" },
]
[[package]]
@@ -1692,16 +1704,16 @@ wheels = [
[[package]]
name = "uvicorn"
-version = "0.42.0"
+version = "0.43.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393, upload-time = "2026-03-16T06:19:50.077Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/62/f2/368268300fb8af33743508d738ef7bb4d56afdb46c6d9c0fa3dd515df171/uvicorn-0.43.0.tar.gz", hash = "sha256:ab1652d2fb23abf124f36ccc399828558880def222c3cb3d98d24021520dc6e8", size = 85686, upload-time = "2026-04-03T18:37:48.984Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" },
+ { url = "https://files.pythonhosted.org/packages/55/df/0cf5b0c451602748fdc7a702d4667f6e209bf96aa6e3160d754234445f2a/uvicorn-0.43.0-py3-none-any.whl", hash = "sha256:46fac64f487fd968cd999e5e49efbbe64bd231b5bd8b4a0b482a23ebce499620", size = 68591, upload-time = "2026-04-03T18:37:47.64Z" },
]
[[package]]
From 6b1651f4499c4641552b9a5702843ac286ee57be Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sun, 5 Apr 2026 08:45:19 -0500
Subject: [PATCH 79/89] .tool-versions(uv, just) uv 0.11.2 -> 0.11.3, just 1.47
-> 1.49
- just
- https://github.com/casey/just/blob/1.49.0/CHANGELOG.md
- https://github.com/casey/just/releases/tag/1.48.0
- https://github.com/casey/just/releases/tag/1.49.0
- uv:
- https://github.com/astral-sh/uv/releases/tag/0.11.3
- https://github.com/astral-sh/uv/blob/0.11.3/CHANGELOG.md
---
.tool-versions | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.tool-versions b/.tool-versions
index 1ced26b3e9..88d422f528 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,3 +1,3 @@
-just 1.47
-uv 0.11.2
+just 1.49
+uv 0.11.3
python 3.14 3.13 3.12 3.11 3.10
From 903041edbc6400acd02e931ad4c298ef8c311ce9 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sun, 5 Apr 2026 08:45:24 -0500
Subject: [PATCH 80/89] py(deps[dev]) Bump dev packages
---
uv.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/uv.lock b/uv.lock
index 0a708b6f9c..67edd3767c 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1656,14 +1656,14 @@ wheels = [
[[package]]
name = "types-pygments"
-version = "2.19.0.20260402"
+version = "2.20.0.20260405"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "types-docutils" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a6/a8/5834c55d900ce31b31367eedb82e664347dfa551a957b74a0ce0cd9f4f9a/types_pygments-2.19.0.20260402.tar.gz", hash = "sha256:bd26e1f662c9a3b8ea56668ddc099b809ffd54931bee97b15853bc4a8e5d4250", size = 18808, upload-time = "2026-04-02T04:21:13.058Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/ad/28/7a11c06b290e370eca368dd59d9738a79657a7518f5a4021b1e187c1a16d/types_pygments-2.20.0.20260405.tar.gz", hash = "sha256:f06fe34d6457044ce7587a5a6cf73e6bc5c769c933cd9edf033379bcd7ed2897", size = 19342, upload-time = "2026-04-05T04:27:06.184Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f7/f2/b23659df4219fc4f39a45ede527cc209d961c7ff957678536a7a9d9ac9c5/types_pygments-2.19.0.20260402-py3-none-any.whl", hash = "sha256:5b0d863cec1c43ba38c946fb6e89d389c1cf287806f72360336fba4482f8daeb", size = 25672, upload-time = "2026-04-02T04:21:11.916Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/51/dabb479c2cda4fbed99a0f2045aee2bb91487c50c654a7ad6dfa327c5b82/types_pygments-2.20.0.20260405-py3-none-any.whl", hash = "sha256:79dc975f7a9c6cbfdcc32f3d31b7eb507d39a41031c3b2124f16fc2e42326954", size = 26688, upload-time = "2026-04-05T04:27:05.215Z" },
]
[[package]]
From a4d11b52bf404f9f661a18bb062562e29db69fe5 Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sun, 5 Apr 2026 07:30:10 -0500
Subject: [PATCH 81/89] py(deps[dev]): Replace sphinx/furo stack with
gp-sphinx==0.0.1a0
why: Consolidate docs dependencies into the gp-sphinx shared platform,
resolving from PyPI now that 0.0.1a0 is published.
what:
- Remove sphinx<9, furo, sphinx-autodoc-typehints, sphinx-inline-tabs,
sphinxext-opengraph, sphinx-copybutton, sphinxext-rediraffe,
sphinx-design, myst-parser, linkify-it-py from dev/docs groups
- Add gp-sphinx==0.0.1a0 and sphinx-argparse-neo==0.0.1a0
- Remove cli_usage_lexer, argparse_lexer, argparse_roles from mypy overrides
- Update uv.lock to resolve from PyPI
---
pyproject.toml | 27 ++---------
uv.lock | 124 ++++++++++++++++++++++++++++++-------------------
2 files changed, 80 insertions(+), 71 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 02fc0e28f6..50327c2125 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -57,18 +57,10 @@ dev = [
# Docs
"aafigure", # https://launchpad.net/aafigure
"pillow", # https://pillow.readthedocs.io/
- "sphinx<9", # https://www.sphinx-doc.org/
- "furo", # https://pradyunsg.me/furo/
+ "gp-sphinx==0.0.1a0", # https://gp-sphinx.git-pull.com/
+ "sphinx-argparse-neo==0.0.1a0", # https://gp-sphinx.git-pull.com/
"gp-libs", # https://gp-libs.git-pull.com/
"sphinx-autobuild", # https://sphinx-extensions.readthedocs.io/en/latest/sphinx-autobuild.html
- "sphinx-autodoc-typehints", # https://sphinx-autodoc-typehints.readthedocs.io/
- "sphinx-inline-tabs", # https://sphinx-inline-tabs.readthedocs.io/
- "sphinxext-opengraph", # https://sphinxext-opengraph.readthedocs.io/
- "sphinx-copybutton", # https://sphinx-copybutton.readthedocs.io/
- "sphinxext-rediraffe", # https://sphinxext-rediraffe.readthedocs.io/
- "sphinx-design", # https://sphinx-design.readthedocs.io/
- "myst-parser", # https://myst-parser.readthedocs.io/
- "linkify-it-py", # https://github.com/tsutsu3/linkify-it-py
# Testing
"gp-libs",
"pytest",
@@ -90,18 +82,10 @@ dev = [
docs = [
"aafigure", # https://launchpad.net/aafigure
"pillow", # https://pillow.readthedocs.io/
- "sphinx<9", # https://www.sphinx-doc.org/
- "furo", # https://pradyunsg.me/furo/
+ "gp-sphinx==0.0.1a0", # https://gp-sphinx.git-pull.com/
+ "sphinx-argparse-neo==0.0.1a0", # https://gp-sphinx.git-pull.com/
"gp-libs", # https://gp-libs.git-pull.com/
"sphinx-autobuild", # https://sphinx-extensions.readthedocs.io/en/latest/sphinx-autobuild.html
- "sphinx-autodoc-typehints", # https://sphinx-autodoc-typehints.readthedocs.io/
- "sphinx-inline-tabs", # https://sphinx-inline-tabs.readthedocs.io/
- "sphinxext-opengraph", # https://sphinxext-opengraph.readthedocs.io/
- "sphinx-copybutton", # https://sphinx-copybutton.readthedocs.io/
- "sphinxext-rediraffe", # https://sphinxext-rediraffe.readthedocs.io/
- "sphinx-design", # https://sphinx-design.readthedocs.io/
- "myst-parser", # https://myst-parser.readthedocs.io/
- "linkify-it-py", # https://github.com/tsutsu3/linkify-it-py
]
testing = [
"gp-libs",
@@ -176,9 +160,6 @@ module = [
"sphinx_argparse_neo",
"sphinx_argparse_neo.*",
"sphinx_fonts",
- "cli_usage_lexer",
- "argparse_lexer",
- "argparse_roles",
"docutils",
"docutils.*",
"pygments",
diff --git a/uv.lock b/uv.lock
index 67edd3767c..32264f4b46 100644
--- a/uv.lock
+++ b/uv.lock
@@ -391,6 +391,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/18/f9/5d78d1dda9cb0f27d6f2305e95a58edbff935a62d53ec3227a3518cb4f72/gp_libs-0.0.17-py3-none-any.whl", hash = "sha256:7ce96d5e09980c0dc82062ab3e3b911600bd44da97a64fb78379f1af9a79d4d3", size = 16157, upload-time = "2025-12-07T22:44:48.036Z" },
]
+[[package]]
+name = "gp-sphinx"
+version = "0.0.1a0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "docutils" },
+ { name = "gp-libs" },
+ { name = "linkify-it-py" },
+ { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "myst-parser", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinx-copybutton" },
+ { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinx-fonts" },
+ { name = "sphinx-gptheme" },
+ { name = "sphinx-inline-tabs" },
+ { name = "sphinxext-opengraph" },
+ { name = "sphinxext-rediraffe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ea/57/7a8ea21c53c83e7c54b17610ed0c48e8db6254c2ff017c1e44ae4f7132ca/gp_sphinx-0.0.1a0.tar.gz", hash = "sha256:5cf583c06dffe6697b05a9a5f0593aa41cfe35fed8a1577324ccc87e0c0c92f7", size = 13989, upload-time = "2026-04-05T10:10:23.038Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cd/8e/5e0a0364be9c80e18bd07ec2bf43fd760c5938629035a356c172f1234daa/gp_sphinx-0.0.1a0-py3-none-any.whl", hash = "sha256:fb8310dd73ffb52827ed834f49d2e769ed3136359b54879aadd9d55ff7c6048d", size = 14399, upload-time = "2026-04-05T10:04:29.578Z" },
+]
+
[[package]]
name = "h11"
version = "0.16.0"
@@ -1201,6 +1229,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" },
]
+[[package]]
+name = "sphinx-argparse-neo"
+version = "0.0.1a0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "docutils" },
+ { name = "pygments" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d8/37/83215aabda61647f0fb0ab489e0c7227a59e041f565e9b44f2af073f6008/sphinx_argparse_neo-0.0.1a0.tar.gz", hash = "sha256:d40c931a687fe79dc465d850fb9904f552de575952b6dee291de61149c7bc66a", size = 37123, upload-time = "2026-04-05T10:10:23.969Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d3/4c/6b9ac99f0639eebcecef7d294c079b66c06d56f0cc9a285ebbe2b05c68c9/sphinx_argparse_neo-0.0.1a0-py3-none-any.whl", hash = "sha256:19cf9ba32d14ca686112c1d8509f268f0ab2b1822a003875de64bbd9449ab5ef", size = 41428, upload-time = "2026-04-05T10:04:31.212Z" },
+]
+
[[package]]
name = "sphinx-autobuild"
version = "2024.10.3"
@@ -1330,6 +1373,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" },
]
+[[package]]
+name = "sphinx-fonts"
+version = "0.0.1a0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c3/4f/be4fe35f90d0bc5090a8bd1367c53d063d5808e367e22274f16cc6978796/sphinx_fonts-0.0.1a0.tar.gz", hash = "sha256:9ca77ba151fa27963e90f899d92b1e43680e223efa3acdd3c532d5e4f0b29eed", size = 5628, upload-time = "2026-04-05T10:10:28.6Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ac/68/c8793bc5a08aee5644aed1ac0eb1ef2368cc61e31d4c1d6fd6cc52192a15/sphinx_fonts-0.0.1a0-py3-none-any.whl", hash = "sha256:aae888b35cc901ad2947c3d171a0bf02b724bc78d2677827673113c8c73e11fd", size = 4345, upload-time = "2026-04-05T10:09:11.134Z" },
+]
+
+[[package]]
+name = "sphinx-gptheme"
+version = "0.0.1a0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "furo" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0c/7c/71908e74939fd4d33d83bc39d31398deae895218dd319f626f6a3e4a1068/sphinx_gptheme-0.0.1a0.tar.gz", hash = "sha256:06f222f557dbd0e3256494f145cdbc1bc971d665e9203db19bc9c105283132ac", size = 13697, upload-time = "2026-04-05T10:10:29.63Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cf/ec/7fe7909d31da9007232a77ac5750da9b9329921e938c3d73d409e4caa4ec/sphinx_gptheme-0.0.1a0-py3-none-any.whl", hash = "sha256:da0e6bb047b01c93a7df2f81be693e46b0709a1960b250991597648f7b320dfa", size = 14690, upload-time = "2026-04-05T10:10:21.577Z" },
+]
+
[[package]]
name = "sphinx-inline-tabs"
version = "2025.12.21.14"
@@ -1455,12 +1523,9 @@ dev = [
{ name = "aafigure" },
{ name = "codecov" },
{ name = "coverage" },
- { name = "furo" },
{ name = "gp-libs" },
- { name = "linkify-it-py" },
+ { name = "gp-sphinx" },
{ name = "mypy" },
- { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "myst-parser", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "pillow" },
{ name = "pytest" },
{ name = "pytest-cov" },
@@ -1468,42 +1533,21 @@ dev = [
{ name = "pytest-rerunfailures" },
{ name = "pytest-watcher" },
{ name = "ruff" },
- { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinx-argparse-neo" },
{ name = "sphinx-autobuild", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "sphinx-copybutton" },
- { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "sphinx-inline-tabs" },
- { name = "sphinxext-opengraph" },
- { name = "sphinxext-rediraffe" },
{ name = "types-docutils" },
{ name = "types-pygments" },
{ name = "types-pyyaml" },
]
docs = [
{ name = "aafigure" },
- { name = "furo" },
{ name = "gp-libs" },
- { name = "linkify-it-py" },
- { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "myst-parser", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "gp-sphinx" },
{ name = "pillow" },
- { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinx-argparse-neo" },
{ name = "sphinx-autobuild", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "sphinx-copybutton" },
- { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "sphinx-inline-tabs" },
- { name = "sphinxext-opengraph" },
- { name = "sphinxext-rediraffe" },
]
lint = [
{ name = "mypy" },
@@ -1536,11 +1580,9 @@ dev = [
{ name = "aafigure" },
{ name = "codecov" },
{ name = "coverage" },
- { name = "furo" },
{ name = "gp-libs" },
- { name = "linkify-it-py" },
+ { name = "gp-sphinx", specifier = "==0.0.1a0" },
{ name = "mypy" },
- { name = "myst-parser" },
{ name = "pillow" },
{ name = "pytest" },
{ name = "pytest-cov" },
@@ -1548,33 +1590,19 @@ dev = [
{ name = "pytest-rerunfailures" },
{ name = "pytest-watcher" },
{ name = "ruff" },
- { name = "sphinx", specifier = "<9" },
+ { name = "sphinx-argparse-neo", specifier = "==0.0.1a0" },
{ name = "sphinx-autobuild" },
- { name = "sphinx-autodoc-typehints" },
- { name = "sphinx-copybutton" },
- { name = "sphinx-design" },
- { name = "sphinx-inline-tabs" },
- { name = "sphinxext-opengraph" },
- { name = "sphinxext-rediraffe" },
{ name = "types-docutils" },
{ name = "types-pygments" },
{ name = "types-pyyaml" },
]
docs = [
{ name = "aafigure" },
- { name = "furo" },
{ name = "gp-libs" },
- { name = "linkify-it-py" },
- { name = "myst-parser" },
+ { name = "gp-sphinx", specifier = "==0.0.1a0" },
{ name = "pillow" },
- { name = "sphinx", specifier = "<9" },
+ { name = "sphinx-argparse-neo", specifier = "==0.0.1a0" },
{ name = "sphinx-autobuild" },
- { name = "sphinx-autodoc-typehints" },
- { name = "sphinx-copybutton" },
- { name = "sphinx-design" },
- { name = "sphinx-inline-tabs" },
- { name = "sphinxext-opengraph" },
- { name = "sphinxext-rediraffe" },
]
lint = [
{ name = "mypy" },
From 587ec3705b88836dc38d63b6817ddb0fc279dcdb Mon Sep 17 00:00:00 2001
From: Tony Narlock
Date: Sun, 5 Apr 2026 07:25:03 -0500
Subject: [PATCH 82/89] docs(chore): Remove bundled extensions and migrate
conf.py to gp-sphinx
why: sphinx-argparse-neo and sphinx-fonts are now provided as PyPI packages
via gp-sphinx; bundled copies in docs/_ext/ are no longer needed.
what:
- Delete docs/_ext/sphinx_fonts.py, sphinx_argparse_neo/, argparse_exemplar.py,
argparse_lexer.py, argparse_roles.py, cli_usage_lexer.py
- Delete docs/_static/js/spa-nav.js (now bundled in sphinx-gptheme)
- Migrate docs/conf.py to use merge_sphinx_config() from gp_sphinx
- Update tests/docs/_ext/ imports to reference installed packages
---
docs/_ext/argparse_exemplar.py | 1305 --------------------
docs/_ext/argparse_lexer.py | 429 -------
docs/_ext/argparse_roles.py | 370 ------
docs/_ext/cli_usage_lexer.py | 115 --
docs/_ext/sphinx_argparse_neo/__init__.py | 101 --
docs/_ext/sphinx_argparse_neo/compat.py | 271 ----
docs/_ext/sphinx_argparse_neo/directive.py | 240 ----
docs/_ext/sphinx_argparse_neo/nodes.py | 647 ----------
docs/_ext/sphinx_argparse_neo/parser.py | 659 ----------
docs/_ext/sphinx_argparse_neo/renderer.py | 604 ---------
docs/_ext/sphinx_argparse_neo/utils.py | 78 --
docs/_ext/sphinx_fonts.py | 209 ----
docs/_static/js/spa-nav.js | 254 ----
docs/conf.py | 300 +----
tests/docs/_ext/conftest.py | 8 -
tests/docs/_ext/test_argparse_exemplar.py | 4 +-
tests/docs/_ext/test_argparse_lexer.py | 2 +-
tests/docs/_ext/test_argparse_roles.py | 4 +-
tests/docs/_ext/test_cli_usage_lexer.py | 2 +-
tests/docs/_ext/test_sphinx_fonts.py | 2 +-
20 files changed, 35 insertions(+), 5569 deletions(-)
delete mode 100644 docs/_ext/argparse_exemplar.py
delete mode 100644 docs/_ext/argparse_lexer.py
delete mode 100644 docs/_ext/argparse_roles.py
delete mode 100644 docs/_ext/cli_usage_lexer.py
delete mode 100644 docs/_ext/sphinx_argparse_neo/__init__.py
delete mode 100644 docs/_ext/sphinx_argparse_neo/compat.py
delete mode 100644 docs/_ext/sphinx_argparse_neo/directive.py
delete mode 100644 docs/_ext/sphinx_argparse_neo/nodes.py
delete mode 100644 docs/_ext/sphinx_argparse_neo/parser.py
delete mode 100644 docs/_ext/sphinx_argparse_neo/renderer.py
delete mode 100644 docs/_ext/sphinx_argparse_neo/utils.py
delete mode 100644 docs/_ext/sphinx_fonts.py
delete mode 100644 docs/_static/js/spa-nav.js
diff --git a/docs/_ext/argparse_exemplar.py b/docs/_ext/argparse_exemplar.py
deleted file mode 100644
index a4a7e1fc8b..0000000000
--- a/docs/_ext/argparse_exemplar.py
+++ /dev/null
@@ -1,1305 +0,0 @@
-"""Transform argparse epilog "examples:" definition lists into documentation sections.
-
-This Sphinx extension post-processes sphinx_argparse_neo output to convert
-specially-formatted "examples:" definition lists in argparse epilogs into
-proper documentation sections with syntax-highlighted code blocks.
-
-The extension is designed to be generic and reusable across different projects.
-All behavior can be customized via Sphinx configuration options.
-
-Purpose
--------
-When documenting CLI tools with argparse, it's useful to include examples in
-the epilog. This extension recognizes a specific definition list format and
-transforms it into structured documentation sections that appear in the TOC.
-
-Input Format
-------------
-Format your argparse epilog with definition lists where terms end with "examples:":
-
-.. code-block:: python
-
- parser = argparse.ArgumentParser(
- epilog=textwrap.dedent('''
- examples:
- myapp sync
- myapp sync myrepo
-
- Machine-readable output examples:
- myapp sync --json
- myapp sync -F json myrepo
- '''),
- formatter_class=argparse.RawDescriptionHelpFormatter,
- )
-
-The epilog text will be parsed as a definition list by docutils, with:
-- Terms: "examples:", "Machine-readable output examples:", etc.
-- Definitions: The example commands (one per line)
-
-Output
-------
-The extension transforms these into proper sections:
-
-- A base "examples:" term creates an "Examples" section
-- Category-prefixed terms like "Machine-readable output examples:" create
- subsections nested under the parent Examples section
-- Each command line becomes a syntax-highlighted console code block
-
-Configuration
--------------
-Configure via conf.py. All options have sensible defaults.
-
-**Term Detection:**
-
-``argparse_examples_term_suffix`` : str (default: "examples")
- Term must end with this string to be treated as an examples header.
-
-``argparse_examples_base_term`` : str (default: "examples")
- Exact match for the base examples section (case-insensitive).
-
-``argparse_examples_section_title`` : str (default: "Examples")
- Title used for the base examples section.
-
-**Usage Detection:**
-
-``argparse_usage_pattern`` : str (default: "usage:")
- Text must start with this to be treated as a usage block (case-insensitive).
-
-**Code Block Formatting:**
-
-``argparse_examples_command_prefix`` : str (default: "$ ")
- Prefix added to each command line in examples code blocks.
-
-``argparse_examples_code_language`` : str (default: "console")
- Language identifier for examples code blocks.
-
-``argparse_examples_code_classes`` : list[str] (default: ["highlight-console"])
- CSS classes added to examples code blocks.
-
-``argparse_usage_code_language`` : str (default: "cli-usage")
- Language identifier for usage blocks.
-
-**Behavior:**
-
-``argparse_reorder_usage_before_examples`` : bool (default: True)
- Whether to reorder nodes so usage appears before examples.
-
-Additional Features
--------------------
-- Removes ANSI escape codes (useful when FORCE_COLOR is set)
-- Applies syntax highlighting to usage blocks
-- Reorders sections so usage appears before examples in the output
-- Extracts sections from argparse_program containers for TOC visibility
-
-Project-Specific Setup
-----------------------
-Projects using this extension should register their own lexers and CSS in
-their conf.py setup() function. For example::
-
- def setup(app):
- from my_lexer import MyLexer
- app.add_lexer("my-output", MyLexer)
- app.add_css_file("css/my-highlight.css")
-"""
-
-from __future__ import annotations
-
-import dataclasses
-import typing as t
-
-from docutils import nodes
-from sphinx_argparse_neo.directive import ArgparseDirective
-from sphinx_argparse_neo.utils import strip_ansi
-
-if t.TYPE_CHECKING:
- import sphinx.config
- from sphinx.application import Sphinx
-
-
-@dataclasses.dataclass
-class ExemplarConfig:
- """Configuration for argparse_exemplar transformation.
-
- This dataclass provides all configurable options for the argparse_exemplar
- extension. Functions accept an optional config parameter with a factory
- default, allowing them to work standalone with defaults or accept custom
- config for full control.
-
- Attributes
- ----------
- examples_term_suffix : str
- Term must end with this string (case-insensitive) to be treated as an
- examples header. Default: "examples".
- examples_base_term : str
- Exact match (case-insensitive, after stripping ":") for the base
- examples section. Default: "examples".
- examples_section_title : str
- Title used for the base examples section. Default: "Examples".
- usage_pattern : str
- Text must start with this string (case-insensitive, after stripping
- whitespace) to be treated as a usage block. Default: "usage:".
- command_prefix : str
- Prefix added to each command line in examples code blocks.
- Default: "$ ".
- code_language : str
- Language identifier for examples code blocks. Default: "console".
- code_classes : tuple[str, ...]
- CSS classes added to examples code blocks.
- Default: ("highlight-console",).
- usage_code_language : str
- Language identifier for usage blocks. Default: "cli-usage".
- reorder_usage_before_examples : bool
- Whether to reorder nodes so usage appears before examples.
- Default: True.
-
- Examples
- --------
- Using default configuration:
-
- >>> config = ExemplarConfig()
- >>> config.examples_term_suffix
- 'examples'
- >>> config.command_prefix
- '$ '
-
- Custom configuration:
-
- >>> config = ExemplarConfig(
- ... command_prefix="> ",
- ... code_language="bash",
- ... )
- >>> config.command_prefix
- '> '
- >>> config.code_language
- 'bash'
- """
-
- # Term detection
- examples_term_suffix: str = "examples"
- examples_base_term: str = "examples"
- examples_section_title: str = "Examples"
-
- # Usage detection
- usage_pattern: str = "usage:"
-
- # Code block formatting
- command_prefix: str = "$ "
- code_language: str = "console"
- code_classes: tuple[str, ...] = ("highlight-console",)
- usage_code_language: str = "cli-usage"
-
- # Behavior
- reorder_usage_before_examples: bool = True
-
- @classmethod
- def from_sphinx_config(cls, config: sphinx.config.Config) -> ExemplarConfig:
- """Create ExemplarConfig from Sphinx configuration.
-
- Parameters
- ----------
- config : sphinx.config.Config
- The Sphinx configuration object.
-
- Returns
- -------
- ExemplarConfig
- Configuration populated from Sphinx config values.
-
- Examples
- --------
- This is typically called from a directive's run() method:
-
- >>> # In CleanArgParseDirective.run():
- >>> # config = ExemplarConfig.from_sphinx_config(self.env.config)
- """
- # Get code_classes as tuple (Sphinx stores lists)
- code_classes_raw = getattr(
- config, "argparse_examples_code_classes", ("highlight-console",)
- )
- code_classes = (
- tuple(code_classes_raw)
- if isinstance(code_classes_raw, list)
- else code_classes_raw
- )
-
- return cls(
- examples_term_suffix=getattr(
- config, "argparse_examples_term_suffix", "examples"
- ),
- examples_base_term=getattr(
- config, "argparse_examples_base_term", "examples"
- ),
- examples_section_title=getattr(
- config, "argparse_examples_section_title", "Examples"
- ),
- usage_pattern=getattr(config, "argparse_usage_pattern", "usage:"),
- command_prefix=getattr(config, "argparse_examples_command_prefix", "$ "),
- code_language=getattr(config, "argparse_examples_code_language", "console"),
- code_classes=code_classes,
- usage_code_language=getattr(
- config, "argparse_usage_code_language", "cli-usage"
- ),
- reorder_usage_before_examples=getattr(
- config, "argparse_reorder_usage_before_examples", True
- ),
- )
-
-
-# Re-export for backwards compatibility and public API
-__all__ = [
- "CleanArgParseDirective",
- "ExemplarConfig",
- "is_base_examples_term",
- "is_examples_term",
- "make_section_id",
- "make_section_title",
- "process_node",
- "strip_ansi",
- "transform_definition_list",
-]
-
-
-def is_examples_term(term_text: str, *, config: ExemplarConfig | None = None) -> bool:
- """Check if a definition term is an examples header.
-
- Parameters
- ----------
- term_text : str
- The text content of a definition term.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- bool
- True if this is an examples header.
-
- Examples
- --------
- >>> is_examples_term("examples:")
- True
- >>> is_examples_term("Machine-readable output examples:")
- True
- >>> is_examples_term("Usage:")
- False
-
- With custom configuration:
-
- >>> custom_config = ExemplarConfig(examples_term_suffix="demos")
- >>> is_examples_term("demos:", config=custom_config)
- True
- >>> is_examples_term("examples:", config=custom_config)
- False
- """
- config = config or ExemplarConfig()
- return term_text.lower().rstrip(":").endswith(config.examples_term_suffix)
-
-
-def is_base_examples_term(
- term_text: str, *, config: ExemplarConfig | None = None
-) -> bool:
- """Check if a definition term is a base "examples:" header (no prefix).
-
- Parameters
- ----------
- term_text : str
- The text content of a definition term.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- bool
- True if this is just "examples:" with no category prefix.
-
- Examples
- --------
- >>> is_base_examples_term("examples:")
- True
- >>> is_base_examples_term("Examples")
- True
- >>> is_base_examples_term("Field-scoped examples:")
- False
-
- With custom configuration:
-
- >>> custom_config = ExemplarConfig(examples_base_term="demos")
- >>> is_base_examples_term("demos:", config=custom_config)
- True
- >>> is_base_examples_term("examples:", config=custom_config)
- False
- """
- config = config or ExemplarConfig()
- return term_text.lower().rstrip(":").strip() == config.examples_base_term
-
-
-def make_section_id(
- term_text: str,
- counter: int = 0,
- *,
- is_subsection: bool = False,
- page_prefix: str = "",
- config: ExemplarConfig | None = None,
-) -> str:
- """Generate a section ID from an examples term.
-
- Parameters
- ----------
- term_text : str
- The examples term text (e.g., "Machine-readable output: examples:")
- counter : int
- Counter for uniqueness if multiple examples sections exist.
- is_subsection : bool
- If True, omit "-examples" suffix for cleaner nested IDs.
- page_prefix : str
- Optional prefix from the page name (e.g., "sync", "add") to ensure
- uniqueness across different documentation pages.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- str
- A normalized section ID.
-
- Examples
- --------
- >>> make_section_id("examples:")
- 'examples'
- >>> make_section_id("examples:", page_prefix="sync")
- 'sync-examples'
- >>> make_section_id("Machine-readable output examples:")
- 'machine-readable-output-examples'
- >>> make_section_id("Field-scoped examples:", is_subsection=True)
- 'field-scoped'
- >>> make_section_id("examples:", counter=1)
- 'examples-1'
-
- With custom configuration:
-
- >>> custom_config = ExemplarConfig(examples_term_suffix="demos")
- >>> make_section_id("demos:", config=custom_config)
- 'demos'
- >>> make_section_id("Machine-readable output demos:", config=custom_config)
- 'machine-readable-output-demos'
- """
- config = config or ExemplarConfig()
- term_suffix = config.examples_term_suffix
-
- # Extract prefix before the term suffix (e.g., "Machine-readable output")
- lower_text = term_text.lower().rstrip(":")
- if term_suffix in lower_text:
- prefix = lower_text.rsplit(term_suffix, 1)[0].strip()
- # Remove trailing colon from prefix (handles ": examples" pattern)
- prefix = prefix.rstrip(":").strip()
- if prefix:
- normalized_prefix = prefix.replace(" ", "-")
- # Subsections don't need "-examples" suffix
- if is_subsection:
- section_id = normalized_prefix
- else:
- section_id = f"{normalized_prefix}-{term_suffix}"
- else:
- # Plain "examples" - add page prefix if provided for uniqueness
- section_id = f"{page_prefix}-{term_suffix}" if page_prefix else term_suffix
- else:
- section_id = term_suffix
-
- # Add counter suffix for uniqueness
- if counter > 0:
- section_id = f"{section_id}-{counter}"
-
- return section_id
-
-
-def make_section_title(
- term_text: str,
- *,
- is_subsection: bool = False,
- config: ExemplarConfig | None = None,
-) -> str:
- """Generate a section title from an examples term.
-
- Parameters
- ----------
- term_text : str
- The examples term text (e.g., "Machine-readable output: examples:")
- is_subsection : bool
- If True, omit "Examples" suffix for cleaner nested titles.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- str
- A proper title (e.g., "Machine-readable Output Examples" or just
- "Machine-Readable Output" if is_subsection=True).
-
- Examples
- --------
- >>> make_section_title("examples:")
- 'Examples'
- >>> make_section_title("Machine-readable output examples:")
- 'Machine-Readable Output Examples'
- >>> make_section_title("Field-scoped examples:", is_subsection=True)
- 'Field-Scoped'
-
- With custom configuration:
-
- >>> custom_config = ExemplarConfig(
- ... examples_base_term="demos",
- ... examples_term_suffix="demos",
- ... examples_section_title="Demos",
- ... )
- >>> make_section_title("demos:", config=custom_config)
- 'Demos'
- >>> make_section_title("Machine-readable output demos:", config=custom_config)
- 'Machine-Readable Output Demos'
- """
- config = config or ExemplarConfig()
- base_term = config.examples_base_term
- term_suffix = config.examples_term_suffix
- section_title = config.examples_section_title
-
- # Remove trailing colon and normalize
- text = term_text.rstrip(":").strip()
- # Handle base term case (e.g., "examples:")
- if text.lower() == base_term:
- return section_title
-
- # Extract the prefix (category name) before the term suffix
- lower = text.lower()
- colon_suffix = f": {term_suffix}"
- space_suffix = f" {term_suffix}"
- if lower.endswith(colon_suffix):
- prefix = text[: -len(colon_suffix)]
- elif lower.endswith(space_suffix):
- prefix = text[: -len(space_suffix)]
- else:
- prefix = text
-
- # Title case the prefix
- titled_prefix = prefix.title()
-
- # For subsections, just use the prefix (cleaner nested titles)
- if is_subsection:
- return titled_prefix
-
- # For top-level sections, append the section title
- return f"{titled_prefix} {section_title}"
-
-
-def _create_example_section(
- term_text: str,
- def_node: nodes.definition,
- *,
- is_subsection: bool = False,
- page_prefix: str = "",
- config: ExemplarConfig | None = None,
-) -> nodes.section:
- """Create a section node for an examples item.
-
- Parameters
- ----------
- term_text : str
- The examples term text.
- def_node : nodes.definition
- The definition node containing example commands.
- is_subsection : bool
- If True, create a subsection with simpler title/id.
- page_prefix : str
- Optional prefix from the page name for unique section IDs.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- nodes.section
- A section node with title and code blocks.
-
- Examples
- --------
- Create a section from a definition node containing example commands:
-
- >>> from docutils import nodes
- >>> def_node = nodes.definition()
- >>> def_node += nodes.paragraph(text="myapp sync")
- >>> section = _create_example_section("examples:", def_node)
- >>> section["ids"]
- ['examples']
- >>> section[0].astext()
- 'Examples'
-
- With a page prefix for uniqueness across documentation pages:
-
- >>> section = _create_example_section("examples:", def_node, page_prefix="sync")
- >>> section["ids"]
- ['sync-examples']
-
- Category-prefixed examples create descriptive section IDs:
-
- >>> section = _create_example_section("Machine-readable output examples:", def_node)
- >>> section["ids"]
- ['machine-readable-output-examples']
- >>> section[0].astext()
- 'Machine-Readable Output Examples'
- """
- config = config or ExemplarConfig()
- section_id = make_section_id(
- term_text, is_subsection=is_subsection, page_prefix=page_prefix, config=config
- )
- section_title = make_section_title(
- term_text, is_subsection=is_subsection, config=config
- )
-
- section = nodes.section()
- section["ids"] = [section_id]
- section["names"] = [nodes.fully_normalize_name(section_title)]
-
- title = nodes.title(text=section_title)
- section += title
-
- # Extract commands from definition and create separate code blocks
- def_text = strip_ansi(def_node.astext())
- for line in def_text.split("\n"):
- line = line.strip()
- if line:
- code_block = nodes.literal_block(
- text=f"{config.command_prefix}{line}",
- classes=list(config.code_classes),
- )
- code_block["language"] = config.code_language
- section += code_block
-
- return section
-
-
-def transform_definition_list(
- dl_node: nodes.definition_list,
- *,
- page_prefix: str = "",
- config: ExemplarConfig | None = None,
-) -> list[nodes.Node]:
- """Transform a definition list, converting examples items to code blocks.
-
- If there's a base "examples:" item followed by category-specific examples
- (e.g., "Field-scoped: examples:"), the categories are nested under the
- parent Examples section for cleaner ToC structure.
-
- Parameters
- ----------
- dl_node : nodes.definition_list
- A definition list node.
- page_prefix : str
- Optional prefix from the page name for unique section IDs.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- list[nodes.Node]
- Transformed nodes - code blocks for examples, original for others.
-
- Note
- ----
- **Intentional reordering behavior:** This function always emits non-example
- items (preamble text, descriptions, etc.) before example sections, regardless
- of their original position in the definition list. This "flush first" approach
- groups conceptually related content: introductory material appears before
- examples, even if the source document interleaves them. This produces cleaner
- documentation structure where descriptions introduce their examples.
-
- If you need to preserve the original interleaved order, you would need to
- modify this function to track item positions during the first pass.
- """
- config = config or ExemplarConfig()
-
- # First pass: collect examples and non-examples items separately
- example_items: list[tuple[str, nodes.definition]] = [] # (term_text, def_node)
- non_example_items: list[nodes.Node] = []
- base_examples_index: int | None = None
-
- for item in dl_node.children:
- if not isinstance(item, nodes.definition_list_item):
- continue
-
- # Get the term and definition
- term_node = None
- def_node = None
- for child in item.children:
- if isinstance(child, nodes.term):
- term_node = child
- elif isinstance(child, nodes.definition):
- def_node = child
-
- if term_node is None or def_node is None:
- non_example_items.append(item)
- continue
-
- term_text = strip_ansi(term_node.astext())
-
- if is_examples_term(term_text, config=config):
- if is_base_examples_term(term_text, config=config):
- base_examples_index = len(example_items)
- example_items.append((term_text, def_node))
- else:
- non_example_items.append(item)
-
- # Build result nodes
- result_nodes: list[nodes.Node] = []
-
- # Emit non-example items first (see docstring Note on reordering behavior)
- if non_example_items:
- new_dl = nodes.definition_list()
- new_dl.extend(non_example_items)
- result_nodes.append(new_dl)
-
- # Determine nesting strategy
- # Nest if: there's a base "examples:" AND at least one other example category
- should_nest = base_examples_index is not None and len(example_items) > 1
-
- if should_nest and base_examples_index is not None:
- # Create parent "Examples" section
- base_term, base_def = example_items[base_examples_index]
- parent_section = _create_example_section(
- base_term,
- base_def,
- is_subsection=False,
- page_prefix=page_prefix,
- config=config,
- )
-
- # Add other examples as nested subsections
- for i, (term_text, def_node) in enumerate(example_items):
- if i == base_examples_index:
- continue # Skip the base (already used as parent)
- subsection = _create_example_section(
- term_text,
- def_node,
- is_subsection=True,
- page_prefix=page_prefix,
- config=config,
- )
- parent_section += subsection
-
- result_nodes.append(parent_section)
- else:
- # No nesting - create flat sections (backwards compatible)
- for term_text, def_node in example_items:
- section = _create_example_section(
- term_text,
- def_node,
- is_subsection=False,
- page_prefix=page_prefix,
- config=config,
- )
- result_nodes.append(section)
-
- return result_nodes
-
-
-def process_node(
- node: nodes.Node,
- *,
- page_prefix: str = "",
- config: ExemplarConfig | None = None,
-) -> nodes.Node | list[nodes.Node]:
- """Process a node: strip ANSI codes and transform examples.
-
- Parameters
- ----------
- node : nodes.Node
- A docutils node to process.
- page_prefix : str
- Optional prefix from the page name for unique section IDs.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- nodes.Node | list[nodes.Node]
- The processed node(s).
- """
- config = config or ExemplarConfig()
-
- # Handle text nodes - strip ANSI
- if isinstance(node, nodes.Text):
- cleaned = strip_ansi(node.astext())
- if cleaned != node.astext():
- return nodes.Text(cleaned)
- return node
-
- # Handle definition lists - transform examples
- if isinstance(node, nodes.definition_list):
- # Check if any items are examples
- has_examples = False
- for item in node.children:
- if isinstance(item, nodes.definition_list_item):
- for child in item.children:
- if isinstance(child, nodes.term) and is_examples_term(
- strip_ansi(child.astext()), config=config
- ):
- has_examples = True
- break
- if has_examples:
- break
-
- if has_examples:
- return transform_definition_list(
- node, page_prefix=page_prefix, config=config
- )
-
- # Handle literal_block nodes - strip ANSI and apply usage highlighting
- if isinstance(node, nodes.literal_block):
- text = strip_ansi(node.astext())
- needs_update = text != node.astext()
-
- # Check if this is a usage block (starts with configured pattern)
- is_usage = text.lstrip().lower().startswith(config.usage_pattern.lower())
-
- if needs_update or is_usage:
- new_block = nodes.literal_block(text=text)
- # Preserve attributes
- for attr in ("language", "classes"):
- if attr in node:
- new_block[attr] = node[attr]
- # Apply configured language to usage blocks
- if is_usage:
- new_block["language"] = config.usage_code_language
- return new_block
- return node
-
- # Handle paragraph nodes - strip ANSI and lift sections out
- if isinstance(node, nodes.paragraph):
- # Process children and check if any become sections
- processed_children: list[nodes.Node] = []
- changed = False
- has_sections = False
-
- for child in node.children:
- if isinstance(child, nodes.Text):
- cleaned = strip_ansi(child.astext())
- if cleaned != child.astext():
- processed_children.append(nodes.Text(cleaned))
- changed = True
- else:
- processed_children.append(child)
- else:
- result = process_node(child, page_prefix=page_prefix, config=config)
- if isinstance(result, list):
- processed_children.extend(result)
- changed = True
- # Check if any results are sections
- if any(isinstance(r, nodes.section) for r in result):
- has_sections = True
- elif result is not child:
- processed_children.append(result)
- changed = True
- if isinstance(result, nodes.section):
- has_sections = True
- else:
- processed_children.append(child)
-
- if not changed:
- return node
-
- # If no sections, return a normal paragraph
- if not has_sections:
- new_para = nodes.paragraph()
- new_para.extend(processed_children)
- return new_para
-
- # Sections found - lift them out of the paragraph
- # Return a list: [para_before, section1, section2, ..., para_after]
- result_nodes: list[nodes.Node] = []
- current_para_children: list[nodes.Node] = []
-
- for child in processed_children:
- if isinstance(child, nodes.section):
- # Flush current paragraph content
- if current_para_children:
- para = nodes.paragraph()
- para.extend(current_para_children)
- result_nodes.append(para)
- current_para_children = []
- # Add section as a sibling
- result_nodes.append(child)
- else:
- current_para_children.append(child)
-
- # Flush remaining paragraph content
- if current_para_children:
- para = nodes.paragraph()
- para.extend(current_para_children)
- result_nodes.append(para)
-
- return result_nodes
-
- # Recursively process children for other node types
- if hasattr(node, "children"):
- new_children: list[nodes.Node] = []
- children_changed = False
- for child in node.children:
- result = process_node(child, page_prefix=page_prefix, config=config)
- if isinstance(result, list):
- new_children.extend(result)
- children_changed = True
- elif result is not child:
- new_children.append(result)
- children_changed = True
- else:
- new_children.append(child)
- if children_changed:
- node[:] = new_children # type: ignore[index]
-
- return node
-
-
-def _is_usage_block(node: nodes.Node, *, config: ExemplarConfig | None = None) -> bool:
- """Check if a node is a usage literal block.
-
- Parameters
- ----------
- node : nodes.Node
- A docutils node to check.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- bool
- True if this is a usage block (literal_block starting with usage pattern).
-
- Examples
- --------
- >>> from docutils import nodes
- >>> _is_usage_block(nodes.literal_block(text="usage: cmd [-h]"))
- True
- >>> _is_usage_block(nodes.literal_block(text="Usage: myapp sync"))
- True
- >>> _is_usage_block(nodes.literal_block(text=" usage: cmd"))
- True
- >>> _is_usage_block(nodes.literal_block(text="some other text"))
- False
- >>> _is_usage_block(nodes.paragraph(text="usage: cmd"))
- False
- >>> _is_usage_block(nodes.section())
- False
-
- With custom configuration:
-
- >>> custom_config = ExemplarConfig(usage_pattern="synopsis:")
- >>> _is_usage_block(nodes.literal_block(text="synopsis: cmd"), config=custom_config)
- True
- >>> _is_usage_block(nodes.literal_block(text="usage: cmd"), config=custom_config)
- False
- """
- config = config or ExemplarConfig()
- if not isinstance(node, nodes.literal_block):
- return False
- text = node.astext()
- return text.lstrip().lower().startswith(config.usage_pattern.lower())
-
-
-def _is_usage_section(node: nodes.Node) -> bool:
- """Check if a node is a usage section.
-
- Parameters
- ----------
- node : nodes.Node
- A docutils node to check.
-
- Returns
- -------
- bool
- True if this is a section with "usage" in its ID.
-
- Examples
- --------
- >>> from docutils import nodes
- >>> section = nodes.section()
- >>> section["ids"] = ["usage"]
- >>> _is_usage_section(section)
- True
- >>> section2 = nodes.section()
- >>> section2["ids"] = ["sync-usage"]
- >>> _is_usage_section(section2)
- True
- >>> section3 = nodes.section()
- >>> section3["ids"] = ["options"]
- >>> _is_usage_section(section3)
- False
- >>> _is_usage_section(nodes.paragraph())
- False
- """
- if not isinstance(node, nodes.section):
- return False
- ids: list[str] = node.get("ids", [])
- return any(id_str == "usage" or id_str.endswith("-usage") for id_str in ids)
-
-
-def _is_examples_section(
- node: nodes.Node, *, config: ExemplarConfig | None = None
-) -> bool:
- """Check if a node is an examples section.
-
- Parameters
- ----------
- node : nodes.Node
- A docutils node to check.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- bool
- True if this is an examples section (section with term suffix in its ID).
-
- Examples
- --------
- >>> from docutils import nodes
- >>> section = nodes.section()
- >>> section["ids"] = ["examples"]
- >>> _is_examples_section(section)
- True
- >>> section2 = nodes.section()
- >>> section2["ids"] = ["machine-readable-output-examples"]
- >>> _is_examples_section(section2)
- True
- >>> section3 = nodes.section()
- >>> section3["ids"] = ["positional-arguments"]
- >>> _is_examples_section(section3)
- False
- >>> _is_examples_section(nodes.paragraph())
- False
- >>> _is_examples_section(nodes.literal_block(text="examples"))
- False
-
- With custom configuration:
-
- >>> custom_config = ExemplarConfig(examples_term_suffix="demos")
- >>> section = nodes.section()
- >>> section["ids"] = ["demos"]
- >>> _is_examples_section(section, config=custom_config)
- True
- >>> section2 = nodes.section()
- >>> section2["ids"] = ["examples"]
- >>> _is_examples_section(section2, config=custom_config)
- False
- """
- config = config or ExemplarConfig()
- if not isinstance(node, nodes.section):
- return False
- ids: list[str] = node.get("ids", [])
- return any(config.examples_term_suffix in id_str.lower() for id_str in ids)
-
-
-def _reorder_nodes(
- processed: list[nodes.Node], *, config: ExemplarConfig | None = None
-) -> list[nodes.Node]:
- """Reorder nodes so usage sections/blocks appear before examples sections.
-
- This ensures the CLI usage synopsis appears above examples in the
- documentation, making it easier to understand command syntax before
- seeing example invocations.
-
- The function handles both:
- - Usage as literal_block (legacy format from older renderer)
- - Usage as section#usage (new format with TOC support)
-
- Parameters
- ----------
- processed : list[nodes.Node]
- List of processed docutils nodes.
- config : ExemplarConfig | None
- Optional configuration. If None, uses default ExemplarConfig().
-
- Returns
- -------
- list[nodes.Node]
- Reordered nodes with usage before examples (if enabled).
-
- Examples
- --------
- >>> from docutils import nodes
-
- Create test nodes:
-
- >>> desc = nodes.paragraph(text="Description")
- >>> examples = nodes.section()
- >>> examples["ids"] = ["examples"]
- >>> usage = nodes.literal_block(text="usage: cmd [-h]")
- >>> args = nodes.section()
- >>> args["ids"] = ["arguments"]
-
- When usage appears after examples, it gets moved before:
-
- >>> result = _reorder_nodes([desc, examples, usage, args])
- >>> [type(n).__name__ for n in result]
- ['paragraph', 'literal_block', 'section', 'section']
-
- When no examples exist, order is unchanged:
-
- >>> result = _reorder_nodes([desc, usage, args])
- >>> [type(n).__name__ for n in result]
- ['paragraph', 'literal_block', 'section']
-
- When usage already before examples, order is preserved:
-
- >>> result = _reorder_nodes([desc, usage, examples, args])
- >>> [type(n).__name__ for n in result]
- ['paragraph', 'literal_block', 'section', 'section']
-
- Empty list returns empty:
-
- >>> _reorder_nodes([])
- []
-
- Usage sections (with TOC heading) are also handled:
-
- >>> usage_section = nodes.section()
- >>> usage_section["ids"] = ["usage"]
- >>> result = _reorder_nodes([desc, examples, usage_section, args])
- >>> [n.get("ids", []) for n in result if isinstance(n, nodes.section)]
- [['usage'], ['examples'], ['arguments']]
-
- Reordering can be disabled via config:
-
- >>> no_reorder_config = ExemplarConfig(reorder_usage_before_examples=False)
- >>> result = _reorder_nodes([desc, examples, usage, args], config=no_reorder_config)
- >>> [type(n).__name__ for n in result]
- ['paragraph', 'section', 'literal_block', 'section']
- """
- config = config or ExemplarConfig()
-
- # If reordering is disabled, return as-is
- if not config.reorder_usage_before_examples:
- return processed
-
- # First pass: check if there are any examples sections
- has_examples = any(_is_examples_section(node, config=config) for node in processed)
- if not has_examples:
- # No examples, preserve original order
- return processed
-
- usage_nodes: list[nodes.Node] = []
- examples_sections: list[nodes.Node] = []
- other_before_examples: list[nodes.Node] = []
- other_after_examples: list[nodes.Node] = []
-
- seen_examples = False
- for node in processed:
- # Check for both usage block (literal_block) and usage section
- if _is_usage_block(node, config=config) or _is_usage_section(node):
- usage_nodes.append(node)
- elif _is_examples_section(node, config=config):
- examples_sections.append(node)
- seen_examples = True
- elif not seen_examples:
- other_before_examples.append(node)
- else:
- other_after_examples.append(node)
-
- # Order: before_examples → usage → examples → after_examples
- return (
- other_before_examples + usage_nodes + examples_sections + other_after_examples
- )
-
-
-def _extract_sections_from_container(
- container: nodes.Node,
-) -> tuple[nodes.Node, list[nodes.section]]:
- """Extract section nodes from a container, returning modified container.
-
- This function finds any section nodes that are children of the container
- (typically argparse_program), removes them from the container, and returns
- them separately so they can be made siblings.
-
- This is needed because Sphinx's TocTreeCollector only discovers sections
- that are direct children of the document or properly nested in the section
- hierarchy - sections inside arbitrary div containers are invisible to TOC.
-
- Parameters
- ----------
- container : nodes.Node
- A container node (typically argparse_program) that may contain sections.
-
- Returns
- -------
- tuple[nodes.Node, list[nodes.section]]
- A tuple of (modified_container, extracted_sections).
-
- Examples
- --------
- >>> from docutils import nodes
- >>> from sphinx_argparse_neo.nodes import argparse_program
- >>> container = argparse_program()
- >>> para = nodes.paragraph(text="Description")
- >>> examples = nodes.section()
- >>> examples["ids"] = ["examples"]
- >>> container += para
- >>> container += examples
- >>> modified, extracted = _extract_sections_from_container(container)
- >>> len(modified.children)
- 1
- >>> len(extracted)
- 1
- >>> extracted[0]["ids"]
- ['examples']
- """
- if not hasattr(container, "children"):
- return container, []
-
- extracted_sections: list[nodes.section] = []
- remaining_children: list[nodes.Node] = []
-
- for child in container.children:
- if isinstance(child, nodes.section):
- extracted_sections.append(child)
- else:
- remaining_children.append(child)
-
- # Update container with remaining children only
- container[:] = remaining_children # type: ignore[index]
-
- return container, extracted_sections
-
-
-class CleanArgParseDirective(ArgparseDirective): # type: ignore[misc]
- """ArgParse directive that strips ANSI codes and formats examples."""
-
- def run(self) -> list[nodes.Node]:
- """Run the directive, clean output, format examples, and reorder.
-
- The processing pipeline:
- 1. Run base directive to get initial nodes
- 2. Load configuration from Sphinx config
- 3. Process each node (strip ANSI, transform examples definition lists)
- 4. Extract sections from inside argparse_program containers
- 5. Reorder so usage appears before examples (if enabled)
- """
- result = super().run()
-
- # Load configuration from Sphinx
- config = ExemplarConfig.from_sphinx_config(self.env.config)
-
- # Extract page name for unique section IDs across different CLI pages
- page_prefix = ""
- if hasattr(self.state, "document"):
- settings = self.state.document.settings
- if hasattr(settings, "env") and hasattr(settings.env, "docname"):
- # docname is like "cli/sync" - extract "sync"
- docname = settings.env.docname
- page_prefix = docname.split("/")[-1]
-
- processed: list[nodes.Node] = []
- for node in result:
- processed_node = process_node(node, page_prefix=page_prefix, config=config)
- if isinstance(processed_node, list):
- processed.extend(processed_node)
- else:
- processed.append(processed_node)
-
- # Extract sections from inside argparse_program containers
- # This is needed because sections inside divs are invisible to Sphinx TOC
- flattened: list[nodes.Node] = []
- for node in processed:
- # Check if this is an argparse_program (or similar container)
- # that might have sections inside
- node_class_name = type(node).__name__
- if node_class_name == "argparse_program":
- modified, extracted = _extract_sections_from_container(node)
- flattened.append(modified)
- flattened.extend(extracted)
- else:
- flattened.append(node)
-
- # Reorder: usage sections/blocks before examples sections
- return _reorder_nodes(flattened, config=config)
-
-
-def setup(app: Sphinx) -> dict[str, t.Any]:
- """Register the clean argparse directive, lexers, and CLI roles.
-
- Configuration Options
- ---------------------
- The following configuration options can be set in conf.py:
-
- ``argparse_examples_term_suffix`` : str (default: "examples")
- Term must end with this string to be treated as examples header.
-
- ``argparse_examples_base_term`` : str (default: "examples")
- Exact match for the base examples section.
-
- ``argparse_examples_section_title`` : str (default: "Examples")
- Title used for the base examples section.
-
- ``argparse_usage_pattern`` : str (default: "usage:")
- Text must start with this to be treated as a usage block.
-
- ``argparse_examples_command_prefix`` : str (default: "$ ")
- Prefix added to each command line in examples code blocks.
-
- ``argparse_examples_code_language`` : str (default: "console")
- Language identifier for examples code blocks.
-
- ``argparse_examples_code_classes`` : list[str] (default: ["highlight-console"])
- CSS classes added to examples code blocks.
-
- ``argparse_usage_code_language`` : str (default: "cli-usage")
- Language identifier for usage blocks.
-
- ``argparse_reorder_usage_before_examples`` : bool (default: True)
- Whether to reorder nodes so usage appears before examples.
-
- Parameters
- ----------
- app : Sphinx
- The Sphinx application object.
-
- Returns
- -------
- dict
- Extension metadata.
- """
- # Load the base sphinx_argparse_neo extension first
- app.setup_extension("sphinx_argparse_neo")
-
- # Register configuration options
- app.add_config_value("argparse_examples_term_suffix", "examples", "html")
- app.add_config_value("argparse_examples_base_term", "examples", "html")
- app.add_config_value("argparse_examples_section_title", "Examples", "html")
- app.add_config_value("argparse_usage_pattern", "usage:", "html")
- app.add_config_value("argparse_examples_command_prefix", "$ ", "html")
- app.add_config_value("argparse_examples_code_language", "console", "html")
- app.add_config_value(
- "argparse_examples_code_classes", ["highlight-console"], "html"
- )
- app.add_config_value("argparse_usage_code_language", "cli-usage", "html")
- app.add_config_value("argparse_reorder_usage_before_examples", True, "html")
-
- # Override the argparse directive with our enhanced version
- app.add_directive("argparse", CleanArgParseDirective, override=True)
-
- # Register CLI usage lexer for usage block highlighting
- from cli_usage_lexer import CLIUsageLexer
-
- app.add_lexer("cli-usage", CLIUsageLexer)
-
- # Register argparse lexers for help output highlighting
- from argparse_lexer import (
- ArgparseHelpLexer,
- ArgparseLexer,
- ArgparseUsageLexer,
- )
-
- app.add_lexer("argparse", ArgparseLexer)
- app.add_lexer("argparse-usage", ArgparseUsageLexer)
- app.add_lexer("argparse-help", ArgparseHelpLexer)
-
- # Register CLI inline roles for documentation
- from argparse_roles import register_roles
-
- register_roles()
-
- return {"version": "4.0", "parallel_read_safe": True}
diff --git a/docs/_ext/argparse_lexer.py b/docs/_ext/argparse_lexer.py
deleted file mode 100644
index 14aed55649..0000000000
--- a/docs/_ext/argparse_lexer.py
+++ /dev/null
@@ -1,429 +0,0 @@
-"""Pygments lexers for argparse help output.
-
-This module provides custom Pygments lexers for highlighting argparse-generated
-command-line help text, including usage lines, section headers, and full help output.
-
-Three lexer classes are provided:
-- ArgparseUsageLexer: For usage lines only
-- ArgparseHelpLexer: For full -h output (delegates usage to ArgparseUsageLexer)
-- ArgparseLexer: Smart auto-detecting wrapper
-"""
-
-from __future__ import annotations
-
-from pygments.lexer import RegexLexer, bygroups, include
-from pygments.token import Generic, Name, Operator, Punctuation, Text, Whitespace
-
-
-class ArgparseUsageLexer(RegexLexer):
- """Lexer for argparse usage lines only.
-
- Handles patterns like:
- - usage: PROG [-h] [--foo FOO] bar {a,b,c}
- - Mutually exclusive: [-a | -b], (--foo | --bar)
- - Choices: {json,yaml,table}
- - Variadic: FILE ..., [FILE ...], [--foo [FOO]]
-
- Examples
- --------
- >>> from pygments.token import Token
- >>> lexer = ArgparseUsageLexer()
- >>> tokens = list(lexer.get_tokens("usage: cmd [-h]"))
- >>> tokens[0]
- (Token.Generic.Heading, 'usage:')
- >>> tokens[2]
- (Token.Name.Label, 'cmd')
- """
-
- name = "Argparse Usage"
- aliases = ["argparse-usage"] # noqa: RUF012
- filenames: list[str] = [] # noqa: RUF012
- mimetypes = ["text/x-argparse-usage"] # noqa: RUF012
-
- tokens = { # noqa: RUF012
- "root": [
- # "usage:" at start of line - then look for program name
- (
- r"^(usage:)(\s+)",
- bygroups(Generic.Heading, Whitespace), # type: ignore[no-untyped-call]
- "after_usage",
- ),
- # Continuation lines (leading whitespace for wrapped usage)
- (r"^(\s+)(?=\S)", Whitespace),
- include("inline"),
- ],
- "after_usage": [
- # Whitespace
- (r"\s+", Whitespace),
- # Program name (first lowercase word after usage:)
- (r"\b[a-z][-a-z0-9_]*\b", Name.Label, "usage_body"),
- # Fallback to inline if something unexpected
- include("inline"),
- ],
- "usage_body": [
- # Whitespace
- (r"\s+", Whitespace),
- # Ellipsis for variadic args (before other patterns)
- (r"\.\.\.", Punctuation),
- # Long options with = value (e.g., --log-level=VALUE)
- (
- r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)",
- bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Long options standalone
- (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag),
- # Short options with space-separated value (e.g., -S socket-path)
- (
- r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)",
- bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Short options standalone
- (r"-[a-zA-Z0-9]", Name.Attribute),
- # Opening brace - enter choices state
- (r"\{", Punctuation, "choices"),
- # Opening bracket - enter optional state
- (r"\[", Punctuation, "optional"),
- # Closing bracket (fallback for unmatched)
- (r"\]", Punctuation),
- # Opening paren - enter required mutex state
- (r"\(", Punctuation, "required"),
- # Closing paren (fallback for unmatched)
- (r"\)", Punctuation),
- # Choice separator (pipe) for mutex groups
- (r"\|", Operator),
- # UPPERCASE meta-variables (COMMAND, FILE, PATH)
- (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable),
- # Subcommand/positional names (Name.Function for distinct styling)
- (r"\b[a-z][-a-z0-9_]*\b", Name.Function),
- # Catch-all for any other text
- (r"[^\s\[\]|(){},]+", Text),
- ],
- "inline": [
- # Whitespace
- (r"\s+", Whitespace),
- # Ellipsis for variadic args (before other patterns)
- (r"\.\.\.", Punctuation),
- # Long options with = value (e.g., --log-level=VALUE)
- (
- r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)",
- bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Long options standalone
- (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag),
- # Short options with space-separated value (e.g., -S socket-path)
- (
- r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)",
- bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Short options standalone
- (r"-[a-zA-Z0-9]", Name.Attribute),
- # Opening brace - enter choices state
- (r"\{", Punctuation, "choices"),
- # Opening bracket - enter optional state
- (r"\[", Punctuation, "optional"),
- # Closing bracket (fallback for unmatched)
- (r"\]", Punctuation),
- # Opening paren - enter required mutex state
- (r"\(", Punctuation, "required"),
- # Closing paren (fallback for unmatched)
- (r"\)", Punctuation),
- # Choice separator (pipe) for mutex groups
- (r"\|", Operator),
- # UPPERCASE meta-variables (COMMAND, FILE, PATH)
- (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable),
- # Positional/command names (lowercase with dashes)
- (r"\b[a-z][-a-z0-9_]*\b", Name.Label),
- # Catch-all for any other text
- (r"[^\s\[\]|(){},]+", Text),
- ],
- "optional": [
- # Nested optional bracket
- (r"\[", Punctuation, "#push"),
- # End optional
- (r"\]", Punctuation, "#pop"),
- # Contents use usage_body rules (subcommands are green)
- include("usage_body"),
- ],
- "required": [
- # Nested required paren
- (r"\(", Punctuation, "#push"),
- # End required
- (r"\)", Punctuation, "#pop"),
- # Contents use usage_body rules (subcommands are green)
- include("usage_body"),
- ],
- "choices": [
- # Choice values (comma-separated inside braces)
- (r"[a-zA-Z0-9][-a-zA-Z0-9_]*", Name.Constant),
- # Comma separator
- (r",", Punctuation),
- # End choices
- (r"\}", Punctuation, "#pop"),
- # Whitespace
- (r"\s+", Whitespace),
- ],
- }
-
-
-class ArgparseHelpLexer(RegexLexer):
- """Lexer for full argparse -h help output.
-
- Handles:
- - Usage lines (delegates to ArgparseUsageLexer patterns)
- - Section headers (positional arguments:, options:, etc.)
- - Option entries with help text
- - Indented descriptions
-
- Examples
- --------
- >>> from pygments.token import Token
- >>> lexer = ArgparseHelpLexer()
- >>> tokens = list(lexer.get_tokens("positional arguments:"))
- >>> any(t[0] == Token.Generic.Subheading for t in tokens)
- True
- >>> tokens = list(lexer.get_tokens(" -h, --help show help"))
- >>> any(t[0] == Token.Name.Attribute for t in tokens)
- True
- """
-
- name = "Argparse Help"
- aliases = ["argparse-help"] # noqa: RUF012
- filenames: list[str] = [] # noqa: RUF012
- mimetypes = ["text/x-argparse-help"] # noqa: RUF012
-
- tokens = { # noqa: RUF012
- "root": [
- # "usage:" line - switch to after_usage to find program name
- (
- r"^(usage:)(\s+)",
- bygroups(Generic.Heading, Whitespace), # type: ignore[no-untyped-call]
- "after_usage",
- ),
- # Section headers (e.g., "positional arguments:", "options:")
- (r"^([a-zA-Z][-a-zA-Z0-9_ ]*:)\s*$", Generic.Subheading),
- # Option entry lines (indented with spaces/tabs, not just newlines)
- (r"^([ \t]+)", Whitespace, "option_line"),
- # Continuation of usage (leading spaces/tabs followed by content)
- (r"^([ \t]+)(?=\S)", Whitespace),
- # Anything else (must match at least one char to avoid infinite loop)
- (r".+\n?", Text),
- # Standalone newlines
- (r"\n", Whitespace),
- ],
- "after_usage": [
- # Whitespace
- (r"\s+", Whitespace),
- # Program name (first lowercase word after usage:)
- (r"\b[a-z][-a-z0-9_]*\b", Name.Label, "usage"),
- # Fallback to usage if something unexpected
- include("usage_inline"),
- ],
- "usage": [
- # End of usage on blank line or section header
- (r"\n(?=[a-zA-Z][-a-zA-Z0-9_ ]*:\s*$)", Text, "#pop:2"),
- (r"\n(?=\n)", Text, "#pop:2"),
- # Usage content - use usage_inline rules (subcommands are green)
- include("usage_inline"),
- # Line continuation
- (r"\n", Text),
- ],
- "usage_inline": [
- # Whitespace
- (r"\s+", Whitespace),
- # Ellipsis for variadic args
- (r"\.\.\.", Punctuation),
- # Long options with = value
- (
- r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)",
- bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Long options standalone
- (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag),
- # Short options with value
- (
- r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)",
- bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Short options standalone
- (r"-[a-zA-Z0-9]", Name.Attribute),
- # Choices in braces
- (r"\{", Punctuation, "choices"),
- # Optional brackets
- (r"\[", Punctuation, "optional"),
- (r"\]", Punctuation),
- # Required parens (mutex)
- (r"\(", Punctuation, "required"),
- (r"\)", Punctuation),
- # Pipe for mutex
- (r"\|", Operator),
- # UPPERCASE metavars
- (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable),
- # Subcommand/positional names (Name.Function for distinct styling)
- (r"\b[a-z][-a-z0-9_]*\b", Name.Function),
- # Other text
- (r"[^\s\[\]|(){},\n]+", Text),
- ],
- "option_line": [
- # Short option with comma (e.g., "-h, --help")
- (
- r"(-[a-zA-Z0-9])(,)(\s*)(--[a-zA-Z0-9][-a-zA-Z0-9]*)",
- bygroups(Name.Attribute, Punctuation, Whitespace, Name.Tag), # type: ignore[no-untyped-call]
- ),
- # Long options with = value
- (
- r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)",
- bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Long options with space-separated metavar
- (
- r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(\s+)([A-Z][A-Z0-9_]+)",
- bygroups(Name.Tag, Whitespace, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Long options standalone
- (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag),
- # Short options with metavar
- (
- r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]+)",
- bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Short options standalone
- (r"-[a-zA-Z0-9]", Name.Attribute),
- # Choices in braces
- (r"\{", Punctuation, "option_choices"),
- # Help text (everything after double space or large gap)
- (r"([ \t]{2,})(.+)$", bygroups(Whitespace, Text)), # type: ignore[no-untyped-call]
- # End of line - MUST come before \s+ to properly pop on newlines
- (r"\n", Text, "#pop"),
- # Other whitespace (spaces/tabs only, not newlines)
- (r"[ \t]+", Whitespace),
- # UPPERCASE metavars
- (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable),
- # Anything else on the line
- (r"[^\s\n]+", Text),
- ],
- "optional": [
- (r"\[", Punctuation, "#push"),
- (r"\]", Punctuation, "#pop"),
- include("usage_inline"),
- ],
- "required": [
- (r"\(", Punctuation, "#push"),
- (r"\)", Punctuation, "#pop"),
- include("usage_inline"),
- ],
- "choices": [
- (r"[a-zA-Z0-9][-a-zA-Z0-9_]*", Name.Constant),
- (r",", Punctuation),
- (r"\}", Punctuation, "#pop"),
- (r"\s+", Whitespace),
- ],
- "option_choices": [
- (r"[a-zA-Z0-9][-a-zA-Z0-9_]*", Name.Constant),
- (r",", Punctuation),
- (r"\}", Punctuation, "#pop"),
- (r"\s+", Whitespace),
- ],
- }
-
-
-class ArgparseLexer(ArgparseHelpLexer):
- """Smart auto-detecting lexer for argparse output.
-
- Inherits from ArgparseHelpLexer to properly handle Pygments' metaclass
- token processing. Using inheritance (not token dict copying) avoids
- shared mutable state that causes memory corruption.
-
- This is the recommended lexer for general argparse highlighting.
-
- Examples
- --------
- >>> from pygments.token import Token
- >>> lexer = ArgparseLexer()
-
- Usage line detection:
-
- >>> tokens = list(lexer.get_tokens("usage: cmd [-h]"))
- >>> tokens[0]
- (Token.Generic.Heading, 'usage:')
-
- Section header detection (Pygments appends newline to input):
-
- >>> tokens = list(lexer.get_tokens("positional arguments:"))
- >>> any(t[0] == Token.Generic.Subheading for t in tokens)
- True
-
- Option highlighting in option line context:
-
- >>> tokens = list(lexer.get_tokens(" -h, --help show help"))
- >>> any(t[0] == Token.Name.Attribute for t in tokens)
- True
- """
-
- name = "Argparse"
- aliases = ["argparse"] # noqa: RUF012
- filenames: list[str] = [] # noqa: RUF012
- mimetypes = ["text/x-argparse"] # noqa: RUF012
-
- # Tokens inherited from ArgparseHelpLexer - do NOT redefine or copy
-
-
-def tokenize_argparse(text: str) -> list[tuple[str, str]]:
- """Tokenize argparse text and return list of (token_type, value) tuples.
-
- Parameters
- ----------
- text : str
- Argparse help or usage text to tokenize.
-
- Returns
- -------
- list[tuple[str, str]]
- List of (token_type_name, text_value) tuples.
-
- Examples
- --------
- >>> result = tokenize_argparse("usage: cmd [-h]")
- >>> result[0]
- ('Token.Generic.Heading', 'usage:')
- >>> result[2]
- ('Token.Name.Label', 'cmd')
-
- >>> result = tokenize_argparse("positional arguments:")
- >>> any('Token.Generic.Subheading' in t[0] for t in result)
- True
- """
- lexer = ArgparseLexer()
- return [
- (str(tok_type), tok_value) for tok_type, tok_value in lexer.get_tokens(text)
- ]
-
-
-def tokenize_usage(text: str) -> list[tuple[str, str]]:
- """Tokenize usage text and return list of (token_type, value) tuples.
-
- Parameters
- ----------
- text : str
- CLI usage text to tokenize.
-
- Returns
- -------
- list[tuple[str, str]]
- List of (token_type_name, text_value) tuples.
-
- Examples
- --------
- >>> result = tokenize_usage("usage: cmd [-h]")
- >>> result[0]
- ('Token.Generic.Heading', 'usage:')
- >>> result[2]
- ('Token.Name.Label', 'cmd')
- >>> result[4]
- ('Token.Punctuation', '[')
- >>> result[5]
- ('Token.Name.Attribute', '-h')
- """
- lexer = ArgparseUsageLexer()
- return [
- (str(tok_type), tok_value) for tok_type, tok_value in lexer.get_tokens(text)
- ]
diff --git a/docs/_ext/argparse_roles.py b/docs/_ext/argparse_roles.py
deleted file mode 100644
index 86e5459a28..0000000000
--- a/docs/_ext/argparse_roles.py
+++ /dev/null
@@ -1,370 +0,0 @@
-"""Docutils inline roles for CLI/argparse highlighting.
-
-This module provides custom docutils roles for inline highlighting of CLI
-elements in reStructuredText and MyST documentation.
-
-Available roles:
-- :cli-option: - CLI options (--verbose, -h)
-- :cli-metavar: - Metavar placeholders (FILE, PATH)
-- :cli-command: - Command names (sync, add)
-- :cli-default: - Default values (None, "default")
-- :cli-choice: - Choice values (json, yaml)
-"""
-
-from __future__ import annotations
-
-import typing as t
-
-from docutils import nodes
-from docutils.parsers.rst import roles
-
-if t.TYPE_CHECKING:
- from docutils.parsers.rst.states import Inliner
-
-
-def normalize_options(options: dict[str, t.Any] | None) -> dict[str, t.Any]:
- """Normalize role options, converting None to empty dict.
-
- Parameters
- ----------
- options : dict | None
- Options passed to the role.
-
- Returns
- -------
- dict
- Normalized options dict (never None).
-
- Examples
- --------
- >>> normalize_options(None)
- {}
- >>> normalize_options({"class": "custom"})
- {'class': 'custom'}
- """
- return options if options is not None else {}
-
-
-def cli_option_role(
- name: str,
- rawtext: str,
- text: str,
- lineno: int,
- inliner: Inliner | None,
- options: dict[str, t.Any] | None = None,
- content: list[str] | None = None,
-) -> tuple[list[nodes.Node], list[nodes.system_message]]:
- """Role for CLI options like --foo or -h.
-
- Generates a literal node with appropriate CSS classes for styling.
- Long options (--foo) get 'cli-option-long', short options (-h) get
- 'cli-option-short'.
-
- Parameters
- ----------
- name : str
- Local name of the role used in document.
- rawtext : str
- Full interpreted text including role markup.
- text : str
- Content between backticks.
- lineno : int
- Line number.
- inliner : Inliner | None
- Object that called the role (has .reporter, .document).
- options : dict | None
- Options from role directive.
- content : list | None
- Content from role directive.
-
- Returns
- -------
- tuple[list[nodes.Node], list[nodes.system_message]]
- Nodes to insert and any messages.
-
- Examples
- --------
- >>> node_list, messages = cli_option_role(
- ... "cli-option", ":cli-option:`--verbose`", "--verbose",
- ... 1, None
- ... )
- >>> node_list[0]["classes"]
- ['cli-option', 'cli-option-long']
-
- >>> node_list, messages = cli_option_role(
- ... "cli-option", ":cli-option:`-h`", "-h",
- ... 1, None
- ... )
- >>> node_list[0]["classes"]
- ['cli-option', 'cli-option-short']
-
- >>> node_list, messages = cli_option_role(
- ... "cli-option", ":cli-option:`--no-color`", "--no-color",
- ... 1, None
- ... )
- >>> node_list[0].astext()
- '--no-color'
- """
- options = normalize_options(options)
- node = nodes.literal(rawtext, text, classes=["cli-option"])
-
- if text.startswith("--"):
- node["classes"].append("cli-option-long")
- elif text.startswith("-"):
- node["classes"].append("cli-option-short")
-
- return [node], []
-
-
-def cli_metavar_role(
- name: str,
- rawtext: str,
- text: str,
- lineno: int,
- inliner: Inliner | None,
- options: dict[str, t.Any] | None = None,
- content: list[str] | None = None,
-) -> tuple[list[nodes.Node], list[nodes.system_message]]:
- """Role for CLI metavar placeholders like FILE or PATH.
-
- Generates a literal node with 'cli-metavar' CSS class for styling.
-
- Parameters
- ----------
- name : str
- Local name of the role used in document.
- rawtext : str
- Full interpreted text including role markup.
- text : str
- Content between backticks.
- lineno : int
- Line number.
- inliner : Inliner | None
- Object that called the role.
- options : dict | None
- Options from role directive.
- content : list | None
- Content from role directive.
-
- Returns
- -------
- tuple[list[nodes.Node], list[nodes.system_message]]
- Nodes to insert and any messages.
-
- Examples
- --------
- >>> node_list, messages = cli_metavar_role(
- ... "cli-metavar", ":cli-metavar:`FILE`", "FILE",
- ... 1, None
- ... )
- >>> node_list[0]["classes"]
- ['cli-metavar']
- >>> node_list[0].astext()
- 'FILE'
-
- >>> node_list, messages = cli_metavar_role(
- ... "cli-metavar", ":cli-metavar:`PATH`", "PATH",
- ... 1, None
- ... )
- >>> "cli-metavar" in node_list[0]["classes"]
- True
- """
- options = normalize_options(options)
- node = nodes.literal(rawtext, text, classes=["cli-metavar"])
- return [node], []
-
-
-def cli_command_role(
- name: str,
- rawtext: str,
- text: str,
- lineno: int,
- inliner: Inliner | None,
- options: dict[str, t.Any] | None = None,
- content: list[str] | None = None,
-) -> tuple[list[nodes.Node], list[nodes.system_message]]:
- """Role for CLI command names like sync or add.
-
- Generates a literal node with 'cli-command' CSS class for styling.
-
- Parameters
- ----------
- name : str
- Local name of the role used in document.
- rawtext : str
- Full interpreted text including role markup.
- text : str
- Content between backticks.
- lineno : int
- Line number.
- inliner : Inliner | None
- Object that called the role.
- options : dict | None
- Options from role directive.
- content : list | None
- Content from role directive.
-
- Returns
- -------
- tuple[list[nodes.Node], list[nodes.system_message]]
- Nodes to insert and any messages.
-
- Examples
- --------
- >>> node_list, messages = cli_command_role(
- ... "cli-command", ":cli-command:`sync`", "sync",
- ... 1, None
- ... )
- >>> node_list[0]["classes"]
- ['cli-command']
- >>> node_list[0].astext()
- 'sync'
-
- >>> node_list, messages = cli_command_role(
- ... "cli-command", ":cli-command:`myapp`", "myapp",
- ... 1, None
- ... )
- >>> "cli-command" in node_list[0]["classes"]
- True
- """
- options = normalize_options(options)
- node = nodes.literal(rawtext, text, classes=["cli-command"])
- return [node], []
-
-
-def cli_default_role(
- name: str,
- rawtext: str,
- text: str,
- lineno: int,
- inliner: Inliner | None,
- options: dict[str, t.Any] | None = None,
- content: list[str] | None = None,
-) -> tuple[list[nodes.Node], list[nodes.system_message]]:
- """Role for CLI default values like None or "default".
-
- Generates a literal node with 'cli-default' CSS class for styling.
-
- Parameters
- ----------
- name : str
- Local name of the role used in document.
- rawtext : str
- Full interpreted text including role markup.
- text : str
- Content between backticks.
- lineno : int
- Line number.
- inliner : Inliner | None
- Object that called the role.
- options : dict | None
- Options from role directive.
- content : list | None
- Content from role directive.
-
- Returns
- -------
- tuple[list[nodes.Node], list[nodes.system_message]]
- Nodes to insert and any messages.
-
- Examples
- --------
- >>> node_list, messages = cli_default_role(
- ... "cli-default", ":cli-default:`None`", "None",
- ... 1, None
- ... )
- >>> node_list[0]["classes"]
- ['cli-default']
- >>> node_list[0].astext()
- 'None'
-
- >>> node_list, messages = cli_default_role(
- ... "cli-default", ':cli-default:`"auto"`', '"auto"',
- ... 1, None
- ... )
- >>> "cli-default" in node_list[0]["classes"]
- True
- """
- options = normalize_options(options)
- node = nodes.literal(rawtext, text, classes=["cli-default"])
- return [node], []
-
-
-def cli_choice_role(
- name: str,
- rawtext: str,
- text: str,
- lineno: int,
- inliner: Inliner | None,
- options: dict[str, t.Any] | None = None,
- content: list[str] | None = None,
-) -> tuple[list[nodes.Node], list[nodes.system_message]]:
- """Role for CLI choice values like json or yaml.
-
- Generates a literal node with 'cli-choice' CSS class for styling.
-
- Parameters
- ----------
- name : str
- Local name of the role used in document.
- rawtext : str
- Full interpreted text including role markup.
- text : str
- Content between backticks.
- lineno : int
- Line number.
- inliner : Inliner | None
- Object that called the role.
- options : dict | None
- Options from role directive.
- content : list | None
- Content from role directive.
-
- Returns
- -------
- tuple[list[nodes.Node], list[nodes.system_message]]
- Nodes to insert and any messages.
-
- Examples
- --------
- >>> node_list, messages = cli_choice_role(
- ... "cli-choice", ":cli-choice:`json`", "json",
- ... 1, None
- ... )
- >>> node_list[0]["classes"]
- ['cli-choice']
- >>> node_list[0].astext()
- 'json'
-
- >>> node_list, messages = cli_choice_role(
- ... "cli-choice", ":cli-choice:`yaml`", "yaml",
- ... 1, None
- ... )
- >>> "cli-choice" in node_list[0]["classes"]
- True
- """
- options = normalize_options(options)
- node = nodes.literal(rawtext, text, classes=["cli-choice"])
- return [node], []
-
-
-def register_roles() -> None:
- """Register all CLI roles with docutils.
-
- This function registers the following roles:
- - cli-option: For CLI options (--verbose, -h)
- - cli-metavar: For metavar placeholders (FILE, PATH)
- - cli-command: For command names (sync, add)
- - cli-default: For default values (None, "default")
- - cli-choice: For choice values (json, yaml)
-
- Examples
- --------
- >>> register_roles()
- >>> # Roles are now available in docutils RST parsing
- """
- roles.register_local_role("cli-option", cli_option_role) # type: ignore[arg-type]
- roles.register_local_role("cli-metavar", cli_metavar_role) # type: ignore[arg-type]
- roles.register_local_role("cli-command", cli_command_role) # type: ignore[arg-type]
- roles.register_local_role("cli-default", cli_default_role) # type: ignore[arg-type]
- roles.register_local_role("cli-choice", cli_choice_role) # type: ignore[arg-type]
diff --git a/docs/_ext/cli_usage_lexer.py b/docs/_ext/cli_usage_lexer.py
deleted file mode 100644
index 40170e3178..0000000000
--- a/docs/_ext/cli_usage_lexer.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""Pygments lexer for CLI usage/help output.
-
-This module provides a custom Pygments lexer for highlighting command-line
-usage text typically generated by argparse, getopt, or similar libraries.
-"""
-
-from __future__ import annotations
-
-from pygments.lexer import RegexLexer, bygroups, include
-from pygments.token import Generic, Name, Operator, Punctuation, Text, Whitespace
-
-
-class CLIUsageLexer(RegexLexer):
- """Lexer for CLI usage/help text (argparse, etc.).
-
- Highlights usage patterns including options, arguments, and meta-variables.
-
- Examples
- --------
- >>> from pygments.token import Token
- >>> lexer = CLIUsageLexer()
- >>> tokens = list(lexer.get_tokens("usage: cmd [-h]"))
- >>> tokens[0]
- (Token.Generic.Heading, 'usage:')
- >>> tokens[2]
- (Token.Name.Label, 'cmd')
- """
-
- name = "CLI Usage"
- aliases = ["cli-usage", "usage"] # noqa: RUF012
- filenames: list[str] = [] # noqa: RUF012
- mimetypes = ["text/x-cli-usage"] # noqa: RUF012
-
- tokens = { # noqa: RUF012
- "root": [
- # "usage:" at start of line
- (r"^(usage:)(\s+)", bygroups(Generic.Heading, Whitespace)), # type: ignore[no-untyped-call]
- # Continuation lines (leading whitespace for wrapped usage)
- (r"^(\s+)(?=\S)", Whitespace),
- include("inline"),
- ],
- "inline": [
- # Whitespace
- (r"\s+", Whitespace),
- # Long options with = value (e.g., --log-level=VALUE)
- (
- r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)",
- bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Long options standalone
- (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag),
- # Short options with space-separated value (e.g., -S socket-path)
- (
- r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)",
- bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call]
- ),
- # Short options standalone
- (r"-[a-zA-Z0-9]", Name.Attribute),
- # UPPERCASE meta-variables (COMMAND, FILE, PATH)
- (r"\b[A-Z][A-Z0-9_]+\b", Name.Constant),
- # Opening bracket - enter optional state
- (r"\[", Punctuation, "optional"),
- # Closing bracket (fallback for unmatched)
- (r"\]", Punctuation),
- # Choice separator (pipe)
- (r"\|", Operator),
- # Parentheses for grouping
- (r"[()]", Punctuation),
- # Positional/command names (lowercase with dashes)
- (r"\b[a-z][-a-z0-9]*\b", Name.Label),
- # Catch-all for any other text
- (r"[^\s\[\]|()]+", Text),
- ],
- "optional": [
- # Nested optional bracket
- (r"\[", Punctuation, "#push"),
- # End optional
- (r"\]", Punctuation, "#pop"),
- # Contents use inline rules
- include("inline"),
- ],
- }
-
-
-def tokenize_usage(text: str) -> list[tuple[str, str]]:
- """Tokenize usage text and return list of (token_type, value) tuples.
-
- Parameters
- ----------
- text : str
- CLI usage text to tokenize.
-
- Returns
- -------
- list[tuple[str, str]]
- List of (token_type_name, text_value) tuples.
-
- Examples
- --------
- >>> result = tokenize_usage("usage: cmd [-h]")
- >>> result[0]
- ('Token.Generic.Heading', 'usage:')
- >>> result[2]
- ('Token.Name.Label', 'cmd')
- >>> result[4]
- ('Token.Punctuation', '[')
- >>> result[5]
- ('Token.Name.Attribute', '-h')
- >>> result[6]
- ('Token.Punctuation', ']')
- """
- lexer = CLIUsageLexer()
- return [
- (str(tok_type), tok_value) for tok_type, tok_value in lexer.get_tokens(text)
- ]
diff --git a/docs/_ext/sphinx_argparse_neo/__init__.py b/docs/_ext/sphinx_argparse_neo/__init__.py
deleted file mode 100644
index 5fa8dd94fe..0000000000
--- a/docs/_ext/sphinx_argparse_neo/__init__.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""sphinx_argparse_neo - Modern sphinx-argparse replacement.
-
-A Sphinx extension for documenting argparse-based CLI tools that:
-- Works with Sphinx 8.x AND 9.x (no autodoc.mock dependency)
-- Fixes long-standing sphinx-argparse issues (TOC pollution, heading levels)
-- Provides configurable output (rubrics vs sections, flattened subcommands)
-- Supports extensibility via renderer classes
-- Text processing utilities (ANSI stripping)
-"""
-
-from __future__ import annotations
-
-import typing as t
-
-from sphinx_argparse_neo.directive import ArgparseDirective
-from sphinx_argparse_neo.nodes import (
- argparse_argument,
- argparse_group,
- argparse_program,
- argparse_subcommand,
- argparse_subcommands,
- argparse_usage,
- depart_argparse_argument_html,
- depart_argparse_group_html,
- depart_argparse_program_html,
- depart_argparse_subcommand_html,
- depart_argparse_subcommands_html,
- depart_argparse_usage_html,
- visit_argparse_argument_html,
- visit_argparse_group_html,
- visit_argparse_program_html,
- visit_argparse_subcommand_html,
- visit_argparse_subcommands_html,
- visit_argparse_usage_html,
-)
-from sphinx_argparse_neo.utils import strip_ansi
-
-__all__ = [
- "ArgparseDirective",
- "strip_ansi",
-]
-
-if t.TYPE_CHECKING:
- from sphinx.application import Sphinx
-
-__version__ = "1.0.0"
-
-
-def setup(app: Sphinx) -> dict[str, t.Any]:
- """Register the argparse directive and configuration options.
-
- Parameters
- ----------
- app : Sphinx
- The Sphinx application object.
-
- Returns
- -------
- dict[str, t.Any]
- Extension metadata.
- """
- # Configuration options
- app.add_config_value("argparse_group_title_prefix", "", "html")
- app.add_config_value("argparse_show_defaults", True, "html")
- app.add_config_value("argparse_show_choices", True, "html")
- app.add_config_value("argparse_show_types", True, "html")
-
- # Register custom nodes
- app.add_node(
- argparse_program,
- html=(visit_argparse_program_html, depart_argparse_program_html),
- )
- app.add_node(
- argparse_usage,
- html=(visit_argparse_usage_html, depart_argparse_usage_html),
- )
- app.add_node(
- argparse_group,
- html=(visit_argparse_group_html, depart_argparse_group_html),
- )
- app.add_node(
- argparse_argument,
- html=(visit_argparse_argument_html, depart_argparse_argument_html),
- )
- app.add_node(
- argparse_subcommands,
- html=(visit_argparse_subcommands_html, depart_argparse_subcommands_html),
- )
- app.add_node(
- argparse_subcommand,
- html=(visit_argparse_subcommand_html, depart_argparse_subcommand_html),
- )
-
- # Register directive
- app.add_directive("argparse", ArgparseDirective)
-
- return {
- "version": __version__,
- "parallel_read_safe": True,
- "parallel_write_safe": True,
- }
diff --git a/docs/_ext/sphinx_argparse_neo/compat.py b/docs/_ext/sphinx_argparse_neo/compat.py
deleted file mode 100644
index 15816d574c..0000000000
--- a/docs/_ext/sphinx_argparse_neo/compat.py
+++ /dev/null
@@ -1,271 +0,0 @@
-"""Compatibility utilities for module loading.
-
-This module provides utilities for loading Python modules safely,
-including mock handling for imports that may fail during documentation
-builds.
-
-Unlike sphinx-argparse, this module does NOT depend on autodoc's mock
-functionality, which moved in Sphinx 9.x.
-"""
-
-from __future__ import annotations
-
-import contextlib
-import importlib
-import sys
-import typing as t
-
-if t.TYPE_CHECKING:
- import argparse
- from collections.abc import Iterator
-
-
-class MockModule:
- """Simple mock for unavailable imports.
-
- This class provides a minimal mock that can be used as a placeholder
- for modules that aren't available during documentation builds.
-
- Parameters
- ----------
- name : str
- The module name being mocked.
-
- Examples
- --------
- >>> mock = MockModule("mypackage.submodule")
- >>> mock.__name__
- 'mypackage.submodule'
- >>> child = mock.child_attr
- >>> child.__name__
- 'mypackage.submodule.child_attr'
- >>> callable(mock.some_function)
- True
- >>> mock.some_function()
-
- """
-
- def __init__(self, name: str) -> None:
- """Initialize the mock module."""
- self.__name__ = name
- self._name = name
-
- def __repr__(self) -> str:
- """Return string representation."""
- return f""
-
- def __getattr__(self, name: str) -> MockModule:
- """Return a child mock for any attribute access.
-
- Parameters
- ----------
- name : str
- The attribute name.
-
- Returns
- -------
- MockModule
- A new mock for the child attribute.
- """
- return MockModule(f"{self._name}.{name}")
-
- def __call__(self, *args: t.Any, **kwargs: t.Any) -> MockModule:
- """Return self when called as a function.
-
- Parameters
- ----------
- *args : t.Any
- Positional arguments (ignored).
- **kwargs : t.Any
- Keyword arguments (ignored).
-
- Returns
- -------
- MockModule
- Self.
- """
- return self
-
-
-@contextlib.contextmanager
-def mock_imports(modules: list[str]) -> Iterator[None]:
- """Context manager to mock missing imports.
-
- This provides a simple way to temporarily add mock modules to
- sys.modules, allowing imports to succeed during documentation builds
- even when the actual modules aren't available.
-
- Parameters
- ----------
- modules : list[str]
- List of module names to mock.
-
- Yields
- ------
- None
- Context manager yields nothing.
-
- Examples
- --------
- >>> import sys
- >>> "fake_module" in sys.modules
- False
- >>> with mock_imports(["fake_module", "fake_module.sub"]):
- ... import fake_module
- ... fake_module.__name__
- 'fake_module'
- >>> "fake_module" in sys.modules
- False
- """
- mocked: dict[str, MockModule] = {}
-
- for name in modules:
- if name not in sys.modules:
- mocked[name] = MockModule(name)
- sys.modules[name] = mocked[name] # type: ignore[assignment]
-
- try:
- yield
- finally:
- for name in mocked:
- del sys.modules[name]
-
-
-def import_module(module_name: str) -> t.Any:
- """Import a module by name.
-
- Parameters
- ----------
- module_name : str
- The fully qualified module name.
-
- Returns
- -------
- t.Any
- The imported module.
-
- Raises
- ------
- ImportError
- If the module cannot be imported.
-
- Examples
- --------
- >>> mod = import_module("argparse")
- >>> hasattr(mod, "ArgumentParser")
- True
- """
- return importlib.import_module(module_name)
-
-
-def get_parser_from_module(
- module_name: str,
- func_name: str,
- mock_modules: list[str] | None = None,
-) -> argparse.ArgumentParser:
- """Import a module and call a function to get an ArgumentParser.
-
- Parameters
- ----------
- module_name : str
- The module containing the parser factory function.
- func_name : str
- The name of the function that returns an ArgumentParser.
- Can be a dotted path like "Class.method".
- mock_modules : list[str] | None
- Optional list of module names to mock during import.
-
- Returns
- -------
- argparse.ArgumentParser
- The argument parser returned by the function.
-
- Raises
- ------
- ImportError
- If the module cannot be imported.
- AttributeError
- If the function is not found.
- TypeError
- If the function doesn't return an ArgumentParser.
-
- Examples
- --------
- Load tmuxp's parser factory:
-
- >>> parser = get_parser_from_module("tmuxp.cli", "create_parser")
- >>> parser.prog
- 'tmuxp'
- >>> hasattr(parser, 'parse_args')
- True
- """
- ctx = mock_imports(mock_modules) if mock_modules else contextlib.nullcontext()
-
- with ctx:
- module = import_module(module_name)
-
- # Handle dotted paths like "Class.method"
- obj = module
- for part in func_name.split("."):
- obj = getattr(obj, part)
-
- # Call the function if it's callable
- parser = obj() if callable(obj) else obj
-
- # Validate the return type at runtime
- import argparse as argparse_module
-
- if not isinstance(parser, argparse_module.ArgumentParser):
- msg = (
- f"{module_name}:{func_name} returned {type(parser).__name__}, "
- f"expected ArgumentParser"
- )
- raise TypeError(msg)
-
- return parser
-
-
-def get_parser_from_entry_point(
- entry_point: str,
- mock_modules: list[str] | None = None,
-) -> argparse.ArgumentParser:
- """Get an ArgumentParser from a setuptools-style entry point string.
-
- Parameters
- ----------
- entry_point : str
- Entry point in the format "module:function" or "module:Class.method".
- mock_modules : list[str] | None
- Optional list of module names to mock during import.
-
- Returns
- -------
- argparse.ArgumentParser
- The argument parser.
-
- Raises
- ------
- ValueError
- If the entry point format is invalid.
-
- Examples
- --------
- Load tmuxp's parser using entry point syntax:
-
- >>> parser = get_parser_from_entry_point("tmuxp.cli:create_parser")
- >>> parser.prog
- 'tmuxp'
-
- Invalid format raises ValueError:
-
- >>> get_parser_from_entry_point("no_colon")
- Traceback (most recent call last):
- ...
- ValueError: Invalid entry point format: 'no_colon'. Expected 'module:function'
- """
- if ":" not in entry_point:
- msg = f"Invalid entry point format: {entry_point!r}. Expected 'module:function'"
- raise ValueError(msg)
-
- module_name, func_name = entry_point.split(":", 1)
- return get_parser_from_module(module_name, func_name, mock_modules)
diff --git a/docs/_ext/sphinx_argparse_neo/directive.py b/docs/_ext/sphinx_argparse_neo/directive.py
deleted file mode 100644
index 80d6d155ab..0000000000
--- a/docs/_ext/sphinx_argparse_neo/directive.py
+++ /dev/null
@@ -1,240 +0,0 @@
-"""Sphinx directive for argparse documentation.
-
-This module provides the ArgparseDirective class that integrates
-with Sphinx to generate documentation from ArgumentParser instances.
-"""
-
-from __future__ import annotations
-
-import typing as t
-
-from docutils import nodes
-from docutils.parsers.rst import directives
-from sphinx.util.docutils import SphinxDirective
-from sphinx_argparse_neo.compat import get_parser_from_module
-from sphinx_argparse_neo.parser import extract_parser
-from sphinx_argparse_neo.renderer import ArgparseRenderer, RenderConfig
-
-if t.TYPE_CHECKING:
- import argparse
-
-
-class ArgparseDirective(SphinxDirective):
- """Sphinx directive for documenting argparse-based CLI tools.
-
- Usage
- -----
- .. argparse::
- :module: myapp.cli
- :func: create_parser
- :prog: myapp
-
- Options
- -------
- :module:
- The Python module containing the parser factory function.
- :func:
- The function name that returns an ArgumentParser.
- Can be a dotted path like "Class.method".
- :prog:
- Override the program name (optional).
- :path:
- Navigate to a specific subparser by path (e.g., "sync pull").
- :no-defaults:
- Don't show default values (flag).
- :no-description:
- Don't show parser description (flag).
- :no-epilog:
- Don't show parser epilog (flag).
- :mock-modules:
- Comma-separated list of modules to mock during import.
-
- Examples
- --------
- In RST documentation::
-
- .. argparse::
- :module: myapp.cli
- :func: create_parser
- :prog: myapp
-
- :path: subcommand
- """
-
- has_content = True
- required_arguments = 0
- optional_arguments = 0
-
- option_spec: t.ClassVar[dict[str, t.Any]] = {
- "module": directives.unchanged_required,
- "func": directives.unchanged_required,
- "prog": directives.unchanged,
- "path": directives.unchanged,
- "no-defaults": directives.flag,
- "no-description": directives.flag,
- "no-epilog": directives.flag,
- "no-choices": directives.flag,
- "no-types": directives.flag,
- "mock-modules": directives.unchanged,
- # sphinx-argparse compatibility options
- "nosubcommands": directives.flag,
- "nodefault": directives.flag,
- "noepilog": directives.flag,
- "nodescription": directives.flag,
- }
-
- def run(self) -> list[nodes.Node]:
- """Execute the directive and return docutils nodes.
-
- Returns
- -------
- list[nodes.Node]
- List of docutils nodes representing the CLI documentation.
- """
- # Get required options
- module_name = self.options.get("module")
- func_name = self.options.get("func")
-
- if not module_name or not func_name:
- error = self.state_machine.reporter.error(
- "argparse directive requires :module: and :func: options",
- line=self.lineno,
- )
- return [error]
-
- # Parse mock modules
- mock_modules: list[str] | None = None
- if "mock-modules" in self.options:
- mock_modules = [m.strip() for m in self.options["mock-modules"].split(",")]
-
- # Load the parser
- try:
- parser = get_parser_from_module(module_name, func_name, mock_modules)
- except Exception as e:
- error = self.state_machine.reporter.error(
- f"Failed to load parser from {module_name}:{func_name}: {e}",
- line=self.lineno,
- )
- return [error]
-
- # Override prog if specified
- if "prog" in self.options:
- parser.prog = self.options["prog"]
-
- # Navigate to subparser if path specified
- if "path" in self.options:
- parser = self._navigate_to_subparser(parser, self.options["path"])
- if parser is None:
- error = self.state_machine.reporter.error(
- f"Subparser path not found: {self.options['path']}",
- line=self.lineno,
- )
- return [error]
-
- # Build render config from directive options and Sphinx config
- config = self._build_render_config()
-
- # Extract parser info
- parser_info = extract_parser(parser)
-
- # Apply directive-level overrides
- # Handle both new-style and sphinx-argparse compatibility options
- if "no-description" in self.options or "nodescription" in self.options:
- parser_info = parser_info.__class__(
- prog=parser_info.prog,
- usage=parser_info.usage,
- bare_usage=parser_info.bare_usage,
- description=None,
- epilog=parser_info.epilog,
- argument_groups=parser_info.argument_groups,
- subcommands=parser_info.subcommands,
- subcommand_dest=parser_info.subcommand_dest,
- )
- if "no-epilog" in self.options or "noepilog" in self.options:
- parser_info = parser_info.__class__(
- prog=parser_info.prog,
- usage=parser_info.usage,
- bare_usage=parser_info.bare_usage,
- description=parser_info.description,
- epilog=None,
- argument_groups=parser_info.argument_groups,
- subcommands=parser_info.subcommands,
- subcommand_dest=parser_info.subcommand_dest,
- )
- if "nosubcommands" in self.options:
- parser_info = parser_info.__class__(
- prog=parser_info.prog,
- usage=parser_info.usage,
- bare_usage=parser_info.bare_usage,
- description=parser_info.description,
- epilog=parser_info.epilog,
- argument_groups=parser_info.argument_groups,
- subcommands=None,
- subcommand_dest=None,
- )
-
- # Render to nodes
- renderer = ArgparseRenderer(config=config, state=self.state)
- return t.cast(list[nodes.Node], renderer.render(parser_info))
-
- def _build_render_config(self) -> RenderConfig:
- """Build RenderConfig from directive and Sphinx config options.
-
- Returns
- -------
- RenderConfig
- Configuration for the renderer.
- """
- # Start with Sphinx config defaults
- config = RenderConfig.from_sphinx_config(self.config)
-
- # Override with directive options
- # Handle both new-style and sphinx-argparse compatibility options
- if "no-defaults" in self.options or "nodefault" in self.options:
- config.show_defaults = False
- if "no-choices" in self.options:
- config.show_choices = False
- if "no-types" in self.options:
- config.show_types = False
-
- return config
-
- def _navigate_to_subparser(
- self, parser: argparse.ArgumentParser, path: str
- ) -> argparse.ArgumentParser | None:
- """Navigate to a nested subparser by path.
-
- Parameters
- ----------
- parser : argparse.ArgumentParser
- The root parser.
- path : str
- Space-separated path to the subparser (e.g., "sync pull").
-
- Returns
- -------
- argparse.ArgumentParser | None
- The subparser, or None if not found.
- """
- import argparse as argparse_module
-
- current = parser
- for name in path.split():
- # Find subparsers action
- subparser_action = None
- for action in current._actions:
- if isinstance(action, argparse_module._SubParsersAction):
- subparser_action = action
- break
-
- if subparser_action is None:
- return None
-
- # Find the named subparser
- choices = subparser_action.choices or {}
- if name not in choices:
- return None
-
- current = choices[name]
-
- return current
diff --git a/docs/_ext/sphinx_argparse_neo/nodes.py b/docs/_ext/sphinx_argparse_neo/nodes.py
deleted file mode 100644
index 468b5876a5..0000000000
--- a/docs/_ext/sphinx_argparse_neo/nodes.py
+++ /dev/null
@@ -1,647 +0,0 @@
-"""Custom docutils node types for argparse documentation.
-
-This module defines custom node types that represent the structure of
-CLI documentation, along with HTML visitor functions for rendering.
-"""
-
-from __future__ import annotations
-
-import typing as t
-
-from docutils import nodes
-
-if t.TYPE_CHECKING:
- from sphinx.writers.html5 import HTML5Translator
-
-# Import the lexer - use absolute import from parent package
-import pathlib
-import sys
-
-# Add parent directory to path for lexer import
-_ext_dir = pathlib.Path(__file__).parent.parent
-if str(_ext_dir) not in sys.path:
- sys.path.insert(0, str(_ext_dir))
-
-from argparse_lexer import ArgparseUsageLexer # noqa: E402
-from sphinx_argparse_neo.utils import strip_ansi # noqa: E402
-
-
-def _generate_argument_id(names: list[str], id_prefix: str = "") -> str:
- """Generate unique ID for an argument based on its names.
-
- Creates a slug-style ID suitable for HTML anchors by:
- 1. Stripping leading dashes from option names
- 2. Joining multiple names with hyphens
- 3. Prepending optional prefix for namespace isolation
-
- Parameters
- ----------
- names : list[str]
- List of argument names (e.g., ["-L", "--socket-name"]).
- id_prefix : str
- Optional prefix for uniqueness (e.g., "shell" -> "shell-L-socket-name").
-
- Returns
- -------
- str
- A slug-style ID suitable for HTML anchors.
-
- Examples
- --------
- >>> _generate_argument_id(["-L"])
- 'L'
- >>> _generate_argument_id(["--help"])
- 'help'
- >>> _generate_argument_id(["-v", "--verbose"])
- 'v-verbose'
- >>> _generate_argument_id(["-L"], "shell")
- 'shell-L'
- >>> _generate_argument_id(["filename"])
- 'filename'
- >>> _generate_argument_id([])
- ''
- """
- clean_names = [name.lstrip("-") for name in names if name.lstrip("-")]
- if not clean_names:
- return ""
- name_part = "-".join(clean_names)
- return f"{id_prefix}-{name_part}" if id_prefix else name_part
-
-
-def _token_to_css_class(token_type: t.Any) -> str:
- """Map a Pygments token type to its CSS class abbreviation.
-
- Pygments uses hierarchical token names like Token.Name.Attribute.
- These map to CSS classes using abbreviations of the last two parts:
- - Token.Name.Attribute → 'na' (Name.Attribute)
- - Token.Generic.Heading → 'gh' (Generic.Heading)
- - Token.Punctuation → 'p' (just Punctuation)
-
- Parameters
- ----------
- token_type : Any
- A Pygments token type (from pygments.token).
-
- Returns
- -------
- str
- CSS class abbreviation, or empty string if not mappable.
-
- Examples
- --------
- >>> from pygments.token import Token
- >>> _token_to_css_class(Token.Name.Attribute)
- 'na'
- >>> _token_to_css_class(Token.Generic.Heading)
- 'gh'
- >>> _token_to_css_class(Token.Punctuation)
- 'p'
- >>> _token_to_css_class(Token.Text.Whitespace)
- 'tw'
- """
- type_str = str(token_type)
- # Token string looks like "Token.Name.Attribute" or "Token.Punctuation"
- parts = type_str.split(".")
-
- if len(parts) >= 3:
- # Token.Name.Attribute -> "na" (first char of each of last two parts)
- return parts[-2][0].lower() + parts[-1][0].lower()
- elif len(parts) == 2:
- # Token.Punctuation -> "p" (first char of last part)
- return parts[-1][0].lower()
- return ""
-
-
-def _highlight_usage(usage_text: str, encode: t.Callable[[str], str]) -> str:
- """Tokenize usage text and wrap tokens in highlighted span elements.
-
- Uses ArgparseUsageLexer to tokenize the usage string, then wraps each
- token in a with the appropriate CSS class for styling.
-
- Parameters
- ----------
- usage_text : str
- The usage string to highlight (should include "usage: " prefix).
- encode : Callable[[str], str]
- HTML encoding function (typically translator.encode).
-
- Returns
- -------
- str
- HTML string with tokens wrapped in styled elements.
-
- Examples
- --------
- >>> def mock_encode(s: str) -> str:
- ... return s.replace("&", "&").replace("<", "<")
- >>> html = _highlight_usage("usage: cmd [-h]", mock_encode)
- >>> 'usage:' in html
- True
- >>> 'cmd' in html
- True
- >>> '-h' in html
- True
- """
- lexer = ArgparseUsageLexer()
- parts: list[str] = []
-
- for tok_type, tok_value in lexer.get_tokens(usage_text):
- if not tok_value:
- continue
-
- css_class = _token_to_css_class(tok_type)
- escaped = encode(tok_value)
- type_str = str(tok_type).lower()
-
- # Skip wrapping for whitespace and plain text tokens
- if css_class and "whitespace" not in type_str and "text" not in type_str:
- parts.append(f'{escaped}')
- else:
- parts.append(escaped)
-
- return "".join(parts)
-
-
-def _highlight_argument_names(
- names: list[str], metavar: str | None, encode: t.Callable[[str], str]
-) -> str:
- """Highlight argument names and metavar with appropriate CSS classes.
-
- Short options (-h) get class 'na' (Name.Attribute).
- Long options (--help) get class 'nt' (Name.Tag).
- Positional arguments get class 'nl' (Name.Label).
- Metavars get class 'nv' (Name.Variable).
-
- Parameters
- ----------
- names : list[str]
- List of argument names (e.g., ["-v", "--verbose"]).
- metavar : str | None
- Optional metavar (e.g., "FILE", "PATH").
- encode : Callable[[str], str]
- HTML encoding function.
-
- Returns
- -------
- str
- HTML string with highlighted argument signature.
-
- Examples
- --------
- >>> def mock_encode(s: str) -> str:
- ... return s
- >>> html = _highlight_argument_names(["-h", "--help"], None, mock_encode)
- >>> '-h' in html
- True
- >>> '--help' in html
- True
- >>> html = _highlight_argument_names(["--output"], "FILE", mock_encode)
- >>> 'FILE' in html
- True
- >>> html = _highlight_argument_names(["sync"], None, mock_encode)
- >>> 'sync' in html
- True
- """
- sig_parts: list[str] = []
-
- for name in names:
- escaped = encode(name)
- if name.startswith("--"):
- sig_parts.append(f'{escaped}')
- elif name.startswith("-"):
- sig_parts.append(f'{escaped}')
- else:
- # Positional argument or subcommand
- sig_parts.append(f'{escaped}')
-
- result = ", ".join(sig_parts)
-
- if metavar:
- escaped_metavar = encode(metavar)
- result = f'{result} {escaped_metavar}'
-
- return result
-
-
-class argparse_program(nodes.General, nodes.Element):
- """Root node for an argparse program documentation block.
-
- Attributes
- ----------
- prog : str
- The program name.
-
- Examples
- --------
- >>> node = argparse_program()
- >>> node["prog"] = "myapp"
- >>> node["prog"]
- 'myapp'
- """
-
- pass
-
-
-class argparse_usage(nodes.General, nodes.Element):
- """Node for displaying program usage.
-
- Contains the usage string as a literal block.
-
- Examples
- --------
- >>> node = argparse_usage()
- >>> node["usage"] = "myapp [-h] [--verbose] command"
- >>> node["usage"]
- 'myapp [-h] [--verbose] command'
- """
-
- pass
-
-
-class argparse_group(nodes.General, nodes.Element):
- """Node for an argument group (positional, optional, or custom).
-
- Attributes
- ----------
- title : str
- The group title.
- description : str | None
- Optional group description.
-
- Examples
- --------
- >>> node = argparse_group()
- >>> node["title"] = "Output Options"
- >>> node["title"]
- 'Output Options'
- """
-
- pass
-
-
-class argparse_argument(nodes.Part, nodes.Element):
- """Node for a single CLI argument.
-
- Attributes
- ----------
- names : list[str]
- Argument names/flags.
- help : str | None
- Help text.
- default : str | None
- Default value string.
- choices : list[str] | None
- Available choices.
- required : bool
- Whether the argument is required.
- metavar : str | None
- Metavar for display.
-
- Examples
- --------
- >>> node = argparse_argument()
- >>> node["names"] = ["-v", "--verbose"]
- >>> node["names"]
- ['-v', '--verbose']
- """
-
- pass
-
-
-class argparse_subcommands(nodes.General, nodes.Element):
- """Container node for subcommands section.
-
- Examples
- --------
- >>> node = argparse_subcommands()
- >>> node["title"] = "Commands"
- >>> node["title"]
- 'Commands'
- """
-
- pass
-
-
-class argparse_subcommand(nodes.General, nodes.Element):
- """Node for a single subcommand.
-
- Attributes
- ----------
- name : str
- Subcommand name.
- aliases : list[str]
- Subcommand aliases.
- help : str | None
- Subcommand help text.
-
- Examples
- --------
- >>> node = argparse_subcommand()
- >>> node["name"] = "sync"
- >>> node["aliases"] = ["s"]
- >>> node["name"]
- 'sync'
- """
-
- pass
-
-
-# HTML Visitor Functions
-
-
-def visit_argparse_program_html(self: HTML5Translator, node: argparse_program) -> None:
- """Visit argparse_program node - start program container.
-
- Parameters
- ----------
- self : HTML5Translator
- The Sphinx HTML translator.
- node : argparse_program
- The program node being visited.
- """
- prog = node.get("prog", "")
- self.body.append(f'
\n')
-
-
-def depart_argparse_program_html(self: HTML5Translator, node: argparse_program) -> None:
- """Depart argparse_program node - close program container.
-
- Parameters
- ----------
- self : HTML5Translator
- The Sphinx HTML translator.
- node : argparse_program
- The program node being departed.
- """
- self.body.append("
\n")
-
-
-def visit_argparse_usage_html(self: HTML5Translator, node: argparse_usage) -> None:
- """Visit argparse_usage node - render usage block with syntax highlighting.
-
- The usage text is tokenized using ArgparseUsageLexer and wrapped in
- styled elements for semantic highlighting of options, metavars,
- commands, and punctuation.
-
- Parameters
- ----------
- self : HTML5Translator
- The Sphinx HTML translator.
- node : argparse_usage
- The usage node being visited.
- """
- usage = strip_ansi(node.get("usage", ""))
- # Add both argparse-usage class and highlight class for CSS targeting
- self.body.append('
')
- # Prepend "usage: " and highlight the full usage string
- highlighted = _highlight_usage(f"usage: {usage}", self.encode)
- self.body.append(highlighted)
-
-
-def depart_argparse_usage_html(self: HTML5Translator, node: argparse_usage) -> None:
- """Depart argparse_usage node - close usage block.
-
- Parameters
- ----------
- self : HTML5Translator
- The Sphinx HTML translator.
- node : argparse_usage
- The usage node being departed.
- """
- self.body.append("
\n")
-
-
-def visit_argparse_group_html(self: HTML5Translator, node: argparse_group) -> None:
- """Visit argparse_group node - start argument group.
-
- The title is now rendered by the parent section node, so this visitor
- only handles the group container and description.
-
- Parameters
- ----------
- self : HTML5Translator
- The Sphinx HTML translator.
- node : argparse_group
- The group node being visited.
- """
- title = node.get("title", "")
- group_id = title.lower().replace(" ", "-") if title else "arguments"
- self.body.append(f'
\n')
- # Title rendering removed - parent section now provides the heading
- description = node.get("description")
- if description:
- self.body.append(
- f'
{self.encode(description)}
\n'
- )
- self.body.append('
\n')
-
-
-def depart_argparse_group_html(self: HTML5Translator, node: argparse_group) -> None:
- """Depart argparse_group node - close argument group.
-
- Parameters
- ----------
- self : HTML5Translator
- The Sphinx HTML translator.
- node : argparse_group
- The group node being departed.
- """
- self.body.append("
\n")
- self.body.append("
\n")
-
-
-def visit_argparse_argument_html(
- self: HTML5Translator, node: argparse_argument
-) -> None:
- """Visit argparse_argument node - render argument entry with highlighting.
-
- Argument names are highlighted with semantic CSS classes:
- - Short options (-h) get class 'na' (Name.Attribute)
- - Long options (--help) get class 'nt' (Name.Tag)
- - Positional arguments get class 'nl' (Name.Label)
- - Metavars get class 'nv' (Name.Variable)
-
- The argument is wrapped in a container div with a unique ID for linking.
- A headerlink anchor (¶) is added for direct navigation.
-
- Parameters
- ----------
- self : HTML5Translator
- The Sphinx HTML translator.
- node : argparse_argument
- The argument node being visited.
- """
- names: list[str] = node.get("names", [])
- metavar = node.get("metavar")
- id_prefix: str = node.get("id_prefix", "")
-
- # Generate unique ID for this argument
- arg_id = _generate_argument_id(names, id_prefix)
-
- # Open wrapper div with ID for linking
- if arg_id:
- self.body.append(f'
\n')
- else:
- self.body.append('
\n')
-
- # Build the argument signature with syntax highlighting
- highlighted_sig = _highlight_argument_names(names, metavar, self.encode)
-
- # Add headerlink anchor inside dt for navigation
- headerlink = ""
- if arg_id:
- headerlink = f'¶'
-
- self.body.append(
- f'
{highlighted_sig}{headerlink}
\n'
- )
- self.body.append('
')
-
- # Add help text
- help_text = node.get("help")
- if help_text:
- self.body.append(f"
{self.encode(help_text)}
")
-
-
-def depart_argparse_argument_html(
- self: HTML5Translator, node: argparse_argument
-) -> None:
- """Depart argparse_argument node - close argument entry.
-
- Adds default, choices, and type information if present.
- Default values are wrapped in ```` for styled display.
-
- Parameters
- ----------
- self : HTML5Translator
- The Sphinx HTML translator.
- node : argparse_argument
- The argument node being departed.
- """
- # Build metadata as definition list items
- default = node.get("default_string")
- choices = node.get("choices")
- type_name = node.get("type_name")
- required = node.get("required", False)
-
- if default is not None or choices or type_name or required:
- self.body.append('
\n')
-
- if default is not None:
- self.body.append('
')
- self.body.append('
Default
')
- self.body.append(
- f'
'
- f'{self.encode(default)}
'
- )
- self.body.append("
\n")
-
- if type_name:
- self.body.append('
')
- self.body.append('
Type
')
- self.body.append(
- f'
'
- f'{self.encode(type_name)}
'
- )
- self.body.append("
\n")
-
- if choices:
- choices_str = ", ".join(str(c) for c in choices)
- self.body.append('