cappa-0.32.2/.readthedocs.yml0000644000000000000000000000024713615410400012673 0ustar00version: 2 sphinx: configuration: docs/source/conf.py build: os: ubuntu-20.04 tools: python: "3.10" python: install: - method: pip path: docs cappa-0.32.2/CHANGELOG.md0000644000000000000000000003573613615410400011431 0ustar00# Changelog ## 0.32 ### 0.32.2 - fix: Negated boolean args rendering default help text in incorrect location. - fix: Preserve multiline help text formatting. - fix: Dedent attribute docstrings' content. ### 0.32.1 - fix: kw_only dataclass inheritance incorrect ordering. ### 0.32.0 - feat: Add `epilog=` field on commands for text which should go after the argument help. - refactor: All root objects (Command/Arg/Subcommand) to have "Final" variants which represent their post-normalization shape. - feat: Add support for fixed size optional-value num_args arguments (e.g. --foo / --foo 4). - feat: Add `aliases=` support for subcommands. Each entry is either a string (visible alias) or a `cappa.Alias(name, hidden=..., deprecated=...)` for finer control. Hidden aliases dispatch but are omitted from help and completion; deprecated aliases emit a runtime warning when used. Collisions are detected at construction time. - fix: Display Args with the same group identity as alternatives in helptext on the same line. - fix: Ensure automatic bool variants (--foo/--no-foo) are mutually exclusive. - fix: Allow negative number arguments and option values. - fix: Precalculate implicit deps during class construction rather than traversing the output shape. - fix: Thread `output` into `parse_result.instance` resolution in `parse`/`parse_async`/`invoke`/`invoke_async`, so `cappa.Exit` raised from `Arg(parse=...)` callbacks renders an error message instead of silently exiting non-zero. - refactor: Destructured implementation to be more like Arg handling to fix default and helptext behavior. - fix: Destructured args to inherit `default_long` and `default_short`. ## 0.31 ### 0.31.0 - fix: Allow options accepting zero-length unbounded num_args. - feat: Apply default parsing automatically, with Arg.parse_inference == False to disable. ## 0.30 ### 0.30.4 - fix: Use of a synchronous context manager inside an `invoke_async` invoke context. - fix: Stripped type aliases losing e.g. Dep metadata. ### 0.30.3 - fix: Scenario where static defaults were not being shown in helptext. ### 0.30.2 - fix: Prioritize Arg.default over dataclass-level default. Technically this might be considered a breaking change, but the original behavior was unintended. `Arg.default` should **always** take precedence over inferred defaults or class-level defaults. This only manifests in cases where someone was supplying both a class-level default and an `Arg` level default like: `foo: Annotated[int, Arg(default=5)] = 10`. This **should** result in a `5` default but was erroneously yielding `10`. There's no obvious practical reason to ever supply both with the old behavior because the `Arg.default` would never have an effect. Now, it's possible to use the `Arg.default` distinctly from the class level default outside the context of use with cappa. ### 0.30.1 - fix: Infer class constructor as default for `Destructured` class. ### 0.30.0 - fix: BREAKING CHANGE: Provide simpler `Destructured[T]` annotation. Previously `Annotated[T, Arg(destructured=Destructured())]`, this was comparatively verbose, when there is currently practical use for configuring `Arg` options on the destructured root argument. As such the `Destructured` name has been repurposed as a convenience annotation similar to `Subcommands`. The forms: `Annotated[T, Destructure]`, `Annotated[T, Arg.destructured()]`, `Annotated[T, Arg(destructure=True]`, and `Annotated[T, Arg(destructure=Destructure())]` still remain in the event future use is assigned to configuration on the root field. - fix: Destructured relative context, e.g. counting. - fix: Add support for explicit type alias `Dep`s. - fix: Propagation of mutually exclusive options that map to the same `Arg.field_name`. ## 0.29 ### 0.29.1 - fix: Lower bounds of dependency versions and test for them. - feat: Handle python 3.12 syntax for explicit `type ` aliases. - fix: Explicit choice sequence runtime error. ### 0.29.0 - fix: Undocumented completions. - docs: Add argument validation documentation. - feat: Test against python 3.14. - fix: Apply pyright strict typing. - fix: Function callables ability to depend on implicit Deps. - docs: Improve docs relating to `Output`. ## 0.28 ### 0.28.1 - fix: Sphinx warning in docutils extension with `--jobs`. ### 0.28.0 - feat: Allow `deps` to be indirect function reference strings. - feat: Allow styling of individual sections of argument help text. ## 0.27 ### 0.27.3 - fix: Make rich/typing-extensions version requirements more precise. ### 0.27.2 - fix: Ensure API doesnt yield pyright errors in user code. ### 0.27.1 - fix: Handle multiple = in explicit arg. ### 0.27.0 - feat: Add "Self" concept for invoke dependencies. ## 0.26 ### 0.26.6 - fix: Typo in the exported member `HelpFormattable` ### 0.26.5 - fix: Coerce `Env(default=None)` to `Empty` to avoid interpreting the value as a string. ### 0.26.4 - fix: Ensure `propagate=True` args supply the correct parent context to the arg's action handler. ### 0.26.3 - fix: Reexport Empty/EmptyType types at the root. ### 0.26.2 - fix: Optional positional arguments should not get a default value from their action. ### 0.26.1 - fix: Default formatting when value is None/Empty and formatter is supplied. ### 0.26.0 - feat: Add `Default` object with associated fallback semantics for sequences of default handlers. - feat: Add `ValueFrom` for handling default_factory lazily, as well as arbitrary function dispatch. - feat: Add `State` as object accessible to invoke, Arg.parse, and ValueFrom.callable for sharing state amongst different stages of argument parsing. - fix: Skip non-init fields in dataclasses. - fix: Required positional arguments in the native parser. - fix: Infer num_args on unbounded sequence options (e.g. `list[list[str]]` annotation on an option). - feat: Add DefaultFormatter as optional input to `show_default`. ## 0.25 ### 0.25.1 - fix: Perform choices validation after parse/mapping time. ### 0.25.0 - feat: Add `Arg.propagate`. ## 0.24 ### 0.24.3 - fix: Incorrect handling of methods in Arg.parse. ### 0.24.2 - fix: Literal contained inside non-variadic tuple should not imply "choices". - fix: Optional non-variadic tuple should be allowed (None shouldn't fail arity check). ### 0.24.1 - feat: Add Group.section to enable ordering of groups separately from the items within them. - fix: invoke(help_formatter=...) not applying to explicitly decorated commands. ### 0.24.0 - feat: Support native inference parser for dataclass-like annotation s. Note this contains a minor breaking changes: * The `Arg.annotations` attribute was swapped for `Arg.type_view`. This was never a documented feature, but rather a side-effect required to implement certain built-in parser inferences. Swapping to `type_view` exposes strictly more information, and in a nicer interface. - feat: User defined parsers can access the `TypeView` by accepting it as an argument. - feat: Add new API for defining subcommands as methods. - feat: Add Arg.show_default option to optionally avoid displaying default in help text. - fix: Only show_default on `--*/--no-*` pair bool arguments for the actual default. ## 0.23 ### 0.23.0 - fix: Avoid storing has_value=False values in the parser. - feat: Introduce `Arg.destructure()` and `Arg.has_value`. - chore: Swap custom MISSING/missing for EmptyType/Empty. - refactor: Adopt the type-lens package. ## 0.22 ### 0.22.5 - fix: Refactor parser combinators into dedicated module, and document the behavior more thoroughly. - refactor: Pull handling of `--no-*` bool arguments out of the parser - fix: Only apply `--no-*` handling when there is both a positive and negative variant ### 0.22.4 - fix: Avoid applying annotated type parsing to default value. ### 0.22.3 - fix: Ensure compatibility with python 3.13 ### 0.22.2 - fix: Ensure `Arg.choices` is inferred when `T | None` where `T` would have inferred `choices` is encountered. ### 0.22.1 - fix: Route arg help formatting through markdown processing. ### 0.22.0 - feat: Allow "attribute docstrings" as additional method of documenting args. ## 0.21 ### 0.21.2 - fix: action inference when `default` is an `Env`. ### 0.21.1 - feat: Update automatic inference to support `date`, `time`, and `datetime` parsing (for isoformat). ### 0.21.0 - feat: Add `help_formatter` system wherein help text rendering can be customized to include or exclude things like default values, or choices. Notably changes to include default values in help text by default. ## 0.20 ### 0.20.1 - feat: Improve union error message. ### 0.20.0 - feat: Implement exclusive argument groups. ## 0.19 ### 0.19.1 - fix: Eagerly attempt pydantic BaseModel import to ensure its skipped if unavailable. ### 0.19.0 - feat: Add support for `msgspec` based class definitions. ## 0.18 ## 0.18.1 - feat: Add `deprecated` option, allowing deprecation of args, options, and subcommands ### 0.18.0 - feat: Add `default_short=False` and `default_long=False` options to command for ease of defining option-heavy commands. ## 0.17 ### 0.17.3 - fix: Ensure class default combined with `default=Env(...)` still attempts to read env var (rather than just unconditionally taking the class default). ### 0.17.2 - Increase minimum typing_extensions bound to reflect actual dependency. ### 0.17.1 - Fixes bounded-tuple options like `tuple[str, str]` to infer as `num_args=2` - Fixes bounded-tuple options to fail parsing if you give it a different number of values - Fixes "double sequence" inference on explicit `num_args=N` values which would produce sequences. I.e. infer `action=ArgAction.set` in such cases to avoid e.x. `num_args=3, action=ArgAction.append`; resulting in nonsensical nested sequence `["[]"]` ### 0.17.0 - feat: Add `hidden=True/False` option to Command, which allows hiding individual subcommands options. ## 0.16 ### 0.16.6 - fix: Correct the testing.CommandRunner deps signature. ### 0.16.5 - fix: Error short help should be contextual to the failing command. ### 0.16.4 - fix: Ensure optional bool retains bool action inference. ### 0.16.3 - fix: Use eval_type_backport to allow new syntax in python 3.8/9. ### 0.16.2 - feat: Improve parse error formatting. Include short help by default. ### 0.16.1 - feat: Support `Dep` on function based commands. ### 0.16.0 - feat: Add support for `BinaryIO` and `TextIO` for representing preconfigured file objects to be returned the caller. ## 0.15 ### 0.15.4 - feat: Support pydantic v1. ### 0.15.3 - fix: Incorrect error message when using an invalid base class type. ### 0.15.2 - fix: Process `action` inference, taking into account `Optional`/`| None`. ### 0.15.1 - feat: Support explicit context managers as invoke dependencies. ### 0.15.0 - feat: Add docutils directive extension. ## 0.14 ### 0.14.3 - fix: Handle TypeError in mapping failures ### 0.14.2 - fix: Default bool fields to `False` when omitted. ### 0.14.1 - fix: zsh completion script error ### 0.14.0 - feat: Support functions as interface for simple CLIs. ## 0.13 ### 0.13.2 - Support "discriminated unions" (i.e. unions which have type distinctions that affect how they're mapped.) ### 0.13.1 - Prefer the field default (if set), if an `Env` is used, but no default is supplied. ### 0.13.0 - Support `yield` in invoke dependencies to support context-manager-like dependencies (for example those which require cleanup). ## 0.12 ### 0.12.1 - When used in combination with `parse=...`, handle the "optional" part of `T | None` **before** `parse`. ### 0.12.0 - Add `invoke_async` to support async invoke functions and dependencies ## 0.11 ### 0.11.6 - Disallow certain combinations of apparently incompatible annotations, i.e. sequences and scalars ### 0.11.5 - Fix double dash following an invalid option (with num_args>0) ### 0.11.4 - Fix num_args=-1 on options ### 0.11.3 - Continue to parse docstrings without docstring_parser extra - Fix rendering issue with markdown in docstrings ### 0.11.2 - Make docstring_parser dependency optional - Fix parser error if option followed unknown argument ### 0.11.1 - (Hopefully) Configure rich properly to deal with line overflow when printing terminal escape codes. ### 0.11.0 - Add option for explicit Output object, and add `error_format` option to allow customizing output formatting. ## 0.10 ### 0.10.2 - Disallow explicit `required=False` in combination with the lack of a field level default. ### 0.10.1 - Fix regression resulting from `value_name`/`field_name` split. ### 0.10.0 - Split Arg `value_name`/`field_name`. `value_name` controls help/error output naming. `field_name` controls the the destination field on the dataclass object. ## 0.9 ### 0.9.3 - Ensure output of missing required options is deterministically ordered - Output all required options when listing out missing required options - Fix ignore num_args=0 override ### 0.9.2 - Invoke the specific callable subcommand instance being targeted. ### 0.9.1 - Supply the parsed Command instance as an invoke dependency. ### 0.9.0 - Change default backend to `cappa.parser.backend`. To opt into argparse backend, explicitly supply it with `backend=cappa.argparse.backend`. ## 0.8 ### 0.8.9 - Avoid mutating command when adding meta arguments - Avoid setting global NO_COLOR env var when disabling color ### 0.8.8 - Clean up help text output formatting - Show rich-style help text when using argparse backend ### 0.8.7 - Allow defining custom callable as an `action`. - Improve behavior consuming concatenated short arguments ### 0.8.6 - Improve behavior consuming concatenated short arguments ### 0.8.5 - Add metadata to package distribution ### 0.8.4 - Loosen dependency version specifiers ### 0.8.3 - Fix `Literal["one", "two"]` syntax vs `Literal["one"] | Literal["two"]` - Apply custom completions to already "valid" arguments values - Deduplicate the --completion helptext choices ### 0.8.2 - The command's name should now always translate to the prog name - Explicitly provided argv should now **not** include the prog name ### 0.8.1 - Correct the version long name when long=True. ### 0.8.0 - Implement support for PEP-727 help text inference. ## 0.7 ### 0.7.1 - Provide clear error message when a version Arg is supplied without a name. - Documentation updates ### 0.7.0 - Adds native cappa parser option vs argparse - Renames render option to "backend" - Collapse separate `Subcommands` abstraction into Subcommand - Renames `Subcmd` to `Subcommands` - Adds support for autocompletion when using new parser - Removes "rich" extra - Adds rich as a required extra dependency - It is core to custom/colored help formatting for new parser, and the now more-unified stdout/stderr output. - rich-argparse needs to now be installed separately, if used - Adds a new `Output` implicit dependency option - Adds new theme option (currently only applied to custom parser) - Adds group option to `Arg` to control how arguments are grouped together in help text - Adds hidden option to `Arg`, to hide arguments from help text. - Ensures `help=` applies to all levels of subcommands cappa-0.32.2/Makefile0000644000000000000000000000250013615410400011237 0ustar00.PHONY: install-lowest install test lint lint-base lint-312 lint-typos lint-examples format install-lowest: uv sync --all-extras --resolution lowest-direct install: uv sync --all-extras --resolution highest test: @PYTEST_ARGS="$$(uv run --frozen python -c 'import sys; print("--ignore=tests/py312" if sys.version_info < (3,12) else "")')"; \ uv run --frozen coverage run -m pytest src tests "$$PYTEST_ARGS" coverage: uv run --frozen coverage combine uv run --frozen coverage report uv run --frozen coverage xml lint: uv run --frozen ruff check src tests examples || exit 1 uv run --frozen --all-extras mypy src tests || exit 1 uv run --frozen --all-extras pyright src tests || exit 1 uv run --frozen ruff format --check src tests || exit 1 lint-examples: uv run --directory examples/defer_import_slow_startup --all-extras mypy -p defer_import || exit 1 uv run --directory examples/defer_import_slow_startup --all-extras pyright defer_import || exit 1 uv run --directory examples/defer_import_slow_startup --all-extras basedpyright defer_import || exit 1 lint-typos: typos format: uv run --frozen ruff check src tests examples --fix uv run --frozen ruff format src tests examples readme-image: FORCE_COLOR=true uv run --no-sync python readme.py --help | ansitoimg --title '' --width 80 docs/source/_static/example.svg cappa-0.32.2/uv.lock0000644000000000000000000111436013615410400011114 0ustar00version = 1 revision = 3 requires-python = ">=3.8, <4" resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version < '3.9'", ] [[package]] name = "alabaster" version = "0.7.13" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/94/71/a8ee96d1fd95ca04a0d2e2d9c4081dac4c2d2b12f7ddb899c8cb9bfd1532/alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2", size = 11454, upload-time = "2023-01-13T06:42:53.797Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/64/88/c7083fc61120ab661c5d0b82cb77079fc1429d3f913a456c1c82cf4658f7/alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3", size = 13857, upload-time = "2023-01-13T06:42:52.336Z" }, ] [[package]] name = "alabaster" version = "0.7.16" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" }, ] [[package]] name = "alabaster" version = "1.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] name = "attrs" version = "25.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] [[package]] name = "babel" version = "2.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytz", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] [[package]] name = "cappa" version = "0.32.2" source = { editable = "." } dependencies = [ { name = "rich" }, { name = "type-lens" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] [package.optional-dependencies] docstring = [ { name = "docstring-parser" }, ] [package.dev-dependencies] dev = [ { name = "attrs" }, { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "coverage", version = "7.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "docutils", version = "0.20.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "msgspec", marker = "python_full_version >= '3.9'" }, { name = "mypy", version = "1.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "mypy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pydantic", version = "2.10.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pydantic", version = "2.11.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.14'" }, { name = "pyright" }, { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "ruff" }, { name = "sphinx", version = "7.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "types-docutils", version = "0.21.0.20241128", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "types-docutils", version = "0.21.0.20250728", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] [package.metadata] requires-dist = [ { name = "docstring-parser", marker = "python_full_version >= '3.14' and extra == 'docstring'", specifier = ">=0.17" }, { name = "docstring-parser", marker = "extra == 'docstring'", specifier = ">=0.15" }, { name = "rich", specifier = ">=12.1.0" }, { name = "type-lens", specifier = ">=0.2.5" }, { name = "typing-extensions", specifier = ">=4.8.0" }, { name = "typing-extensions", marker = "python_full_version >= '3.13'", specifier = ">=4.12.0" }, ] provides-extras = ["docstring"] [package.metadata.requires-dev] dev = [ { name = "attrs", specifier = ">=18.0" }, { name = "coverage", specifier = ">=7.3.0" }, { name = "docutils", specifier = ">=0.20.0" }, { name = "msgspec", marker = "python_full_version >= '3.9'", specifier = ">=0.19.0" }, { name = "mypy", specifier = ">=1.0.0" }, { name = "pydantic", marker = "python_full_version < '3.14'", specifier = ">=1.8.0" }, { name = "pyright", specifier = ">=1.1.398" }, { name = "pytest", specifier = ">=8.1.1,<9" }, { name = "ruff", specifier = ">=0.9.0" }, { name = "sphinx", specifier = ">=7.1.0" }, { name = "types-docutils", specifier = ">=0.20.0" }, ] [[package]] name = "certifi" version = "2025.7.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, { url = "https://files.pythonhosted.org/packages/4c/fd/f700cfd4ad876def96d2c769d8a32d808b12d1010b6003dc6639157f99ee/charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", size = 198257, upload-time = "2025-05-02T08:33:45.511Z" }, { url = "https://files.pythonhosted.org/packages/3a/95/6eec4cbbbd119e6a402e3bfd16246785cc52ce64cf21af2ecdf7b3a08e91/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", size = 143453, upload-time = "2025-05-02T08:33:47.463Z" }, { url = "https://files.pythonhosted.org/packages/b6/b3/d4f913660383b3d93dbe6f687a312ea9f7e89879ae883c4e8942048174d4/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", size = 153130, upload-time = "2025-05-02T08:33:50.568Z" }, { url = "https://files.pythonhosted.org/packages/e5/69/7540141529eabc55bf19cc05cd9b61c2078bebfcdbd3e799af99b777fc28/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", size = 145688, upload-time = "2025-05-02T08:33:52.828Z" }, { url = "https://files.pythonhosted.org/packages/2e/bb/d76d3d6e340fb0967c43c564101e28a78c9a363ea62f736a68af59ee3683/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", size = 147418, upload-time = "2025-05-02T08:33:54.718Z" }, { url = "https://files.pythonhosted.org/packages/3e/ef/b7c1f39c0dc3808160c8b72e0209c2479393966313bfebc833533cfff9cc/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", size = 150066, upload-time = "2025-05-02T08:33:56.597Z" }, { url = "https://files.pythonhosted.org/packages/20/26/4e47cc23d2a4a5eb6ed7d6f0f8cda87d753e2f8abc936d5cf5ad2aae8518/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", size = 144499, upload-time = "2025-05-02T08:33:58.637Z" }, { url = "https://files.pythonhosted.org/packages/d7/9c/efdf59dd46593cecad0548d36a702683a0bdc056793398a9cd1e1546ad21/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", size = 152954, upload-time = "2025-05-02T08:34:00.552Z" }, { url = "https://files.pythonhosted.org/packages/59/b3/4e8b73f7299d9aaabd7cd26db4a765f741b8e57df97b034bb8de15609002/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", size = 155876, upload-time = "2025-05-02T08:34:02.527Z" }, { url = "https://files.pythonhosted.org/packages/53/cb/6fa0ccf941a069adce3edb8a1e430bc80e4929f4d43b5140fdf8628bdf7d/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", size = 153186, upload-time = "2025-05-02T08:34:04.481Z" }, { url = "https://files.pythonhosted.org/packages/ac/c6/80b93fabc626b75b1665ffe405e28c3cef0aae9237c5c05f15955af4edd8/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", size = 148007, upload-time = "2025-05-02T08:34:06.888Z" }, { url = "https://files.pythonhosted.org/packages/41/eb/c7367ac326a2628e4f05b5c737c86fe4a8eb3ecc597a4243fc65720b3eeb/charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", size = 97923, upload-time = "2025-05-02T08:34:08.792Z" }, { url = "https://files.pythonhosted.org/packages/7c/02/1c82646582ccf2c757fa6af69b1a3ea88744b8d2b4ab93b7686b2533e023/charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", size = 105020, upload-time = "2025-05-02T08:34:10.6Z" }, { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" }, { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" }, { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" }, { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" }, { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" }, { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" }, { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" }, { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" }, { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" }, { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" }, { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" }, { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" }, { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" }, { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.6.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690, upload-time = "2024-08-04T19:43:07.695Z" }, { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127, upload-time = "2024-08-04T19:43:10.15Z" }, { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654, upload-time = "2024-08-04T19:43:12.405Z" }, { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598, upload-time = "2024-08-04T19:43:14.078Z" }, { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732, upload-time = "2024-08-04T19:43:16.632Z" }, { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816, upload-time = "2024-08-04T19:43:19.049Z" }, { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325, upload-time = "2024-08-04T19:43:21.246Z" }, { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418, upload-time = "2024-08-04T19:43:22.945Z" }, { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343, upload-time = "2024-08-04T19:43:25.121Z" }, { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136, upload-time = "2024-08-04T19:43:26.851Z" }, { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796, upload-time = "2024-08-04T19:43:29.115Z" }, { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244, upload-time = "2024-08-04T19:43:31.285Z" }, { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279, upload-time = "2024-08-04T19:43:33.581Z" }, { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859, upload-time = "2024-08-04T19:43:35.301Z" }, { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549, upload-time = "2024-08-04T19:43:37.578Z" }, { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477, upload-time = "2024-08-04T19:43:39.92Z" }, { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134, upload-time = "2024-08-04T19:43:41.453Z" }, { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910, upload-time = "2024-08-04T19:43:43.037Z" }, { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348, upload-time = "2024-08-04T19:43:44.787Z" }, { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230, upload-time = "2024-08-04T19:43:46.707Z" }, { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983, upload-time = "2024-08-04T19:43:49.082Z" }, { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221, upload-time = "2024-08-04T19:43:52.15Z" }, { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342, upload-time = "2024-08-04T19:43:53.746Z" }, { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371, upload-time = "2024-08-04T19:43:55.993Z" }, { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455, upload-time = "2024-08-04T19:43:57.618Z" }, { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924, upload-time = "2024-08-04T19:44:00.012Z" }, { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252, upload-time = "2024-08-04T19:44:01.713Z" }, { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897, upload-time = "2024-08-04T19:44:03.898Z" }, { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007, upload-time = "2024-08-04T19:44:09.453Z" }, { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269, upload-time = "2024-08-04T19:44:11.045Z" }, { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886, upload-time = "2024-08-04T19:44:12.83Z" }, { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037, upload-time = "2024-08-04T19:44:15.393Z" }, { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038, upload-time = "2024-08-04T19:44:17.466Z" }, { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690, upload-time = "2024-08-04T19:44:19.336Z" }, { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765, upload-time = "2024-08-04T19:44:20.994Z" }, { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611, upload-time = "2024-08-04T19:44:22.616Z" }, { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671, upload-time = "2024-08-04T19:44:24.418Z" }, { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368, upload-time = "2024-08-04T19:44:26.276Z" }, { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758, upload-time = "2024-08-04T19:44:29.028Z" }, { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035, upload-time = "2024-08-04T19:44:30.673Z" }, { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839, upload-time = "2024-08-04T19:44:32.412Z" }, { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569, upload-time = "2024-08-04T19:44:34.547Z" }, { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927, upload-time = "2024-08-04T19:44:36.313Z" }, { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401, upload-time = "2024-08-04T19:44:38.155Z" }, { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301, upload-time = "2024-08-04T19:44:39.883Z" }, { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598, upload-time = "2024-08-04T19:44:41.59Z" }, { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307, upload-time = "2024-08-04T19:44:43.301Z" }, { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453, upload-time = "2024-08-04T19:44:45.677Z" }, { url = "https://files.pythonhosted.org/packages/81/d0/d9e3d554e38beea5a2e22178ddb16587dbcbe9a1ef3211f55733924bf7fa/coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", size = 206674, upload-time = "2024-08-04T19:44:47.694Z" }, { url = "https://files.pythonhosted.org/packages/38/ea/cab2dc248d9f45b2b7f9f1f596a4d75a435cb364437c61b51d2eb33ceb0e/coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", size = 207101, upload-time = "2024-08-04T19:44:49.32Z" }, { url = "https://files.pythonhosted.org/packages/ca/6f/f82f9a500c7c5722368978a5390c418d2a4d083ef955309a8748ecaa8920/coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", size = 236554, upload-time = "2024-08-04T19:44:51.631Z" }, { url = "https://files.pythonhosted.org/packages/a6/94/d3055aa33d4e7e733d8fa309d9adf147b4b06a82c1346366fc15a2b1d5fa/coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", size = 234440, upload-time = "2024-08-04T19:44:53.464Z" }, { url = "https://files.pythonhosted.org/packages/e4/6e/885bcd787d9dd674de4a7d8ec83faf729534c63d05d51d45d4fa168f7102/coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", size = 235889, upload-time = "2024-08-04T19:44:55.165Z" }, { url = "https://files.pythonhosted.org/packages/f4/63/df50120a7744492710854860783d6819ff23e482dee15462c9a833cc428a/coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", size = 235142, upload-time = "2024-08-04T19:44:57.269Z" }, { url = "https://files.pythonhosted.org/packages/3a/5d/9d0acfcded2b3e9ce1c7923ca52ccc00c78a74e112fc2aee661125b7843b/coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", size = 233805, upload-time = "2024-08-04T19:44:59.033Z" }, { url = "https://files.pythonhosted.org/packages/c4/56/50abf070cb3cd9b1dd32f2c88f083aab561ecbffbcd783275cb51c17f11d/coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", size = 234655, upload-time = "2024-08-04T19:45:01.398Z" }, { url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296, upload-time = "2024-08-04T19:45:03.819Z" }, { url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137, upload-time = "2024-08-04T19:45:06.25Z" }, { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688, upload-time = "2024-08-04T19:45:08.358Z" }, { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120, upload-time = "2024-08-04T19:45:11.526Z" }, { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249, upload-time = "2024-08-04T19:45:13.202Z" }, { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237, upload-time = "2024-08-04T19:45:14.961Z" }, { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311, upload-time = "2024-08-04T19:45:16.924Z" }, { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453, upload-time = "2024-08-04T19:45:18.672Z" }, { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958, upload-time = "2024-08-04T19:45:20.63Z" }, { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938, upload-time = "2024-08-04T19:45:23.062Z" }, { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352, upload-time = "2024-08-04T19:45:25.042Z" }, { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153, upload-time = "2024-08-04T19:45:27.079Z" }, { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926, upload-time = "2024-08-04T19:45:28.875Z" }, ] [[package]] name = "coverage" version = "7.10.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/87/0e/66dbd4c6a7f0758a8d18044c048779ba21fb94856e1edcf764bd5403e710/coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57", size = 819938, upload-time = "2025-07-27T14:13:39.045Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/e7/0f4e35a15361337529df88151bddcac8e8f6d6fd01da94a4b7588901c2fe/coverage-7.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1c86eb388bbd609d15560e7cc0eb936c102b6f43f31cf3e58b4fd9afe28e1372", size = 214627, upload-time = "2025-07-27T14:11:01.211Z" }, { url = "https://files.pythonhosted.org/packages/e0/fd/17872e762c408362072c936dbf3ca28c67c609a1f5af434b1355edcb7e12/coverage-7.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b4ba0f488c1bdb6bd9ba81da50715a372119785458831c73428a8566253b86b", size = 215015, upload-time = "2025-07-27T14:11:03.988Z" }, { url = "https://files.pythonhosted.org/packages/54/50/c9d445ba38ee5f685f03876c0f8223469e2e46c5d3599594dca972b470c8/coverage-7.10.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083442ecf97d434f0cb3b3e3676584443182653da08b42e965326ba12d6b5f2a", size = 241995, upload-time = "2025-07-27T14:11:05.983Z" }, { url = "https://files.pythonhosted.org/packages/cc/83/4ae6e0f60376af33de543368394d21b9ac370dc86434039062ef171eebf8/coverage-7.10.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c1a40c486041006b135759f59189385da7c66d239bad897c994e18fd1d0c128f", size = 243253, upload-time = "2025-07-27T14:11:07.424Z" }, { url = "https://files.pythonhosted.org/packages/49/90/17a4d9ac7171be364ce8c0bb2b6da05e618ebfe1f11238ad4f26c99f5467/coverage-7.10.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3beb76e20b28046989300c4ea81bf690df84ee98ade4dc0bbbf774a28eb98440", size = 245110, upload-time = "2025-07-27T14:11:09.152Z" }, { url = "https://files.pythonhosted.org/packages/e1/f7/edc3f485d536ed417f3af2b4969582bcb5fab456241721825fa09354161e/coverage-7.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc265a7945e8d08da28999ad02b544963f813a00f3ed0a7a0ce4165fd77629f8", size = 243056, upload-time = "2025-07-27T14:11:10.586Z" }, { url = "https://files.pythonhosted.org/packages/58/2c/c4c316a57718556b8d0cc8304437741c31b54a62934e7c8c551a7915c2f4/coverage-7.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:47c91f32ba4ac46f1e224a7ebf3f98b4b24335bad16137737fe71a5961a0665c", size = 241731, upload-time = "2025-07-27T14:11:12.145Z" }, { url = "https://files.pythonhosted.org/packages/f7/93/c78e144c6f086043d0d7d9237c5b880e71ac672ed2712c6f8cca5544481f/coverage-7.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1a108dd78ed185020f66f131c60078f3fae3f61646c28c8bb4edd3fa121fc7fc", size = 242023, upload-time = "2025-07-27T14:11:13.573Z" }, { url = "https://files.pythonhosted.org/packages/8f/e1/34e8505ca81fc144a612e1cc79fadd4a78f42e96723875f4e9f1f470437e/coverage-7.10.1-cp310-cp310-win32.whl", hash = "sha256:7092cc82382e634075cc0255b0b69cb7cada7c1f249070ace6a95cb0f13548ef", size = 217130, upload-time = "2025-07-27T14:11:15.11Z" }, { url = "https://files.pythonhosted.org/packages/75/2b/82adfce6edffc13d804aee414e64c0469044234af9296e75f6d13f92f6a2/coverage-7.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac0c5bba938879c2fc0bc6c1b47311b5ad1212a9dcb8b40fe2c8110239b7faed", size = 218015, upload-time = "2025-07-27T14:11:16.836Z" }, { url = "https://files.pythonhosted.org/packages/20/8e/ef088112bd1b26e2aa931ee186992b3e42c222c64f33e381432c8ee52aae/coverage-7.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45e2f9d5b0b5c1977cb4feb5f594be60eb121106f8900348e29331f553a726f", size = 214747, upload-time = "2025-07-27T14:11:18.217Z" }, { url = "https://files.pythonhosted.org/packages/2d/76/a1e46f3c6e0897758eb43af88bb3c763cb005f4950769f7b553e22aa5f89/coverage-7.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a7a4d74cb0f5e3334f9aa26af7016ddb94fb4bfa11b4a573d8e98ecba8c34f1", size = 215128, upload-time = "2025-07-27T14:11:19.706Z" }, { url = "https://files.pythonhosted.org/packages/78/4d/903bafb371a8c887826ecc30d3977b65dfad0e1e66aa61b7e173de0828b0/coverage-7.10.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4b0aab55ad60ead26159ff12b538c85fbab731a5e3411c642b46c3525863437", size = 245140, upload-time = "2025-07-27T14:11:21.261Z" }, { url = "https://files.pythonhosted.org/packages/55/f1/1f8f09536f38394a8698dd08a0e9608a512eacee1d3b771e2d06397f77bf/coverage-7.10.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dcc93488c9ebd229be6ee1f0d9aad90da97b33ad7e2912f5495804d78a3cd6b7", size = 246977, upload-time = "2025-07-27T14:11:23.15Z" }, { url = "https://files.pythonhosted.org/packages/57/cc/ed6bbc5a3bdb36ae1bca900bbbfdcb23b260ef2767a7b2dab38b92f61adf/coverage-7.10.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa309df995d020f3438407081b51ff527171cca6772b33cf8f85344b8b4b8770", size = 249140, upload-time = "2025-07-27T14:11:24.743Z" }, { url = "https://files.pythonhosted.org/packages/10/f5/e881ade2d8e291b60fa1d93d6d736107e940144d80d21a0d4999cff3642f/coverage-7.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cfb8b9d8855c8608f9747602a48ab525b1d320ecf0113994f6df23160af68262", size = 246869, upload-time = "2025-07-27T14:11:26.156Z" }, { url = "https://files.pythonhosted.org/packages/53/b9/6a5665cb8996e3cd341d184bb11e2a8edf01d8dadcf44eb1e742186cf243/coverage-7.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:320d86da829b012982b414c7cdda65f5d358d63f764e0e4e54b33097646f39a3", size = 244899, upload-time = "2025-07-27T14:11:27.622Z" }, { url = "https://files.pythonhosted.org/packages/27/11/24156776709c4e25bf8a33d6bb2ece9a9067186ddac19990f6560a7f8130/coverage-7.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc60ddd483c556590da1d9482a4518292eec36dd0e1e8496966759a1f282bcd0", size = 245507, upload-time = "2025-07-27T14:11:29.544Z" }, { url = "https://files.pythonhosted.org/packages/43/db/a6f0340b7d6802a79928659c9a32bc778ea420e87a61b568d68ac36d45a8/coverage-7.10.1-cp311-cp311-win32.whl", hash = "sha256:4fcfe294f95b44e4754da5b58be750396f2b1caca8f9a0e78588e3ef85f8b8be", size = 217167, upload-time = "2025-07-27T14:11:31.349Z" }, { url = "https://files.pythonhosted.org/packages/f5/6f/1990eb4fd05cea4cfabdf1d587a997ac5f9a8bee883443a1d519a2a848c9/coverage-7.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:efa23166da3fe2915f8ab452dde40319ac84dc357f635737174a08dbd912980c", size = 218054, upload-time = "2025-07-27T14:11:33.202Z" }, { url = "https://files.pythonhosted.org/packages/b4/4d/5e061d6020251b20e9b4303bb0b7900083a1a384ec4e5db326336c1c4abd/coverage-7.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:d12b15a8c3759e2bb580ffa423ae54be4f184cf23beffcbd641f4fe6e1584293", size = 216483, upload-time = "2025-07-27T14:11:34.663Z" }, { url = "https://files.pythonhosted.org/packages/a5/3f/b051feeb292400bd22d071fdf933b3ad389a8cef5c80c7866ed0c7414b9e/coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4", size = 214934, upload-time = "2025-07-27T14:11:36.096Z" }, { url = "https://files.pythonhosted.org/packages/f8/e4/a61b27d5c4c2d185bdfb0bfe9d15ab4ac4f0073032665544507429ae60eb/coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e", size = 215173, upload-time = "2025-07-27T14:11:38.005Z" }, { url = "https://files.pythonhosted.org/packages/8a/01/40a6ee05b60d02d0bc53742ad4966e39dccd450aafb48c535a64390a3552/coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4", size = 246190, upload-time = "2025-07-27T14:11:39.887Z" }, { url = "https://files.pythonhosted.org/packages/11/ef/a28d64d702eb583c377255047281305dc5a5cfbfb0ee36e721f78255adb6/coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a", size = 248618, upload-time = "2025-07-27T14:11:41.841Z" }, { url = "https://files.pythonhosted.org/packages/6a/ad/73d018bb0c8317725370c79d69b5c6e0257df84a3b9b781bda27a438a3be/coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe", size = 250081, upload-time = "2025-07-27T14:11:43.705Z" }, { url = "https://files.pythonhosted.org/packages/2d/dd/496adfbbb4503ebca5d5b2de8bed5ec00c0a76558ffc5b834fd404166bc9/coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386", size = 247990, upload-time = "2025-07-27T14:11:45.244Z" }, { url = "https://files.pythonhosted.org/packages/18/3c/a9331a7982facfac0d98a4a87b36ae666fe4257d0f00961a3a9ef73e015d/coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6", size = 246191, upload-time = "2025-07-27T14:11:47.093Z" }, { url = "https://files.pythonhosted.org/packages/62/0c/75345895013b83f7afe92ec595e15a9a525ede17491677ceebb2ba5c3d85/coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f", size = 247400, upload-time = "2025-07-27T14:11:48.643Z" }, { url = "https://files.pythonhosted.org/packages/e2/a9/98b268cfc5619ef9df1d5d34fee408ecb1542d9fd43d467e5c2f28668cd4/coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca", size = 217338, upload-time = "2025-07-27T14:11:50.258Z" }, { url = "https://files.pythonhosted.org/packages/fe/31/22a5440e4d1451f253c5cd69fdcead65e92ef08cd4ec237b8756dc0b20a7/coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3", size = 218125, upload-time = "2025-07-27T14:11:52.034Z" }, { url = "https://files.pythonhosted.org/packages/d6/2b/40d9f0ce7ee839f08a43c5bfc9d05cec28aaa7c9785837247f96cbe490b9/coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4", size = 216523, upload-time = "2025-07-27T14:11:53.965Z" }, { url = "https://files.pythonhosted.org/packages/ef/72/135ff5fef09b1ffe78dbe6fcf1e16b2e564cd35faeacf3d63d60d887f12d/coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39", size = 214960, upload-time = "2025-07-27T14:11:55.959Z" }, { url = "https://files.pythonhosted.org/packages/b1/aa/73a5d1a6fc08ca709a8177825616aa95ee6bf34d522517c2595484a3e6c9/coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7", size = 215220, upload-time = "2025-07-27T14:11:57.899Z" }, { url = "https://files.pythonhosted.org/packages/8d/40/3124fdd45ed3772a42fc73ca41c091699b38a2c3bd4f9cb564162378e8b6/coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892", size = 245772, upload-time = "2025-07-27T14:12:00.422Z" }, { url = "https://files.pythonhosted.org/packages/42/62/a77b254822efa8c12ad59e8039f2bc3df56dc162ebda55e1943e35ba31a5/coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7", size = 248116, upload-time = "2025-07-27T14:12:03.099Z" }, { url = "https://files.pythonhosted.org/packages/1d/01/8101f062f472a3a6205b458d18ef0444a63ae5d36a8a5ed5dd0f6167f4db/coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994", size = 249554, upload-time = "2025-07-27T14:12:04.668Z" }, { url = "https://files.pythonhosted.org/packages/8f/7b/e51bc61573e71ff7275a4f167aecbd16cb010aefdf54bcd8b0a133391263/coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0", size = 247766, upload-time = "2025-07-27T14:12:06.234Z" }, { url = "https://files.pythonhosted.org/packages/4b/71/1c96d66a51d4204a9d6d12df53c4071d87e110941a2a1fe94693192262f5/coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7", size = 245735, upload-time = "2025-07-27T14:12:08.305Z" }, { url = "https://files.pythonhosted.org/packages/13/d5/efbc2ac4d35ae2f22ef6df2ca084c60e13bd9378be68655e3268c80349ab/coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7", size = 247118, upload-time = "2025-07-27T14:12:09.903Z" }, { url = "https://files.pythonhosted.org/packages/d1/22/073848352bec28ca65f2b6816b892fcf9a31abbef07b868487ad15dd55f1/coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7", size = 217381, upload-time = "2025-07-27T14:12:11.535Z" }, { url = "https://files.pythonhosted.org/packages/b7/df/df6a0ff33b042f000089bd11b6bb034bab073e2ab64a56e78ed882cba55d/coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e", size = 218152, upload-time = "2025-07-27T14:12:13.182Z" }, { url = "https://files.pythonhosted.org/packages/30/e3/5085ca849a40ed6b47cdb8f65471c2f754e19390b5a12fa8abd25cbfaa8f/coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4", size = 216559, upload-time = "2025-07-27T14:12:14.807Z" }, { url = "https://files.pythonhosted.org/packages/cc/93/58714efbfdeb547909feaabe1d67b2bdd59f0597060271b9c548d5efb529/coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72", size = 215677, upload-time = "2025-07-27T14:12:16.68Z" }, { url = "https://files.pythonhosted.org/packages/c0/0c/18eaa5897e7e8cb3f8c45e563e23e8a85686b4585e29d53cacb6bc9cb340/coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af", size = 215899, upload-time = "2025-07-27T14:12:18.758Z" }, { url = "https://files.pythonhosted.org/packages/84/c1/9d1affacc3c75b5a184c140377701bbf14fc94619367f07a269cd9e4fed6/coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7", size = 257140, upload-time = "2025-07-27T14:12:20.357Z" }, { url = "https://files.pythonhosted.org/packages/3d/0f/339bc6b8fa968c346df346068cca1f24bdea2ddfa93bb3dc2e7749730962/coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759", size = 259005, upload-time = "2025-07-27T14:12:22.007Z" }, { url = "https://files.pythonhosted.org/packages/c8/22/89390864b92ea7c909079939b71baba7e5b42a76bf327c1d615bd829ba57/coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324", size = 261143, upload-time = "2025-07-27T14:12:23.746Z" }, { url = "https://files.pythonhosted.org/packages/2c/56/3d04d89017c0c41c7a71bd69b29699d919b6bbf2649b8b2091240b97dd6a/coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53", size = 258735, upload-time = "2025-07-27T14:12:25.73Z" }, { url = "https://files.pythonhosted.org/packages/cb/40/312252c8afa5ca781063a09d931f4b9409dc91526cd0b5a2b84143ffafa2/coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f", size = 256871, upload-time = "2025-07-27T14:12:27.767Z" }, { url = "https://files.pythonhosted.org/packages/1f/2b/564947d5dede068215aaddb9e05638aeac079685101462218229ddea9113/coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd", size = 257692, upload-time = "2025-07-27T14:12:29.347Z" }, { url = "https://files.pythonhosted.org/packages/93/1b/c8a867ade85cb26d802aea2209b9c2c80613b9c122baa8c8ecea6799648f/coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c", size = 218059, upload-time = "2025-07-27T14:12:31.076Z" }, { url = "https://files.pythonhosted.org/packages/a1/fe/cd4ab40570ae83a516bf5e754ea4388aeedd48e660e40c50b7713ed4f930/coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18", size = 219150, upload-time = "2025-07-27T14:12:32.746Z" }, { url = "https://files.pythonhosted.org/packages/8d/16/6e5ed5854be6d70d0c39e9cb9dd2449f2c8c34455534c32c1a508c7dbdb5/coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4", size = 217014, upload-time = "2025-07-27T14:12:34.406Z" }, { url = "https://files.pythonhosted.org/packages/54/8e/6d0bfe9c3d7121cf936c5f8b03e8c3da1484fb801703127dba20fb8bd3c7/coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c", size = 214951, upload-time = "2025-07-27T14:12:36.069Z" }, { url = "https://files.pythonhosted.org/packages/f2/29/e3e51a8c653cf2174c60532aafeb5065cea0911403fa144c9abe39790308/coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e", size = 215229, upload-time = "2025-07-27T14:12:37.759Z" }, { url = "https://files.pythonhosted.org/packages/e0/59/3c972080b2fa18b6c4510201f6d4dc87159d450627d062cd9ad051134062/coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b", size = 245738, upload-time = "2025-07-27T14:12:39.453Z" }, { url = "https://files.pythonhosted.org/packages/2e/04/fc0d99d3f809452654e958e1788454f6e27b34e43f8f8598191c8ad13537/coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41", size = 248045, upload-time = "2025-07-27T14:12:41.387Z" }, { url = "https://files.pythonhosted.org/packages/5e/2e/afcbf599e77e0dfbf4c97197747250d13d397d27e185b93987d9eaac053d/coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f", size = 249666, upload-time = "2025-07-27T14:12:43.056Z" }, { url = "https://files.pythonhosted.org/packages/6e/ae/bc47f7f8ecb7a06cbae2bf86a6fa20f479dd902bc80f57cff7730438059d/coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1", size = 247692, upload-time = "2025-07-27T14:12:44.83Z" }, { url = "https://files.pythonhosted.org/packages/b6/26/cbfa3092d31ccba8ba7647e4d25753263e818b4547eba446b113d7d1efdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2", size = 245536, upload-time = "2025-07-27T14:12:46.527Z" }, { url = "https://files.pythonhosted.org/packages/56/77/9c68e92500e6a1c83d024a70eadcc9a173f21aadd73c4675fe64c9c43fdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4", size = 246954, upload-time = "2025-07-27T14:12:49.279Z" }, { url = "https://files.pythonhosted.org/packages/7f/a5/ba96671c5a669672aacd9877a5987c8551501b602827b4e84256da2a30a7/coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613", size = 217616, upload-time = "2025-07-27T14:12:51.214Z" }, { url = "https://files.pythonhosted.org/packages/e7/3c/e1e1eb95fc1585f15a410208c4795db24a948e04d9bde818fe4eb893bc85/coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e", size = 218412, upload-time = "2025-07-27T14:12:53.429Z" }, { url = "https://files.pythonhosted.org/packages/b0/85/7e1e5be2cb966cba95566ba702b13a572ca744fbb3779df9888213762d67/coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652", size = 216776, upload-time = "2025-07-27T14:12:55.482Z" }, { url = "https://files.pythonhosted.org/packages/62/0f/5bb8f29923141cca8560fe2217679caf4e0db643872c1945ac7d8748c2a7/coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894", size = 215698, upload-time = "2025-07-27T14:12:57.225Z" }, { url = "https://files.pythonhosted.org/packages/80/29/547038ffa4e8e4d9e82f7dfc6d152f75fcdc0af146913f0ba03875211f03/coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5", size = 215902, upload-time = "2025-07-27T14:12:59.071Z" }, { url = "https://files.pythonhosted.org/packages/e1/8a/7aaa8fbfaed900147987a424e112af2e7790e1ac9cd92601e5bd4e1ba60a/coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2", size = 257230, upload-time = "2025-07-27T14:13:01.248Z" }, { url = "https://files.pythonhosted.org/packages/e5/1d/c252b5ffac44294e23a0d79dd5acf51749b39795ccc898faeabf7bee903f/coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb", size = 259194, upload-time = "2025-07-27T14:13:03.247Z" }, { url = "https://files.pythonhosted.org/packages/16/ad/6c8d9f83d08f3bac2e7507534d0c48d1a4f52c18e6f94919d364edbdfa8f/coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b", size = 261316, upload-time = "2025-07-27T14:13:04.957Z" }, { url = "https://files.pythonhosted.org/packages/d6/4e/f9bbf3a36c061e2e0e0f78369c006d66416561a33d2bee63345aee8ee65e/coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea", size = 258794, upload-time = "2025-07-27T14:13:06.715Z" }, { url = "https://files.pythonhosted.org/packages/87/82/e600bbe78eb2cb0541751d03cef9314bcd0897e8eea156219c39b685f869/coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd", size = 256869, upload-time = "2025-07-27T14:13:08.933Z" }, { url = "https://files.pythonhosted.org/packages/ce/5d/2fc9a9236c5268f68ac011d97cd3a5ad16cc420535369bedbda659fdd9b7/coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d", size = 257765, upload-time = "2025-07-27T14:13:10.778Z" }, { url = "https://files.pythonhosted.org/packages/8a/05/b4e00b2bd48a2dc8e1c7d2aea7455f40af2e36484ab2ef06deb85883e9fe/coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47", size = 218420, upload-time = "2025-07-27T14:13:12.882Z" }, { url = "https://files.pythonhosted.org/packages/77/fb/d21d05f33ea27ece327422240e69654b5932b0b29e7fbc40fbab3cf199bf/coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651", size = 219536, upload-time = "2025-07-27T14:13:14.718Z" }, { url = "https://files.pythonhosted.org/packages/a6/68/7fea94b141281ed8be3d1d5c4319a97f2befc3e487ce33657fc64db2c45e/coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab", size = 217190, upload-time = "2025-07-27T14:13:16.85Z" }, { url = "https://files.pythonhosted.org/packages/c3/98/9b19d4aebfb31552596a7ac55cd678c3ebd74be6153888c56d39e23f376b/coverage-7.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:57b6e8789cbefdef0667e4a94f8ffa40f9402cee5fc3b8e4274c894737890145", size = 214625, upload-time = "2025-07-27T14:13:18.661Z" }, { url = "https://files.pythonhosted.org/packages/ea/24/e2391365d0940fc757666ecd7572aced0963e859188e57169bd18fba5d29/coverage-7.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85b22a9cce00cb03156334da67eb86e29f22b5e93876d0dd6a98646bb8a74e53", size = 215001, upload-time = "2025-07-27T14:13:20.478Z" }, { url = "https://files.pythonhosted.org/packages/ce/0c/c1740d7fac57cb0c54cd04786f3dbfc4d0bfa0a6cc9f19f69c170ae67f6a/coverage-7.10.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97b6983a2f9c76d345ca395e843a049390b39652984e4a3b45b2442fa733992d", size = 241082, upload-time = "2025-07-27T14:13:22.318Z" }, { url = "https://files.pythonhosted.org/packages/45/b5/965b26315ecae6455bc40f1de8563a57e82cb31af8af2e2844655cf400f1/coverage-7.10.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ddf2a63b91399a1c2f88f40bc1705d5a7777e31c7e9eb27c602280f477b582ba", size = 242979, upload-time = "2025-07-27T14:13:24.123Z" }, { url = "https://files.pythonhosted.org/packages/0b/48/80c5c6a5a792348ba71b2315809c5a2daab2981564e31d1f3cd092c8cd97/coverage-7.10.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47ab6dbbc31a14c5486420c2c1077fcae692097f673cf5be9ddbec8cdaa4cdbc", size = 244550, upload-time = "2025-07-27T14:13:25.9Z" }, { url = "https://files.pythonhosted.org/packages/ab/73/332667b91cfa3c27130026af220fca478b07e913e96932d12c100e1a7314/coverage-7.10.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:21eb7d8b45d3700e7c2936a736f732794c47615a20f739f4133d5230a6512a88", size = 242482, upload-time = "2025-07-27T14:13:28.121Z" }, { url = "https://files.pythonhosted.org/packages/ae/e6/24c9120ad91314be82f793a2a174fe738583a716264b1523fe95ad731cb3/coverage-7.10.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:283005bb4d98ae33e45f2861cd2cde6a21878661c9ad49697f6951b358a0379b", size = 240717, upload-time = "2025-07-27T14:13:29.93Z" }, { url = "https://files.pythonhosted.org/packages/94/9a/21a4d5135eb4b8064fd9bf8a8eb8d4465982611d2d7fb569d6c2edf38f04/coverage-7.10.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fefe31d61d02a8b2c419700b1fade9784a43d726de26495f243b663cd9fe1513", size = 241669, upload-time = "2025-07-27T14:13:31.726Z" }, { url = "https://files.pythonhosted.org/packages/3f/1d/e4ce3b23f8b8b0fe196c436499414b1af06b9e1610cefedaaad37c9668d0/coverage-7.10.1-cp39-cp39-win32.whl", hash = "sha256:e8ab8e4c7ec7f8a55ac05b5b715a051d74eac62511c6d96d5bb79aaafa3b04cf", size = 217138, upload-time = "2025-07-27T14:13:33.481Z" }, { url = "https://files.pythonhosted.org/packages/d3/c6/b7fcf41c341e686610fdf9ef1a4b29045015f36d3eecd17679874e4739ed/coverage-7.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:c36baa0ecde742784aa76c2b816466d3ea888d5297fda0edbac1bf48fa94688a", size = 218035, upload-time = "2025-07-27T14:13:35.337Z" }, { url = "https://files.pythonhosted.org/packages/0f/64/922899cff2c0fd3496be83fa8b81230f5a8d82a2ad30f98370b133c2c83b/coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7", size = 206597, upload-time = "2025-07-27T14:13:37.221Z" }, ] [[package]] name = "docstring-parser" version = "0.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, ] [[package]] name = "docutils" version = "0.20.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365, upload-time = "2023-05-16T23:39:19.748Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666, upload-time = "2023-05-16T23:39:15.976Z" }, ] [[package]] name = "docutils" version = "0.21.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, ] [[package]] name = "eval-type-backport" version = "0.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" }, ] [[package]] name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "imagesize" version = "1.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, ] [[package]] name = "importlib-metadata" version = "8.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, ] [[package]] name = "importlib-metadata" version = "8.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", ] dependencies = [ { name = "zipp", version = "3.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "markupsafe", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "markdown-it-py" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "markupsafe" version = "2.1.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206, upload-time = "2024-02-02T16:30:04.105Z" }, { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079, upload-time = "2024-02-02T16:30:06.5Z" }, { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620, upload-time = "2024-02-02T16:30:08.31Z" }, { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818, upload-time = "2024-02-02T16:30:09.577Z" }, { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493, upload-time = "2024-02-02T16:30:11.488Z" }, { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630, upload-time = "2024-02-02T16:30:13.144Z" }, { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745, upload-time = "2024-02-02T16:30:14.222Z" }, { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021, upload-time = "2024-02-02T16:30:16.032Z" }, { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659, upload-time = "2024-02-02T16:30:17.079Z" }, { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213, upload-time = "2024-02-02T16:30:18.251Z" }, { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" }, { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" }, { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" }, { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220, upload-time = "2024-02-02T16:30:24.76Z" }, { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756, upload-time = "2024-02-02T16:30:25.877Z" }, { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988, upload-time = "2024-02-02T16:30:26.935Z" }, { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718, upload-time = "2024-02-02T16:30:28.111Z" }, { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317, upload-time = "2024-02-02T16:30:29.214Z" }, { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670, upload-time = "2024-02-02T16:30:30.915Z" }, { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224, upload-time = "2024-02-02T16:30:32.09Z" }, { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215, upload-time = "2024-02-02T16:30:33.081Z" }, { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069, upload-time = "2024-02-02T16:30:34.148Z" }, { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452, upload-time = "2024-02-02T16:30:35.149Z" }, { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462, upload-time = "2024-02-02T16:30:36.166Z" }, { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869, upload-time = "2024-02-02T16:30:37.834Z" }, { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906, upload-time = "2024-02-02T16:30:39.366Z" }, { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296, upload-time = "2024-02-02T16:30:40.413Z" }, { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038, upload-time = "2024-02-02T16:30:42.243Z" }, { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572, upload-time = "2024-02-02T16:30:43.326Z" }, { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127, upload-time = "2024-02-02T16:30:44.418Z" }, { url = "https://files.pythonhosted.org/packages/f8/ff/2c942a82c35a49df5de3a630ce0a8456ac2969691b230e530ac12314364c/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", size = 18192, upload-time = "2024-02-02T16:30:57.715Z" }, { url = "https://files.pythonhosted.org/packages/4f/14/6f294b9c4f969d0c801a4615e221c1e084722ea6114ab2114189c5b8cbe0/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", size = 14072, upload-time = "2024-02-02T16:30:58.844Z" }, { url = "https://files.pythonhosted.org/packages/81/d4/fd74714ed30a1dedd0b82427c02fa4deec64f173831ec716da11c51a50aa/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", size = 26928, upload-time = "2024-02-02T16:30:59.922Z" }, { url = "https://files.pythonhosted.org/packages/c7/bd/50319665ce81bb10e90d1cf76f9e1aa269ea6f7fa30ab4521f14d122a3df/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", size = 26106, upload-time = "2024-02-02T16:31:01.582Z" }, { url = "https://files.pythonhosted.org/packages/4c/6f/f2b0f675635b05f6afd5ea03c094557bdb8622fa8e673387444fe8d8e787/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68", size = 25781, upload-time = "2024-02-02T16:31:02.71Z" }, { url = "https://files.pythonhosted.org/packages/51/e0/393467cf899b34a9d3678e78961c2c8cdf49fb902a959ba54ece01273fb1/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", size = 30518, upload-time = "2024-02-02T16:31:04.392Z" }, { url = "https://files.pythonhosted.org/packages/f6/02/5437e2ad33047290dafced9df741d9efc3e716b75583bbd73a9984f1b6f7/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", size = 29669, upload-time = "2024-02-02T16:31:05.53Z" }, { url = "https://files.pythonhosted.org/packages/0e/7d/968284145ffd9d726183ed6237c77938c021abacde4e073020f920e060b2/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", size = 29933, upload-time = "2024-02-02T16:31:06.636Z" }, { url = "https://files.pythonhosted.org/packages/bf/f3/ecb00fc8ab02b7beae8699f34db9357ae49d9f21d4d3de6f305f34fa949e/MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", size = 16656, upload-time = "2024-02-02T16:31:07.767Z" }, { url = "https://files.pythonhosted.org/packages/92/21/357205f03514a49b293e214ac39de01fadd0970a6e05e4bf1ddd0ffd0881/MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", size = 17206, upload-time = "2024-02-02T16:31:08.843Z" }, { url = "https://files.pythonhosted.org/packages/0f/31/780bb297db036ba7b7bbede5e1d7f1e14d704ad4beb3ce53fb495d22bc62/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", size = 18193, upload-time = "2024-02-02T16:31:10.155Z" }, { url = "https://files.pythonhosted.org/packages/6c/77/d77701bbef72892affe060cdacb7a2ed7fd68dae3b477a8642f15ad3b132/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", size = 14073, upload-time = "2024-02-02T16:31:11.442Z" }, { url = "https://files.pythonhosted.org/packages/d9/a7/1e558b4f78454c8a3a0199292d96159eb4d091f983bc35ef258314fe7269/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", size = 26486, upload-time = "2024-02-02T16:31:12.488Z" }, { url = "https://files.pythonhosted.org/packages/5f/5a/360da85076688755ea0cceb92472923086993e86b5613bbae9fbc14136b0/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", size = 25685, upload-time = "2024-02-02T16:31:13.726Z" }, { url = "https://files.pythonhosted.org/packages/6a/18/ae5a258e3401f9b8312f92b028c54d7026a97ec3ab20bfaddbdfa7d8cce8/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", size = 25338, upload-time = "2024-02-02T16:31:14.812Z" }, { url = "https://files.pythonhosted.org/packages/0b/cc/48206bd61c5b9d0129f4d75243b156929b04c94c09041321456fd06a876d/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", size = 30439, upload-time = "2024-02-02T16:31:15.946Z" }, { url = "https://files.pythonhosted.org/packages/d1/06/a41c112ab9ffdeeb5f77bc3e331fdadf97fa65e52e44ba31880f4e7f983c/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", size = 29531, upload-time = "2024-02-02T16:31:17.13Z" }, { url = "https://files.pythonhosted.org/packages/02/8c/ab9a463301a50dab04d5472e998acbd4080597abc048166ded5c7aa768c8/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", size = 29823, upload-time = "2024-02-02T16:31:18.247Z" }, { url = "https://files.pythonhosted.org/packages/bc/29/9bc18da763496b055d8e98ce476c8e718dcfd78157e17f555ce6dd7d0895/MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", size = 16658, upload-time = "2024-02-02T16:31:19.583Z" }, { url = "https://files.pythonhosted.org/packages/f6/f8/4da07de16f10551ca1f640c92b5f316f9394088b183c6a57183df6de5ae4/MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", size = 17211, upload-time = "2024-02-02T16:31:20.96Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" }, { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" }, { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" }, { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" }, { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" }, { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" }, { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" }, { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" }, { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" }, { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "msgspec" version = "0.19.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cf/9b/95d8ce458462b8b71b8a70fa94563b2498b89933689f3a7b8911edfae3d7/msgspec-0.19.0.tar.gz", hash = "sha256:604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e", size = 216934, upload-time = "2024-12-27T17:40:28.597Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/13/40/817282b42f58399762267b30deb8ac011d8db373f8da0c212c85fbe62b8f/msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259", size = 190019, upload-time = "2024-12-27T17:39:13.803Z" }, { url = "https://files.pythonhosted.org/packages/92/99/bd7ed738c00f223a8119928661167a89124140792af18af513e6519b0d54/msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36", size = 183680, upload-time = "2024-12-27T17:39:17.847Z" }, { url = "https://files.pythonhosted.org/packages/e5/27/322badde18eb234e36d4a14122b89edd4e2973cdbc3da61ca7edf40a1ccd/msgspec-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe2c4bf29bf4e89790b3117470dea2c20b59932772483082c468b990d45fb947", size = 209334, upload-time = "2024-12-27T17:39:19.065Z" }, { url = "https://files.pythonhosted.org/packages/c6/65/080509c5774a1592b2779d902a70b5fe008532759927e011f068145a16cb/msgspec-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e87ecfa9795ee5214861eab8326b0e75475c2e68a384002aa135ea2a27d909", size = 211551, upload-time = "2024-12-27T17:39:21.767Z" }, { url = "https://files.pythonhosted.org/packages/6f/2e/1c23c6b4ca6f4285c30a39def1054e2bee281389e4b681b5e3711bd5a8c9/msgspec-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c4ec642689da44618f68c90855a10edbc6ac3ff7c1d94395446c65a776e712a", size = 215099, upload-time = "2024-12-27T17:39:24.71Z" }, { url = "https://files.pythonhosted.org/packages/83/fe/95f9654518879f3359d1e76bc41189113aa9102452170ab7c9a9a4ee52f6/msgspec-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2719647625320b60e2d8af06b35f5b12d4f4d281db30a15a1df22adb2295f633", size = 218211, upload-time = "2024-12-27T17:39:27.396Z" }, { url = "https://files.pythonhosted.org/packages/79/f6/71ca7e87a1fb34dfe5efea8156c9ef59dd55613aeda2ca562f122cd22012/msgspec-0.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:695b832d0091edd86eeb535cd39e45f3919f48d997685f7ac31acb15e0a2ed90", size = 186174, upload-time = "2024-12-27T17:39:29.647Z" }, { url = "https://files.pythonhosted.org/packages/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa77046904db764b0462036bc63ef71f02b75b8f72e9c9dd4c447d6da1ed8f8e", size = 187939, upload-time = "2024-12-27T17:39:32.347Z" }, { url = "https://files.pythonhosted.org/packages/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:047cfa8675eb3bad68722cfe95c60e7afabf84d1bd8938979dd2b92e9e4a9551", size = 182202, upload-time = "2024-12-27T17:39:33.633Z" }, { url = "https://files.pythonhosted.org/packages/81/25/3a4b24d468203d8af90d1d351b77ea3cffb96b29492855cf83078f16bfe4/msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e78f46ff39a427e10b4a61614a2777ad69559cc8d603a7c05681f5a595ea98f7", size = 209029, upload-time = "2024-12-27T17:39:35.023Z" }, { url = "https://files.pythonhosted.org/packages/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c7adf191e4bd3be0e9231c3b6dc20cf1199ada2af523885efc2ed218eafd011", size = 210682, upload-time = "2024-12-27T17:39:36.384Z" }, { url = "https://files.pythonhosted.org/packages/03/97/7c8895c9074a97052d7e4a1cc1230b7b6e2ca2486714eb12c3f08bb9d284/msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f04cad4385e20be7c7176bb8ae3dca54a08e9756cfc97bcdb4f18560c3042063", size = 214003, upload-time = "2024-12-27T17:39:39.097Z" }, { url = "https://files.pythonhosted.org/packages/61/61/e892997bcaa289559b4d5869f066a8021b79f4bf8e955f831b095f47a4cd/msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45c8fb410670b3b7eb884d44a75589377c341ec1392b778311acdbfa55187716", size = 216833, upload-time = "2024-12-27T17:39:41.203Z" }, { url = "https://files.pythonhosted.org/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:70eaef4934b87193a27d802534dc466778ad8d536e296ae2f9334e182ac27b6c", size = 186184, upload-time = "2024-12-27T17:39:43.702Z" }, { url = "https://files.pythonhosted.org/packages/b2/5f/a70c24f075e3e7af2fae5414c7048b0e11389685b7f717bb55ba282a34a7/msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f", size = 190485, upload-time = "2024-12-27T17:39:44.974Z" }, { url = "https://files.pythonhosted.org/packages/89/b0/1b9763938cfae12acf14b682fcf05c92855974d921a5a985ecc197d1c672/msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2", size = 183910, upload-time = "2024-12-27T17:39:46.401Z" }, { url = "https://files.pythonhosted.org/packages/87/81/0c8c93f0b92c97e326b279795f9c5b956c5a97af28ca0fbb9fd86c83737a/msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12", size = 210633, upload-time = "2024-12-27T17:39:49.099Z" }, { url = "https://files.pythonhosted.org/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc", size = 213594, upload-time = "2024-12-27T17:39:51.204Z" }, { url = "https://files.pythonhosted.org/packages/19/2b/4137bc2ed45660444842d042be2cf5b18aa06efd2cda107cff18253b9653/msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c", size = 214053, upload-time = "2024-12-27T17:39:52.866Z" }, { url = "https://files.pythonhosted.org/packages/9d/e6/8ad51bdc806aac1dc501e8fe43f759f9ed7284043d722b53323ea421c360/msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537", size = 219081, upload-time = "2024-12-27T17:39:55.142Z" }, { url = "https://files.pythonhosted.org/packages/b1/ef/27dd35a7049c9a4f4211c6cd6a8c9db0a50647546f003a5867827ec45391/msgspec-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0", size = 187467, upload-time = "2024-12-27T17:39:56.531Z" }, { url = "https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f12d30dd6266557aaaf0aa0f9580a9a8fbeadfa83699c487713e355ec5f0bd86", size = 190498, upload-time = "2024-12-27T17:40:00.427Z" }, { url = "https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82b2c42c1b9ebc89e822e7e13bbe9d17ede0c23c187469fdd9505afd5a481314", size = 183950, upload-time = "2024-12-27T17:40:04.219Z" }, { url = "https://files.pythonhosted.org/packages/e8/f0/5b764e066ce9aba4b70d1db8b087ea66098c7c27d59b9dd8a3532774d48f/msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19746b50be214a54239aab822964f2ac81e38b0055cca94808359d779338c10e", size = 210647, upload-time = "2024-12-27T17:40:05.606Z" }, { url = "https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60ef4bdb0ec8e4ad62e5a1f95230c08efb1f64f32e6e8dd2ced685bcc73858b5", size = 213563, upload-time = "2024-12-27T17:40:10.516Z" }, { url = "https://files.pythonhosted.org/packages/53/2f/2b1c2b056894fbaa975f68f81e3014bb447516a8b010f1bed3fb0e016ed7/msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac7f7c377c122b649f7545810c6cd1b47586e3aa3059126ce3516ac7ccc6a6a9", size = 213996, upload-time = "2024-12-27T17:40:12.244Z" }, { url = "https://files.pythonhosted.org/packages/aa/5a/4cd408d90d1417e8d2ce6a22b98a6853c1b4d7cb7669153e4424d60087f6/msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5bc1472223a643f5ffb5bf46ccdede7f9795078194f14edd69e3aab7020d327", size = 219087, upload-time = "2024-12-27T17:40:14.881Z" }, { url = "https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:317050bc0f7739cb30d257ff09152ca309bf5a369854bbf1e57dffc310c1f20f", size = 187432, upload-time = "2024-12-27T17:40:16.256Z" }, { url = "https://files.pythonhosted.org/packages/ea/d0/323f867eaec1f2236ba30adf613777b1c97a7e8698e2e881656b21871fa4/msgspec-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15c1e86fff77184c20a2932cd9742bf33fe23125fa3fcf332df9ad2f7d483044", size = 189926, upload-time = "2024-12-27T17:40:18.939Z" }, { url = "https://files.pythonhosted.org/packages/a8/37/c3e1b39bdae90a7258d77959f5f5e36ad44b40e2be91cff83eea33c54d43/msgspec-0.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b5541b2b3294e5ffabe31a09d604e23a88533ace36ac288fa32a420aa38d229", size = 183873, upload-time = "2024-12-27T17:40:20.214Z" }, { url = "https://files.pythonhosted.org/packages/cb/a2/48f2c15c7644668e51f4dce99d5f709bd55314e47acb02e90682f5880f35/msgspec-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f5c043ace7962ef188746e83b99faaa9e3e699ab857ca3f367b309c8e2c6b12", size = 209272, upload-time = "2024-12-27T17:40:21.534Z" }, { url = "https://files.pythonhosted.org/packages/25/3c/aa339cf08b990c3f07e67b229a3a8aa31bf129ed974b35e5daa0df7d9d56/msgspec-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca06aa08e39bf57e39a258e1996474f84d0dd8130d486c00bec26d797b8c5446", size = 211396, upload-time = "2024-12-27T17:40:22.897Z" }, { url = "https://files.pythonhosted.org/packages/c7/00/c7fb9d524327c558b2803973cc3f988c5100a1708879970a9e377bdf6f4f/msgspec-0.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e695dad6897896e9384cf5e2687d9ae9feaef50e802f93602d35458e20d1fb19", size = 215002, upload-time = "2024-12-27T17:40:24.341Z" }, { url = "https://files.pythonhosted.org/packages/3f/bf/d9f9fff026c1248cde84a5ce62b3742e8a63a3c4e811f99f00c8babf7615/msgspec-0.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3be5c02e1fee57b54130316a08fe40cca53af92999a302a6054cd451700ea7db", size = 218132, upload-time = "2024-12-27T17:40:25.744Z" }, { url = "https://files.pythonhosted.org/packages/00/03/b92011210f79794958167a3a3ea64a71135d9a2034cfb7597b545a42606d/msgspec-0.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:0684573a821be3c749912acf5848cce78af4298345cb2d7a8b8948a0a5a27cfe", size = 186301, upload-time = "2024-12-27T17:40:27.076Z" }, ] [[package]] name = "mypy" version = "1.14.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "mypy-extensions", marker = "python_full_version < '3.9'" }, { name = "tomli", marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051, upload-time = "2024-12-30T16:39:07.335Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002, upload-time = "2024-12-30T16:37:22.435Z" }, { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400, upload-time = "2024-12-30T16:37:53.526Z" }, { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172, upload-time = "2024-12-30T16:37:50.332Z" }, { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732, upload-time = "2024-12-30T16:37:29.96Z" }, { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197, upload-time = "2024-12-30T16:38:05.037Z" }, { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836, upload-time = "2024-12-30T16:37:19.726Z" }, { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432, upload-time = "2024-12-30T16:37:11.533Z" }, { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515, upload-time = "2024-12-30T16:37:40.724Z" }, { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791, upload-time = "2024-12-30T16:36:58.73Z" }, { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203, upload-time = "2024-12-30T16:37:03.741Z" }, { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900, upload-time = "2024-12-30T16:37:57.948Z" }, { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869, upload-time = "2024-12-30T16:37:33.428Z" }, { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668, upload-time = "2024-12-30T16:38:02.211Z" }, { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060, upload-time = "2024-12-30T16:37:46.131Z" }, { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167, upload-time = "2024-12-30T16:37:43.534Z" }, { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341, upload-time = "2024-12-30T16:37:36.249Z" }, { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991, upload-time = "2024-12-30T16:37:06.743Z" }, { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016, upload-time = "2024-12-30T16:37:15.02Z" }, { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097, upload-time = "2024-12-30T16:37:25.144Z" }, { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728, upload-time = "2024-12-30T16:38:08.634Z" }, { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965, upload-time = "2024-12-30T16:38:12.132Z" }, { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660, upload-time = "2024-12-30T16:38:17.342Z" }, { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198, upload-time = "2024-12-30T16:38:32.839Z" }, { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276, upload-time = "2024-12-30T16:38:20.828Z" }, { url = "https://files.pythonhosted.org/packages/39/02/1817328c1372be57c16148ce7d2bfcfa4a796bedaed897381b1aad9b267c/mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31", size = 11143050, upload-time = "2024-12-30T16:38:29.743Z" }, { url = "https://files.pythonhosted.org/packages/b9/07/99db9a95ece5e58eee1dd87ca456a7e7b5ced6798fd78182c59c35a7587b/mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6", size = 10321087, upload-time = "2024-12-30T16:38:14.739Z" }, { url = "https://files.pythonhosted.org/packages/9a/eb/85ea6086227b84bce79b3baf7f465b4732e0785830726ce4a51528173b71/mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319", size = 12066766, upload-time = "2024-12-30T16:38:47.038Z" }, { url = "https://files.pythonhosted.org/packages/4b/bb/f01bebf76811475d66359c259eabe40766d2f8ac8b8250d4e224bb6df379/mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac", size = 12787111, upload-time = "2024-12-30T16:39:02.444Z" }, { url = "https://files.pythonhosted.org/packages/2f/c9/84837ff891edcb6dcc3c27d85ea52aab0c4a34740ff5f0ccc0eb87c56139/mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b", size = 12974331, upload-time = "2024-12-30T16:38:23.849Z" }, { url = "https://files.pythonhosted.org/packages/84/5f/901e18464e6a13f8949b4909535be3fa7f823291b8ab4e4b36cfe57d6769/mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837", size = 9763210, upload-time = "2024-12-30T16:38:36.299Z" }, { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493, upload-time = "2024-12-30T16:38:26.935Z" }, { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702, upload-time = "2024-12-30T16:38:50.623Z" }, { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104, upload-time = "2024-12-30T16:38:53.735Z" }, { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167, upload-time = "2024-12-30T16:38:56.437Z" }, { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834, upload-time = "2024-12-30T16:38:59.204Z" }, { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231, upload-time = "2024-12-30T16:39:05.124Z" }, { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905, upload-time = "2024-12-30T16:38:42.021Z" }, ] [[package]] name = "mypy" version = "1.17.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "mypy-extensions", marker = "python_full_version >= '3.9'" }, { name = "pathspec", marker = "python_full_version >= '3.9'" }, { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299, upload-time = "2025-07-31T07:54:06.425Z" }, { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451, upload-time = "2025-07-31T07:53:52.974Z" }, { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211, upload-time = "2025-07-31T07:53:18.879Z" }, { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687, upload-time = "2025-07-31T07:53:30.544Z" }, { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322, upload-time = "2025-07-31T07:53:50.74Z" }, { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962, upload-time = "2025-07-31T07:53:08.431Z" }, { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, { url = "https://files.pythonhosted.org/packages/29/cb/673e3d34e5d8de60b3a61f44f80150a738bff568cd6b7efb55742a605e98/mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9", size = 10992466, upload-time = "2025-07-31T07:53:57.574Z" }, { url = "https://files.pythonhosted.org/packages/0c/d0/fe1895836eea3a33ab801561987a10569df92f2d3d4715abf2cfeaa29cb2/mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99", size = 10117638, upload-time = "2025-07-31T07:53:34.256Z" }, { url = "https://files.pythonhosted.org/packages/97/f3/514aa5532303aafb95b9ca400a31054a2bd9489de166558c2baaeea9c522/mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8", size = 11915673, upload-time = "2025-07-31T07:52:59.361Z" }, { url = "https://files.pythonhosted.org/packages/ab/c3/c0805f0edec96fe8e2c048b03769a6291523d509be8ee7f56ae922fa3882/mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8", size = 12649022, upload-time = "2025-07-31T07:53:45.92Z" }, { url = "https://files.pythonhosted.org/packages/45/3e/d646b5a298ada21a8512fa7e5531f664535a495efa672601702398cea2b4/mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259", size = 12895536, upload-time = "2025-07-31T07:53:06.17Z" }, { url = "https://files.pythonhosted.org/packages/14/55/e13d0dcd276975927d1f4e9e2ec4fd409e199f01bdc671717e673cc63a22/mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d", size = 9512564, upload-time = "2025-07-31T07:53:12.346Z" }, { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pydantic" version = "2.10.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "annotated-types", marker = "python_full_version < '3.9'" }, { name = "pydantic-core", version = "2.27.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" }, ] [[package]] name = "pydantic" version = "2.11.7" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "annotated-types", marker = "python_full_version >= '3.9' and python_full_version < '3.14'" }, { name = "pydantic-core", version = "2.33.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.14'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.14'" }, { name = "typing-inspection", marker = "python_full_version >= '3.9' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] [[package]] name = "pydantic-core" version = "2.27.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938, upload-time = "2024-12-18T11:27:14.406Z" }, { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684, upload-time = "2024-12-18T11:27:16.489Z" }, { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169, upload-time = "2024-12-18T11:27:22.16Z" }, { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227, upload-time = "2024-12-18T11:27:25.097Z" }, { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695, upload-time = "2024-12-18T11:27:28.656Z" }, { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662, upload-time = "2024-12-18T11:27:30.798Z" }, { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370, upload-time = "2024-12-18T11:27:33.692Z" }, { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813, upload-time = "2024-12-18T11:27:37.111Z" }, { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287, upload-time = "2024-12-18T11:27:40.566Z" }, { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414, upload-time = "2024-12-18T11:27:43.757Z" }, { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301, upload-time = "2024-12-18T11:27:47.36Z" }, { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685, upload-time = "2024-12-18T11:27:50.508Z" }, { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876, upload-time = "2024-12-18T11:27:53.54Z" }, { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421, upload-time = "2024-12-18T11:27:55.409Z" }, { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998, upload-time = "2024-12-18T11:27:57.252Z" }, { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167, upload-time = "2024-12-18T11:27:59.146Z" }, { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071, upload-time = "2024-12-18T11:28:02.625Z" }, { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244, upload-time = "2024-12-18T11:28:04.442Z" }, { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470, upload-time = "2024-12-18T11:28:07.679Z" }, { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291, upload-time = "2024-12-18T11:28:10.297Z" }, { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613, upload-time = "2024-12-18T11:28:13.362Z" }, { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355, upload-time = "2024-12-18T11:28:16.587Z" }, { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661, upload-time = "2024-12-18T11:28:18.407Z" }, { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261, upload-time = "2024-12-18T11:28:21.471Z" }, { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361, upload-time = "2024-12-18T11:28:23.53Z" }, { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484, upload-time = "2024-12-18T11:28:25.391Z" }, { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102, upload-time = "2024-12-18T11:28:28.593Z" }, { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" }, { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" }, { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" }, { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" }, { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" }, { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" }, { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" }, { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" }, { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" }, { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" }, { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" }, { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" }, { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" }, { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" }, { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" }, { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" }, { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" }, { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" }, { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" }, { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" }, { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" }, { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" }, { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" }, { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" }, { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" }, { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" }, { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" }, { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" }, { url = "https://files.pythonhosted.org/packages/43/53/13e9917fc69c0a4aea06fd63ed6a8d6cda9cf140ca9584d49c1650b0ef5e/pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506", size = 1899595, upload-time = "2024-12-18T11:29:40.887Z" }, { url = "https://files.pythonhosted.org/packages/f4/20/26c549249769ed84877f862f7bb93f89a6ee08b4bee1ed8781616b7fbb5e/pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320", size = 1775010, upload-time = "2024-12-18T11:29:44.823Z" }, { url = "https://files.pythonhosted.org/packages/35/eb/8234e05452d92d2b102ffa1b56d801c3567e628fdc63f02080fdfc68fd5e/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145", size = 1830727, upload-time = "2024-12-18T11:29:46.904Z" }, { url = "https://files.pythonhosted.org/packages/8f/df/59f915c8b929d5f61e5a46accf748a87110ba145156f9326d1a7d28912b2/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1", size = 1868393, upload-time = "2024-12-18T11:29:49.098Z" }, { url = "https://files.pythonhosted.org/packages/d5/52/81cf4071dca654d485c277c581db368b0c95b2b883f4d7b736ab54f72ddf/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228", size = 2040300, upload-time = "2024-12-18T11:29:51.43Z" }, { url = "https://files.pythonhosted.org/packages/9c/00/05197ce1614f5c08d7a06e1d39d5d8e704dc81971b2719af134b844e2eaf/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046", size = 2738785, upload-time = "2024-12-18T11:29:55.001Z" }, { url = "https://files.pythonhosted.org/packages/f7/a3/5f19bc495793546825ab160e530330c2afcee2281c02b5ffafd0b32ac05e/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5", size = 1996493, upload-time = "2024-12-18T11:29:57.13Z" }, { url = "https://files.pythonhosted.org/packages/ed/e8/e0102c2ec153dc3eed88aea03990e1b06cfbca532916b8a48173245afe60/pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a", size = 1998544, upload-time = "2024-12-18T11:30:00.681Z" }, { url = "https://files.pythonhosted.org/packages/fb/a3/4be70845b555bd80aaee9f9812a7cf3df81550bce6dadb3cfee9c5d8421d/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d", size = 2007449, upload-time = "2024-12-18T11:30:02.985Z" }, { url = "https://files.pythonhosted.org/packages/e3/9f/b779ed2480ba355c054e6d7ea77792467631d674b13d8257085a4bc7dcda/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9", size = 2129460, upload-time = "2024-12-18T11:30:06.55Z" }, { url = "https://files.pythonhosted.org/packages/a0/f0/a6ab0681f6e95260c7fbf552874af7302f2ea37b459f9b7f00698f875492/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da", size = 2159609, upload-time = "2024-12-18T11:30:09.428Z" }, { url = "https://files.pythonhosted.org/packages/8a/2b/e1059506795104349712fbca647b18b3f4a7fd541c099e6259717441e1e0/pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b", size = 1819886, upload-time = "2024-12-18T11:30:11.777Z" }, { url = "https://files.pythonhosted.org/packages/aa/6d/df49c17f024dfc58db0bacc7b03610058018dd2ea2eaf748ccbada4c3d06/pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad", size = 1980773, upload-time = "2024-12-18T11:30:14.828Z" }, { url = "https://files.pythonhosted.org/packages/27/97/3aef1ddb65c5ccd6eda9050036c956ff6ecbfe66cb7eb40f280f121a5bb0/pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993", size = 1896475, upload-time = "2024-12-18T11:30:18.316Z" }, { url = "https://files.pythonhosted.org/packages/ad/d3/5668da70e373c9904ed2f372cb52c0b996426f302e0dee2e65634c92007d/pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308", size = 1772279, upload-time = "2024-12-18T11:30:20.547Z" }, { url = "https://files.pythonhosted.org/packages/8a/9e/e44b8cb0edf04a2f0a1f6425a65ee089c1d6f9c4c2dcab0209127b6fdfc2/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4", size = 1829112, upload-time = "2024-12-18T11:30:23.255Z" }, { url = "https://files.pythonhosted.org/packages/1c/90/1160d7ac700102effe11616e8119e268770f2a2aa5afb935f3ee6832987d/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf", size = 1866780, upload-time = "2024-12-18T11:30:25.742Z" }, { url = "https://files.pythonhosted.org/packages/ee/33/13983426df09a36d22c15980008f8d9c77674fc319351813b5a2739b70f3/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76", size = 2037943, upload-time = "2024-12-18T11:30:28.036Z" }, { url = "https://files.pythonhosted.org/packages/01/d7/ced164e376f6747e9158c89988c293cd524ab8d215ae4e185e9929655d5c/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118", size = 2740492, upload-time = "2024-12-18T11:30:30.412Z" }, { url = "https://files.pythonhosted.org/packages/8b/1f/3dc6e769d5b7461040778816aab2b00422427bcaa4b56cc89e9c653b2605/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630", size = 1995714, upload-time = "2024-12-18T11:30:34.358Z" }, { url = "https://files.pythonhosted.org/packages/07/d7/a0bd09bc39283530b3f7c27033a814ef254ba3bd0b5cfd040b7abf1fe5da/pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54", size = 1997163, upload-time = "2024-12-18T11:30:37.979Z" }, { url = "https://files.pythonhosted.org/packages/2d/bb/2db4ad1762e1c5699d9b857eeb41959191980de6feb054e70f93085e1bcd/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f", size = 2005217, upload-time = "2024-12-18T11:30:40.367Z" }, { url = "https://files.pythonhosted.org/packages/53/5f/23a5a3e7b8403f8dd8fc8a6f8b49f6b55c7d715b77dcf1f8ae919eeb5628/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362", size = 2127899, upload-time = "2024-12-18T11:30:42.737Z" }, { url = "https://files.pythonhosted.org/packages/c2/ae/aa38bb8dd3d89c2f1d8362dd890ee8f3b967330821d03bbe08fa01ce3766/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96", size = 2155726, upload-time = "2024-12-18T11:30:45.279Z" }, { url = "https://files.pythonhosted.org/packages/98/61/4f784608cc9e98f70839187117ce840480f768fed5d386f924074bf6213c/pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e", size = 1817219, upload-time = "2024-12-18T11:30:47.718Z" }, { url = "https://files.pythonhosted.org/packages/57/82/bb16a68e4a1a858bb3768c2c8f1ff8d8978014e16598f001ea29a25bf1d1/pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67", size = 1985382, upload-time = "2024-12-18T11:30:51.871Z" }, { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159, upload-time = "2024-12-18T11:30:54.382Z" }, { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331, upload-time = "2024-12-18T11:30:58.178Z" }, { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467, upload-time = "2024-12-18T11:31:00.6Z" }, { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797, upload-time = "2024-12-18T11:31:07.243Z" }, { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839, upload-time = "2024-12-18T11:31:09.775Z" }, { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861, upload-time = "2024-12-18T11:31:13.469Z" }, { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582, upload-time = "2024-12-18T11:31:17.423Z" }, { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985, upload-time = "2024-12-18T11:31:19.901Z" }, { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715, upload-time = "2024-12-18T11:31:22.821Z" }, { url = "https://files.pythonhosted.org/packages/29/0e/dcaea00c9dbd0348b723cae82b0e0c122e0fa2b43fa933e1622fd237a3ee/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656", size = 1891733, upload-time = "2024-12-18T11:31:26.876Z" }, { url = "https://files.pythonhosted.org/packages/86/d3/e797bba8860ce650272bda6383a9d8cad1d1c9a75a640c9d0e848076f85e/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278", size = 1768375, upload-time = "2024-12-18T11:31:29.276Z" }, { url = "https://files.pythonhosted.org/packages/41/f7/f847b15fb14978ca2b30262548f5fc4872b2724e90f116393eb69008299d/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb", size = 1822307, upload-time = "2024-12-18T11:31:33.123Z" }, { url = "https://files.pythonhosted.org/packages/9c/63/ed80ec8255b587b2f108e514dc03eed1546cd00f0af281e699797f373f38/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd", size = 1979971, upload-time = "2024-12-18T11:31:35.755Z" }, { url = "https://files.pythonhosted.org/packages/a9/6d/6d18308a45454a0de0e975d70171cadaf454bc7a0bf86b9c7688e313f0bb/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc", size = 1987616, upload-time = "2024-12-18T11:31:38.534Z" }, { url = "https://files.pythonhosted.org/packages/82/8a/05f8780f2c1081b800a7ca54c1971e291c2d07d1a50fb23c7e4aef4ed403/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b", size = 1998943, upload-time = "2024-12-18T11:31:41.853Z" }, { url = "https://files.pythonhosted.org/packages/5e/3e/fe5b6613d9e4c0038434396b46c5303f5ade871166900b357ada4766c5b7/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b", size = 2116654, upload-time = "2024-12-18T11:31:44.756Z" }, { url = "https://files.pythonhosted.org/packages/db/ad/28869f58938fad8cc84739c4e592989730bfb69b7c90a8fff138dff18e1e/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2", size = 2152292, upload-time = "2024-12-18T11:31:48.613Z" }, { url = "https://files.pythonhosted.org/packages/a1/0c/c5c5cd3689c32ed1fe8c5d234b079c12c281c051759770c05b8bed6412b5/pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35", size = 2004961, upload-time = "2024-12-18T11:31:52.446Z" }, ] [[package]] name = "pydantic-core" version = "2.33.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, { url = "https://files.pythonhosted.org/packages/53/ea/bbe9095cdd771987d13c82d104a9c8559ae9aec1e29f139e286fd2e9256e/pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", size = 2028677, upload-time = "2025-04-23T18:32:27.227Z" }, { url = "https://files.pythonhosted.org/packages/49/1d/4ac5ed228078737d457a609013e8f7edc64adc37b91d619ea965758369e5/pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", size = 1864735, upload-time = "2025-04-23T18:32:29.019Z" }, { url = "https://files.pythonhosted.org/packages/23/9a/2e70d6388d7cda488ae38f57bc2f7b03ee442fbcf0d75d848304ac7e405b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", size = 1898467, upload-time = "2025-04-23T18:32:31.119Z" }, { url = "https://files.pythonhosted.org/packages/ff/2e/1568934feb43370c1ffb78a77f0baaa5a8b6897513e7a91051af707ffdc4/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", size = 1983041, upload-time = "2025-04-23T18:32:33.655Z" }, { url = "https://files.pythonhosted.org/packages/01/1a/1a1118f38ab64eac2f6269eb8c120ab915be30e387bb561e3af904b12499/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", size = 2136503, upload-time = "2025-04-23T18:32:35.519Z" }, { url = "https://files.pythonhosted.org/packages/5c/da/44754d1d7ae0f22d6d3ce6c6b1486fc07ac2c524ed8f6eca636e2e1ee49b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", size = 2736079, upload-time = "2025-04-23T18:32:37.659Z" }, { url = "https://files.pythonhosted.org/packages/4d/98/f43cd89172220ec5aa86654967b22d862146bc4d736b1350b4c41e7c9c03/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", size = 2006508, upload-time = "2025-04-23T18:32:39.637Z" }, { url = "https://files.pythonhosted.org/packages/2b/cc/f77e8e242171d2158309f830f7d5d07e0531b756106f36bc18712dc439df/pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", size = 2113693, upload-time = "2025-04-23T18:32:41.818Z" }, { url = "https://files.pythonhosted.org/packages/54/7a/7be6a7bd43e0a47c147ba7fbf124fe8aaf1200bc587da925509641113b2d/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", size = 2074224, upload-time = "2025-04-23T18:32:44.033Z" }, { url = "https://files.pythonhosted.org/packages/2a/07/31cf8fadffbb03be1cb520850e00a8490c0927ec456e8293cafda0726184/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", size = 2245403, upload-time = "2025-04-23T18:32:45.836Z" }, { url = "https://files.pythonhosted.org/packages/b6/8d/bbaf4c6721b668d44f01861f297eb01c9b35f612f6b8e14173cb204e6240/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", size = 2242331, upload-time = "2025-04-23T18:32:47.618Z" }, { url = "https://files.pythonhosted.org/packages/bb/93/3cc157026bca8f5006250e74515119fcaa6d6858aceee8f67ab6dc548c16/pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", size = 1910571, upload-time = "2025-04-23T18:32:49.401Z" }, { url = "https://files.pythonhosted.org/packages/5b/90/7edc3b2a0d9f0dda8806c04e511a67b0b7a41d2187e2003673a996fb4310/pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", size = 1956504, upload-time = "2025-04-23T18:32:51.287Z" }, { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, { url = "https://files.pythonhosted.org/packages/08/98/dbf3fdfabaf81cda5622154fda78ea9965ac467e3239078e0dcd6df159e7/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", size = 2024034, upload-time = "2025-04-23T18:33:32.843Z" }, { url = "https://files.pythonhosted.org/packages/8d/99/7810aa9256e7f2ccd492590f86b79d370df1e9292f1f80b000b6a75bd2fb/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", size = 1858578, upload-time = "2025-04-23T18:33:34.912Z" }, { url = "https://files.pythonhosted.org/packages/d8/60/bc06fa9027c7006cc6dd21e48dbf39076dc39d9abbaf718a1604973a9670/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", size = 1892858, upload-time = "2025-04-23T18:33:36.933Z" }, { url = "https://files.pythonhosted.org/packages/f2/40/9d03997d9518816c68b4dfccb88969756b9146031b61cd37f781c74c9b6a/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", size = 2068498, upload-time = "2025-04-23T18:33:38.997Z" }, { url = "https://files.pythonhosted.org/packages/d8/62/d490198d05d2d86672dc269f52579cad7261ced64c2df213d5c16e0aecb1/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", size = 2108428, upload-time = "2025-04-23T18:33:41.18Z" }, { url = "https://files.pythonhosted.org/packages/9a/ec/4cd215534fd10b8549015f12ea650a1a973da20ce46430b68fc3185573e8/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", size = 2069854, upload-time = "2025-04-23T18:33:43.446Z" }, { url = "https://files.pythonhosted.org/packages/1a/1a/abbd63d47e1d9b0d632fee6bb15785d0889c8a6e0a6c3b5a8e28ac1ec5d2/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", size = 2237859, upload-time = "2025-04-23T18:33:45.56Z" }, { url = "https://files.pythonhosted.org/packages/80/1c/fa883643429908b1c90598fd2642af8839efd1d835b65af1f75fba4d94fe/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", size = 2239059, upload-time = "2025-04-23T18:33:47.735Z" }, { url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661, upload-time = "2025-04-23T18:33:49.995Z" }, ] [[package]] name = "pygments" version = "2.19.2" 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" } 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" }, ] [[package]] name = "pyright" version = "1.1.403" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526, upload-time = "2025-07-09T07:15:52.882Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" }, ] [[package]] name = "pytest" version = "8.3.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.9'" }, { name = "iniconfig", marker = "python_full_version < '3.9'" }, { name = "packaging", marker = "python_full_version < '3.9'" }, { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "tomli", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] name = "pytest" version = "8.4.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.9' and sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, { name = "iniconfig", marker = "python_full_version >= '3.9'" }, { name = "packaging", marker = "python_full_version >= '3.9'" }, { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pygments", marker = "python_full_version >= '3.9'" }, { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] name = "pytz" version = "2025.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] [[package]] name = "requests" version = "2.32.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] name = "rich" version = "14.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, ] [[package]] name = "roman-numerals-py" version = "3.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, ] [[package]] name = "ruff" version = "0.12.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814, upload-time = "2025-07-29T22:32:35.877Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189, upload-time = "2025-07-29T22:31:41.281Z" }, { url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389, upload-time = "2025-07-29T22:31:54.265Z" }, { url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384, upload-time = "2025-07-29T22:31:59.575Z" }, { url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759, upload-time = "2025-07-29T22:32:01.95Z" }, { url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028, upload-time = "2025-07-29T22:32:04.367Z" }, { url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209, upload-time = "2025-07-29T22:32:06.952Z" }, { url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353, upload-time = "2025-07-29T22:32:10.053Z" }, { url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555, upload-time = "2025-07-29T22:32:12.644Z" }, { url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556, upload-time = "2025-07-29T22:32:15.312Z" }, { url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784, upload-time = "2025-07-29T22:32:17.69Z" }, { url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356, upload-time = "2025-07-29T22:32:20.134Z" }, { url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124, upload-time = "2025-07-29T22:32:22.645Z" }, { url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945, upload-time = "2025-07-29T22:32:24.765Z" }, { url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677, upload-time = "2025-07-29T22:32:27.022Z" }, { url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687, upload-time = "2025-07-29T22:32:29.381Z" }, { url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365, upload-time = "2025-07-29T22:32:31.517Z" }, { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" }, ] [[package]] name = "snowballstemmer" version = "3.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, ] [[package]] name = "sphinx" version = "7.1.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "alabaster", version = "0.7.13", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "babel", marker = "python_full_version < '3.9'" }, { name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" }, { name = "docutils", version = "0.20.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "imagesize", marker = "python_full_version < '3.9'" }, { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "jinja2", marker = "python_full_version < '3.9'" }, { name = "packaging", marker = "python_full_version < '3.9'" }, { name = "pygments", marker = "python_full_version < '3.9'" }, { name = "requests", marker = "python_full_version < '3.9'" }, { name = "snowballstemmer", marker = "python_full_version < '3.9'" }, { name = "sphinxcontrib-applehelp", version = "1.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "sphinxcontrib-devhelp", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "sphinxcontrib-htmlhelp", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.9'" }, { name = "sphinxcontrib-qthelp", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "sphinxcontrib-serializinghtml", version = "1.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/01/688bdf9282241dca09fe6e3a1110eda399fa9b10d0672db609e37c2e7a39/sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f", size = 6828258, upload-time = "2023-08-02T02:06:09.375Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/48/17/325cf6a257d84751a48ae90752b3d8fe0be8f9535b6253add61c49d0d9bc/sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe", size = 3169543, upload-time = "2023-08-02T02:06:06.816Z" }, ] [[package]] name = "sphinx" version = "7.4.7" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", ] dependencies = [ { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "babel", marker = "python_full_version == '3.9.*'" }, { name = "colorama", marker = "python_full_version == '3.9.*' and sys_platform == 'win32'" }, { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "imagesize", marker = "python_full_version == '3.9.*'" }, { name = "importlib-metadata", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "jinja2", marker = "python_full_version == '3.9.*'" }, { name = "packaging", marker = "python_full_version == '3.9.*'" }, { name = "pygments", marker = "python_full_version == '3.9.*'" }, { name = "requests", marker = "python_full_version == '3.9.*'" }, { name = "snowballstemmer", marker = "python_full_version == '3.9.*'" }, { name = "sphinxcontrib-applehelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "sphinxcontrib-devhelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "sphinxcontrib-htmlhelp", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.9.*'" }, { name = "sphinxcontrib-qthelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "sphinxcontrib-serializinghtml", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "tomli", marker = "python_full_version == '3.9.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911, upload-time = "2024-07-20T14:46:56.059Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624, upload-time = "2024-07-20T14:46:52.142Z" }, ] [[package]] name = "sphinx" version = "8.1.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "babel", marker = "python_full_version == '3.10.*'" }, { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "imagesize", marker = "python_full_version == '3.10.*'" }, { name = "jinja2", marker = "python_full_version == '3.10.*'" }, { name = "packaging", marker = "python_full_version == '3.10.*'" }, { name = "pygments", marker = "python_full_version == '3.10.*'" }, { name = "requests", marker = "python_full_version == '3.10.*'" }, { name = "snowballstemmer", marker = "python_full_version == '3.10.*'" }, { name = "sphinxcontrib-applehelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinxcontrib-devhelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinxcontrib-htmlhelp", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.10.*'" }, { name = "sphinxcontrib-qthelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinxcontrib-serializinghtml", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "tomli", marker = "python_full_version == '3.10.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, ] [[package]] name = "sphinx" version = "8.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", ] dependencies = [ { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "babel", marker = "python_full_version >= '3.11'" }, { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "imagesize", marker = "python_full_version >= '3.11'" }, { name = "jinja2", marker = "python_full_version >= '3.11'" }, { name = "packaging", marker = "python_full_version >= '3.11'" }, { name = "pygments", marker = "python_full_version >= '3.11'" }, { name = "requests", marker = "python_full_version >= '3.11'" }, { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, { name = "sphinxcontrib-applehelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinxcontrib-devhelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinxcontrib-htmlhelp", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, { name = "sphinxcontrib-qthelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinxcontrib-serializinghtml", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, ] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/32/df/45e827f4d7e7fcc84e853bcef1d836effd762d63ccb86f43ede4e98b478c/sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e", size = 24766, upload-time = "2023-01-23T09:41:54.435Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/06/c1/5e2cafbd03105ce50d8500f9b4e8a6e8d02e22d0475b574c3b3e9451a15f/sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228", size = 120601, upload-time = "2023-01-23T09:41:52.364Z" }, ] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, ] [[package]] name = "sphinxcontrib-devhelp" version = "1.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/98/33/dc28393f16385f722c893cb55539c641c9aaec8d1bc1c15b69ce0ac2dbb3/sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4", size = 17398, upload-time = "2020-02-29T04:14:43.378Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", size = 84690, upload-time = "2020-02-29T04:14:40.765Z" }, ] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, ] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/b3/47/64cff68ea3aa450c373301e5bebfbb9fce0a3e70aca245fcadd4af06cd75/sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff", size = 27967, upload-time = "2023-01-31T17:29:20.935Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6e/ee/a1f5e39046cbb5f8bc8fba87d1ddf1c6643fbc9194e58d26e606de4b9074/sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903", size = 99833, upload-time = "2023-01-31T17:29:18.489Z" }, ] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, ] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] [[package]] name = "sphinxcontrib-qthelp" version = "1.0.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/b1/8e/c4846e59f38a5f2b4a0e3b27af38f2fcf904d4bfd82095bf92de0b114ebd/sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", size = 21658, upload-time = "2020-02-29T04:19:10.026Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2b/14/05f9206cf4e9cfca1afb5fd224c7cd434dcc3a433d6d9e4e0264d29c6cdb/sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6", size = 90609, upload-time = "2020-02-29T04:19:08.451Z" }, ] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, ] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/b5/72/835d6fadb9e5d02304cf39b18f93d227cd93abd3c41ebf58e6853eeb1455/sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952", size = 21019, upload-time = "2021-05-22T16:07:43.043Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c6/77/5464ec50dd0f1c1037e3c93249b040c8fc8078fdda97530eeb02424b6eea/sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", size = 94021, upload-time = "2021-05-22T16:07:41.627Z" }, ] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[package]] name = "type-lens" version = "0.2.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/55/8846322acedf26cafb4a66dd23fdca6bc73cb3f8c0029040a9b96f5aa0dd/type_lens-0.2.6.tar.gz", hash = "sha256:5d46ab3bc2dfafc174cad702eccb4418c31e38e258e87945a19d86ebc204d08b", size = 362277, upload-time = "2025-10-20T20:08:28.07Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/c5/30de077e06f7ffc3fe21d45ce26b8088816227b678a7157f6b9e080ff2db/type_lens-0.2.6-py3-none-any.whl", hash = "sha256:aade6beb3eca337c90d1b627b509768473bbd591e233ed6b57fb93ec2a01bca6", size = 14451, upload-time = "2025-10-20T20:08:26.936Z" }, ] [[package]] name = "types-docutils" version = "0.21.0.20241128" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/dd/df/64e7ab01a4fc5ce46895dc94e31cffc8b8087c8d91ee54c45ac2d8d82445/types_docutils-0.21.0.20241128.tar.gz", hash = "sha256:4dd059805b83ac6ec5a223699195c4e9eeb0446a4f7f2aeff1759a4a7cc17473", size = 26739, upload-time = "2024-11-28T02:54:57.756Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/59/b6/10ba95739f2cbb9c5bd2f6568148d62b468afe01a94c633e8892a2936d8a/types_docutils-0.21.0.20241128-py3-none-any.whl", hash = "sha256:e0409204009639e9b0bf4521eeabe58b5e574ce9c0db08421c2ac26c32be0039", size = 34677, upload-time = "2024-11-28T02:54:55.64Z" }, ] [[package]] name = "types-docutils" version = "0.21.0.20250728" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/49/9c/223feef75550722e428bed338bff6d1692e1f32f0e3484a9c5c15a0bc601/types_docutils-0.21.0.20250728.tar.gz", hash = "sha256:fbfe44496c98c71437cd9ac20d71df2ea44878084f604960af7cf3696d562bab", size = 54656, upload-time = "2025-07-28T03:29:16.386Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/25/91/45beab84b3484446d4966deca4606c3e8427af168677776ebafaaf277690/types_docutils-0.21.0.20250728-py3-none-any.whl", hash = "sha256:48b8caaac0d572295f94865b7f97a93a78a305625e7c0d10ea289f3fc15d2fe5", size = 89533, upload-time = "2025-07-28T03:29:15.132Z" }, ] [[package]] name = "typing-extensions" version = "4.13.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]] name = "typing-extensions" version = "4.14.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] name = "typing-inspection" version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] [[package]] name = "urllib3" version = "2.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677, upload-time = "2024-09-12T10:52:18.401Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload-time = "2024-09-12T10:52:16.589Z" }, ] [[package]] name = "urllib3" version = "2.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version == '3.13.*'", "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] name = "zipp" version = "3.20.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199, upload-time = "2024-09-13T13:44:16.101Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200, upload-time = "2024-09-13T13:44:14.38Z" }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] cappa-0.32.2/.github/FUNDING.yml0000644000000000000000000000002213615410400012751 0ustar00github: DanCardin cappa-0.32.2/.github/workflows/release.yml0000644000000000000000000000161113615410400015341 0ustar00name: Github Release/Publish PyPi on: push: tags: - "v*.*.*" jobs: gh-release: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Release uses: softprops/action-gh-release@v1 with: generate_release_notes: true pypi-publish: name: Upload release to PyPI runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/cappa permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - name: Set up uv run: curl -LsSf https://astral.sh/uv/install.sh | sh - name: Checkout uses: actions/checkout@v4 - name: Build dist run: uvx --from build pyproject-build --installer uv - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 cappa-0.32.2/.github/workflows/test.yml0000644000000000000000000000427613615410400014712 0ustar00name: Test on: pull_request: branches: - main push: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] env: UV_CACHE_DIR: /tmp/.uv-cache steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: x64 allow-prereleases: true - name: Set up uv run: curl -LsSf https://astral.sh/uv/install.sh | sh - name: Restore uv cache uses: actions/cache@v4 with: path: /tmp/.uv-cache key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} restore-keys: | uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} uv-${{ runner.os }} - name: Install dependencies run: make install - name: Run Linters run: make lint if: ${{ !endsWith(matrix.python-version, '-dev') }} - name: Lint Examples run: make lint-examples if: ${{ matrix.python-version != '3.8' && matrix.python-version != '3.9' }} - name: Run tests run: make test - name: Install dependencies run: make install-lowest - name: Run tests run: make test - name: Generate coverage run: make coverage - name: Store test result artifacts uses: actions/upload-artifact@v4 with: path: coverage-${{ matrix.python-version }}.xml - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} name: python=${{ matrix.python-version }} files: coverage.xml typos: name: Check for typos runs-on: ubuntu-latest steps: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Spell Check Repo uses: crate-ci/typos@v1.30.0 finish: needs: - test - typos runs-on: ubuntu-latest steps: - name: Join run: echo Done cappa-0.32.2/docs/Makefile0000644000000000000000000000135213615410400012173 0ustar00# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @uv run $(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile autobuild # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @uv run $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) autobuild: rm -rf build uv run sphinx-autobuild --host 0.0.0.0 source build/html cappa-0.32.2/docs/poetry.lock0000644000000000000000000036340613615410400012742 0ustar00# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.16" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] name = "anyascii" version = "0.3.2" description = "Unicode to ASCII transliteration" optional = false python-versions = ">=3.3" groups = ["main"] files = [ {file = "anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4"}, {file = "anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730"}, ] [[package]] name = "anyio" version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] name = "apeye" version = "1.4.1" description = "Handy tools for working with URLs and APIs." optional = false python-versions = ">=3.6.1" groups = ["main"] files = [ {file = "apeye-1.4.1-py3-none-any.whl", hash = "sha256:44e58a9104ec189bf42e76b3a7fe91e2b2879d96d48e9a77e5e32ff699c9204e"}, {file = "apeye-1.4.1.tar.gz", hash = "sha256:14ea542fad689e3bfdbda2189a354a4908e90aee4bf84c15ab75d68453d76a36"}, ] [package.dependencies] apeye-core = ">=1.0.0b2" domdf-python-tools = ">=2.6.0" platformdirs = ">=2.3.0" requests = ">=2.24.0" [package.extras] all = ["cachecontrol[filecache] (>=0.12.6)", "lockfile (>=0.12.2)"] limiter = ["cachecontrol[filecache] (>=0.12.6)", "lockfile (>=0.12.2)"] [[package]] name = "apeye-core" version = "1.1.5" description = "Core (offline) functionality for the apeye library." optional = false python-versions = ">=3.6.1" groups = ["main"] files = [ {file = "apeye_core-1.1.5-py3-none-any.whl", hash = "sha256:dc27a93f8c9e246b3b238c5ea51edf6115ab2618ef029b9f2d9a190ec8228fbf"}, {file = "apeye_core-1.1.5.tar.gz", hash = "sha256:5de72ed3d00cc9b20fea55e54b7ab8f5ef8500eb33a5368bc162a5585e238a55"}, ] [package.dependencies] domdf-python-tools = ">=2.6.0" idna = ">=2.5" [[package]] name = "astroid" version = "3.3.9" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" groups = ["main"] files = [ {file = "astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248"}, {file = "astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550"}, ] [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} [[package]] name = "autodocsumm" version = "0.2.14" description = "Extended sphinx autodoc including automatic autosummaries" optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "autodocsumm-0.2.14-py3-none-any.whl", hash = "sha256:3bad8717fc5190802c60392a7ab04b9f3c97aa9efa8b3780b3d81d615bfe5dc0"}, {file = "autodocsumm-0.2.14.tar.gz", hash = "sha256:2839a9d4facc3c4eccd306c08695540911042b46eeafcdc3203e6d0bab40bc77"}, ] [package.dependencies] Sphinx = ">=4.0,<9.0" [[package]] name = "babel" version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] [package.extras] dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] [[package]] name = "beautifulsoup4" version = "4.13.4" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, ] [package.dependencies] soupsieve = ">1.2" typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] chardet = ["chardet"] charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] [[package]] name = "cachecontrol" version = "0.14.2" description = "httplib2 caching for requests" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "cachecontrol-0.14.2-py3-none-any.whl", hash = "sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0"}, {file = "cachecontrol-0.14.2.tar.gz", hash = "sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2"}, ] [package.dependencies] filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} msgpack = ">=0.5.2,<2.0.0" requests = ">=2.16.0" [package.extras] dev = ["CacheControl[filecache,redis]", "build", "cherrypy", "codespell[tomli]", "furo", "mypy", "pytest", "pytest-cov", "ruff", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"] filecache = ["filelock (>=3.8.0)"] redis = ["redis (>=2.10.5)"] [[package]] name = "certifi" version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "dev"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] name = "charset-normalizer" version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "cssutils" version = "2.11.1" description = "A CSS Cascading Style Sheets library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1"}, {file = "cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2"}, ] [package.dependencies] more-itertools = "*" [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] test = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "dict2css" version = "0.3.0.post1" description = "A μ-library for constructing cascading style sheets from Python dictionaries." optional = false python-versions = ">=3.6" groups = ["main"] files = [ {file = "dict2css-0.3.0.post1-py3-none-any.whl", hash = "sha256:f006a6b774c3e31869015122ae82c491fd25e7de4a75607a62aa3e798f837e0d"}, {file = "dict2css-0.3.0.post1.tar.gz", hash = "sha256:89c544c21c4ca7472c3fffb9d37d3d926f606329afdb751dc1de67a411b70719"}, ] [package.dependencies] cssutils = ">=2.2.0" domdf-python-tools = ">=2.2.0" [[package]] name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] [[package]] name = "dom-toml" version = "2.0.1" description = "Dom's tools for Tom's Obvious, Minimal Language." optional = false python-versions = ">=3.6.1" groups = ["main"] files = [ {file = "dom_toml-2.0.1-py3-none-any.whl", hash = "sha256:6681a793c94a5d1fb7b59690009cc3b1dfb1bcaae108fb8054c712a8f128efc7"}, {file = "dom_toml-2.0.1.tar.gz", hash = "sha256:31c5874595c777f41cc191c34c66dbea215c8289a7b148b425fb7a10c3f4fbc8"}, ] [package.dependencies] domdf-python-tools = ">=2.8.0" tomli = ">=1.2.3" [[package]] name = "domdf-python-tools" version = "3.10.0" description = "Helpful functions for Python 🐍 🛠️" optional = false python-versions = ">=3.6" groups = ["main"] files = [ {file = "domdf_python_tools-3.10.0-py3-none-any.whl", hash = "sha256:5e71c1be71bbcc1f881d690c8984b60e64298ec256903b3147f068bc33090c36"}, {file = "domdf_python_tools-3.10.0.tar.gz", hash = "sha256:2ae308d2f4f1e9145f5f4ba57f840fbfd1c2983ee26e4824347789649d3ae298"}, ] [package.dependencies] natsort = ">=7.0.1" typing-extensions = ">=3.7.4.1" [package.extras] all = ["pytz (>=2019.1)"] dates = ["pytz (>=2019.1)"] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main", "dev"] markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filelock" version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "furo" version = "2024.8.6" description = "A clean customisable Sphinx documentation theme." optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"}, {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"}, ] [package.dependencies] beautifulsoup4 = "*" pygments = ">=2.7" sphinx = ">=6.0,<9.0" sphinx-basic-ng = ">=1.0.0.beta2" [[package]] name = "h11" version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] [[package]] name = "html5lib" version = "1.1" description = "HTML parser based on the WHATWG HTML specification" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" groups = ["main"] files = [ {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, ] [package.dependencies] six = ">=1.9" webencodings = "*" [package.extras] all = ["chardet (>=2.2)", "genshi", "lxml"] chardet = ["chardet (>=2.2)"] genshi = ["genshi"] lxml = ["lxml"] [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["main", "dev"] files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "jinja2" version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "linkify-it-py" version = "2.0.3" description = "Links recognition library with FULL unicode support." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, ] [package.dependencies] uc-micro-py = "*" [package.extras] benchmark = ["pytest", "pytest-benchmark"] dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"] doc = ["myst-parser", "sphinx", "sphinx-book-theme"] test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] name = "mdit-py-plugins" version = "0.4.2" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, ] [package.dependencies] markdown-it-py = ">=1.0.0,<4.0.0" [package.extras] code-style = ["pre-commit"] rtd = ["myst-parser", "sphinx-book-theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "more-itertools" version = "10.7.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e"}, {file = "more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3"}, ] [[package]] name = "msgpack" version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] [[package]] name = "myst-parser" version = "4.0.1" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false python-versions = ">=3.10" groups = ["main"] files = [ {file = "myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"}, {file = "myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"}, ] [package.dependencies] docutils = ">=0.19,<0.22" jinja2 = "*" linkify-it-py = {version = ">=2.0,<3.0", optional = true, markers = "extra == \"linkify\""} markdown-it-py = ">=3.0,<4.0" mdit-py-plugins = ">=0.4.1,<1.0" pyyaml = "*" sphinx = ">=7,<9" [package.extras] code-style = ["pre-commit (>=4.0,<5.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] [[package]] name = "natsort" version = "8.4.0" description = "Simple yet flexible natural sorting in Python." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c"}, {file = "natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581"}, ] [package.extras] fast = ["fastnumbers (>=2.0.0)"] icu = ["PyICU (>=1.0.0)"] [[package]] name = "packaging" version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "platformdirs" version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.14.1)"] [[package]] name = "pygments" version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruamel-yaml" version = "0.18.10" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1"}, {file = "ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58"}, ] [package.dependencies] "ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} [package.extras] docs = ["mercurial (>5.7)", "ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel-yaml-clib" version = "0.2.12" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false python-versions = ">=3.9" groups = ["main"] markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, ] [[package]] name = "setuptools" version = "79.0.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "setuptools-79.0.1-py3-none-any.whl", hash = "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51"}, {file = "setuptools-79.0.1.tar.gz", hash = "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" groups = ["main", "dev"] files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "soupsieve" version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, ] [[package]] name = "sphinx" version = "7.1.2" description = "Python documentation generator" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" Jinja2 = ">=3.0" packaging = ">=21.0" Pygments = ">=2.13" requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-autoapi" version = "3.0.0" description = "Sphinx API documentation generator" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "sphinx-autoapi-3.0.0.tar.gz", hash = "sha256:09ebd674a32b44467222b0fb8a917b97c89523f20dbf05b52cb8a3f0e15714de"}, {file = "sphinx_autoapi-3.0.0-py2.py3-none-any.whl", hash = "sha256:ea207793cba1feff7b2ded0e29364f2995a4d157303a98603cee0ce94cea2688"}, ] [package.dependencies] anyascii = "*" astroid = [ {version = ">=2.7", markers = "python_version < \"3.12\""}, {version = ">=3.0.0a1", markers = "python_version >= \"3.12\""}, ] Jinja2 = "*" PyYAML = "*" sphinx = ">=6.1.0" [package.extras] docs = ["furo", "sphinx", "sphinx-design"] [[package]] name = "sphinx-autobuild" version = "2024.10.3" description = "Rebuild Sphinx documentation on changes, with hot reloading in the browser." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa"}, {file = "sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1"}, ] [package.dependencies] colorama = ">=0.4.6" sphinx = "*" starlette = ">=0.35" uvicorn = ">=0.25" watchfiles = ">=0.20" websockets = ">=11" [package.extras] test = ["httpx", "pytest (>=6)"] [[package]] name = "sphinx-autodoc-typehints" version = "1.25.3" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "sphinx_autodoc_typehints-1.25.3-py3-none-any.whl", hash = "sha256:d3da7fa9a9761eff6ff09f8b1956ae3090a2d4f4ad54aebcade8e458d6340835"}, {file = "sphinx_autodoc_typehints-1.25.3.tar.gz", hash = "sha256:70db10b391acf4e772019765991d2de0ff30ec0899b9ba137706dc0b3c4835e0"}, ] [package.dependencies] sphinx = ">=7.1.2" [package.extras] docs = ["furo (>=2023.9.10)"] numpy = ["nptyping (>=2.5)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.8)"] [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" description = "A modern skeleton for Sphinx themes." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, ] [package.dependencies] sphinx = ">=4.0" [package.extras] docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] [[package]] name = "sphinx-copybutton" version = "0.5.2" description = "Add a copy button to each of your code cells." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, ] [package.dependencies] sphinx = ">=1.8" [package.extras] code-style = ["pre-commit (==2.12.1)"] rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] [[package]] name = "sphinx-jinja2-compat" version = "0.3.0" description = "Patches Jinja2 v3 to restore compatibility with earlier Sphinx versions." optional = false python-versions = ">=3.6" groups = ["main"] files = [ {file = "sphinx_jinja2_compat-0.3.0-py3-none-any.whl", hash = "sha256:b1e4006d8e1ea31013fa9946d1b075b0c8d2a42c6e3425e63542c1e9f8be9084"}, {file = "sphinx_jinja2_compat-0.3.0.tar.gz", hash = "sha256:f3c1590b275f42e7a654e081db5e3e5fb97f515608422bde94015ddf795dfe7c"}, ] [package.dependencies] jinja2 = ">=2.10" markupsafe = ">=1" standard-imghdr = {version = "3.10.14", markers = "python_version >= \"3.13\""} [[package]] name = "sphinx-prompt" version = "1.8.0" description = "Sphinx directive to add unselectable prompt" optional = false python-versions = ">=3.9,<4.0" groups = ["main"] files = [ {file = "sphinx_prompt-1.8.0-py3-none-any.whl", hash = "sha256:369ecc633f0711886f9b3a078c83264245be1adf46abeeb9b88b5519e4b51007"}, {file = "sphinx_prompt-1.8.0.tar.gz", hash = "sha256:47482f86fcec29662fdfd23e7c04ef03582714195d01f5d565403320084372ed"}, ] [package.dependencies] docutils = "*" pygments = "*" Sphinx = ">=7.0.0,<8.0.0" [[package]] name = "sphinx-pyproject" version = "0.3.0" description = "Move some of your Sphinx configuration into pyproject.toml" optional = false python-versions = ">=3.6" groups = ["main"] files = [ {file = "sphinx_pyproject-0.3.0-py3-none-any.whl", hash = "sha256:3aca968919f5ecd390f96874c3f64a43c9c7fcfdc2fd4191a781ad9228501b52"}, {file = "sphinx_pyproject-0.3.0.tar.gz", hash = "sha256:efc4ee9d96f579c4e4ed1ac273868c64565e88c8e37fe6ec2dc59fbcd57684ab"}, ] [package.dependencies] dom-toml = ">=0.3.0" domdf-python-tools = ">=2.7.0" [[package]] name = "sphinx-tabs" version = "3.4.5" description = "Tabbed views for Sphinx" optional = false python-versions = "~=3.7" groups = ["main"] files = [ {file = "sphinx-tabs-3.4.5.tar.gz", hash = "sha256:ba9d0c1e3e37aaadd4b5678449eb08176770e0fc227e769b6ce747df3ceea531"}, {file = "sphinx_tabs-3.4.5-py3-none-any.whl", hash = "sha256:92cc9473e2ecf1828ca3f6617d0efc0aa8acb06b08c56ba29d1413f2f0f6cf09"}, ] [package.dependencies] docutils = "*" pygments = "*" sphinx = "*" [package.extras] code-style = ["pre-commit (==2.13.0)"] testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "pytest-regressions", "rinohtype"] [[package]] name = "sphinx-togglebutton" version = "0.3.2" description = "Toggle page content and collapse admonitions in Sphinx." optional = false python-versions = "*" groups = ["main"] files = [ {file = "sphinx-togglebutton-0.3.2.tar.gz", hash = "sha256:ab0c8b366427b01e4c89802d5d078472c427fa6e9d12d521c34fa0442559dc7a"}, {file = "sphinx_togglebutton-0.3.2-py3-none-any.whl", hash = "sha256:9647ba7874b7d1e2d43413d8497153a85edc6ac95a3fea9a75ef9c1e08aaae2b"}, ] [package.dependencies] docutils = "*" setuptools = "*" sphinx = "*" wheel = "*" [package.extras] sphinx = ["matplotlib", "myst-nb", "numpy", "sphinx-book-theme", "sphinx-design", "sphinx-examples"] [[package]] name = "sphinx-toolbox" version = "3.9.0" description = "Box of handy tools for Sphinx 🧰 📔" optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "sphinx_toolbox-3.9.0-py3-none-any.whl", hash = "sha256:49024961c7791ad6e9dd39c611f89b5162550afa26ccad087be38388c3dd3c1e"}, {file = "sphinx_toolbox-3.9.0.tar.gz", hash = "sha256:9ee0603b090762d6eed4d0ec9fa91445e3ef95d40a584af125308541c1bf7b8d"}, ] [package.dependencies] apeye = ">=0.4.0" autodocsumm = ">=0.2.0" beautifulsoup4 = ">=4.9.1" cachecontrol = {version = ">=0.13.0", extras = ["filecache"]} dict2css = ">=0.2.3" docutils = ">=0.16" domdf-python-tools = ">=2.9.0" filelock = ">=3.8.0" html5lib = ">=1.1" "ruamel.yaml" = ">=0.16.12" sphinx = ">=3.2.0" sphinx-autodoc-typehints = ">=1.11.1" sphinx-jinja2-compat = ">=0.1.0" sphinx-prompt = ">=1.1.0" sphinx-tabs = ">=1.2.1,<3.4.7" tabulate = ">=0.8.7" typing-extensions = ">=3.7.4.3,<3.10.0.1 || >3.10.0.1" [package.extras] all = ["coincidence (>=0.4.3)", "pygments (>=2.7.4,<=2.13.0)"] testing = ["coincidence (>=0.4.3)", "pygments (>=2.7.4,<=2.13.0)"] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" groups = ["main", "dev"] files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "standard-imghdr" version = "3.10.14" description = "Standard library imghdr redistribution. \"dead battery\"." optional = false python-versions = "*" groups = ["main"] markers = "python_version >= \"3.13\"" files = [ {file = "standard_imghdr-3.10.14-py3-none-any.whl", hash = "sha256:cdf6883163349624dee9a81d2853a20260337c4cd41c04e99c082e01833a08e2"}, {file = "standard_imghdr-3.10.14.tar.gz", hash = "sha256:2598fe2e7c540dbda34b233295e10957ab8dc8ac6f3bd9eaa8d38be167232e52"}, ] [[package]] name = "starlette" version = "0.46.2" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, ] [package.dependencies] anyio = ">=3.6.2,<5" [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] [[package]] name = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, ] [package.extras] widechars = ["wcwidth"] [[package]] name = "tomli" version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "typing-extensions" version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] markers = {dev = "python_version < \"3.13\""} [[package]] name = "uc-micro-py" version = "1.0.3" description = "Micro subset of unicode data files for linkify-it-py projects." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, ] [package.extras] test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "urllib3" version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" version = "0.34.2" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403"}, {file = "uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328"}, ] [package.dependencies] click = ">=7.0" h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "watchfiles" version = "1.0.5" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "watchfiles-1.0.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5c40fe7dd9e5f81e0847b1ea64e1f5dd79dd61afbedb57759df06767ac719b40"}, {file = "watchfiles-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c0db396e6003d99bb2d7232c957b5f0b5634bbd1b24e381a5afcc880f7373fb"}, {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b551d4fb482fc57d852b4541f911ba28957d051c8776e79c3b4a51eb5e2a1b11"}, {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:830aa432ba5c491d52a15b51526c29e4a4b92bf4f92253787f9726fe01519487"}, {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a16512051a822a416b0d477d5f8c0e67b67c1a20d9acecb0aafa3aa4d6e7d256"}, {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe0cbc787770e52a96c6fda6726ace75be7f840cb327e1b08d7d54eadc3bc85"}, {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d363152c5e16b29d66cbde8fa614f9e313e6f94a8204eaab268db52231fe5358"}, {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee32c9a9bee4d0b7bd7cbeb53cb185cf0b622ac761efaa2eba84006c3b3a614"}, {file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29c7fd632ccaf5517c16a5188e36f6612d6472ccf55382db6c7fe3fcccb7f59f"}, {file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e637810586e6fe380c8bc1b3910accd7f1d3a9a7262c8a78d4c8fb3ba6a2b3d"}, {file = "watchfiles-1.0.5-cp310-cp310-win32.whl", hash = "sha256:cd47d063fbeabd4c6cae1d4bcaa38f0902f8dc5ed168072874ea11d0c7afc1ff"}, {file = "watchfiles-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:86c0df05b47a79d80351cd179893f2f9c1b1cae49d96e8b3290c7f4bd0ca0a92"}, {file = "watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827"}, {file = "watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4"}, {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d"}, {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63"}, {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418"}, {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9"}, {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6"}, {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25"}, {file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5"}, {file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01"}, {file = "watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246"}, {file = "watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096"}, {file = "watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed"}, {file = "watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2"}, {file = "watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f"}, {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec"}, {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21"}, {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512"}, {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d"}, {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6"}, {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234"}, {file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2"}, {file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663"}, {file = "watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249"}, {file = "watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705"}, {file = "watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417"}, {file = "watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d"}, {file = "watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763"}, {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40"}, {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563"}, {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04"}, {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f"}, {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a"}, {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827"}, {file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a"}, {file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936"}, {file = "watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc"}, {file = "watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11"}, {file = "watchfiles-1.0.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2cfb371be97d4db374cba381b9f911dd35bb5f4c58faa7b8b7106c8853e5d225"}, {file = "watchfiles-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3904d88955fda461ea2531fcf6ef73584ca921415d5cfa44457a225f4a42bc1"}, {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b7a21715fb12274a71d335cff6c71fe7f676b293d322722fe708a9ec81d91f5"}, {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dfd6ae1c385ab481766b3c61c44aca2b3cd775f6f7c0fa93d979ddec853d29d5"}, {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b659576b950865fdad31fa491d31d37cf78b27113a7671d39f919828587b429b"}, {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1909e0a9cd95251b15bff4261de5dd7550885bd172e3536824bf1cf6b121e200"}, {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:832ccc221927c860e7286c55c9b6ebcc0265d5e072f49c7f6456c7798d2b39aa"}, {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85fbb6102b3296926d0c62cfc9347f6237fb9400aecd0ba6bbda94cae15f2b3b"}, {file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:15ac96dd567ad6c71c71f7b2c658cb22b7734901546cd50a475128ab557593ca"}, {file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b6227351e11c57ae997d222e13f5b6f1f0700d84b8c52304e8675d33a808382"}, {file = "watchfiles-1.0.5-cp39-cp39-win32.whl", hash = "sha256:974866e0db748ebf1eccab17862bc0f0303807ed9cda465d1324625b81293a18"}, {file = "watchfiles-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:9848b21ae152fe79c10dd0197304ada8f7b586d3ebc3f27f43c506e5a52a863c"}, {file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f59b870db1f1ae5a9ac28245707d955c8721dd6565e7f411024fa374b5362d1d"}, {file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9475b0093767e1475095f2aeb1d219fb9664081d403d1dff81342df8cd707034"}, {file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc533aa50664ebd6c628b2f30591956519462f5d27f951ed03d6c82b2dfd9965"}, {file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed1cd825158dcaae36acce7b2db33dcbfd12b30c34317a88b8ed80f0541cc57"}, {file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:554389562c29c2c182e3908b149095051f81d28c2fec79ad6c8997d7d63e0009"}, {file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a74add8d7727e6404d5dc4dcd7fac65d4d82f95928bbee0cf5414c900e86773e"}, {file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb1489f25b051a89fae574505cc26360c8e95e227a9500182a7fe0afcc500ce0"}, {file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0901429650652d3f0da90bad42bdafc1f9143ff3605633c455c999a2d786cac"}, {file = "watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9"}, ] [package.dependencies] anyio = ">=3.0.0" [[package]] name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" optional = false python-versions = "*" groups = ["main"] files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] [[package]] name = "websockets" version = "15.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"}, {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"}, {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"}, {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"}, {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"}, {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"}, {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"}, {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"}, {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"}, {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"}, {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"}, {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, ] [[package]] name = "wheel" version = "0.45.1" description = "A built-package format for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, ] [package.extras] test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4" content-hash = "0357d2aa0be1ad6f8e051b426d753d153e0b5d2ab61688d4750a7202d3d97be0" cappa-0.32.2/docs/pyproject.toml0000644000000000000000000000117613615410400013453 0ustar00[project] name = "docs" version = "0.1.0" description = "" authors = [ {name = "Dan Cardin", email = "ddcardin@gmail.com"}, ] requires-python = ">=3.10,<4" dependencies = [ "furo >= 2022.4.7", "linkify-it-py", "myst-parser[linkify] >=4", "sphinx ~= 7.1.0", "sphinx-autoapi ~= 3.0.0", "sphinx-autobuild", "sphinx-autodoc-typehints ~= 1.25.0", "sphinx-copybutton", "sphinx-pyproject >= 0.3.0", "sphinx-togglebutton >= 0.3.2", "sphinx-toolbox (>=3.9.0,<4.0.0)", ] [tool.poetry.dev-dependencies] sphinx-autobuild = "*" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" cappa-0.32.2/docs/uv.lock0000644000000000000000000041050113615410400012037 0ustar00version = 1 requires-python = ">=3.10, <4" resolution-markers = [ "python_full_version == '3.11.*'", "python_full_version < '3.11'", "python_full_version >= '3.13'", "python_full_version == '3.12.*'", ] [[package]] name = "alabaster" version = "0.7.16" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } wheels = [ { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, ] [[package]] name = "anyascii" version = "0.3.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9f/52/93b9ea99063f7cf37fb67f5e3f49480686cbe7f228c48b9d713326223b6e/anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730", size = 214052 } wheels = [ { url = "https://files.pythonhosted.org/packages/4f/7b/a9a747e0632271d855da379532b05a62c58e979813814a57fa3b3afeb3a4/anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4", size = 289923 }, ] [[package]] name = "anyio" version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e6/e3/c4c8d473d6780ef1853d630d581f70d655b4f8d7553c6997958c283039a2/anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94", size = 163930 } wheels = [ { url = "https://files.pythonhosted.org/packages/7b/a2/10639a79341f6c019dedc95bd48a4928eed9f1d1197f4c04f546fc7ae0ff/anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7", size = 86780 }, ] [[package]] name = "apeye" version = "1.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "apeye-core" }, { name = "domdf-python-tools" }, { name = "platformdirs" }, { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4f/6b/cc65e31843d7bfda8313a9dc0c77a21e8580b782adca53c7cb3e511fe023/apeye-1.4.1.tar.gz", hash = "sha256:14ea542fad689e3bfdbda2189a354a4908e90aee4bf84c15ab75d68453d76a36", size = 99219 } wheels = [ { url = "https://files.pythonhosted.org/packages/89/7b/2d63664777b3e831ac1b1d8df5bbf0b7c8bee48e57115896080890527b1b/apeye-1.4.1-py3-none-any.whl", hash = "sha256:44e58a9104ec189bf42e76b3a7fe91e2b2879d96d48e9a77e5e32ff699c9204e", size = 107989 }, ] [[package]] name = "apeye-core" version = "1.1.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "domdf-python-tools" }, { name = "idna" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e5/4c/4f108cfd06923bd897bf992a6ecb6fb122646ee7af94d7f9a64abd071d4c/apeye_core-1.1.5.tar.gz", hash = "sha256:5de72ed3d00cc9b20fea55e54b7ab8f5ef8500eb33a5368bc162a5585e238a55", size = 96511 } wheels = [ { url = "https://files.pythonhosted.org/packages/77/9f/fa9971d2a0c6fef64c87ba362a493a4f230eff4ea8dfb9f4c7cbdf71892e/apeye_core-1.1.5-py3-none-any.whl", hash = "sha256:dc27a93f8c9e246b3b238c5ea51edf6115ab2618ef029b9f2d9a190ec8228fbf", size = 99286 }, ] [[package]] name = "astroid" version = "3.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0c/34/b41c9234c4ce896297235dd757aef6a20e2761dc19d7d000249492a58d24/astroid-3.3.2.tar.gz", hash = "sha256:99e9b5b602cbb005434084309213d6af32bf7a9b743c836749168b8e2b330cbd", size = 395691 } wheels = [ { url = "https://files.pythonhosted.org/packages/8c/2b/07683b86375a9f35a5d61b78aecd662a78437e2c59ba279dcd8a253b3d0a/astroid-3.3.2-py3-none-any.whl", hash = "sha256:9f8136ce9770e0f912401b25a0f15d5c2ec20b50e99b1b413ac0778fe53ff6f1", size = 274227 }, ] [[package]] name = "autodocsumm" version = "0.2.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/03/96/92afe8a7912b327c01f0a8b6408c9556ee13b1aba5b98d587ac7327ff32d/autodocsumm-0.2.14.tar.gz", hash = "sha256:2839a9d4facc3c4eccd306c08695540911042b46eeafcdc3203e6d0bab40bc77", size = 46357 } wheels = [ { url = "https://files.pythonhosted.org/packages/87/bc/3f66af9beb683728e06ca08797e4e9d3e44f432f339718cae3ba856a9cad/autodocsumm-0.2.14-py3-none-any.whl", hash = "sha256:3bad8717fc5190802c60392a7ab04b9f3c97aa9efa8b3780b3d81d615bfe5dc0", size = 14640 }, ] [[package]] name = "babel" version = "2.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } wheels = [ { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, ] [[package]] name = "beautifulsoup4" version = "4.12.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } wheels = [ { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, ] [[package]] name = "cachecontrol" version = "0.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "msgpack" }, { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/3a/0cbeb04ea57d2493f3ec5a069a117ab467f85e4a10017c6d854ddcbff104/cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11", size = 28985 } wheels = [ { url = "https://files.pythonhosted.org/packages/81/4c/800b0607b00b3fd20f1087f80ab53d6b4d005515b0f773e4831e37cfa83f/cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae", size = 21802 }, ] [package.optional-dependencies] filecache = [ { name = "filelock" }, ] [[package]] name = "certifi" version = "2024.7.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c2/02/a95f2b11e207f68bc64d7aae9666fed2e2b3f307748d5123dffb72a1bbea/certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", size = 164065 } wheels = [ { url = "https://files.pythonhosted.org/packages/1c/d5/c84e1a17bf61d4df64ca866a1c9a913874b4e9bdc131ec689a0ad013fb36/certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90", size = 162960 }, ] [[package]] name = "charset-normalizer" version = "3.3.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } wheels = [ { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219 }, { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521 }, { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383 }, { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223 }, { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101 }, { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699 }, { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065 }, { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505 }, { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425 }, { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287 }, { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929 }, { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605 }, { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646 }, { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846 }, { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343 }, { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] [[package]] name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] name = "cssutils" version = "2.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/9f/329d26121fe165be44b1dfff21aa0dc348f04633931f1d20ed6cf448a236/cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2", size = 711657 } wheels = [ { url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747 }, ] [[package]] name = "dict2css" version = "0.3.0.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cssutils" }, { name = "domdf-python-tools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/24/eb/776eef1f1aa0188c0fc165c3a60b71027539f71f2eedc43ad21b060e9c39/dict2css-0.3.0.post1.tar.gz", hash = "sha256:89c544c21c4ca7472c3fffb9d37d3d926f606329afdb751dc1de67a411b70719", size = 7845 } wheels = [ { url = "https://files.pythonhosted.org/packages/fe/47/290daabcf91628f4fc0e17c75a1690b354ba067066cd14407712600e609f/dict2css-0.3.0.post1-py3-none-any.whl", hash = "sha256:f006a6b774c3e31869015122ae82c491fd25e7de4a75607a62aa3e798f837e0d", size = 25647 }, ] [[package]] name = "docs" version = "0.1.0" source = { editable = "." } dependencies = [ { name = "furo" }, { name = "linkify-it-py" }, { name = "myst-parser", extra = ["linkify"] }, { name = "sphinx" }, { name = "sphinx-autoapi" }, { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-copybutton" }, { name = "sphinx-pyproject" }, { name = "sphinx-togglebutton" }, { name = "sphinx-toolbox" }, ] [package.metadata] requires-dist = [ { name = "furo", specifier = ">=2022.4.7" }, { name = "linkify-it-py" }, { name = "myst-parser", extras = ["linkify"], specifier = ">=4" }, { name = "sphinx", specifier = "~=7.1.0" }, { name = "sphinx-autoapi", specifier = "~=3.0.0" }, { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints", specifier = "~=1.25.0" }, { name = "sphinx-copybutton" }, { name = "sphinx-pyproject", specifier = ">=0.3.0" }, { name = "sphinx-togglebutton", specifier = ">=0.3.2" }, { name = "sphinx-toolbox", specifier = ">=3.9.0,<4.0.0" }, ] [[package]] name = "docutils" version = "0.20.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365 } wheels = [ { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666 }, ] [[package]] name = "dom-toml" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "domdf-python-tools" }, { name = "tomli" }, ] sdist = { url = "https://files.pythonhosted.org/packages/70/34/f7690cf288eaa86b55c8f1b890d0834e6df44a026a88eca12274fcd624ab/dom_toml-2.0.0.tar.gz", hash = "sha256:3c07e8436538994974127b1ae037661d1a779ac915c44fd06b3ab5fe140ff589", size = 11133 } wheels = [ { url = "https://files.pythonhosted.org/packages/22/99/b6fc87dff3138491d81676bdcbf1531080925ba41486ec1dafd86e33fdbc/dom_toml-2.0.0-py3-none-any.whl", hash = "sha256:0b6d02a72bcbc6be8175c61afc30623bbb6b74c4650f2a806fbc3fb7fe86935d", size = 13376 }, ] [[package]] name = "domdf-python-tools" version = "3.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "natsort" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6b/78/974e10c583ba9d2302e748c9585313a7f2c7ba00e4f600324f432e38fe68/domdf_python_tools-3.9.0.tar.gz", hash = "sha256:1f8a96971178333a55e083e35610d7688cd7620ad2b99790164e1fc1a3614c18", size = 103792 } wheels = [ { url = "https://files.pythonhosted.org/packages/de/e9/7447a88b217650a74927d3444a89507986479a69b83741900eddd34167fe/domdf_python_tools-3.9.0-py3-none-any.whl", hash = "sha256:4e1ef365cbc24627d6d1e90cf7d46d8ab8df967e1237f4a26885f6986c78872e", size = 127106 }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] [[package]] name = "filelock" version = "3.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } wheels = [ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, ] [[package]] name = "furo" version = "2024.8.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, { name = "pygments" }, { name = "sphinx" }, { name = "sphinx-basic-ng" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a0/e2/d351d69a9a9e4badb4a5be062c2d0e87bd9e6c23b5e57337fef14bef34c8/furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01", size = 1661506 } wheels = [ { url = "https://files.pythonhosted.org/packages/27/48/e791a7ed487dbb9729ef32bb5d1af16693d8925f4366befef54119b2e576/furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c", size = 341333 }, ] [[package]] name = "h11" version = "0.14.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } wheels = [ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, ] [[package]] name = "html5lib" version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, { name = "webencodings" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215 } wheels = [ { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173 }, ] [[package]] name = "idna" version = "3.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/e349c5e6d4543326c6883ee9491e3921e0d07b55fdf3cce184b40d63e72a/idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603", size = 189467 } wheels = [ { url = "https://files.pythonhosted.org/packages/22/7e/d71db821f177828df9dea8c42ac46473366f191be53080e552e628aad991/idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", size = 66894 }, ] [[package]] name = "imagesize" version = "1.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } wheels = [ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, ] [[package]] name = "jinja2" version = "3.1.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } wheels = [ { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, ] [[package]] name = "linkify-it-py" version = "2.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "uc-micro-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946 } wheels = [ { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820 }, ] [[package]] name = "markdown-it-py" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, ] [[package]] name = "markupsafe" version = "2.1.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 } wheels = [ { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206 }, { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079 }, { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620 }, { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818 }, { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493 }, { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630 }, { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745 }, { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021 }, { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659 }, { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213 }, { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219 }, { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098 }, { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014 }, { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220 }, { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756 }, { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988 }, { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718 }, { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317 }, { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670 }, { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224 }, { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 }, { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 }, { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 }, { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 }, { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 }, { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 }, { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 }, { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 }, { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 }, { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 }, ] [[package]] name = "mdit-py-plugins" version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/6c/79c52651b22b64dba5c7bbabd7a294cc410bfb2353250dc8ade44d7d8ad8/mdit_py_plugins-0.4.1.tar.gz", hash = "sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c", size = 42713 } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/f7/8a4dcea720a581e69ac8c5a38524baf0e3e2bb5f3819a9ff661464fe7d10/mdit_py_plugins-0.4.1-py3-none-any.whl", hash = "sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a", size = 54794 }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] [[package]] name = "more-itertools" version = "10.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671 } wheels = [ { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278 }, ] [[package]] name = "msgpack" version = "1.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555 } wheels = [ { url = "https://files.pythonhosted.org/packages/33/52/f30da112c1dc92cf64f57d08a273ac771e7b29dea10b4b30369b2d7e8546/msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed", size = 81799 }, { url = "https://files.pythonhosted.org/packages/e4/35/7bfc0def2f04ab4145f7f108e3563f9b4abae4ab0ed78a61f350518cc4d2/msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8", size = 78278 }, { url = "https://files.pythonhosted.org/packages/e8/c5/df5d6c1c39856bc55f800bf82778fd4c11370667f9b9e9d51b2f5da88f20/msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2", size = 402805 }, { url = "https://files.pythonhosted.org/packages/20/8e/0bb8c977efecfe6ea7116e2ed73a78a8d32a947f94d272586cf02a9757db/msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4", size = 408642 }, { url = "https://files.pythonhosted.org/packages/59/a1/731d52c1aeec52006be6d1f8027c49fdc2cfc3ab7cbe7c28335b2910d7b6/msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0", size = 395143 }, { url = "https://files.pythonhosted.org/packages/2b/92/b42911c52cda2ba67a6418ffa7d08969edf2e760b09015593c8a8a27a97d/msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26", size = 395986 }, { url = "https://files.pythonhosted.org/packages/61/dc/8ae165337e70118d4dab651b8b562dd5066dd1e6dd57b038f32ebc3e2f07/msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75", size = 402682 }, { url = "https://files.pythonhosted.org/packages/58/27/555851cb98dcbd6ce041df1eacb25ac30646575e9cd125681aa2f4b1b6f1/msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338", size = 406368 }, { url = "https://files.pythonhosted.org/packages/d4/64/39a26add4ce16f24e99eabb9005e44c663db00e3fce17d4ae1ae9d61df99/msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd", size = 65004 }, { url = "https://files.pythonhosted.org/packages/7d/18/73dfa3e9d5d7450d39debde5b0d848139f7de23bd637a4506e36c9800fd6/msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8", size = 71548 }, { url = "https://files.pythonhosted.org/packages/7f/83/97f24bf9848af23fe2ba04380388216defc49a8af6da0c28cc636d722502/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", size = 82728 }, { url = "https://files.pythonhosted.org/packages/aa/7f/2eaa388267a78401f6e182662b08a588ef4f3de6f0eab1ec09736a7aaa2b/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", size = 79279 }, { url = "https://files.pythonhosted.org/packages/f8/46/31eb60f4452c96161e4dfd26dbca562b4ec68c72e4ad07d9566d7ea35e8a/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", size = 423859 }, { url = "https://files.pythonhosted.org/packages/45/16/a20fa8c32825cc7ae8457fab45670c7a8996d7746ce80ce41cc51e3b2bd7/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", size = 429975 }, { url = "https://files.pythonhosted.org/packages/86/ea/6c958e07692367feeb1a1594d35e22b62f7f476f3c568b002a5ea09d443d/msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", size = 413528 }, { url = "https://files.pythonhosted.org/packages/75/05/ac84063c5dae79722bda9f68b878dc31fc3059adb8633c79f1e82c2cd946/msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", size = 413338 }, { url = "https://files.pythonhosted.org/packages/69/e8/fe86b082c781d3e1c09ca0f4dacd457ede60a13119b6ce939efe2ea77b76/msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", size = 422658 }, { url = "https://files.pythonhosted.org/packages/3b/2b/bafc9924df52d8f3bb7c00d24e57be477f4d0f967c0a31ef5e2225e035c7/msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", size = 427124 }, { url = "https://files.pythonhosted.org/packages/a2/3b/1f717e17e53e0ed0b68fa59e9188f3f610c79d7151f0e52ff3cd8eb6b2dc/msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", size = 65016 }, { url = "https://files.pythonhosted.org/packages/48/45/9d1780768d3b249accecc5a38c725eb1e203d44a191f7b7ff1941f7df60c/msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", size = 72267 }, { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359 }, { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172 }, { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013 }, { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905 }, { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336 }, { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485 }, { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182 }, { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883 }, { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406 }, { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558 }, { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677 }, { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603 }, { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504 }, { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749 }, { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458 }, { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976 }, { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607 }, { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172 }, { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347 }, { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341 }, ] [[package]] name = "myst-parser" version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "jinja2" }, { name = "markdown-it-py" }, { name = "mdit-py-plugins" }, { name = "pyyaml" }, { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/85/55/6d1741a1780e5e65038b74bce6689da15f620261c490c3511eb4c12bac4b/myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531", size = 93858 } wheels = [ { url = "https://files.pythonhosted.org/packages/ca/b4/b036f8fdb667587bb37df29dc6644681dd78b7a2a6321a34684b79412b28/myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d", size = 84563 }, ] [package.optional-dependencies] linkify = [ { name = "linkify-it-py" }, ] [[package]] name = "natsort" version = "8.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575 } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268 }, ] [[package]] name = "packaging" version = "24.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } wheels = [ { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, ] [[package]] name = "platformdirs" version = "4.3.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } wheels = [ { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, ] [[package]] name = "pygments" version = "2.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } wheels = [ { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] [[package]] name = "requests" version = "2.32.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] [[package]] name = "ruamel-yaml" version = "0.18.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml-clib", marker = "python_full_version < '3.14' and platform_python_implementation == 'CPython'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/39/87/6da0df742a4684263261c253f00edd5829e6aca970fff69e75028cccc547/ruamel.yaml-0.18.14.tar.gz", hash = "sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7", size = 145511 } wheels = [ { url = "https://files.pythonhosted.org/packages/af/6d/6fe4805235e193aad4aaf979160dd1f3c487c57d48b810c816e6e842171b/ruamel.yaml-0.18.14-py3-none-any.whl", hash = "sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2", size = 118570 }, ] [[package]] name = "ruamel-yaml-clib" version = "0.2.12" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315 } wheels = [ { url = "https://files.pythonhosted.org/packages/70/57/40a958e863e299f0c74ef32a3bde9f2d1ea8d69669368c0c502a0997f57f/ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", size = 131301 }, { url = "https://files.pythonhosted.org/packages/98/a8/29a3eb437b12b95f50a6bcc3d7d7214301c6c529d8fdc227247fa84162b5/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", size = 633728 }, { url = "https://files.pythonhosted.org/packages/35/6d/ae05a87a3ad540259c3ad88d71275cbd1c0f2d30ae04c65dcbfb6dcd4b9f/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df", size = 722230 }, { url = "https://files.pythonhosted.org/packages/7f/b7/20c6f3c0b656fe609675d69bc135c03aac9e3865912444be6339207b6648/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", size = 686712 }, { url = "https://files.pythonhosted.org/packages/cd/11/d12dbf683471f888d354dac59593873c2b45feb193c5e3e0f2ebf85e68b9/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", size = 663936 }, { url = "https://files.pythonhosted.org/packages/72/14/4c268f5077db5c83f743ee1daeb236269fa8577133a5cfa49f8b382baf13/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", size = 696580 }, { url = "https://files.pythonhosted.org/packages/30/fc/8cd12f189c6405a4c1cf37bd633aa740a9538c8e40497c231072d0fef5cf/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a", size = 663393 }, { url = "https://files.pythonhosted.org/packages/80/29/c0a017b704aaf3cbf704989785cd9c5d5b8ccec2dae6ac0c53833c84e677/ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", size = 100326 }, { url = "https://files.pythonhosted.org/packages/3a/65/fa39d74db4e2d0cd252355732d966a460a41cd01c6353b820a0952432839/ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", size = 118079 }, { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224 }, { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480 }, { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068 }, { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012 }, { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352 }, { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344 }, { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498 }, { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205 }, { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185 }, { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433 }, { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362 }, { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118 }, { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497 }, { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042 }, { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831 }, { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692 }, { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777 }, { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523 }, { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011 }, { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488 }, { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066 }, { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785 }, { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017 }, { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270 }, { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059 }, { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583 }, { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190 }, ] [[package]] name = "setuptools" version = "73.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8d/37/f4d4ce9bc15e61edba3179f9b0f763fc6d439474d28511b11f0d95bab7a2/setuptools-73.0.1.tar.gz", hash = "sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193", size = 2526506 } wheels = [ { url = "https://files.pythonhosted.org/packages/07/6a/0270e295bf30c37567736b7fca10167640898214ff911273af37ddb95770/setuptools-73.0.1-py3-none-any.whl", hash = "sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e", size = 2346588 }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] [[package]] name = "snowballstemmer" version = "2.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } wheels = [ { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, ] [[package]] name = "soupsieve" version = "2.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, ] [[package]] name = "sphinx" version = "7.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, { name = "babel" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "docutils" }, { name = "imagesize" }, { name = "jinja2" }, { name = "packaging" }, { name = "pygments" }, { name = "requests" }, { name = "snowballstemmer" }, { name = "sphinxcontrib-applehelp" }, { name = "sphinxcontrib-devhelp" }, { name = "sphinxcontrib-htmlhelp" }, { name = "sphinxcontrib-jsmath" }, { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/01/688bdf9282241dca09fe6e3a1110eda399fa9b10d0672db609e37c2e7a39/sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f", size = 6828258 } wheels = [ { url = "https://files.pythonhosted.org/packages/48/17/325cf6a257d84751a48ae90752b3d8fe0be8f9535b6253add61c49d0d9bc/sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe", size = 3169543 }, ] [[package]] name = "sphinx-autoapi" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyascii" }, { name = "astroid" }, { name = "jinja2" }, { name = "pyyaml" }, { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/48/cf3538dda02ec6f62013f9baa01b11e6049a120e67fec159d79c028d341d/sphinx-autoapi-3.0.0.tar.gz", hash = "sha256:09ebd674a32b44467222b0fb8a917b97c89523f20dbf05b52cb8a3f0e15714de", size = 58081 } wheels = [ { url = "https://files.pythonhosted.org/packages/f3/f8/489774c0ebcd96bec1e44e4c23ac741f74895efd5ba1da778b2984e0e3cb/sphinx_autoapi-3.0.0-py2.py3-none-any.whl", hash = "sha256:ea207793cba1feff7b2ded0e29364f2995a4d157303a98603cee0ce94cea2688", size = 35009 }, ] [[package]] name = "sphinx-autobuild" version = "2024.4.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, { name = "sphinx" }, { name = "starlette" }, { name = "uvicorn" }, { name = "watchfiles" }, { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d2/bc/8016eee9ffb86069ae7e658c136bad31a9854b476a212492b5aab4d4d5f6/sphinx_autobuild-2024.4.16.tar.gz", hash = "sha256:1c0ed37a1970eed197f9c5a66d65759e7c4e4cba7b5a5d77940752bf1a59f2c7", size = 12892 } wheels = [ { url = "https://files.pythonhosted.org/packages/53/de/1d005ba60b4b754d6e05079a479f16a8f6e08c1ec4f8d80288238502b4b6/sphinx_autobuild-2024.4.16-py3-none-any.whl", hash = "sha256:f2522779d30fcbf0253e09714f274ce8c608cb6ebcd67922b1c54de59faba702", size = 11222 }, ] [[package]] name = "sphinx-autodoc-typehints" version = "1.25.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/19/1131e37f815864efbdb6498cc41f5ce3da82b7e952456900968132c7dcba/sphinx_autodoc_typehints-1.25.3.tar.gz", hash = "sha256:70db10b391acf4e772019765991d2de0ff30ec0899b9ba137706dc0b3c4835e0", size = 37709 } wheels = [ { url = "https://files.pythonhosted.org/packages/ba/d3/8c375e2a6ce21c29021d413e5ebb4d285431acd209f82b89101f157d4b9b/sphinx_autodoc_typehints-1.25.3-py3-none-any.whl", hash = "sha256:d3da7fa9a9761eff6ff09f8b1956ae3090a2d4f4ad54aebcade8e458d6340835", size = 19239 }, ] [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736 } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496 }, ] [[package]] name = "sphinx-copybutton" version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039 } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343 }, ] [[package]] name = "sphinx-jinja2-compat" version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, { name = "markupsafe" }, { name = "standard-imghdr", marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/26/df/27282da6f8c549f765beca9de1a5fc56f9651ed87711a5cac1e914137753/sphinx_jinja2_compat-0.3.0.tar.gz", hash = "sha256:f3c1590b275f42e7a654e081db5e3e5fb97f515608422bde94015ddf795dfe7c", size = 4998 } wheels = [ { url = "https://files.pythonhosted.org/packages/6f/42/2fd09d672eaaa937d6893d8b747d07943f97a6e5e30653aee6ebd339b704/sphinx_jinja2_compat-0.3.0-py3-none-any.whl", hash = "sha256:b1e4006d8e1ea31013fa9946d1b075b0c8d2a42c6e3425e63542c1e9f8be9084", size = 7883 }, ] [[package]] name = "sphinx-prompt" version = "1.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "pygments" }, { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/fb/7a07b8df1ca2418147a6b13e3f6b445071f2565198b45efa631d0d6ef0cd/sphinx_prompt-1.8.0.tar.gz", hash = "sha256:47482f86fcec29662fdfd23e7c04ef03582714195d01f5d565403320084372ed", size = 5121 } wheels = [ { url = "https://files.pythonhosted.org/packages/39/49/f890a2668b7cbf375f5528b549c8d36dd2e801b0fbb7b2b5ef65663ecb6c/sphinx_prompt-1.8.0-py3-none-any.whl", hash = "sha256:369ecc633f0711886f9b3a078c83264245be1adf46abeeb9b88b5519e4b51007", size = 7298 }, ] [[package]] name = "sphinx-pyproject" version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dom-toml" }, { name = "domdf-python-tools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/39/97/aa8cec3da3e78f2c396b63332e2fe92fe43f7ff2ad19b3998735f28b0a7f/sphinx_pyproject-0.3.0.tar.gz", hash = "sha256:efc4ee9d96f579c4e4ed1ac273868c64565e88c8e37fe6ec2dc59fbcd57684ab", size = 7695 } wheels = [ { url = "https://files.pythonhosted.org/packages/90/d5/89cb47c6399fd57ca451af15361499813c5d53e588cb6e00d89411ce724f/sphinx_pyproject-0.3.0-py3-none-any.whl", hash = "sha256:3aca968919f5ecd390f96874c3f64a43c9c7fcfdc2fd4191a781ad9228501b52", size = 23076 }, ] [[package]] name = "sphinx-tabs" version = "3.4.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "pygments" }, { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/27/32/ab475e252dc2b704e82a91141fa404cdd8901a5cf34958fd22afacebfccd/sphinx-tabs-3.4.5.tar.gz", hash = "sha256:ba9d0c1e3e37aaadd4b5678449eb08176770e0fc227e769b6ce747df3ceea531", size = 16070 } wheels = [ { url = "https://files.pythonhosted.org/packages/20/9f/4ac7dbb9f23a2ff5a10903a4f9e9f43e0ff051f63a313e989c962526e305/sphinx_tabs-3.4.5-py3-none-any.whl", hash = "sha256:92cc9473e2ecf1828ca3f6617d0efc0aa8acb06b08c56ba29d1413f2f0f6cf09", size = 9904 }, ] [[package]] name = "sphinx-togglebutton" version = "0.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "setuptools" }, { name = "sphinx" }, { name = "wheel" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f0/df/d151dfbbe588116e450ca7e898750cb218dca6b2e557ced8de6f9bd7242b/sphinx-togglebutton-0.3.2.tar.gz", hash = "sha256:ab0c8b366427b01e4c89802d5d078472c427fa6e9d12d521c34fa0442559dc7a", size = 8324 } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/18/267ce39f29d26cdc7177231428ba823fe5ca94db8c56d1bed69033b364c8/sphinx_togglebutton-0.3.2-py3-none-any.whl", hash = "sha256:9647ba7874b7d1e2d43413d8497153a85edc6ac95a3fea9a75ef9c1e08aaae2b", size = 8249 }, ] [[package]] name = "sphinx-toolbox" version = "3.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "apeye" }, { name = "autodocsumm" }, { name = "beautifulsoup4" }, { name = "cachecontrol", extra = ["filecache"] }, { name = "dict2css" }, { name = "docutils" }, { name = "domdf-python-tools" }, { name = "filelock" }, { name = "html5lib" }, { name = "ruamel-yaml" }, { name = "sphinx" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-jinja2-compat" }, { name = "sphinx-prompt" }, { name = "sphinx-tabs" }, { name = "tabulate" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f4/2d/d916dc5a70bc7b006af8a31bba1a2767e99cdb884f3dfa47aa79a60cc1e9/sphinx_toolbox-3.10.0.tar.gz", hash = "sha256:6afea9ac9afabe76bd5bd4d2b01edfdad81d653a1a34768e776e6a56d5a6f572", size = 113656 } wheels = [ { url = "https://files.pythonhosted.org/packages/12/ec/d09521ae2059fe89d8b59b2b34f5a1713b82a14e70a9a018fca8d3d514be/sphinx_toolbox-3.10.0-py3-none-any.whl", hash = "sha256:675e5978eaee31adf21701054fa75bacf820459d56e93ac30ad01eaee047a6ef", size = 195622 }, ] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, ] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } wheels = [ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, ] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, ] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, ] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } wheels = [ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, ] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] [[package]] name = "standard-imghdr" version = "3.10.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/09/d2/2eb5521072c9598886035c65c023f39f7384bcb73eed70794f469e34efac/standard_imghdr-3.10.14.tar.gz", hash = "sha256:2598fe2e7c540dbda34b233295e10957ab8dc8ac6f3bd9eaa8d38be167232e52", size = 5474 } wheels = [ { url = "https://files.pythonhosted.org/packages/fb/d0/9852f70eb01f814843530c053542b72d30e9fbf74da7abb0107e71938389/standard_imghdr-3.10.14-py3-none-any.whl", hash = "sha256:cdf6883163349624dee9a81d2853a20260337c4cd41c04e99c082e01833a08e2", size = 5598 }, ] [[package]] name = "starlette" version = "0.38.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/e2/d49a94ecb665b3a1c34b40c78165a737abc384fcabc843ccb14a3bd3dc37/starlette-0.38.2.tar.gz", hash = "sha256:c7c0441065252160993a1a37cf2a73bb64d271b17303e0b0c1eb7191cfb12d75", size = 2844770 } wheels = [ { url = "https://files.pythonhosted.org/packages/c1/60/d976da9998e4f4a99e297cda09d61ce305919ea94cbeeb476dba4fece098/starlette-0.38.2-py3-none-any.whl", hash = "sha256:4ec6a59df6bbafdab5f567754481657f7ed90dc9d69b0c9ff017907dd54faeff", size = 72020 }, ] [[package]] name = "tabulate" version = "0.9.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, ] [[package]] name = "tomli" version = "2.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } wheels = [ { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, ] [[package]] name = "typing-extensions" version = "4.12.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] [[package]] name = "uc-micro-py" version = "1.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043 } wheels = [ { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229 }, ] [[package]] name = "urllib3" version = "2.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/6d/fa469ae21497ddc8bc93e5877702dca7cb8f911e337aca7452b5724f1bb6/urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168", size = 292266 } wheels = [ { url = "https://files.pythonhosted.org/packages/ca/1c/89ffc63a9605b583d5df2be791a27bc1a42b7c32bab68d3c8f2f73a98cd4/urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", size = 121444 }, ] [[package]] name = "uvicorn" version = "0.30.6" 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/5a/01/5e637e7aa9dd031be5376b9fb749ec20b86f5a5b6a49b87fabd374d5fa9f/uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788", size = 42825 } wheels = [ { url = "https://files.pythonhosted.org/packages/f5/8e/cdc7d6263db313030e4c257dd5ba3909ebc4e4fb53ad62d5f09b1a2f5458/uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5", size = 62835 }, ] [[package]] name = "watchfiles" version = "0.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9e/1a/b06613ef620d7f5ca712a3d4928ec1c07182159a64277fcdf7738edb0b32/watchfiles-0.23.0.tar.gz", hash = "sha256:9338ade39ff24f8086bb005d16c29f8e9f19e55b18dcb04dfa26fcbc09da497b", size = 37384 } wheels = [ { url = "https://files.pythonhosted.org/packages/20/08/c0a09fc63a6b75fccd3e99b21f07ddb812e64a78da10703397b39653263e/watchfiles-0.23.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bee8ce357a05c20db04f46c22be2d1a2c6a8ed365b325d08af94358e0688eeb4", size = 374424 }, { url = "https://files.pythonhosted.org/packages/8c/ff/2b338016e96ab592e8d9cece0260b9fca54d8bed7b36940c46112eda2e49/watchfiles-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ccd3011cc7ee2f789af9ebe04745436371d36afe610028921cab9f24bb2987b", size = 369288 }, { url = "https://files.pythonhosted.org/packages/43/cf/747f412b75ea4bb5419e659ae8b2713a327b6f879e3f2e0695c7d7275cf3/watchfiles-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb02d41c33be667e6135e6686f1bb76104c88a312a18faa0ef0262b5bf7f1a0f", size = 441212 }, { url = "https://files.pythonhosted.org/packages/c6/64/07f4c50883f1406e5a4187651b4d3d2495582df9f70d320ee3c9e7ed19be/watchfiles-0.23.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf12ac34c444362f3261fb3ff548f0037ddd4c5bb85f66c4be30d2936beb3c5", size = 437765 }, { url = "https://files.pythonhosted.org/packages/d8/e9/4af3cfb2eb161003ce79518eb0cbfd014313e30dc842209776fbee3a64e6/watchfiles-0.23.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0b2c25040a3c0ce0e66c7779cc045fdfbbb8d59e5aabfe033000b42fe44b53e", size = 456151 }, { url = "https://files.pythonhosted.org/packages/b4/45/d97e61c893fc59d4b0c4154fdf26449e103a3783db4d981bb8e7301af532/watchfiles-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf2be4b9eece4f3da8ba5f244b9e51932ebc441c0867bd6af46a3d97eb068d6", size = 472320 }, { url = "https://files.pythonhosted.org/packages/f2/55/7266cd63e736abbde902cbd99fb70a8eddfac7e0e52ed52fbbee6eed5f95/watchfiles-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40cb8fa00028908211eb9f8d47744dca21a4be6766672e1ff3280bee320436f1", size = 480442 }, { url = "https://files.pythonhosted.org/packages/22/ec/c756c012b174ccf5f2ee32202603e66b33b93a54cf16c69a7440c764d7f9/watchfiles-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f48c917ffd36ff9a5212614c2d0d585fa8b064ca7e66206fb5c095015bc8207", size = 427729 }, { url = "https://files.pythonhosted.org/packages/78/94/97ac8d7a19f5439ab5cc28d0b5d648760358e43097f6acb8cb7165c4c1b7/watchfiles-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9d183e3888ada88185ab17064079c0db8c17e32023f5c278d7bf8014713b1b5b", size = 616359 }, { url = "https://files.pythonhosted.org/packages/fc/c9/568a54e07245a068819572a7d51c7d2f6ff8e7018102e956156fadae408c/watchfiles-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9837edf328b2805346f91209b7e660f65fb0e9ca18b7459d075d58db082bf981", size = 597955 }, { url = "https://files.pythonhosted.org/packages/fa/2c/ba3e9d54c17a4014996555a0b31f4be1c8920fdfe067942f60873ac8931a/watchfiles-0.23.0-cp310-none-win32.whl", hash = "sha256:296e0b29ab0276ca59d82d2da22cbbdb39a23eed94cca69aed274595fb3dfe42", size = 264290 }, { url = "https://files.pythonhosted.org/packages/6a/a1/d7aa3f23c78b2c9cac5d1cf46fed5f74340d0ffd47c0f485b76419ec6597/watchfiles-0.23.0-cp310-none-win_amd64.whl", hash = "sha256:4ea756e425ab2dfc8ef2a0cb87af8aa7ef7dfc6fc46c6f89bcf382121d4fff75", size = 275914 }, { url = "https://files.pythonhosted.org/packages/14/5f/787386438d895145099e1415d1fbd3ff047a4f5e329134fd30677fe83f1f/watchfiles-0.23.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e397b64f7aaf26915bf2ad0f1190f75c855d11eb111cc00f12f97430153c2eab", size = 374801 }, { url = "https://files.pythonhosted.org/packages/76/6f/3075cd9c69fdce2544fb13cb9e3c8ad51424cb2c552b019514799a14966e/watchfiles-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4ac73b02ca1824ec0a7351588241fd3953748d3774694aa7ddb5e8e46aef3e3", size = 368210 }, { url = "https://files.pythonhosted.org/packages/ab/6b/cd4faa27088a8b612ffdfa25e3d413e676a6173b8b02a33e7fec152d75ca/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a896d53b48a1cecccfa903f37a1d87dbb74295305f865a3e816452f6e49e4", size = 441356 }, { url = "https://files.pythonhosted.org/packages/39/ba/d361135dac6cd0fb4449f4f058c053eb9b42f70ff4d9a13767808e18851c/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5e7803a65eb2d563c73230e9d693c6539e3c975ccfe62526cadde69f3fda0cf", size = 437615 }, { url = "https://files.pythonhosted.org/packages/34/2c/c279de01628f467d16b444bdcedf9c4ce3bc5242cb23f9bfb8fbff8522ee/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1aa4cc85202956d1a65c88d18c7b687b8319dbe6b1aec8969784ef7a10e7d1a", size = 456227 }, { url = "https://files.pythonhosted.org/packages/a4/9f/a3c9f1fbcd1099554e4f707e14473ff23f0e05013d553755b98c2d86716d/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87f889f6e58849ddb7c5d2cb19e2e074917ed1c6e3ceca50405775166492cca8", size = 472219 }, { url = "https://files.pythonhosted.org/packages/22/ee/06a0a6cbde8ac6fff57c33da9e428f42dd0989e60a6ad72ca6534f650a47/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37fd826dac84c6441615aa3f04077adcc5cac7194a021c9f0d69af20fb9fa788", size = 479948 }, { url = "https://files.pythonhosted.org/packages/b9/f0/76ad5227da9461b1190de2f9dd21fece09660a9a44607de9c728f3d3e93f/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee7db6e36e7a2c15923072e41ea24d9a0cf39658cb0637ecc9307b09d28827e1", size = 427559 }, { url = "https://files.pythonhosted.org/packages/e1/15/daf4361e0a6e6b27f516aaaacbb16baa8d1a266657b2314862fc73f2deaf/watchfiles-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2368c5371c17fdcb5a2ea71c5c9d49f9b128821bfee69503cc38eae00feb3220", size = 616447 }, { url = "https://files.pythonhosted.org/packages/b3/e4/2647ca9aaa072e139a4cc6c83c8a15d2f8fa6740913903ab998917a5ed97/watchfiles-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:857af85d445b9ba9178db95658c219dbd77b71b8264e66836a6eba4fbf49c320", size = 598031 }, { url = "https://files.pythonhosted.org/packages/3d/02/f223537cd0e3c22df45629710b27b7f89fdf4114be2f3399b83faedf1446/watchfiles-0.23.0-cp311-none-win32.whl", hash = "sha256:1d636c8aeb28cdd04a4aa89030c4b48f8b2954d8483e5f989774fa441c0ed57b", size = 264354 }, { url = "https://files.pythonhosted.org/packages/03/31/c1b5ea92100d9774f5a8a89115a43ef1c4fb169b643b6cc930e0cd2c5728/watchfiles-0.23.0-cp311-none-win_amd64.whl", hash = "sha256:46f1d8069a95885ca529645cdbb05aea5837d799965676e1b2b1f95a4206313e", size = 275821 }, { url = "https://files.pythonhosted.org/packages/23/9c/810ede8d4dff7e65393b50cbb1a3ef10b6cdb1312a97d8106712175355c8/watchfiles-0.23.0-cp311-none-win_arm64.whl", hash = "sha256:e495ed2a7943503766c5d1ff05ae9212dc2ce1c0e30a80d4f0d84889298fa304", size = 266906 }, { url = "https://files.pythonhosted.org/packages/61/52/85cdf326a53f1ae3fbe5dcab13f5729ca91ec2d61140e095a2a4cdf6a9ca/watchfiles-0.23.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1db691bad0243aed27c8354b12d60e8e266b75216ae99d33e927ff5238d270b5", size = 373314 }, { url = "https://files.pythonhosted.org/packages/20/5e/a97417a6544615b21c7960a45aeea13e3b42779e0ed3ebdd2d76ad62ab50/watchfiles-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62d2b18cb1edaba311fbbfe83fb5e53a858ba37cacb01e69bc20553bb70911b8", size = 368915 }, { url = "https://files.pythonhosted.org/packages/bc/82/537945ed624af6248c9820a99cbfd5902bb5e6a71a01a5b3de0c00f1872e/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e087e8fdf1270d000913c12e6eca44edd02aad3559b3e6b8ef00f0ce76e0636f", size = 441495 }, { url = "https://files.pythonhosted.org/packages/28/24/060b064f28083866d916052fcced5c3547c5081a8e27b0702434666aa9a0/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd41d5c72417b87c00b1b635738f3c283e737d75c5fa5c3e1c60cd03eac3af77", size = 437357 }, { url = "https://files.pythonhosted.org/packages/b6/00/ac760f3fa8d8975dbeaef9af99b21077e7c38898ac5051c8601649d86d99/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e5f3ca0ff47940ce0a389457b35d6df601c317c1e1a9615981c474452f98de1", size = 456584 }, { url = "https://files.pythonhosted.org/packages/f7/52/2f7bbedc5f524d2ba0e9d792dab01ef4418d0f5045a9f5f4e5aca142a30d/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6991e3a78f642368b8b1b669327eb6751439f9f7eaaa625fae67dd6070ecfa0b", size = 471863 }, { url = "https://files.pythonhosted.org/packages/b1/64/a80f51cb55c967629930682bf120d5ca9d1c65077c38328be635ed0d567c/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f7252f52a09f8fa5435dc82b6af79483118ce6bd51eb74e6269f05ee22a7b9f", size = 478307 }, { url = "https://files.pythonhosted.org/packages/03/f1/fdacfdbffb0635a7d0140ecca6ef7b5bce6566a085f76a65eb796ee54ddd/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e01bcb8d767c58865207a6c2f2792ad763a0fe1119fb0a430f444f5b02a5ea0", size = 427117 }, { url = "https://files.pythonhosted.org/packages/d1/23/89b2bef692c350de8a4c2bde501fdf6087889a55f52a3201f0c53b616087/watchfiles-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8e56fbcdd27fce061854ddec99e015dd779cae186eb36b14471fc9ae713b118c", size = 616352 }, { url = "https://files.pythonhosted.org/packages/2c/35/a683945181a527083a1146620997b5d6ffe06d716c4497d388bfea813f0c/watchfiles-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bd3e2d64500a6cad28bcd710ee6269fbeb2e5320525acd0cfab5f269ade68581", size = 597165 }, { url = "https://files.pythonhosted.org/packages/9e/9b/ec2eabc996e5332fc89c633fbe762e08a58a7df6b5e595dd458c5f7778a4/watchfiles-0.23.0-cp312-none-win32.whl", hash = "sha256:eb99c954291b2fad0eff98b490aa641e128fbc4a03b11c8a0086de8b7077fb75", size = 264293 }, { url = "https://files.pythonhosted.org/packages/e0/3a/62add8d90070f4b17f8bbfd66c9eaa9e08af3bc4020c07a9400d1b959aaf/watchfiles-0.23.0-cp312-none-win_amd64.whl", hash = "sha256:dccc858372a56080332ea89b78cfb18efb945da858fabeb67f5a44fa0bcb4ebb", size = 275514 }, { url = "https://files.pythonhosted.org/packages/e8/9a/2792d4c24105104bfaf959bffefb09e02d14050913a83242ce4eb1e3f2ff/watchfiles-0.23.0-cp312-none-win_arm64.whl", hash = "sha256:6c21a5467f35c61eafb4e394303720893066897fca937bade5b4f5877d350ff8", size = 266607 }, { url = "https://files.pythonhosted.org/packages/f6/5b/1a1d9bca4eae8cf191e74b62cd970f4a010f56f897c11dd2e6caef3ce7e3/watchfiles-0.23.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ba31c32f6b4dceeb2be04f717811565159617e28d61a60bb616b6442027fd4b9", size = 372999 }, { url = "https://files.pythonhosted.org/packages/98/e1/76ad010c0a2bb6efbb80383c0bba56db065238f12b0da6e6026b4e69f6aa/watchfiles-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85042ab91814fca99cec4678fc063fb46df4cbb57b4835a1cc2cb7a51e10250e", size = 368511 }, { url = "https://files.pythonhosted.org/packages/a1/13/d2d59d545b84fd3cf4f08b69da358209b4276c2c932d060d94a421015074/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24655e8c1c9c114005c3868a3d432c8aa595a786b8493500071e6a52f3d09217", size = 441063 }, { url = "https://files.pythonhosted.org/packages/4b/d1/dab28bed3bc9172d44100e5fae8107bd01ef85fc6bddb80d223d0d9f709f/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b1a950ab299a4a78fd6369a97b8763732bfb154fdb433356ec55a5bce9515c1", size = 436805 }, { url = "https://files.pythonhosted.org/packages/06/9c/46e0d17853b62b5d4bf8095e7b9bb0b0ad4babb6c6133138929473f161f3/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8d3c5cd327dd6ce0edfc94374fb5883d254fe78a5e9d9dfc237a1897dc73cd1", size = 456411 }, { url = "https://files.pythonhosted.org/packages/2c/ff/e891b230bcf3a648352a00b920d4a1142a938f0b97c9e8e27c2eaaeda221/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ff785af8bacdf0be863ec0c428e3288b817e82f3d0c1d652cd9c6d509020dd0", size = 471563 }, { url = "https://files.pythonhosted.org/packages/0b/07/f5b54afa8b7c33386c5778d92e681562939900f4ee1c6de9bffc49e7221f/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02b7ba9d4557149410747353e7325010d48edcfe9d609a85cb450f17fd50dc3d", size = 478385 }, { url = "https://files.pythonhosted.org/packages/a3/b6/243c1dd351ac9b8258a3ea99c33d04ecdc9766e6c7f13a43452883e92a7a/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a1b05c0afb2cd2f48c1ed2ae5487b116e34b93b13074ed3c22ad5c743109f0", size = 427485 }, { url = "https://files.pythonhosted.org/packages/28/8a/6d00aa4aa9a9938de645c1d411e3af82e74db8d25a0c05427b7a88b4d8d3/watchfiles-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:109a61763e7318d9f821b878589e71229f97366fa6a5c7720687d367f3ab9eef", size = 615839 }, { url = "https://files.pythonhosted.org/packages/5a/d9/120d212d2952342e2c9673096f5c17cd48e90a7c9ff203ab1ad2f974befe/watchfiles-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9f8e6bb5ac007d4a4027b25f09827ed78cbbd5b9700fd6c54429278dacce05d1", size = 596603 }, { url = "https://files.pythonhosted.org/packages/3b/25/ec3676b140a93ac256d058a6f82810cf5e0e42fd444b948c62bc56f57f52/watchfiles-0.23.0-cp313-none-win32.whl", hash = "sha256:f46c6f0aec8d02a52d97a583782d9af38c19a29900747eb048af358a9c1d8e5b", size = 263898 }, { url = "https://files.pythonhosted.org/packages/1a/c6/bf3b8cbe6944499fbe0d400175560a200cdecadccbacc8ace74486565d74/watchfiles-0.23.0-cp313-none-win_amd64.whl", hash = "sha256:f449afbb971df5c6faeb0a27bca0427d7b600dd8f4a068492faec18023f0dcff", size = 275220 }, { url = "https://files.pythonhosted.org/packages/f7/7c/135a60260dd055227eb3b38f0be5fc16409ad58c5c6636467b27991fd863/watchfiles-0.23.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6a9265cf87a5b70147bfb2fec14770ed5b11a5bb83353f0eee1c25a81af5abfe", size = 376161 }, { url = "https://files.pythonhosted.org/packages/5c/25/6511ed7bc826ddc2a4e879cf469621a1184719e97d63e7f723e95991ebd3/watchfiles-0.23.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f02a259fcbbb5fcfe7a0805b1097ead5ba7a043e318eef1db59f93067f0b49b", size = 369829 }, { url = "https://files.pythonhosted.org/packages/73/d3/00d561a66aa000251ed598f576e8bfd1c4102f9956fc06310e9b53258d3e/watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebaebb53b34690da0936c256c1cdb0914f24fb0e03da76d185806df9328abed", size = 443386 }, { url = "https://files.pythonhosted.org/packages/00/24/1c089457e39a0e6a142df8cb795a690b71f05c948bc60df4ec12359956b8/watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd257f98cff9c6cb39eee1a83c7c3183970d8a8d23e8cf4f47d9a21329285cee", size = 429214 }, ] [[package]] name = "webencodings" version = "0.5.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, ] [[package]] name = "websockets" version = "13.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0f/b0/e53bdd53d86447d211694f3cf66f163d077c5d68e6bcaa726bf64e88ae3a/websockets-13.0.tar.gz", hash = "sha256:b7bf950234a482b7461afdb2ec99eee3548ec4d53f418c7990bb79c620476602", size = 147622 } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/cc/8b3007ecf2d4e423251b2b3606c276e3fe85298982fc4fd0785a17b73ffb/websockets-13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad4fa707ff9e2ffee019e946257b5300a45137a58f41fbd9a4db8e684ab61528", size = 150919 }, { url = "https://files.pythonhosted.org/packages/1b/17/44553bd98608378b0d17432431a0f8f4633a6799826418f93ac036125000/websockets-13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6fd757f313c13c34dae9f126d3ba4cf97175859c719e57c6a614b781c86b617e", size = 148574 }, { url = "https://files.pythonhosted.org/packages/ee/38/ac6d8f50dc8ac81c29036d6d26aafae3fcbb43cfe88e8bc35a0e6af24525/websockets-13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cbac2eb7ce0fac755fb983c9247c4a60c4019bcde4c0e4d167aeb17520cc7ef1", size = 148830 }, { url = "https://files.pythonhosted.org/packages/6e/6e/b831097bb1843200d8636245f45fb8daaf4512329e8036f0f0b7ecd80f1c/websockets-13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4b83cf7354cbbc058e97b3e545dceb75b8d9cf17fd5a19db419c319ddbaaf7a", size = 157909 }, { url = "https://files.pythonhosted.org/packages/e0/d9/4ceef7fb370eca3c33d02966e972c08ef49073199ad02ef9f0f9f2f6f107/websockets-13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9202c0010c78fad1041e1c5285232b6508d3633f92825687549540a70e9e5901", size = 156920 }, { url = "https://files.pythonhosted.org/packages/b0/b1/8fb8bfad33f01d9085934c39bf5171c372edebed4c5440b28cb3270c0d56/websockets-13.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6566e79c8c7cbea75ec450f6e1828945fc5c9a4769ceb1c7b6e22470539712", size = 157233 }, { url = "https://files.pythonhosted.org/packages/ad/0a/1dbe4f15cb2fc6d2efea9e7c55651102dc52a10d34d322c0af8d332592be/websockets-13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e7fcad070dcd9ad37a09d89a4cbc2a5e3e45080b88977c0da87b3090f9f55ead", size = 157632 }, { url = "https://files.pythonhosted.org/packages/9f/97/4e7e98b694ef3db9a9776cbc4f72121cf408c47d7bc1ec582cfd9fa16de1/websockets-13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a8f7d65358a25172db00c69bcc7df834155ee24229f560d035758fd6613111a", size = 157049 }, { url = "https://files.pythonhosted.org/packages/4f/8c/50c0b58e99a7dc19282b706b99316327380065d8b2325aa0c7ae0479a98a/websockets-13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:63b702fb31e3f058f946ccdfa551f4d57a06f7729c369e8815eb18643099db37", size = 157001 }, { url = "https://files.pythonhosted.org/packages/a4/6f/cf602a9addf38396a2543bcd2c120651324169c0e88aa68a86a9b1e1f648/websockets-13.0-cp310-cp310-win32.whl", hash = "sha256:3a20cf14ba7b482c4a1924b5e061729afb89c890ca9ed44ac4127c6c5986e424", size = 151754 }, { url = "https://files.pythonhosted.org/packages/d0/bf/4a0bab951456884638c9bbf4a6b5314e755217632be8da831f8b3c6d3954/websockets-13.0-cp310-cp310-win_amd64.whl", hash = "sha256:587245f0704d0bb675f919898d7473e8827a6d578e5a122a21756ca44b811ec8", size = 152192 }, { url = "https://files.pythonhosted.org/packages/12/29/9fdf8a7f1ced2bac55d36e0b879991498c9858f1e524763434025948d254/websockets-13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06df8306c241c235075d2ae77367038e701e53bc8c1bb4f6644f4f53aa6dedd0", size = 150915 }, { url = "https://files.pythonhosted.org/packages/b9/27/723276e7fcb41a3e0859e347014e3e24637982a29222132746b98095ec02/websockets-13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85a1f92a02f0b8c1bf02699731a70a8a74402bb3f82bee36e7768b19a8ed9709", size = 148575 }, { url = "https://files.pythonhosted.org/packages/04/54/39b1f809e34f78ebb1dcb9cf57465db9705bbf59f30bd1b3b381272dff2b/websockets-13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9ed02c604349068d46d87ef4c2012c112c791f2bec08671903a6bb2bd9c06784", size = 148825 }, { url = "https://files.pythonhosted.org/packages/fe/df/0a8a90162c32ceb9f28415291c1d689310b503288d29169302964105a351/websockets-13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b89849171b590107f6724a7b0790736daead40926ddf47eadf998b4ff51d6414", size = 158482 }, { url = "https://files.pythonhosted.org/packages/20/05/227dbb1861cd1e2eb04ac79b136da841dbf6f196e4dc0bd1e67edb4ee69d/websockets-13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:939a16849d71203628157a5e4a495da63967c744e1e32018e9b9e2689aca64d4", size = 157478 }, { url = "https://files.pythonhosted.org/packages/fe/dd/3384d3eb26022703895d6ed65aec2d3af6976c3d9aed06200a322e7192cb/websockets-13.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad818cdac37c0ad4c58e51cb4964eae4f18b43c4a83cb37170b0d90c31bd80cf", size = 157855 }, { url = "https://files.pythonhosted.org/packages/93/ad/0320a24cd8309e1a257d43d762a732162f2956b769c1ad950b70d4d4d15a/websockets-13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cbfe82a07596a044de78bb7a62519e71690c5812c26c5f1d4b877e64e4f46309", size = 158160 }, { url = "https://files.pythonhosted.org/packages/d0/33/acc24e576228301d1dc23ce9d3f7d20f51dfe6c16d1b241e6ba4b2904d3e/websockets-13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e07e76c49f39c5b45cbd7362b94f001ae209a3ea4905ae9a09cfd53b3c76373d", size = 157598 }, { url = "https://files.pythonhosted.org/packages/83/47/01645a0ea041e32a9d8946a324845beb8daba2e2f00ee4fd2d04d3ceb598/websockets-13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:372f46a0096cfda23c88f7e42349a33f8375e10912f712e6b496d3a9a557290f", size = 157548 }, { url = "https://files.pythonhosted.org/packages/73/89/ea73bc41934eb3ea3f0c04fa7b16455ec5925b8b72aa5e016bd22df5feb5/websockets-13.0-cp311-cp311-win32.whl", hash = "sha256:376a43a4fd96725f13450d3d2e98f4f36c3525c562ab53d9a98dd2950dca9a8a", size = 151756 }, { url = "https://files.pythonhosted.org/packages/9b/b1/81f655476532b31c39814d55a1dc1e97ecedc5a1b4f9517ee665aec398f6/websockets-13.0-cp311-cp311-win_amd64.whl", hash = "sha256:2be1382a4daa61e2f3e2be3b3c86932a8db9d1f85297feb6e9df22f391f94452", size = 152200 }, { url = "https://files.pythonhosted.org/packages/ad/0a/baeea2931827e73ebe3d958fad9df74ec66d08341d0cf701ced0381adc91/websockets-13.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5407c34776b9b77bd89a5f95eb0a34aaf91889e3f911c63f13035220eb50107", size = 150928 }, { url = "https://files.pythonhosted.org/packages/6d/f7/306e2940829db34c5866e869eb5b1a08dd04d1c6d25c71327a028d124871/websockets-13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4782ec789f059f888c1e8fdf94383d0e64b531cffebbf26dd55afd53ab487ca4", size = 148585 }, { url = "https://files.pythonhosted.org/packages/2b/3c/183a4f79e0ce6be8733f824e0a48db3771a373a7206aef900bc1ae4c176e/websockets-13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8feb8e19ef65c9994e652c5b0324abd657bedd0abeb946fb4f5163012c1e730", size = 148821 }, { url = "https://files.pythonhosted.org/packages/03/32/37e1c9dd9aa1e7fa6fb3147d6992d61a20ba63ffee2adc88a392e1ae7376/websockets-13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f3d2e20c442b58dbac593cb1e02bc02d149a86056cc4126d977ad902472e3b", size = 158746 }, { url = "https://files.pythonhosted.org/packages/6c/da/0cace6358289c7de1ee02ed0d572dfe92e5cb97270bda60f04a4e49ac5c5/websockets-13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e39d393e0ab5b8bd01717cc26f2922026050188947ff54fe6a49dc489f7750b7", size = 157699 }, { url = "https://files.pythonhosted.org/packages/c7/ab/b763b0e8598c4251ec6e17d18f46cbced157772b991200fb0d32550844c5/websockets-13.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f661a4205741bdc88ac9c2b2ec003c72cee97e4acd156eb733662ff004ba429", size = 158124 }, { url = "https://files.pythonhosted.org/packages/d0/2d/40b8c3ba08792c2ecdb81613671a4b9bd33b83c50519b235e8eeb0ae21a0/websockets-13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:384129ad0490e06bab2b98c1da9b488acb35bb11e2464c728376c6f55f0d45f3", size = 158415 }, { url = "https://files.pythonhosted.org/packages/4c/5e/9a42db20f6c38d247a900bfb8633953df93d8873a99ed9432645a4d5e185/websockets-13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:df5c0eff91f61b8205a6c9f7b255ff390cdb77b61c7b41f79ca10afcbb22b6cb", size = 157795 }, { url = "https://files.pythonhosted.org/packages/87/52/7fb5f052eefaa5d2b42da06b314c2af0467fadbd7f360716a1a4d4f7ab67/websockets-13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02cc9bb1a887dac0e08bf657c5d00aa3fac0d03215d35a599130c2034ae6663a", size = 157791 }, { url = "https://files.pythonhosted.org/packages/9c/8b/4b7064d1a40fcb85f64bc051d8bdc8a9e388572eb5bec5cb85ffb2c43e01/websockets-13.0-cp312-cp312-win32.whl", hash = "sha256:d9726d2c9bd6aed8cb994d89b3910ca0079406edce3670886ec828a73e7bdd53", size = 151765 }, { url = "https://files.pythonhosted.org/packages/8b/a3/297207726b292e85b9a8ce24ef6ab16a056c457100e915a67b6928a58fa9/websockets-13.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0839f35322f7b038d8adcf679e2698c3a483688cc92e3bd15ee4fb06669e9a", size = 152202 }, { url = "https://files.pythonhosted.org/packages/03/b6/778678e1ff104df3a869dacb0bc845df34d74f2ff7451f99babccd212203/websockets-13.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:da7e501e59857e8e3e9d10586139dc196b80445a591451ca9998aafba1af5278", size = 150936 }, { url = "https://files.pythonhosted.org/packages/fa/25/28609b2555f11e4913a4021147b7a7c5117b5c41da5d26a604a91bae85b9/websockets-13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a00e1e587c655749afb5b135d8d3edcfe84ec6db864201e40a882e64168610b3", size = 148590 }, { url = "https://files.pythonhosted.org/packages/cb/1f/e06fb15fde90683fd98e6ca44fb54fe579161ce553d54fdbb578014ae1a7/websockets-13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a7fbf2a8fe7556a8f4e68cb3e736884af7bf93653e79f6219f17ebb75e97d8f0", size = 148826 }, { url = "https://files.pythonhosted.org/packages/22/00/9892eee346f44cd814c18888bc1a05880e3f8091e4eb999e6b34634cd278/websockets-13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ea9c9c7443a97ea4d84d3e4d42d0e8c4235834edae652993abcd2aff94affd7", size = 158717 }, { url = "https://files.pythonhosted.org/packages/dc/ad/2bdc3a5dd60b639e0f8e76ee4a57fda27abaf05f604708c61c6fd7f8ad88/websockets-13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35c2221b539b360203f3f9ad168e527bf16d903e385068ae842c186efb13d0ea", size = 157660 }, { url = "https://files.pythonhosted.org/packages/0c/14/5585de16939608b77a37f8b88e1bd1d430d95ec19d3a8c26ec42a91f2815/websockets-13.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:358d37c5c431dd050ffb06b4b075505aae3f4f795d7fff9794e5ed96ce99b998", size = 158104 }, { url = "https://files.pythonhosted.org/packages/7b/1e/6cd9063fd34fe7f649ed9a56d3c91e80dea95cf3ab3344203ee774d51a56/websockets-13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:038e7a0f1bfafc7bf52915ab3506b7a03d1e06381e9f60440c856e8918138151", size = 158463 }, { url = "https://files.pythonhosted.org/packages/d9/4d/c3282f8e54103f3d38b5e56851d00911dafd0c37c8d03a9ecc7a25f2a9da/websockets-13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fd038bc9e2c134847f1e0ce3191797fad110756e690c2fdd9702ed34e7a43abb", size = 157850 }, { url = "https://files.pythonhosted.org/packages/a1/08/af4f67b74cc6891ee1c34a77b47a3cb77081b824c3df92c1196980df9a4f/websockets-13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93b8c2008f372379fb6e5d2b3f7c9ec32f7b80316543fd3a5ace6610c5cde1b0", size = 157843 }, { url = "https://files.pythonhosted.org/packages/b4/b7/2c991e51d48b1b98847d0a0b608508a3b687f215a2390f99cf0ee7dd2777/websockets-13.0-cp313-cp313-win32.whl", hash = "sha256:851fd0afb3bc0b73f7c5b5858975d42769a5fdde5314f4ef2c106aec63100687", size = 151763 }, { url = "https://files.pythonhosted.org/packages/bc/0f/f06ed6485cf9cdea7d89c2f6e9d19f1be963ba5d26fb79760bfd17dd4aa5/websockets-13.0-cp313-cp313-win_amd64.whl", hash = "sha256:7d14901fdcf212804970c30ab9ee8f3f0212e620c7ea93079d6534863444fb4e", size = 152197 }, { url = "https://files.pythonhosted.org/packages/e0/1e/f7260a625b210f8242d0d858a3006a54b632843b796db39d9deb90068031/websockets-13.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:602cbd010d8c21c8475f1798b705bb18567eb189c533ab5ef568bc3033fdf417", size = 148603 }, { url = "https://files.pythonhosted.org/packages/b7/b6/3462a3a2688a62ee52aa1555fd47c61ffad0b12d0ed6ccdefd1ef8c3eef4/websockets-13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:bf8eb5dca4f484a60f5327b044e842e0d7f7cdbf02ea6dc4a4f811259f1f1f0b", size = 148837 }, { url = "https://files.pythonhosted.org/packages/ca/74/9f7c4669c5b5e154384eace44a5a3e24609c230f1428fea6b9af257a66c5/websockets-13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d795c1802d99a643bf689b277e8604c14b5af1bc0a31dade2cd7a678087212", size = 150200 }, { url = "https://files.pythonhosted.org/packages/c0/33/a307018b358f5cca141497e95f9af19c3e8be748219773afc4fcd4791123/websockets-13.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:788bc841d250beccff67a20a5a53a15657a60111ef9c0c0a97fbdd614fae0fe2", size = 149804 }, { url = "https://files.pythonhosted.org/packages/d9/62/c514d5b087f7b2cab8d97c80213d7ee8196b5954f8466886146c09d4fc46/websockets-13.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7334752052532c156d28b8eaf3558137e115c7871ea82adff69b6d94a7bee273", size = 149754 }, { url = "https://files.pythonhosted.org/packages/f4/d7/b11dd0a18b9bd876158c463ac1a6cab7b1b38093866fce22d03ab5462258/websockets-13.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7a1963302947332c3039e3f66209ec73b1626f8a0191649e0713c391e9f5b0d", size = 152235 }, { url = "https://files.pythonhosted.org/packages/b2/89/c0be9f09eea478659e9d936210ff03e6a2a3a8d4b8dfac6b1143ff646ded/websockets-13.0-py3-none-any.whl", hash = "sha256:dbbac01e80aee253d44c4f098ab3cc17c822518519e869b284cfbb8cd16cc9de", size = 142957 }, ] [[package]] name = "wheel" version = "0.44.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b7/a0/95e9e962c5fd9da11c1e28aa4c0d8210ab277b1ada951d2aee336b505813/wheel-0.44.0.tar.gz", hash = "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49", size = 100733 } wheels = [ { url = "https://files.pythonhosted.org/packages/1b/d1/9babe2ccaecff775992753d8686970b1e2755d21c8a63be73aba7a4e7d77/wheel-0.44.0-py3-none-any.whl", hash = "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f", size = 67059 }, ] cappa-0.32.2/docs/docs/__init__.py0000644000000000000000000000000013615410400013561 0ustar00cappa-0.32.2/docs/source/annotation.md0000644000000000000000000002600713615410400014533 0ustar00# Annotations Annotations perform two functions: - Inferring specific `Arg`-level options, depending on the specific annotation, most of which control parsing behavior for that argument. - Mapping the raw parsed arguments into the specified output types. In any case where the "supported" set of behaviors given by the inference system does not produce the obvious result, feel free to submit a bug report. The intent is that it should feel natural for all supportable cases. For any cases where it does not make sense to build in specific behavior for a type (for example, third-party library types), you can instead use the correct annotation while providing the `parse`, or other [arguments](./arg.md). ```{note} Use of `Annotated` throughout the examples and docs is fairly pervasive. When using versions of python below 3.9, `Annotated` is not included in the `typing` module. Instead, you can install [typing_extensions](https://pypi.org/project/typing_extensions/), which backports typing objects to earlier versions of python. ``` ```{note} Similarly, use of `|` for `Union`s is universally preferred in the docs. When using versions of python below 3.10, you may need to use `from __future__ import annotations` at the top of your file in order for the annotations to be ignored by the python runtime. ``` ## Positional vs Option Below, any reference to "positional" implies, a field which does not set `short=...` or `long=...`. This includes any fields with no explicit `Arg` instance at all. - `foo: str` - `foo: Annotated[str, Arg()]` Whereas an "option" is a field which **does** set `short=...` or `long=...`. - `foo: Annotated[str, Arg(short=True)]` - `foo: Annotated[str, Arg(long=True)]` - `foo: Annotated[str, Arg(short="-f", long=True)]` This is an important distinction to note, because the inference system makes difference decisions, depending on whether a given field is positional or not. ## `Arg` Inference Note that in all cases below, any individual inferred value can be overridden in any case where the default inference is not what you're looking for! ### Bool In most other cases, an un-annotated field like `foo: str` is assumed to be a positional argument. By contrast a `foo: bool` is assumed to be an optional flag. This is because bools corresponding to flags represent the much more common case. By default a `foo: bool` implies `foo: Annotated[bool, Arg(long=True, num_args=0, action=ArgAction.store_true, required=False)]`. - If you also want to enable a "short" version of the flag, you can set `Arg(short=True)`. - If you include a `--no-` long name variant like `Arg(long=['--foo', '--no-foo'])`, the `--no-foo` variant will be inferred as `ArgAction.store_false`. - If a vanilla bool annotation (without an inverted variant) is combined with a default value of `True`, then the action is inverted to `ArgAction.store_false`. This is because `foo: bool = True` could never result in a `False` value, if supplying `--foo` at the command line also stored `True`. See [Arg.short](cappa.Arg.short) and [Arg.long](cappa.Arg.long) for more details on customization of the specific flag names. ```{note} If (for whatever reason) you require a positional argument interpreted as a bool, you can explicitly set `Arg(long=None, action=ArgAction.set, num_args=1)` ``` ### Sequence Types Annotations like `list[...]`, `set[...]`, `tuple[...]`, etc are what we call "sequence types". - In the case of a positional argument, a sequence type annotation implies `Arg(..., num_args=length_bound)`. For "bounded" length sequences (i.e. tuples like `tuple[int]`, `tuple[int, int]`, `tuple[int, int, int]`, etc), `length_bound` corresponds to the indicated length of the sequence. For "unbounded" length sequences (i.e. list, set, and unbounded tuples: `tuple[int, ...]`), `length_bound=-1`, i.e. the argument will consume an unbounded number of positional arguments (`prog foo bar baz ...`). ```python @dataclass class Prog: foo: list[str] prog = cappa.parse(Prog, argv=['foo', 'bar', 'baz']) assert prog == Prog(foo=['foo', 'bar', 'baz']) ``` - In the case of option arguments, a sequence type annotation implies `Arg(..., action=ArgAction.append)`. That is, it allows multiple uses of the option to accumulate the values into a sequence. ```python @dataclass class Prog: foo: Annotated[list[str], Arg(short=True)] prog = cappa.parse(Prog, argv=['-f', 'foo', '-f', 'bar', '-f', 'baz']) assert prog == Prog(foo=['foo', 'bar', 'baz']) ``` ```{note} You can specify `Annotated[list[str], Arg(short=True, num_args=n)]` where `n` would yield a sequence (`-1` or > 1). In such a case, `action` would instead be inferred as `ArgAction.set`. ``` See [Argument](./arg.md) for more details on the difference between `ArgAction.append` and `num_args=-1`. ### `| None` or `Optional[...]` Either form of `Optional`-type annotation implies `Arg(required=False, default=None)`. ### Unions Unions don't currently apply any specific inference behavior, but they do come with some restrictions. - Unioning "scalar" and "sequence" types will raise an `ValueError`. For example, `int | list[str]`. `list[str]` wants to produce a list, whereas `int` wants to produce a single value, and it's unclear how the parser ought to react to such an annotation. - Unioning types which would produce different inferred `num_args` values will raise a `ValueError`. For example, `foo: tuple[str, str] | list[str]`. `int` produces `num_args=2` and `list[str]` will produce `num_args=-1`, which are incompatible. ### Literals and Enums Any form of explicit "choice", like `Literal["one", "two"]`, `Literal["one"] | Literal["two"]`, `Enum` implies `Arg(choices=[...])`. In case of literals, it is obviously the list of all unioned literals. In case of `Enum` subclasses, all variants are given as the set of choices. `choices` represents a parser-level evaluation of the CLI input value versus the available choices, resulting in a parse error in the event the value does not match. ### Subcommands Unions among subcommand options `Subcommands[One | Two | Three]` are how subcommand options are expressed. See [Command](./command.md) docs for more details. The order of the unioned subcommand options does not have any effect because each subcommand has a unambiguous name. ## Mapping Inference Mapping inference is a sort of subset of `Arg`-settings inference, in that it effectively uses the annotated type to set `Arg(parse=...)` in a way that maps the raw values coming out of a successful CLI parse into the annotated types. As such you can again, opt out of the "Mapping inference" entirely, by supplying your own `parse` function. ```{note} Mapping inference is built up out of component functions defined in `cappa.parse`, such as `parse_list`, which know how to translate `list[int]` and a source list of raw parser strings into a list of ints. These functions can/could be utilized in your own custom `parse` functions. ``` ### Basic Scalar Types Any "basic" data type (like `int`, `float`, `str`) are supported and coerced to directly, by calling their constructor. This also applies even to "complex" ones who's constructor accepts a string and returns the type (such as `pathlib.Path`, or `decimal.Decimal`). Note this also applies to `Enum`s, who's raw values by map-time should be guaranteed to be compatible with the Enum's variants. ### `date`/`datetime`/`time` Both types are directly supported through inference, by calling the `fromisoformat` method on each type. ```{note} The set of supported input formats are [python version specific](https://docs.python.org/3/library/datetime.html#datetime.date.fromisoformat). ``` To support more general input formats, you should instead supply a function to `Arg(parse=...)` which accepts a string and returns the given type. ### Union (and Optional) Unions are handled by recursively processing the set of unioned inner types using whatever logic applies to that type. The primary important point here, is that the order of the set of unioned types **can** matter, but generally shouldnt. The unioned types are sorted and processed in descending order from: - "other" types/None - float - int - bool/str This specific order order is given to prioritize types which are less likely to succeed parsing when given incorrect input. `bool` and `str` are notable in that will always "succeed" (in that given any input they will produce an output) and not fail. As such it **may** not make much sense to union those types together without an explicit `parse`. Therefore, when unioning "other"-type types, it **may** be important to consider the order of the unioned types, if parsing for one or the other type would "succeed" incorrectly. In such cases, `parse` may be appropriate. ```{note} It's possible for more complex value mapping to happen automatically, if the input types have distinct "success criteria". This amounts to something akin to a discriminated union. For example, take the annotation `tuple[Literal["foo"], str] | tuple[Literal["bar"], int]`. Supplying `foo bar` as the input value should produce `("foo", "bar")`, whereas `bar 4` should produce `("bar", 4)`. ``` ### List/Tuple/Set `list[...]`, `tuple[...]`, `set[...]` all will coerce the parsed sequences of values into their corresponding type. The inner type will be mapped for each item in the sequence. ### `typing.BinaryIO`/`typing.TextIO` [BinaryIO](typing.BinaryIO) and [TextIO](typing.TextIO) are used to produce an open file handle to the file path given by the CLI input for that argument. This can be thought of as equivalent to `open("foo.py")`, given some `cli --foo foo.py`, which is roughly equivalent to the [FileType](https://docs.python.org/3/library/argparse.html#argparse.FileType) feature from `argparse`. ```python @dataclass class Args: foo: typing.BinaryIO args = cappa.parse(Args) with args.foo: print(args.foo.read()) ``` ```{note} The supported types do not map to concrete, instantiatable types. This is important, because neither of these types would otherwise be valid type annotations in the context of cappa's other inference rules. It's also important, because there are no concrete types which correspond to the underlying types returned by `open()`, which would allow the distinction between binary and text content. ``` #### Controlling `open(...)` options like `mode="w"` An un-`Annotated` IO type translates to `open()` with no additional arguments, with the exception that `BinaryIO` infers `mode='b'`. In order to directly customize arguments like `mode`, `buffering`, `encoding`, and `errors`, a [FileMode](cappa.FileMode) must be annotated on the input argument. ```python import dataclasses import typing import cappa @dataclasses.dataclass class Args: foo: typing.Annotated[typing.BinaryIO, cappa.FileMode(mode='wb', encoding='utf-8')] bar: typing.Annotated[typing.BinaryIO, cappa.Arg(short=True), cappa.FileMode(mode='wb')] ``` As shown, [FileMode](cappa.FileMode) is annotated much like a [Arg](cappa.Arg), and can be used alongside one depending on the details of the argument in question. cappa-0.32.2/docs/source/api.md0000644000000000000000000000071513615410400013130 0ustar00# API ```{eval-rst} .. autoapimodule:: cappa :members: parse, invoke, invoke_async, collect, command, Command, Subcommand, Alias, Dep, Arg, ArgAction, Exit, Env, Completion, Output, FileMode, unpack_arguments, Group, Prompt, Confirm, ValueFrom, Default, State, Self, default_parse ``` ```{eval-rst} .. autoapimodule:: cappa.testing :members: CommandRunner, RunnerArgs ``` ```{eval-rst} .. autoapimodule:: cappa.parser :members: Value, RawOption ``` cappa-0.32.2/docs/source/arg.md0000644000000000000000000010062513615410400013131 0ustar00# Arguments Similar to commands, use of cappa-specific argument annotations are "only" required when the default behavior is not what you want. A completely un-annotated dataclass like ```python @dataclass class Example: name: str ``` is perfectly valid. However, a CLI of any real complexity **will** need to use the `Arg` annotation. By default: - Each class member is inferred to be a positional argument - Its name is the dash-case version of the class member name - Its value is coerced to the annotated type - Its help text is inferred from various natural sources (See [Help Text Inference](./help.md) for details) However, by using `Arg`, with syntax like `name: Annotated[str, cappa.Arg(...)]`, you can customize the behavior of the field. For each field of customization, there is a default inferred value, based on the particulars of the dataclass and/or other configured fields. Below we document when configuring a given field might **imply** some other field by default (e.g. `count=True` **implies** `num_args=0`), however in all cases, an explicitly provided value will take precedence over the default inference. ```{note} See the [annotation docs](./annotation.md) for more details on how annotations can be used and are interpreted to handle different kinds of CLI input. ``` (arg-value-name)= ## `Arg.value_name` This field controls the name of the field in helptext/errors. By default it matches the dataclass' field name. ```python @dataclass class Command: foo: Annotated[int, cappa.Arg(value_name="bar")] happy: Annotated[int, cappa.Arg(value_name="sad", long=True)] = 1 ``` Would yield help text like: ``` Options [--happy SAD] Arguments BAR ``` (arg-short)= ## `Arg.short` By default, unannotated dataclass fields are assumed to be positional arguments. An `Arg.short` argument is a non-positional argument which can be selected by a single `-` followed by the short name. The value can be supplied in a few different ways: * `foo: Annotated[int, Arg(short=True)]`: Rendered as `-f`. If there are more than one field starting with "f" with `short=True`, at least one will need to be disambiguated. * `foo: Annotated[int, Arg(short='-b')]`: Rendered as-is * `foo: Annotated[int, Arg(short='b')]`: Rendered as `-b`. * `foo: Annotated[int, Arg(short=['-b', '-f'])]`: Rendered as **both/either** `-b` and `-f`. * `foo: Annotated[int, Arg(short='-b/-f')]`: Rendered as **both/either** `-b` and `-f`. (arg-long)= ## `Arg.long` By default, unannotated dataclass fields are assumed to be positional arguments. An `Arg.long` argument is a non-positional argument which can be selected by a single `--` followed by the long name. ```{note} `snake_case` field names will be converted to `dash-case` by default. ``` The value can be supplied in a few different ways: * `foo: Annotated[int, Arg(long=True)]`: Rendered as `--foo`. field starting with "f" with `long=True`, at least one will need to be disambiguated. * `foo: Annotated[int, Arg(long='--bar')]`: Rendered as-is * `foo: Annotated[int, Arg(long='bar')]`: Rendered as `--bar`. * `foo: Annotated[int, Arg(long=['--bar', '-foo'])]`: Rendered as **both/either** `--bar` and `--foo`. * `foo: Annotated[int, Arg(long='--bar/--foo')]`: Rendered as **both/either** `--bar` and `--foo`. (arg-count)= ## `Arg.count` Counts instances of the argument in question. This implies `num_args=0` and `action=ArgAction.count` by default. The canonical example of this is counting verbosity flags. ```python @dataclass class Example: verbose: Annotated[int, cappa.Arg(short="-v", count=True), ``` (arg-help)= ## `Arg.help` Controls the per-argument help text. See [Help](./help.md) for more details, but in short help text is inferred by default from a few different sources, including docstrings and "attribute docstrings". (arg-action)= ## `Arg.action` Obliquely referenced through other `Arg` options like `count`, every `Arg` has a corresponding "action". The action is automatically inferred, most of the time, based on other options (i.e. `count`), the annotated type (i.e. `bool` -> `ArgAction.store_true/ArgAction.store_false`, `list` -> `ArgAction.append`), etc. However the inferred action can always be directly set, in order to override the default inferred behavior. ### Custom Actions ```{note} This feature is currently experimental, in particular because the parser state available to either backend's callable is radically different. However, for an action callable which accepts no arguments, the behavior is unlikely to change. ``` In addition to one of the literal `ArgAction` variants, the provided action can be given as an arbitrary callable. The callable will be called as the parser "action" in response to parsing that argument. ```{note} Custom actions may interfere with general inference features, depending on what you're doing (given that you're taking over the parser's duty of determining how the code ought to handle the argument in question). As such, you may need to specify options like `num_args`, where you wouldn't have otherwise needed to. ``` Similarly to the [invoke](./invoke.md) system, you can use the type system to automatically inject objects of supported types from the parse context into the function in question. The return value of the function will be used as the result of parsing that particular argument. The set of available objects to inject include: - [Command](cappa.Command): The command currently being parsed (a relevant piece of context when using subcommands) - [Arg](cappa.Arg): The argument being parsed. - [Value](cappa.parser.Value): The raw input value parsed in the context of the argument. Depending on other settings, this may be a list (when num_args > 1), or a raw value otherwise. - [RawOption](cappa.parser.RawOption): In the event the value in question corresponds to an option value, the representation of that option from the original input. The above set of objects is of potentially limited value. More parser state will likely be exposed through this interface in the future. If you think some specific bit of parser state is missing and could be useful to you, please raise an issue! For example: ```python def example(): return 'foo!' def example(arg: Arg): return 'foo!' def example2(value: Value): return value.value[1:] @dataclass class Example: ex1: Annotated[str, cappa.Arg(action=example) ex2: Annotated[str, cappa.Arg(action=example2) ex3: Annotated[str, cappa.Arg(action=example3) ``` ```{eval-rst} .. autoapiclass:: cappa.ArgAction :noindex: ``` ```{warning} `ArgAction.append` and `num_args=-1` can be potentially confused, in that they can both produce list/sequence types as outputs. They can be used in conjunction or separately, but they are not the same! Given some example option `--foo`: `ArgAction` generally, affects the way the parser maps input CLI values to a given field overall. As such `ArgAction.append` causes the field-level value to be a sequence, and for multiple instances of `--foo` to accumulate into a list. e.x. `--foo 1 --foo 2` -> `[1, 2]`. `num_args` instead, affects the number of values that a single instance of `--foo` will consume before stopping. e.x. `--foo 1 2` -> `[1, 2]`. From an input perspective, the above examples show how they present differently at the CLI interface, requiring different inputs to successfully parse. From an output perspective, the table below shows how their parsed output will end up looking when mapped to real values. | action | num_args | result | | ------ | -------- | ------ | | set | 1 | 1 | | set | -1 | [1] | | append | 1 | [1] | | append | -1 | [[1]] | ``` ## `Arg.num_args` `num_args` controls the number of arguments that the parser will consume in order to fulfill a specific field. `num_args=1` is the default behavior, meaning only one argument/value will be consumed for that field. This yields a scalar-type value. `num_args=3` would therefore mean that exactly 3 values would be required, resulting in a sequence-type output, rather than a scalar one. `num_args=-1` can be used to indicate that 0 or more (i.e. unbounded number of) arguments will be consumed, similarly resulting in a sequence-type output (even in the zero case). ```{note} Generally `num_args` will be automatically inferred by your type annotation. For example, `tuple[int, int, int]` implies `num_args=3`. However, an explicitly provided value is always preferred to the inferred value. See [annotations](./annotation.md) for more details. ``` ## `Arg.default` Controls the default argument value at the CLI level. Generally, you can avoid direct use of cappa's default by simply using the source class' native default mechanism. (i.e. `foo: int = 0`, `foo: int = field(default=0)`, or `foo: list[str] = field(default_factory=list)` for dataclasses). However it **can** be convenient to use cappa's default because it does not affect whether the underlying class definition makes that field required in the class' constructor. ```{note} The `default` value is not **typically** parsed by the given `parse` function. That is to say, if no value is selected at the CLI and the default value is used instead; it will not be coerced into the annotated type automatically. (`Env`, `Prompt`, and `Confirm` are exceptions to this rule, explained in their sections below). The reason for this is twofold: 1. Typechecking should already emit an error when the given default is of the incorrect type. 2. This interacts poorly with types who's constructor does not accept an instance of the given type as an input argument. For example, `foo: Foo = Foo('')` would infer `parse=Foo` and attempt to pass `Foo(Foo(''))` during parsing. ``` Additionally there are a number of natively integrated objects that can be used as default to create more complex behaviors given a **missing** CLI argument. The below objects will not be evaluated unless the user did not supply a value for the argument it's attached to. - [cappa.Default](cappa.Default) - [cappa.Env](cappa.Env) - [cappa.Prompt](cappa.Prompt)/[cappa.Confirm](cappa.Confirm) - [cappa.ValueFrom](cappa.ValueFrom) ### `Default` All other types of default are ultimately shorthand forms of `Default`. The `Default` construct allows for specifying an ordered chain of default fallback options, with an optional static fallback item at the end. For example: - `foo: int = 4` is the same as `default=Default(default=4)`. This unconditionally defaults to 4. - `foo: Annotated[int, Arg(default=Env("FOO"))] = 4` is the same as `Arg(default=Default(Env("FOO"), default=4)`. This attempts to read the environment variable `FOO`, and falls back to 4 if the env var is unset. - `foo: Annotated[int, Arg(default=Env("FOO") | Prompt("Gimme"))]` is the same as `Arg(default=Default(Env("FOO"), Prompt("Gimme"))`. This attempts to read the environment variable `FOO`, followed by a user prompt if the env var is unset. As shown above, any combination of defaultable values can be used as fallbacks of one another by using the `|` operator to chain them together. As noted above, a value produced by `Default.default` **does not** invoke the `Arg.parse` parser. This is for similar reasons as to native dataclass defaults. The programmer is supplying the default value which should not **need** to be parsed. ```{note} `Default`'s fallback behavior will fallback to lower priority handlers in the "default sequence" when that item's default returns `cappa.Empty`. All the native (`Env`, `Prompt`, `Confirm`) handlers will do this natively, when they fail to produce a value. A `ValueFrom` (described below) accepts a user-provided function, so it **may** need to be aware of this and return `cappa.Empty`, depending on the desired behavior. ``` ### `Env` [cappa.Env](cappa.Env) performs environment variable lookups in an attempt to provide a value to the class field. `Arg(..., default=Env("FOO", default='default value'))`, cappa will attempt to look up the environment variable `FOO` for the default value, if there was no supplied value at the CLI level. As noted above, a value produced by `Env` **does** invoke the `Arg.parse` parser. This is because `Env` values will always be returned as a string, very similarly to a normal pre-parse CLI value. ### `Prompt`/`Confirm` [cappa.Prompt](cappa.Prompt) and [cappa.Confirm](cappa.Confirm) can be used to ask for user input to fulfill the value. ```{note} `rich.prompt.Prompt` and `rich.prompt.Confirm` can also be used transparently for the same purpose. ``` ```python import cappa class Example: value: Annotated[int, cappa.Arg(default=cappa.Prompt("A number value"))] is_ok: Annotated[bool, cappa.Arg(default=cappa.Confirm("You sure?"))] ``` As noted above, a value produced by `Prompt`/`Confirm` **does** invoke the `Arg.parse` parser. This is because these values will always be returned as a string, very similarly to a normal pre-parse CLI value. ### `ValueFrom` [cappa.ValueFrom](cappa.ValueFrom) is a means for calling an arbitrary function at mapping time, to allow for dynamic default values. ```{note} A dataclass `field(default_factory=list)` is internally the same thing as `default=ValueFrom(list)`! ``` ```python from pathlib import Path import cappa def load_default(key): config = json.loads(Path("config.json").read_text()) return config[key] class Example: value: Annotated[int, cappa.Arg(default=cappa.ValueFrom(load_default, key='value'))] ``` This construct is able to be automatically supplied with [cappa.State](State), in the event shared parse state is required to evaluate a field's default. As noted above, a value produced by `ValueFrom` **does not** invoke the `Arg.parse` parser. This is because the called function is programmer-supplied and can/should just return the correct end value. ```{note} If there are scenarios where `ValueFrom` **should** fail to provide a value and fall back to the class-level, or `Default`-level default value, it should return `cappa.Empty` in order to indicate to cappa that it shouldn't accept the returned value as the **actual** value to used when constructing the resulting class instance. ``` ## `Arg.show_default` Defaults to `True` (e.g. `DefaultFormatter(format='{default}', show=True)`). This field controls **both**: * Whether to render the argument's default for helptext * The formatting for the default value itself. `show_default` can be provided as any of: * `bool`: Implies the `DefaultFormatter.show` value (e.g. `DefaultFormatter(show=)`). * `str`: Implies the `DefaultFormatter.format` value (e.g. `DefaultFormatter(format=, show=True)`). * `DefaultFormatter`: Is taken as-is. `DefaultFormatter.show` enables/disables the display of the given `Arg.default`. While the literal default value is formatted through `DefaultFormatter.format`. ```{note} The `str.format` context is provided as named `default` format arg. That is to say, the literal default value can be templated into the string through the named `{default}` syntax. Normal format specifiers can be used to control formatting of the value, for example `{default:.2f}` to limit the precision of a default float value. ``` ```{note} The `format` value **can** simply be a static string, to avoid taking the literal default value. An reasonable example of this might be `source: Annotated[BinaryIO, Arg(show_default='')] = '-'`. ``` (arg-group)= ## `Arg.group`: Groups (and Mutual Exclusion) `Arg(group=...)` can be used to customize the way arguments/options are grouped together and interpreted. The `group` argument can be any of: - `str`: A string that indicates a custom group. This only affects help text output, by placing any `Arg`s with that group string under a common heading. ```python class Example: arg1: Annotated[str, Arg(group='Special')] arg2: Annotated[str, Arg(group='Special')] ``` - `tuple[int, str]`: This additionally provides an `int` which is considered when determining the `order` of group output. ```python class Example: arg1: Annotated[str, Arg(group=(4, 'Special'))] arg2: Annotated[str, Arg(group=(4, 'Special'))] ``` - [Group](Group): Instances of `Group` are the normalized form of the above two shorthand options (which are ultimately coerced into `Group`s). [Group](Group) additionally has an `exclusive: bool` option, which can be used to indicate that options within a group are mutually exclusive to one other. ```{note} Both `order` and `exclusive` options on `Group` are expected (and validated) to be identical across all options sharing that group string. ``` ```python class Example: arg1: Annotated[str, Arg(group=Group('Special', exclusive=True))] arg2: Annotated[str, Arg(group=Group('Special', exclusive=True))] ``` In this case, `example --arg1 foo --arg2` would generate an error like: ``` Argument 'arg1' is not allowed with argument 'arg2' ``` (exclusive-group-syntax)= ### Dedicated Mutual Exclusion Syntax A potentially common use of mutually exclusive arguments would be two distinct CLI-level arguments, which ultimately are different ways of configuring a specific code-level field. In such cases, two (or more) `cappa.Arg`s can be annotated on a single class field. This **implies** a mutually exclusive group automatically, defaulting to the name of the field ("Verbose" in this case) as the group name. For example: ```python @dataclass class Example: verbose: Annotated[ int, cappa.Arg(short="-v", action=cappa.ArgAction.count), cappa.Arg(long="--verbosity"), ] = 0 ``` - `example -vvv` would yield: `verbose=3` - `example --verbosity 4` would yield: `verbose=4` - `example -vvv --verbosity 4` would yield the error: `Argument '--verbosity' is not allowed with argument '-v'` ```{note} An explicit `group=` can still be used in concert with the above syntax to control the `order` and name of the resultant group. ``` (arg-parse)= ## `Arg.parse` `Arg.parse` can be used to provide **specific** instruction to cappa as to how to handle the raw value given from the CLI parser backend. In _general_, this argument shouldn't need to be specified because the annotated type will generally _imply_ how that particular value ought to be parsed, especially for built in python types. However, there will inevitably be cases where the type itself is not enough to infer the specific parsing required. Take for example: ```python from datetime import date @dataclass class Example: iso_date: date american_date: Annotated[date, cappa.Arg(parse=lambda date_str: date.strptime('%d/%m/%y'))] ``` Cappa's default date parsing assumes an input isoformat string. However you might instead want a specific alternate parsing behavior; and `parse=` is how that is achieved. Further, this is likely more useful for parsing any custom classes which dont have simple, single-string-input constructor arguments. ```{note} Note cappa itself contains a number of component `parse_*` functions inside the `parse` module, which can be used in combination with your own custom `parse` functions. ``` ### `default_parse`/`Arg.parse_inference A function [cappa.default_parse](cappa.default_parse) is exposed and **can** be directly referenced in parse function sequences. However by default the `default_parse` function is applied automatically at the end of the user-provided parse function/sequence. With that said, if the default behavior is undesirable, one can set `Arg(parse_inference=False)` to **only** execute the user provided parser function(s). (parsing-json)= ### Parsing JSON Another example of a potentially useful parsing concept could be to parse json string input. For example: ```python import json from dataclasses import dataclass from typing import Annotated import cappa @dataclass class Example: todo: Annotated[dict[str, int], cappa.Arg(parse=json.loads)] todo = cappa.parse(Todo) print(todo) ``` Natively (at present), cappa doesn't have any specific `dict` type inference because it's ambiguous what CLI input shape that ought to map to. However, by combining that with a dedicated `parse=json.loads` annotation, `example.py '{"foo": "bar"}'` now yields `Example({'foo': 'bar'})`. (composing-parsers)= ### Composing Multiple Parsers `Arg.parse` accepts **either** a single parser function, like above, or a sequence of parsers which will be called...in sequence. The return value of earlier parsers in the chain will be routed into the input of later parsers. ```python import json from dataclasses import dataclass from typing import Annotated import cappa def get_key(value: dict[str, str]) -> str: return value.get("todo", "") @dataclass class Example: todo: Annotated[str, cappa.Arg(parse=[json.loads, get_key])] todo = cappa.parse(Todo) print(todo) ``` (unpack-arguments)= ### `unpack_arguments`: Invoking constructors with `*` and `**` operators In some cases, you might want to construct objects that have required keyword arguments (pydantic models!) or otherwise who's constructors do not accept the single argument that would typically be provided to single CLI fields' parser. This is the use case for [cappa.unpack_arguments](cappa.unpack_arguments). Cappa will unpack the arguments based on the incoming type of data (e.g. sequence vs mapping). ```{note} (Arg.destructure)[#argument-destructuring] is a **similar** feature, in that it allows one to compose complex types into the CLI structure; but it in essentially opposite uses. `unpack_arguments` produces a complex type from a single CLI argument, whereas a "destructured" argument composes together multiple CLI arguments into one object without requiring a separate command. ``` #### *args Unpacking Sequence unpacking (`*`) can be used to provide values to constructors that accept multiple arguments positionally. For example: ```python from dataclasses import dataclass from typing import Annotated from cappa import unpack_arguments, Arg, parse @dataclass class Point: x: int y: int @dataclass class CLI: point: Annotated[Point, Arg(num_args=2, parse=unpack_arguments)] print(parse(CLI)) ``` ```bash $ cli.py 1, 2 CLI(Point(1, 2)) ``` **Without** `unpack_arguments`, the `Point(...)` constructor call would have been handed `[1, 2]`. Essentially the above is roughly equivalent to ```python class Point: ... @classmethod def from_list(cls, value: list[int]): return cls(*value) ... class CLI: point: Annotated[Point, Arg(num_args=2, parse=Point.from_list)] ``` #### **kwargs Unpacking Mapping unpacking (`**`) can be used for constructors which accept multiple arguments by keyword. For example, **Pydantic** classes' constructors **require** keyword-only arguments. This poses a challenge to a single CLI argument representing that constructor value. One way around this could be accepting a JSON string, parsing the string as JSON, and then unpacking the result into the constructor, effectively combining [Parsing JSON](#parsing-json) and [Composing Multiple Parsers](#composing-parsers). ```python from pydantic import BaseModel from typing import Annotated from cappa import unpack_arguments, Arg, parse class Point(BaseModel: x: int y: int @dataclass class CLI: point: Annotated[Point, Arg(parse=[json.loads, unpack_arguments])] print(parse(CLI)) ``` ```bash $ cli.py '{"x": 1, "y": 2}' CLI(Point(1, 2)) ``` Similarly, you could imagine swapping `json.loads` for some alternate parser which accepted/parsed `x=1,y=2` or other input formats. ### Parse Dependencies Parse functions can are provided the same dependency injection system given by `invoke` and [actions](#arg-action). Thus, by accepting an additional argument to the parser annotated with a supported type annotation, they'll be provided that runtime value automatically. This enables user-defined parsers that have the same amount of information that built-in parsers have when making decisions, namely the type information. Currently supported injectable values include: * [TypeView](https://github.com/litestar-org/type-lens/blob/main/type_lens/type_view.py) ```python from cappa.type_view import TypeView def parse(value, type_view: TypeView): value = json.loads(value) if type_view.is_mapping: return value raise ValueError("Requires a JSON mapping, string input!") class CLI: arg: Annotated[int, Arg(parse=parse)] ``` ```{note} More injectable dependencies **could** be supported. In particular the `Arg` instance comes to mind, it's just not immediately obvious what that would be. File an issue if you have a usecase! ``` ## `Arg.choices` This can be used to limit the set of valid inputs to one of a few literal values, for example, `Arg(choices=['a', 'b', 'c']`. Both `Literal`s (and literal unions) and `Enum`s automatically infer `choices`. ```{note} It **may** be preferable to use typing inference to **imply** choices as opposed to providing explicit choices depending on what you're doing. In scenarios with simple types/mappings, it wont make any difference (other than yielding more or less precise type information to a type checker). For example `foo: Literal[1, 2]` or `foo: list[Literal[1, 2]]` can be instead provided as `choices=` and it will yield exactly the same behavior. However, the type inference is sufficiently comprehensive that it is **possible** to yield more complex types like `list[tuple[str, Literal["1m", "1s"]]]`, where a simple `choices=` option **cannot** precisely target the required values. In such cases providing `choices=` will likely yield a runtime error, whereas using the inferred type validation will correctly parse the inputs. ``` ## `Arg.completion` This is an optional function which, if provided will be called during CLI (typically TAB) completion events in the shell. You can supply a function which accepts the partial input and returns an optional list of completions for that input. ```{note} `Arg.choices` has a default completion implementation. ``` ```python def complete_file(raw: str) -> list[cappa.Completion]: return [filename for filename in os.listdir() if filename.startswith(raw)] @dataclass class Foo: file: Annotated[str, cappa.Arg(completion=complete_file) ``` ```{note} Filename completion is actually natively supported by the shells, so this example is somewhat unnecessary to **actually** implement yourself. ``` ## `Arg.has_value` `Arg(has_value=True/False)` can be used to explicitly control whether the argument in question corresponds to a destination-type attribute as a value. For example the `Arg` that produces the `--help` option has a dedicated action which produces help text, and as such has a `has_value=False`. While there may not be much point in manually setting this attribute to `True` (because it will default to `True` in most cases), you **could** conceivably manually combine `has_value=False` and a custom [action](#action), to avoid cappa trying to map your `Arg` back to a specific attribute. (argument-destructuring)= ## `Arg.destructured` **Generally** a single class/type corresponds to a command, and that type's attributes correspond to the arguments/options for that command. The [exclusive group syntax](#exclusive-group-syntax) is one counter example, where a single class attribute maps to more than one CLI argument. "Destructured" arguments are essentially the inverse, in that they allow multiple attributes (and thus CLI arguments) to be mapped back to a single command's class attribute. ```python from __future__ import annotations from dataclasses import dataclass from typing import Annotated from cappa import Arg, Destructured @dataclass class Args: sub_object: Destructured[SubObject] @dataclass class SubObject: arg1: Annotated[str, Arg(long=True)] arg2: Annotated[int, Arg(long=True)] ``` This essentially fans out the `--arg1=foo --arg2=2` CLI arguments up into the parent `Args` command, while ultimately mapping the resultant values back into the expected output structure of: `Args(sub_object=SubObject(arg1='foo', arg2=2))`. This concept has a couple of practical uses: - Code/argument reuse: In the above example `SubObject` can now be shared between multiple subcommands to provide the same set of arguments in different places **without** requiring subclassing. - Logical grouping/organization: This allows grouping of logically related fields in the **python** code without affecting how those arguments are represented in the CLI shape. ```{note} Currently `Arg.destructure()` only works with **singular concrete** type annotations. That is, in the above example `Annotated[SubObject, Arg.destructured()]`; whereas it will raise a `ValueError` if given `SubObject | None` or other more exotic annotations. Principally, `Annotated[SubObject | None, Arg.destructured()]` **could** make sense to imply that all child options are therefore optional, or that if any child attributes are missing, then that implies `sub_object=None` at the top level. However both of these are mechanically much more complex than the feature, as it exists today. Additionally, this feature only works with the native backend. This **probably** has a workable solution for argparse, so file an issue if this affects you! ``` ```{note} (unpack_arguments)[#unpack-arguments] is a **similar** feature, in that it allows one to compose complex types into the CLI structure; but it in essentially opposite uses. `unpack_arguments` produces a complex type from a single CLI argument, whereas a "destructured" argument composes together multiple CLI arguments into one object without requiring a separate command. ``` ## `Arg.propagate` Argument propagation is a way of making higher-level arguments available during the parsing of child subcommands. When an argument is marked as `Arg(propagate=True)`, access to that argument will be made available in all **child** subcommands, while still recording the value itself to the object on which it was defined. ```python from __future__ import annotations from dataclasses import dataclass from typing import Annotated import cappa @dataclass class Main: file: Annotated[str, cappa.Arg(long=True, propagate=True)] subcommand: cappa.Subcommands[One | Two | None] = None @dataclass class One: ... @dataclass class Two: subcommand: cappa.Subcommands[Nested | None] = None @dataclass class Nested: ... print(cappa.parse(Main)) ``` Given the above example, all of the following would be valid, and produce `Main(file='config.txt', ...)`: - `main.py --file config.txt` - `main.py one --file config.txt` - `main.py two --file config.txt` - `main.py two nested --file config.txt` If defined on a top-level command object (like above), that argument will effectively be available globally within the CLI, again while actually propagating the value back to the place at which it was defined. However if the propagated argument is **not** defined at the top-level, it will not propagate "upwards" to parent commands; only downward to child subcommands. ```{note} `Arg.propagate` is not currently enabled/allowed for positional arguments (file an issue if this is a problem for you!) largely because it's not clear that the feature makes any sense except on (particularly optional) options. `Arg.propagate` is not implemented in the `argparse` backend. ``` ### Propagated Arg Help By default propagated arguments are added to child subcommand's help as though the argument was defined like any other argument. If you want propagated arguments categorically separated from normal arguments, you can assign them a distinct [group](#arg-group), which will cause them to be displayed separately. For example: ```python group = cappa.Group(name="Global", section=1) @dataclass class Command: other1: Annotated[int, cappa.Arg(long=True)] = 1 other2: Annotated[int, cappa.Arg(long=True)] = 1 foo: Annotated[int, cappa.Arg(long=True, propagate=True, group=group)] = 1 bar: Annotated[str, cappa.Arg(long=True, propagate=True, group=group)] = 1 ``` Would yield: ``` Options [--other1 OTHER1] (Default: 1) [--other2 OTHER2] (Default: 1) Global [--foo FOO] (Default: 1) [--bar BAR] (Default: 1) ``` Note, this is no different from use of `Arg.group` in any other context, except in that the argument only exists at the declaration point, so any grouping configuration will also propagate down into the way child commands render those arguments as well. ## `Arg.hidden` Controls whether an argument is displayed in help text. ## `Arg.required` Controls whether an argument is required. ## `Arg.deprecated` When truthy, will emit a deprecation message upon use of an argument. There is a default message generated, but supplying a string will yield **that** as the deprecation message. ## API ```{eval-rst} .. autoapiclass:: cappa.Arg :noindex: ``` cappa-0.32.2/docs/source/asyncio.md0000644000000000000000000000251613615410400014025 0ustar00# Asyncio ```{note} Asyncio support was added in v0.12.0. ``` Cappa supports asyncio through the [cappa.invoke_async](cappa.invoke_async) function, which has an identical interface to [cappa.invoke](cappa.invoke), **except** in that it is an async function. Given that it is an async function, it must either be called from an async function itself, or be invoked through `asyncio.run` or equivalent functions. By using `invoke_async`, you gain the ability to reference async functions as invoke targets and as explicit dependencies. ```python import asyncio from dataclasses import dataclass import cappa def config() -> dict: return {'db_password': 'password'} async def engine(config: Annotated[dict, cappa.Dep(config)]): return await create_engine(config) async def handler(engine: Annotated[int, cappa.Dep(engine)]): ... @cappa.command(invoke=handler) @dataclass class Command: foo: str command = asyncio.run(cappa.invoke_async(Command)) ``` ```{note} Note the `asyncio.run` call. `invoke_async` is an async function, and as such must be called from an async context. If called from a synchronous context, it must be explicitly scheduled through something like `asyncio.run`. Since cappa defers the actual async scheduling to the caller, it should support all asyncio runtimes, including asyncio, trio, curio, etc. ``` cappa-0.32.2/docs/source/backends.md0000644000000000000000000001042613615410400014131 0ustar00# Parser Backends ```{note} If you're looking for custom parsing of individual arguments, you probably want [Arg.parse](./arg.md#arg-parse) or [Arg.action](./arg.md#arg-action). This document is concerned with the actual e2e CLI parsing process. ``` Cappa is designed in two parts: - The "frontend", which is the vast majority of the public API. All the available objects and functions, the annotation inference engine, the invoke/dependency system, etc. - The parser "backend", which takes the object graph produced by your CLI definition and parses the raw CLI (into a form the "frontend" can understand). The backend also controls the help-text formatter There are currently two backend options: - A custom parser maintained inside cappa itself - An Argparse backend A large majority of tests are run with both parsers to ensure general compatibility. Any difference in backend parse **behavior** should be considered a bug. However, error **messaging** will not match exactly across backends. You select a backend by providing the `backend` argument to [parse](cappa.parse) or [invoke](cappa.invoke). ```python import cappa cappa.parse(..., backend=cappa.argparse.backend) cappa.invoke(..., backend=cappa.argparse.backend) cappa.parse(..., backend=cappa.backend) cappa.invoke(..., backend=cappa.backend) ``` ## Cappa backend This is the default backend. The main "headline" feature you get when using the cappa backend is: Automatic [completion](./completion.md) support. However, generally it's going to be easier to support arbitrary features with the native parser than with an external backend. It is roughly 1/3 the size in LOC, given that much of the featureset and flexibility of argparse are unused and a source of contention with cappa's design (The whole native parser is ~500 LOC, whereas just the argparse adapter is ~350 LOC). ## Argparse backend Cappa was originally written against the argparse backend, a testament to the flexibility of its API, that it's even possible. The main "problem" with the argparse backend is that cappa replaces much of the higher level features it provides at a higher level, outside the parser itself. A majority of the backend's source is working around specific argparse API decisions, and redefining argparse's objects with necessary modifications. This backend will be maintained for the foreseeable future, if for no other reason than it's a useful benchmark for parser compatibility. Some potential reasons you want want to use the argparse backend: - Argparse is also obviously fairly battle tested, and therefore theoretically less likely to contain bugs that might exist in the native cappa backend. - Argparse may have pre-existing libraries designed to extend it. (While we cant guarantee an arbitrary argparse extension will function correctly with cappa, but to the extent possible, it's a goal that they should be supported if possible. ## Custom Backends `cappa.invoke`/`cappa.parse` both accept a `backend=` argument which is used to select between the existing two backends shipped with Cappa. Technically, you could use this `backend` argument to author an entirely new backend to a different argument parser, like `click` for example (although a click backend was attempted at some point and later abandoned due to unforeseen complexities). This would allow you to retain all of cappa's pre-parsing and inference capabilities, as well as the post-processing, mapping, and invoke/dependency injection infrastructure. With that said, it's much more likely that it could be useful to make use of the backend argument to **wrap** one of the existing backends, and mutate the resultant output structure of the backend before it's passed further downstream. This is somewhat of an interesting usecase, and again if you find yourself making use of this in order to work around potential upstream deficiencies in cappa, please bring it up in an issue/discussion! ```{note} The backend **interface** is currently not set in stone. Before relying on the specific details of the input/output shape of a backend, please bring it up in an issue/discussion in hopes that customizing the backend may be made to not be necessary! Further, it's likely that the backend interface is more formalized at some point in the future; at which point it may break those assumptions. ``` cappa-0.32.2/docs/source/changelog.md0000644000000000000000000000012313615410400014277 0ustar00```{include} ../../CHANGELOG.md :relative-docs: docs/source/ :relative-images: ``` cappa-0.32.2/docs/source/class_compatibility.md0000644000000000000000000000307513615410400016417 0ustar00# Dataclasses/Pydantic/Attrs All of the documentation uses `dataclasses` specifically, because it is built into the standard library since python 3.7. With that said, any dataclass-like class description pattern should be able to be supported with relatively little effort. Today Cappa ships with adapters for: - [dataclasses](https://docs.python.org/3/library/dataclasses.html), - [Pydantic](https://pydantic-docs.helpmanual.io/) (v1/v2) - [attrs](https://www.attrs.org) - [msgspec](https://jcristharif.com/msgspec/) Additionally the `default` and/or `default_factory` options defined by each of the above libraries is used to infer CLI defaults. ## PEP681 You can opt to `@cappa.command(...)` with or without the double-decorator. ```python @cappa.command(...) @dataclass ``` `@cappa.command(...)` works without the `@dataclass` decorator at runtime, because it detects whether the class is one of the supported types (dataclasses, pydantic, attrs), and adds `@dataclass` to the declared class automatically, if one is not detected. So long as you use a a [PEP681 compliant](https://peps.python.org/pep-0681/) type checker (e.g. pyright, Mypy>=1.2). ## Metadata Finally, `dataclasses` and `attrs` both allow a `metadata`. You can optionally utilize that metadata field to supply `Arg` and `Subcommand` instances, as an alternative to using `Annotated`. The following snippets of code are equivalent: ```python arg: Annotated[str, cappa.Arg(short=True, help='help!')] # vs arg: str = dataclasses.field(metadata={'cappa': cappa.Arg(short=True, help='help!')}) # same with attrs ``` cappa-0.32.2/docs/source/command.md0000644000000000000000000001135613615410400014000 0ustar00# Commands A command can be as simple as a dataclass-like object, with no additional annotations. Supported object-types include: ````{collapse} dataclasses :open: ```python from cappa import parse from dataclasses import dataclass @dataclass class Dataclass: name: str value = parse(Dataclass) assert isinstance(value, Dataclass) ``` ```` ````{collapse} Pydantic models ```python from cappa import parse from pydantic import BaseModel class PydanticModel(BaseModel): name: str value = parse(PydanticModel) assert isinstance(value, PydanticModel) ``` ```` ````{collapse} Pydantic dataclasses ```python from cappa import parse from pydantic.dataclasses import dataclass as pydantic_dataclass @pydantic_dataclass class PydanticDataclass: name: str value = parse(PydanticDataclass) assert isinstance(value, PydanticDataclass) ``` ```` ````{collapse} Attrs classes ```python from cappa import parse from attr import define @define class AttrsClass: name: str value = parse(AttrsClass) assert isinstance(value, AttrsClass) ``` ```` ````{collapse} cattrs classes ```python from cappa import parse from attr import define @define class CAttrsClass: name: str value = parse(CAttrsClass) assert isinstance(value, CAttrsClass) ``` ```` An undecorated dataclass-class will receive all the default [cappa.command](cappa.command) behavior. In order to customize or change the default behavior, you would begin by wrapping the class in the `@cappa.command` decorator like so: ```python @cappa.command() @dataclass class Dataclass: name: str ``` ```{note} The wrapped class is directly returned from the decorator. So unlike `click`, the resultant object can be directly used in the same way that you'd have been able to do sans decorator. ``` ```{note} All arguments to [cappa.command](cappa.command) directly translate to [cappa.Command](cappa.Command) attributes. When annotating an object with `@command()`, you are essentially pre-configuring the generated [cappa.Command](cappa.Command) for that particular command. ``` ## Subcommands See [Subcommands](./subcommand.md) for details. In short, any supported dataclass-like class that can be a top-level command is also compatible with being a subcommand. The only difference is that subcommands are attached to parent commands in a tree by way of having the parent define which subcommands are available options. ```python from cappa import Subcommands @dataclass class Command: subcommand: Subcommands[SubcmdOne | SubcmdTwo] ``` ## `@command(name=...)` / `Command.name` By default, the name of the command is inferred from the name of the containing class, and converted to dash-case. For example `Example` would become `example`, and `SomeLongName` would become `some-long-name`. The `name` field can be used to override this behavior. ## `@command(help=...)` / `Command.help` See [Help Text Inference](./help.md) for details. Essentially this replaces any inferred (from docstring) **short** help text for the command. ## `@command(description=...)` / `Command.description` See [Help Text Inference](./help.md) for details. Essentially this replaces any inferred (from docstring) **extended** help text for the command. ## `@command(invoke=...)` / `Command.invoke` See [Invoke](./invoke.md) documentation for more details. Essentially this invokes a function upon a command/subcommand being selected during parsing, corresponds to use of the `cappa.invoke` API rather than `cappa.parse`. This API is roughly comparable to how `click` maps subcommands to functions. ## `@command(hidden=...)` / `Command.hidden` Excludes the command in question from parent helptext generation. As such this option is only relevant to subcommands. ## `@command(default_short=...)` / `Command.default_short` By default unannotated arguments are considered to be ordered positional aruguments. This option controls whether un-annotated arguments default to implying [Arg(short=True)](arg-short). For example: ```python @command(default_short=True) @dataclass class Foo: bar: int # which is then called like: # `foo -b 4` ``` ## `@command(default_long=...)` / `Command.default_long` By default unannotated arguments are considered to be ordered positional aruguments. This option controls whether un-annotated arguments default to implying [Arg(long=True)](arg-long). For example: ```python @command(default_long=True) @dataclass class Foo: bar: int # which is then called like: # `foo --bar 4` ``` ## `@command(deprecated=...)` / `Command.deprecated` This generates a warning if the command in question is used at runtime. If `True` is supplied, a default deprecation message is generated. Alternatively it accepts the string message that should be emitted. ## API ```{eval-rst} .. autoapiclass:: cappa.Command :noindex: ``` cappa-0.32.2/docs/source/comparison.md0000644000000000000000000002306413615410400014533 0ustar00# Why A fair question. The incumbents, with click/typer and argparse have been readily serving python for a long time, and will certainly continue to do so. Having used the "derive API" from Rust's [Clap](https://docs.rs/clap/latest/clap/_derive/index.html) library, it's just really really nice to use, because the API is so natural. It became obvious that python could obviously support a similar API and it was surprising that one didn't already exist. Hopefully with `cappa`, you get the flexibility to utilize either the command-forward design given by `click`/`typer` or the manual handling given by `argparse` (and even `clap`). ## Why not click? I've used click for years, it's certainly a good library. There are some aspects of its design that make it really frustrating to use beyond small projects with a single entrypoint. - Many of the issues stem from the fact that click wraps the called function, and changes its behavior. Calling the resultant function invokes the CLI. You want to call that function in two places? you need to use `click.invoke`. You want to do this in tests? You need to deal with its `ClickRunner` that accepts args as a list of string, and output in terms of stdout and stderr. In terms of tests, this is really large drop in UX relative to a plain function (with args) you can call. You want to avoid click-isms in either case? You need to (basically) duplicate your function. One wrapped in click decorators, which just defers to a similar function that just accepts the arguments. - `Cappa` inverts the relationship to invoked functions. You do not wrap the functions, so they end up just being plain functions you can call in any scenario. And in the case of the decorated classes, the resultant class is directly returned. It should act exactly like the dataclass/pydantic model you defined. Even parsing or invoking the CLI through `Cappa`'s mechanisms to parse the argv, in tests for example, the result is **much** more similar to just executing the function in the first place. - Click imperitively accumulates the CLI's shape based on where/how you combine groups and commands. Intuiting what resultant CLI looks like from the series of click commands strung together is not obvious. - `Cappa` centrally declares the CLI shape based on types. For all of the reasons pydantic models can be easily used to describe a potentially deeply nested schema, with `Cappa` it's very easy to look at the model and determine the total set of arguments and subcommands relative to it. - Because click tightly couples the called function with click itself, a large enough CLI will inevitably slow down CLI startup because you're forced to transitively import your whole codebase in order to describe the state of the CLI itself. - `Cappa` allows you to define the CLI in a way where you **could** define the CLI shape/calls in complete isolation with no (lazy) imports to the rest of your code. - The Click "Context" appears to be its only mechanism for providing non-argument dependencies to commands/subcommands. And `pass_context` (and the mutation of the context) is a very blunt tool, that secretly couples the implementations of different commands together. - `Cappa` uses the same dependency resolution mechanisms it uses for arguments to provide the result of arbitrary functions to your commands (Reminiscent of FastAPI's `Depends`). For example, some subcommand require loading config, some commands require a database connection (or whatever) which itself depends on that config. What would be a, relatively, more difficult task using the click context (or manually littering the loading of these things inside the bodies of the commands) uses the same API as the rest of `Cappa`. - With `click`, it's impossible(? or at least not clear how) to perform `argparse`-like (or Clap) ability to just parse the structure and return the parsed output shape. - `Cappa` provides a separate `parse` function and `invoke` function. You can choose either style, or both. ## Why not [Typer](https://typer.tiangolo.com/) Typer has a lot of good ideas, inspired by Hug before it. While the actual inspiration for Cappa is [Clap](https://docs.rs/clap/latest/clap/_derive/index.html), the annotation results for arguments end up looking a lot like typer's args! Unfortunately, Typer is **basically** Click (with type-inferred arguments), and has all of the same drawbacks. If `tiangolo` hadn't based typer on click, it's not crazy to imagine that `cappa` look something like what he might have come up with. In particular the `Dep` feature of `cappa` looks and feels a lot like the `Depends` feature of FastAPI. Further, cappa provides more comprehensive type inference (e.g. translating complex types like `foo: tuple[int, str] | None` into their logical CLI argument shapes. As the input shapes become more complex, typer's inference begins to lose out (at time of writing). ## Why not argparse? The imperative style of argparse can just be tedious to read/write, and hard to intuit what the shape of the resultant CLI will look like. `Cappa` currently uses argparse as the underlying argument parsing library, because it is the most straightforward, maintained, python library for building this sort of declarative system upon. With argparse you lose the useful aspect of click's functional composability. Argparse's API is relatively complex (especially when dealing with subcommands), and performing the dispatch of subcommands to functions with scoped arguments is extremely complicated. The sweet spot for Argparse's API is single-level commands with a bunch of options. Beyond that, it becomes difficult to understand. ## Others/New Contenders I was recently made aware of a couple of other options that seem similarly inspired, namely: ### [Tyro](https://brentyi.github.io/tyro/) At a glance, this seems most similar to Cappa of any alternatives. If we take a look at [their own comparisons](https://brentyi.github.io/tyro/goals_and_alternatives/), Cappa seems to check all of the boxes Tyro does. It certainly **feels** different when authoring an identical CLI with one or the other, but they **do** seem to mostly overlap in terms of functionality. Cappa's `invoke`/dependency system is probably the most standout difference maker, if the overall differences in their API are not important to you. It **seems** to be somewhat tailored towards [datascience(?) usecases by translating object shapes into cli arguments.](https://brentyi.github.io/tyro/examples/02_nesting/01_nesting/) By contrast Cappa is tries to be able to describe a specific/arbitrary CLI shape **using** objects, essentially inverting the focal point. Tyro is built on top of argparse (and we have an argparse backend, wins for argparse!), although Cappa's backend was specifically built because the argparse backend is fairly limiting in terms of ability to hook into the parser for things like completions, and error reporting. ### [Clipstick](https://github.com/sander76/clipstick) Explicitly avoids use of Annotated, which ultimately limits its flexibly. Without additional configuration data, there's only so much it can support. Otherwise it seems very similarly inspired and thus looks rather nice. ### Pydantic-based options I hope it should be uncontroversial to say that either option are a clear subset of at least the CLI shapes you can produce with click/argparse. And cappa is meant to reside in that category. These options probably do their job (of producing a pydantic model given CLI arguments) consisely and well! But they dont **seem** to be targeted at producing CLIs of arbitrary shape like click/argparse/typer. And you can use pydantic models with cappa, so I think you can arrive at many of the same benefits as you might find in either option, namely flexible input value parsing and loading defaults from environment variables, but you can do so for any CLI shape you might want to produce. #### [Pydantic-CLI](https://github.com/mpkocher/pydantic-cli) Doesn't support positional arguments, only supports inferring "simple" types. While it does support subcommands, it seems to do so in a way that's single-level only? Again, it seems much more oriented at translating CLI arguments into an instance of a pydantic model, **rather than** describing an arbitrary CLI shape while utilizing types/models. #### [Pydantic-Settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/#the-basics) Again, you can tell that (I think) from the way nested models are translated into a flattened list of options, that it's very focused on instantiating a pydantic model from a given set of CLI arguments, rather than describing an arbitrarily shaped CLI. It does appear to support subcommands and positional arguments, but it's (imo) done sort of weirdly; which seems to be the result of bolting the CLI parsing onto pydantic rather than the other way around. There appear to be a lot of maybe convenient automatic parsing features that translate arguments from various json or other input formats into the model fields. If you **dont** want all these different input modes, it's not immediately obvious how to turn them all off and arrive at more "normal" CLI input parsing. Cappa provides more comprehensive type inference (e.g. translating complex types like `foo: tuple[int, str] | None` into their logical CLI argument shapes. Pydantic-settings seems to prefer accepting arbitrary inputs as JSON automatically and using pydantic's native parsing capabilities to attempt to interpret the values, rather than translating them into "traditional" CLI behaviors. cappa-0.32.2/docs/source/completion.md0000644000000000000000000000366113615410400014533 0ustar00# Completion ```{note} This feature is only supported when using the [cappa.backend](./backends.md). ``` Completion support is enabled by default. You can disable completion by sending `completion=False` into your [parse](cappa.parse) or [invoke](cappa.invoke) call. By default, this adds a `--completion [generate/complete]` argument to the resultant CLI. You can customize the name/visibility/help/etc of that argument by supplying an [Arg](cappa.Arg) to the same argument (`completion=Arg(...)`). ## Enabling Completions ```{note} Currently supported shells for completions include: - Zsh - Bash - Fish Submissions for additional shell support is very welcome. ``` `--completion generate` outputs `$SHELL`-specific source to power the completions for the CLI A user of your CLI needs to redirect to the appropriate default location for completions for that shell. Alternatively they can write it to an arbitrary location and source the resultant file in their shell-rc file, although this is not recommended! ```bash mycli --completion generate > ~/.mycli.sh ``` ## Completable Values Currently cappa supports completions for - `--option` long/short names - Subcommand names - Argument values By default this does local file completion for most argument types. If the argument defines logical "choices" (Enum, Union of Literals), those choices will be completed instead. ## Custom Completions Any [Arg](cappa.Arg) can supply a `completion=function` argument to produce arbitrary completions for that field. `function` (in this case) should be a function which accepts a string (the partial string to complete), and returns a list of [Completion](cappa.Completion) ```python import cappa def names(partial: str) -> cappa.Completion: names = ['Lucy', 'Bob', 'Ron'] return [ cappa.Completion(name, help='Available Names') for name in names if name.startswith(partial) ] cappa.Arg(..., completion=names) ``` cappa-0.32.2/docs/source/conf.py0000644000000000000000000000235213615410400013333 0ustar00from sphinx_pyproject import SphinxConfig config = SphinxConfig("../../pyproject.toml") project = "Cappa" copyright = "2023, Dan Cardin" author = config.author release = config.version extensions = [ "autoapi.extension", "myst_parser", "sphinx.ext.autodoc", "sphinx.ext.autosectionlabel", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx_copybutton", "sphinx_togglebutton", "sphinx_toolbox.collapse", ] templates_path = ["_templates"] exclude_patterns = [] html_theme = "furo" html_theme_options = { "navigation_with_keys": True, } html_static_path = ["_static"] myst_heading_anchors = 4 myst_enable_extensions = [ "amsmath", "colon_fence", "deflist", "dollarmath", "fieldlist", "html_admonition", "html_image", "linkify", "replacements", "smartquotes", "strikethrough", "substitution", "tasklist", ] intersphinx_mapping = { "python": ("https://docs.python.org/3", None), } autoapi_type = "python" autoapi_dirs = ["../../src/cappa"] autoapi_generate_api_docs = False autodoc_typehints = "description" autodoc_typehints_format = "fully-qualified" suppress_warnings = ["autoapi.python_import_resolution"] autosectionlabel_prefix_document = True cappa-0.32.2/docs/source/functions_and_methods.md0000644000000000000000000000304213615410400016730 0ustar00# Function/Method Based Commands ## Methods Methods can be used as a shorthand to define (typically terminal) subcommands. With methods, the enclosing class corresponds to the parent object CLI arguments, exactly like normal class based definition. Unlike with free functions, (explicitly annotated) methods are able to act as subcommands, who's arguments (similarly to free functions) act as the arguments for the subcommand. While there's nothing **stopping** one from using the `parse` API with a CLI defined with methods, it's really only obviously useful in the context of the `invoke` API. ```python from __future__ import annotations from dataclasses import dataclass import cappa @cappa.command @dataclass class Example: arg: int @cappa.command def add(self, other: int) -> int: """Add two numbers.""" return self.arg + some_dep @cappa.command(help="Subtract two numbers") def subtract(self, other: int) -> int: return self.arg - other cappa.invoke(Example) ``` ```{note} At present any enclosing class which defines method subcommands **must** itself be decorated with `@cappa.command`. This is **not** true for "traditionally" defined class-based CLIs. **Currently** with this style, the resultant class is (secretly) a subclass of the provided one, which adds a hidden `__cappa_subcommand__` attribute used to store the subcommand's CLI argument data. This should 100000% not be relied upon to exist, and may break at any time. Ideally the need for this internal hack will be resolved in the future. ``` cappa-0.32.2/docs/source/help.md0000644000000000000000000001472613615410400013316 0ustar00# Help ## Help Inference Cappa tries to infer help text from a variety of sources, preferring them in descending order of priority: - An explicit `help=` argument - A PEP-727 `Doc` annotation - The class "attribute docstring" - The class docstring argument description If none of the above sources produce help text, no description will be rendered. ### Explicit `help=` All of `Command`, `Subcommand`, and `Arg` accept a `help` argument, which will take priority over any other existent form of help text inference. ```python from typing import Annotated import cappa @cappa.command(help='Command help') class Command: arg: Annotated[str, Arg(help='Arg help')] arg: Annotated[Sub, cappa.Subcommand(help='Subcommand help')] ``` ### PEP-727 `Doc` annotation [PEP-727](https://peps.python.org/pep-0727/) proposes adding a `typing.Doc` object, in an attempt to standardize the location tooling must handle in order to find documentation. **If** accepted, this is currently targeted to land in python 3.13. As of `typing_extensions` version 4.8.0, there exists a `typing_extensions.Doc` object, which preemptively will fall back to the `typing` definition if defined. If PEP-727 is ultimately rejected, this variant may or may not be abruptly removed, it would entirely depend upon `typing_extensions`'s reaction to the rejection. When found, a `Doc` annotation will be used to infer help text, unless explicitly overridden by a `help=` argument. ```python from typing import Annotated from typing_extensions import Doc import cappa @cappa.command class Command: arg: Annotated[str, Doc('Arg help')] ``` ### Class "attribute docstring" Parsing An "attribute docstring" is a common convention whereby a declarative class attribute is "annotated" similarly to a class docstring. For example: ```python from dataclasses import dataclass @dataclass class Command: arg: int """This arg is an int.""" ``` ```{note} Attribute docstrings are not a first-class concept in python today, although there is a [rejected PEP](https://peps.python.org/pep-0224/) associated with the idea. As such, `ast` traversal is required to obtain it, which implies a requirement that `ast.getsource` be able to function on the given source class. For most typical user-written situations this should not be an issue, but it's worth noting the relative complexity involved with this option. ``` ### Class Docstring Parsing ```{note} Docstring parsing is provided by the `docstring-parser` dependency. You can include this dependency through cappa with the `docstring` extra (`cappa[docstring]`). ``` In the event other sources of help text are not found, the command class' docstring will be parsed, supporting either Google or Numpy styles of docstring formatting. - The short and long docstring descriptions are inferred as extended command-level help text - Argument descriptions are inferred from the arguments list within the docstring ```python from typing import Annotated import cappa @cappa.command class Command: arg: Annotated[str, Doc('Arg help')] ``` ```python class Example: """Example CLI. With some long description. Arguments: foo: The number of foos """ foo: int ``` would produce something like the following help text: ``` Usage: example.py [-h] Example CLI. With some long description. Positional Arguments: foo The number of foos ``` ## Argument Help Formatting By default, help text is composed from a few sources: - The actual help text (as described above) - The default argument value (if exists) - The set of available "choices" (if exists) (for `Enum`, `Literal`, and `choices=[...]`) This can be controlled through the use of the `help_formatter` argument to the root `cappa.parse`, `cappa.invoke`, etc functions. Additionally it can be set on a per-command/subcommand level by making use of the `@cappa.command(help_formatter=...)` kwarg. ```{eval-rst} .. autoapiclass:: cappa.HelpFormatter ``` ### Customize "default" help representation By default, the default value will be rendered into the help text as `(Default: {default})`. You can customize this, by altering the default `help_formatter`: ```python from cappa import parse, HelpFormatter class Command: ... parse(Command, help_formatter=HelpFormatter(default_format="(Default `{default}`)")) ``` ### Customizing help formatter "sources" The default `arg_format` is: ```python arg_format: ArgFormat = ( Markdown("{help}"), Markdown("{choices}"), Markdown("{default}", style="dim italic"), ) ``` This means each individual argument's help text will be comprised of 3 `Markdown` interpreted sections concatenated together. Each section may be either `Text` or `Markdown` and accepts any styling customization allowed by that primitive. For example, some argument: `foo: Annotated[Literal["one", "two"], Arg(help="Foo.")] = "two"`. The rendered help text will be will (by default) look like: `Foo. Valid options: one, two. (Default: two)` `arg_format` may be any of: * A `Text`/`Markdown` * A string: This will be coerced to a rich `Text` of the provided string * A callable: Of shape `Callable[[Arg], str | None]` or stricter, returning the formatted help text An example of this might look like: ```python from cappa import parse, HelpFormatter, Arg class Command: foo: Annotated[str, Arg(help="Help text.", deprecated=True)] = "foo" def deprecated(arg: Arg) -> str | None: if arg.deprecated: return "Deprecrated" return None parse(Command, help_formatter=HelpFormatter(arg_format=("{default}", "{help}", deprecated)) ``` Resulting in something like `(Default: foo) Help text. Deprecrated`. * A sequence: Of any of the above. Sequences will be joined together with an empty space. Any arg_format segment who's ultimate formatting results in an empty string or `None` will be omitted. The **purpose** of allowing sequences of individual segments is to ensure consistent formatting when individual format options are not used. For example `"{help} {default}"` would otherwise yield `Foo. ` or ` (Default: foo)` (i.e. trailing or leading spaces). As such, where formatting may be variable (like with `default`), they should be split into different segments. The following format string identifiers are included in the format context for each segment: * `{help}`: The `Arg.help` value * `{default}`: The `Arg.default` will first be rendered with `default_format`. * `{choices}`: `The `Arg.choices` value * `{arg}`: The `Arg` itself. cappa-0.32.2/docs/source/index.md0000644000000000000000000000207013615410400013462 0ustar00```{include} ../../README.md :relative-docs: docs/source/ :relative-images: ``` ```{toctree} :hidden: API ``` ```{toctree} :caption: Concepts :hidden: Commands Args Subcommands Invoke Annotations Parse Vs Invoke ``` ```{toctree} :caption: Topics :hidden: vs Click / Typer / Argparse Dataclasses/Pydantic/Attrs Rich Integration Completions Console Output and Exiting Parser Backends (Cappa/Argparse) Testing Function/Method Based Commands Examples (vs click/typer/argparse) ``` ```{toctree} :caption: Advanced Topics :hidden: Argument Validation Help/Help Inference Asyncio Sphinx/Docutils Directive Shared State Manual Construction ``` ```{toctree} :caption: Useful Links :hidden: GitHub Repository PyPI Changelog ``` cappa-0.32.2/docs/source/invoke.md0000644000000000000000000003010513615410400013646 0ustar00# Invoke If you're coming from `click` or `typer`, then `invoke` may likely be how you'll want to describe your commands. ```{note} See [Invoke Dependencies](./invoke.md#invoke-dependencies) for more information about providing context/dependencies to your invoked functions. ``` You have a few options when choosing how to define the function that will be invoked for your command. 1. you can simply make your dataclass callable: ```python @dataclass class Example: foo: int def __call__(self): print(self.foo) ``` This has the benefit of simplicity, and avoids the need to decorate your class. The potential drawback, is that it couples the behavior to the class itself. However being forced to define function at the same location as your CLI definition may or may not be a drawback at all, for your usecase. 2. You can utilize the explicit `invoke=function` argument: ```python def function(example: Example): print(example.foo) @cappa.command(invoke=function) class Example: foo: int ``` This **does** require you to decorate your class (which you may or may not have already needed to do). However, now `function` can be defined anywhere else in your codebase, decoupling the CLI definition from the implementation of the command itself. 3. You can use a module-reference string to target a callable: ```python # example.py @cappa.command(invoke='foo.bar.function') class Example: foo: int # foo/bar.py def function(example: Example): print(example.foo) ``` The primary benefit of using string references, especially in large applications is import speed. This style enables you to decouple the definition of a CLI's shape from the code that is called for a given CLI command. By using this style, the import to e.g. `foo.bar.function` is deferred until that specific command is selected. With this pattern it is possible to avoid **all** imports to your project's code at runtime to ensure optimal time-to-helptext and (depending on your project's structure) potentially avoid importing large swathes of your project on a given CLI invocation. ```{note} This is equally true in other libraries, and was a significant consideration during cappa's creation. For example with `click`, a naive CLI implementation will result in **all** code that the CLI depends on being imported, even to display help text. The equivalent solution for a click application is to inline all imports into the function handlers themselves. This can certainly work, but then can also cause issues when paired with features like Enum-based `choices` or `type` arguments. ``` 4. In simple cases, you can forgo classes entirely in favor of functions. ```python import cappa def cli(foo: int): return foo + 1 result = cappa.invoke(cli) ``` Such a CLI is exactly equivalent to a CLI defined as a dataclass with the function's arguments as the dataclass's fields, but with an unnameable class. There are various downsides to using functions. Naturally, you lose all ability to reference source classes as dependencies. Subcommands cannot be naturally defined as functions (since there is no type with which to reference the subcommand). As such, functions can only be used for certain kinds of CLI interfaces. However, they **can** reduce the ceremony required to define a given CLI command. ```{note} When dealing with nested subcommands, only the "invoke" function for the **actually** selected command/subcommand will be invoked. ``` Then, in order to opt into the invoke behavior, you need to call [cappa.invoke](cappa.invoke), rather than [cappa.parse](cappa.parse). ```{eval-rst} .. autoapifunction:: cappa.invoke :noindex: ``` ## Implicit invoke imports The `invoke` argument can be a direct reference to a function, as evidenced in the single-file documentation examples. However it can also be a string. When given as a string, it implies a fully qualified module reference to a function. That is, `src/package/module/submodule.py` with function `foo` in it, would be given be the string `package.module.submodule.foo`. ```python @cappa.command(invoke='package.module.submodule.foo') class Example: ... ``` This can help to solve an issue that can be somewhat endemic to `click` applications. Namely, that the act of describing the CLI shape forces the programmer to transitively import all of the code that the CLI might ever reference in any of its subcommands, regardless of whether they're actually called in the actual command selected. The usual approach, in click, is to inline imports into the click handler, thus achieving the same thing. With cappa, it's much the same thing, except for the inversion of control. The dynamic import is performed inside the `invoke` call, which is mostly just a net reduction in boilerplate. ## Invoke Dependencies `cappa.invoke` wouldn't be of much value if all it did was call argument-less functions without any context. Thus, there is a system of "dependencies" that cause arguments to your invoke functions to be supplied on demand, assuming the CLI invocation is capable of fulfilling the dependencies. There are two kinds of invoke dependencies, implicit and explicit. ### Implicit Dependencies Implicit dependencies are known-unique object instances that the invoke system can produce for your function without an explicit `Dep` annotation. These objects include: - Any command or subcommand user-defined objects upstream of the selected command/subcommand. For example: ```python def foo(command: Example, subcmd: SubExample): ... @dataclass(invoke=foo) class SubExample: ... @dataclass class Example: subcommand: Annotated[SubExample, cappa.Subcommand] ``` `Example` and `SubExample` will be available as implicit dependencies. - A [cappa.Command](cappa.Command), which provides the `Command` object corresponding to the command/subcommand that was parsed. - A [cappa.Self](cappa.Self), which can be used to produce the **current** command/subcommand. `Self` is primarily useful as a way to reuse generic invoke functions among a number of separate subcommands. For example, ```python def invoke(self: Self): print(self) @command(invoke=invoke) class Foo: ... @command(invoke=invoke) class Bar: ... ``` - A [cappa.Output](cappa.Output), which can be used to produce (themed) stdout/stderr output. ```{note} In CLIs with nested subcommand hierarchies, only the subcommand path to the user-selected subcommand will be materialized. That is to say, a subcommand which has not been selected will not be available as an invoke dependency. It's therefore programmer error to describe an `invoke` function which depends on command options that would not have been constructed during parsing, based on where it is in the hierarchy. ``` ### Explicit Dependencies ```{note} If you're familiar with FastAPI's `Depends`, this will feel very similar. ``` Custom "explicit dependencies" can be built up, in order to have arbitrary context injected into your invoked functions. Explicit dependencies must be annotated with a `Dep` in order to be recognized as such. ```{note} A function describing an explicit dependency can, itself, depend on other explicit or implicit dependencies. This allows recursively building up a dependency hierarchy. ``` ```python from typing_extensions import Annotated from cappa import Dep # No more dependencies, ends the dependency chain. def config(): return { 'password': os.getenv('DB_PASS'), } # Explicit dependency def database(config: Annotated[Engine, Dep(config)]): return sqlalchemy.create_engine(...) # Implicit dependency def logger(example: Example): ... # The actual invoke function, which can depend upon any/all of the above def invoke_fn( example: Example, logger: Annotated[Logger, Dep(logger)], database: Annotated[Engine, Dep(database)] ): ... ``` - Any dependency can be referenced by any other dependency, or invoked function. - Dependencies are evaluated only once per `cappa.invoke` call, regardless of how many dependencies transitively depend on it. - Dependencies are evaluated on demand, in the order they're found, meaning they will not be evaluated if the specifically invoked function doesn't reference it. #### Yield Dependencies Generator functions can be used as implicit context managers. This can be useful for certain kinds of dependencies which require some kind of shutdown/post-action. ```python from typing_extensions import Annotated from cappa import Dep import sqlalchemy def connection(): engine = sqlalchemy.create_engine(...) with engine.connect() as conn: yield conn def invoke_fn(connection: Annotated[sqlalchemy.Connection, Dep(connection)]): ... ``` As with `contextlib.contextmanager`, the dependency's function should execute exactly one yield given one execution through the function. The context of any yield dependencies will remain "entered" up through the action of calling the `invoke` function. As such, it is probably **not** a good idea to return a context-managed resource out of your invoke functions (`invoke_fn` above)! By the time you receive the value (`foo = invoke(...)`) it will be exited. #### Async Dependencies Async functions can be supported as dependencies. See [asyncio](./asyncio.md) documentation for details. ### Global Dependencies The top-level [cappa.invoke](cappa.invoke) command accepts a `deps` argument which denotes dependency functions which are called unconditionally. These functions will be invoked before any explicit deps have been evaluated. And given that they're executed unconditionally, it's impractical to define one which depends on conditional dependencies like subcommands. Thus it's generally only practical to use this to execute code which depends on the top-level command's implicit dependency. Although it can certainly have **no** dependencies, you could also just call that function before calling `invoke` in the first place. ```python @dataclass class Command: a: int def dep(command: Command): ... def main(): cappa.invoke(Command, deps=[dep]) ``` ```{note} When given as a Sequence (i.e. list, tuple), you can just provide the source function which should act as a `Dep`, and it will be automatically coerced into a proper `Dep`. ``` ```{note} Individual items (like the `dep` function above) can also be given as fully qualified string references to the dep, similar to how [invoke](./invoke.md) functions may be provided. This enables one to fully decouple of the runtime import paths required while defining the CLI from the code required to execute the CLI itself. ``` ### Overriding Dependencies ```{note} See [Testing](./testing.md) for additional details. This option is primarily motivated to aid stubbing dependencies for testing. ``` As an alternative to the above sequence input for `deps`, you can instead supply a `Mapping`, where the key is the "source" dependency function (i.e. the function a dependent invoke function would reference) and the value is the actual dependency which should be used in its place. For example, ```python # We've decided we want to override "foo" def foo(): ... # and here is a function which depends upon it. def invocable_function(foo: Annotated[int, Dep(foo)]): ... ``` You can either override the dep with another, "stub" dep by explicitly wrapping the value with a `Dep` ```python def stub_dep(): return 4 cappa.invoke(Command, deps={foo: Dep(stub_dep)}) ``` Or you can directly provide a literal stub value for the dep, by providing the value without a wrapping `Dep`. ```python cappa.invoke(Command, deps={foo: 4}) ``` ### Unfulfilled Dependencies Should an argument be neither an explicitly annotated `Dep`, nor typed as one of the available implicit dependencies in the hierarchy, then it's considered unfulfilled. If the argument to the callable has a default value, then the argument will simply be omitted, and the function called anyway. In the event the argument is required, a `RuntimeError` will be raised and CLI processing will stop. cappa-0.32.2/docs/source/manual_construction.md0000644000000000000000000000310413615410400016441 0ustar00# Manual Construction The general intention of the library is that you use the inference system to produce the most natural mapping between the source class definition and the output CLI. However, there's nothing preventing the manual construction of the command definition, which allows one to completely decouple the CLI-specific annotations from the class in question, if much more manually. ```python from dataclasses import dataclass import cappa from cappa.parse import parse_list @dataclass class Foo: bar: str baz: list[int] command = cappa.Command( Foo, arguments=[ cappa.Arg(field_name="bar", parse=str), cappa.Arg(field_name="baz", parse=parse_list(int), num_args=-1), ], help="Short help.", description="Long description.", ) result = cappa.parse(command, argv=["one", "2", "3"]) # result == Foo(bar="one", baz=[2, 3]) ``` There are a number of built-in parser functions used to build up the existing inference system. [parse_value](cappa.parse.parse_value) the the main entrypoint used by cappa internally, but each of the parser factory functions below make up the component built-in parsers for each type. For inherent types, like `int`, `float`, etc. Their constructor may serve as their own parser. ```{eval-rst} .. autoapifunction:: cappa.parse.parse_value .. autoapifunction:: cappa.parse.parse_list .. autoapifunction:: cappa.parse.parse_tuple .. autoapifunction:: cappa.parse.parse_literal .. autoapifunction:: cappa.parse.parse_none .. autoapifunction:: cappa.parse.parse_set .. autoapifunction:: cappa.parse.parse_union ``` cappa-0.32.2/docs/source/output.md0000644000000000000000000000501113615410400013711 0ustar00# Console Output By default, `cappa.parse` or `cappa.invoke` will internally construct an [Output](cappa.Output) object which controls the format of all console output, including how [Exit](cappa.Exit) and errors are formatted. Alternatively [cappa.parse](cappa.parse) and [cappa.invoke](cappa.invoke) both accept `Output` instances as an optional input, allowing you to customize the instance or otherwise make use of it before/after the CLI parse time. Any `Output` output will automatically make use of [rich](rich), and thus has access to its native styling syntax, and the ability to output native rich constructs. ## Producing Output An [invoke](cappa.invoke) handler which depends on an argument annotated as `Output` will be automatically provided with the `Output` instance. ```python import cappa from dataclasses import dataclass @cappa.command() def cli(output: Output): output("Hi!") # e.g. output.output("Hi!") cappa.invoke(cli) # or @cappa.command(invoke=cli) @dataclass class CLI: ... cappa.invoke(cli) ``` ```{note} If you're familiar with `click`, `click.echo` is roughly equivalent to `Output.output`. ``` You can alternatively call `Output.error` to make use of error-specific default formatting/coloring ## Exiting and Exit Codes Cappa provides an [Exit](cappa.Exit) class to allow someone to gracefully exit the program, without emitting a traceback. ```python raise cappa.Exit(message="Oh no!", code=1) # or raise cappa.Exit("Oh no!", code=1) # or raise cappa.Exit(code=1) # or raise cappa.Exit("Graceful exit") # i.e. status code 0! ``` If [Exit](cappa.Exit) is encountered anywhere within cappa's running context (e.g. an invoke handler, a parser, an action, etc), it will automatically `Output` the message using the method appropriate to the exit code (`code=0` -> `Output.output` and `code>0` -> `Output.error`), before gracefully exiting. ## Customizing default formatting [cappa.parse](cappa.parse) and [cappa.invoke](cappa.invoke) both accept `Output` instances as an optional input, allowing you to customize default formatting. - output_format (default `{message}`): Exit code == 0 - error_format (default `{short_help}\n\n[red]Error[/red]: {message}`): Exit code != 0 If, for example, you did not want to include `short_help` by default, and wanted "Error" to be orange, you could do as follows: ```python output = cappa.Output(error_format="[orange]Error[/orange]: {message}") cappa.parse(Command, output=output) ``` ## API ```{eval-rst} .. autoapimodule:: cappa :members: Output :noindex: ``` cappa-0.32.2/docs/source/parse_vs_invoke.md0000644000000000000000000000267313615410400015561 0ustar00# Parse Vs Invoke The choice to use [cappa.parse](cappa.parse) or [cappa.invoke](cappa.invoke) boils down to whether you prefer an `argparse`-style CLI or a `click`-style CLI. Given some basic CLI: ```python from dataclasses import dataclass from typing import Annotated import cappa @dataclass class Example: flag: bool name: Annotated[str, cappa.Arg(long=True)] ``` For an `argparse`-style experience, you would opt for [cappa.parse](cappa.parse), preferring to let the library **just** parse the commandline arguments and hand you an `Example` instance like so: ```python def main(): example: Example = cappa.parse(Example) # Do things with your Example instance... if example.flag: print(example.name) ``` Alternatively, if you prefer a `click`-style experience, you're used to decorating a function with a few command/option decorators and letting click take care of calling your function with the appropriate arguments. You would instead use [cappa.invoke](cappa.invoke), like so: ```python def example(ex: Example): if example.flag: print(example.name) @cappa.command(invoke=example) class Example ... # your original example body cappa.invoke(Example) ``` Note, in a basic example like this, [cappa.invoke](cappa.invoke) is not necessarily providing a lot of utility. It is primarily useful in CLIs with subcommands, where dispatching to disparate subcommand target functions can become boilerplate. cappa-0.32.2/docs/source/rich.md0000644000000000000000000000422013615410400013277 0ustar00# Rich ## Color Colored output, including help-text generation, is automatically enabled. [invoke](cappa.invoke) or [parse](cappa.parse) both accept a `color: bool = True` argument which can be used to enable/disable the colored output directly. Alternatively, rich automatically respects the `NO_COLOR` environment variable for disabling colored output. You can control individual styling of the help text by including rich directives inside the help text text. For example `Arg(help="I need [bold]Help[/bold]!")`. ### Theming You can define your own rich `Theme` object and supply it into [invoke](cappa.invoke) or [parse](cappa.parse), which will used when rendering help-text output (and error messaging). Cappa's theme defines and uses the following style groups, which you would need to also supply: - `cappa.prog` - `cappa.group` - `cappa.arg` - `cappa.arg.name` - `cappa.subcommand` - `cappa.help` ## Prompt/Confirm Cappa integrates directly with `rich.prompt.Prompt`/`rich.prompt.Confirm`. You can directly make use of confirm/prompt from within your code anywhere, and it should "just work" ```python from rich.prompt import Prompt name = Prompt.ask("Enter your name") ``` Alternatively, a `Prompt` object can be used as a `default=` value for a [cappa.Arg](cappa.Arg) instance. ```python import cappa from rich.prompt import Prompt @dataclass class Test: name: Annotated[str, cappa.Arg(default=Prompt("Enter one of these", choices=['one', 'two', 'three']))] result = parse(Test) ``` In the event the value for that argument was omitted at the command-line, the prompt will be evaluated. ```{note} Input prompts can be a hazard for testing. As such, you can supply `input=StringIO(...)` to either of `parse`/`invoke` as a way to write tests that exercise the prompt code. ``` ## Pretty Tracebacks Prettily backending tracebacks can be a UX improvement for your CLI. Cappa does not define any specific integration with rich for tracebacks, given that you simply install rich's native traceback handler before calling parse/invoke. ```python import cappa from rich.traceback import install def main(): install(show_locals=True) cappa.invoke(...) ``` cappa-0.32.2/docs/source/sphinx.md0000644000000000000000000000245513615410400013673 0ustar00# Sphinx/Docutils Directive As of version 0.16.0, Cappa ships with a "docutils" directive capable of rendering the CLI to docutils/sphinx documentation. In sphinx, you would load this in `conf.py`, by adding `"cappa.ext.docutils"` to the list of extensions: ```python extensions = [ ... "cappa.ext.docutils", ] ``` The module path to the CLI object is the only required argument. All other options are...optional. The default configuration is equivalent to: ```rst .. cappa:: :style: terminal :terminal-width: 0 ``` or with MyST: ````md ```{cappa} :style: terminal :terminal-width: 0 ``` ```` Options/customization currently include: - style: "native" or "terminal" (defaults to "terminal"). "terminal" renders the `--help` text as it would have rendered at the terminal. "native" renders the CLI structure as native docutils sections/text. Set like `:style: terminal`. - terminal-width: The fixed-width at which to render the terminal-style help text. Defaults to 70. Set like `:terminal-width: 80`. Note, you can also set the width to 0, to utilize browser line-wrapping, although it will not look identical to the line-wrapping in the terminal. Customization is likely increase over time purely based off user requests! cappa-0.32.2/docs/source/state.md0000644000000000000000000000253713615410400013503 0ustar00# State A [cappa.State](cappa.State) is ultimately a thin wrapper around a simple dictionary, that can be used to share state among different parts of the overall cappa parsing process. A `state=` argument can be supplied to [parse](cappa.parse) or [invoke](cappa.invoke), which accepts a `State` instance. If no upfront `State` instance is supplied, one will be constructed automatically so it can always be assumed to exist. ```{note} It is also optionally generic over the dict type. So it can be annotated with `TypedDict` to retain safety over the dict's fields. ``` ```python import cappa from typing import Any, Annotated, Any from dataclasses import dataclass class CliState: config: dict[str, Any] def get_config(key: str, state: State[CliState]): return state.state["config"][key] @dataclass class Example: token: Annotated[str, cappa.Arg(default=cappa.ValueFrom(get_config, key="token"))] config = load_config() state = State({"config": config}) cappa.parse(Example, state=state) ``` The above example shows some pre-cappa data/state being loaded and provided to cappa through `state`. Then some field accesses the shared `state` by getting it dependency injected into the `ValueFrom` callable. ```{note} `Arg.parse` and `invoke` functions can **also** accept `State` annotated inputs in order to be provided with the `State` instance. ``` cappa-0.32.2/docs/source/subcommand.md0000644000000000000000000001145513615410400014512 0ustar00# Subcommand A subcommand class is just regular class capable of being a cappa [Command](./command.md). The primary relevant detail for actually attaching a subcommand to your command, is that the field which captures the subcommand options must be annotated with `Subcommand`. As you'll have already seen throughout the documentation, before now. Subcommands are expressed by annotating a union of subcommand options, and annotating the union with `cappa.Subcommand`. I.e. ```{note} If you want to explicitly control the name of a subcommand beyond the default, you must annotate the command's class with `@cappa.command(name="the-name")`. ``` ```python from __future__ import annotations from dataclasses import dataclass from typing import Annotated from cappa import Subcommand, Subcommands @dataclass class Command: # This subcommand: Subcommands[SubcmdOne | SubcmdTwo] # is shorthand for this subcommand2: Annotated[SubcmdOne | SubcmdTwo, Subcommand] # is shorthand for this subcommand3: Annotated[SubcmdOne | SubcmdTwo, Subcommand()] @dataclass class SubcmdOne: example: str @dataclass class SubcmdTwo: option: int ``` - You can use the `Subcommands[...]` shorthand to avoid the use of `Annotated`, in cases where you dont need to customize `Subcommand`'s constructor arguments. - Annotation with `Subcommand` will instantiate it no arguments, getting the default behavior - The above denotes a required subcommand with two options. To make it optional, you can additionally union the options with `None` and default the field to `None`. - By default, name of the class (converted to dash-case) will be the CLI name for the subcommand option. E.x. `subcmd-one` and `subcmd-two`, from above. You can decorate each subcommand class with `@cappa.command(name='')` to choose your own name. ## Aliases Subcommands can declare alternate names that invoke the same command. This is useful for short forms (`ls` for `list`), legacy spellings, and migration paths when renaming a command. Pass `aliases=` to `@cappa.command(...)`: ```python import cappa from dataclasses import dataclass @cappa.command(aliases=["ls"]) @dataclass class List: pass @cappa.command(name="remove", aliases=["rm"]) @dataclass class Remove: pass @dataclass class Tool: cmd: cappa.Subcommands[List | Remove] ``` With the above, `tool list`, `tool ls`, `tool remove`, and `tool rm` all work, and `--help` displays both names per subcommand: ``` Subcommands list, ls remove, rm ``` ### Hidden aliases A bare string in `aliases=` produces a visible alias — it appears in `--help` output and shell completion. Use `cappa.Alias(name, hidden=True)` to accept the name without advertising it. This is handy for deprecated names you want to keep working without showing in the UI: ```python @cappa.command( aliases=[ "ls", # visible cappa.Alias("dir", hidden=True), # accepted, but absent from help ], ) @dataclass class List: pass ``` ### Deprecated aliases `cappa.Alias(name, deprecated=...)` emits a runtime warning to stderr when the user invokes the command via that alias, while still dispatching to the canonical command. Useful when you've renamed a subcommand but want to give users a transition period: ```python @cappa.command( name="remove", aliases=[ cappa.Alias("rm", deprecated="use 'remove' instead"), cappa.Alias("delete", deprecated=True), # default message ], ) @dataclass class Remove: pass ``` Invoking `tool rm` runs `Remove`, then prints: ``` Error: Command alias `rm` is deprecated: use 'remove' instead ``` `hidden` and `deprecated` compose — a hidden + deprecated alias is the typical shape for an old name you want to keep alive but never show again. ### Imperative construction When constructing commands manually (without the decorator), `aliases=` is a keyword argument on `Command` itself: ```python import cappa cmd = cappa.Command( Tool, arguments=[ cappa.Subcommand( field_name="cmd", options={ "list": cappa.Command(List, name="list", aliases=["ls"]), "remove": cappa.Command( Remove, name="remove", aliases=["rm"] ), }, ), ], ) ``` The dict key in `Subcommand.options` remains the canonical name; aliases are declared on each `Command` and resolved automatically. ### Collisions Aliases must not collide with another subcommand's canonical name, with another alias under the same parent, or with the command's own canonical name. Any of these raises `ValueError` at command construction time so the conflict is caught before the CLI ever runs. ```{eval-rst} .. autoapiclass:: cappa.Subcommand :noindex: ``` ```{eval-rst} .. autoapiclass:: cappa.Alias :noindex: ``` cappa-0.32.2/docs/source/testing.md0000644000000000000000000000510313615410400014030 0ustar00# Testing You can **certainly** just directly call any invoke functions directly, manually constructing and providing arguments yourself. You can also just directly call [cappa.parse](cappa.parse) or [cappa.invoke](cappa.invoke) yourself, with relatively little effort. However, Cappa comes with a built in [CommandRunner](cappa.testing.CommandRunner) which is meant to reduce the verbosity of testing CLI commands and in overriding upstream [Dep](cappa.Dep). With it, you can centralize any always-used options, such that only the options which vary from test to test are provided inside the test bodies.. ```{eval-rst} .. autoapimodule:: cappa.testing :members: CommandRunner, RunnerArgs :noindex: ``` ## Pytest Cappa does not come with a built in pytest fixture because we assume that any test suite which might benefit from a fixture will likely have other fixture dependencies. Most users will want to customize the construction of their `CommandRunner`, if they're going to use one at all. It is very straightforward to define you own fixture to produce an appropriately configured runner. For example, let us describe a base CLI which depends upon an [Explicit Dependency](./invoke.md#explicit-dependencies) on a configuration dictionary which pulls data from the environment; which you need to override in a fixture to work with the rest of your testing structure. ```{note} See [Invoke dependency overrides](./invoke.md#overriding-dependencies) for additional details. ``` Your existing code might look like this: ```python import os from typing import Annotated import cappa def config() -> dict: return { "env": os.getenv("ENV") "foo": os.getenv("FOO") } def fn(config: Annotated[dict, cappa.Dep(config)]): print(config) @cappa.command(invoke=fn) class CLI: name: str ``` In your tests, you've decided you want to hard-code a specific alternative `config` value. You could define a pytest fixture like so: ```python import pytest from cappa.testing import CommandRunner from package import CLI, config @pytest.fixture def runner(): # Note `runner` could itself depend on other fixtures, in more complex scenarios return CommandRunner(CLI, deps={config: {"env": "test", "foo": "bar"}}) # OR def stub_config() -> dict: return { "env": "test" "foo": "bar" } @pytest.fixture def runner(): return CommandRunner(CLI, deps={config: cappa.Dep(stub_config)}) ``` Then your tests will be able to omit most configuration, except for the item under test: ```python def test_foo(runner: CommandRunner): runner.invoke('name!') ``` cappa-0.32.2/docs/source/validation.md0000644000000000000000000000670413615410400014515 0ustar00# Argument Validation The type system is not always descriptive enough to fully validate all user inputs, there is sometimes a desire to manually validate input values **before** completing parsing, or returning control back to the actual program's command implementations. There are a few options that may be more or less applicable depending on the situation. 1. Take control of [Arg.parse](arg-parse) This option is very simple, in that it forgos all cappa type inference in favor of the user provided function. In exchange, you give up the default inference and thus have to parse the raw input string yourself. ```python import cappa from typing import Annotated from dataclasses import dataclass def gt_zero(raw_value: str) -> int: value = int(raw_value) if value <= 0: raise ValueError("Value must be greater than zero") return value @cappa.command @dataclass class Foo: bar: Annotated[int, cappa.Arg(parse=gt_zero)] print(cappa.parse(Foo)) ``` 1. Compose your validation with the default [Arg.parse](arg-parse) You can provide a sequence of `parse` functions, to be called in order, using the default implementation as the first one. While in this particular example, it might not be hugely beneficial, for more complex or nested types, the inference may be doing enough work that it'd be preferable to only layer in your validation on top of the default. ```python import cappa from typing import Annotated from dataclasses import dataclass def gt_zero(value: int) -> int: if value <= 0: raise ValueError("Value must be greater than zero") return value @cappa.command @dataclass class Foo: bar: Annotated[int, cappa.Arg(parse=[cappa.default_parse, gt_zero])] print(cappa.parse(Foo)) ``` 1. Utilize class-level construction validation. Cappa will capture and `ValueError` or `Exit` exceptions raised during the construction of the underlying dataclasses that comprise your CLI. As such, you can instrument those classes to perform class-level validation on construction. ```python import cappa from dataclasses import dataclass @cappa.command @dataclass class Foo: bar: int def __post_init__(self): if self.bar <= 0: raise ValueError("Bar must be greater than zero") print(cappa.parse(Foo)) ``` ````{note} Other base classes (e.g. Pydantic, attrs, msgspec) All of pydantic, attrs, and msgspec either have a similar facility to `__post_init__` and/or specifically have constraint/validation logic built into their APIs. This may or may not serve you, as you will not necessarily be able to control the error messages. For example, (at least by default) pydantic's error messages include a lot of code-specific information that might not be appropriate for a CLI user. However, as you can see, it does produce a very compact class definition and validation pair. ```python import cappa from pydantic import BaseModel, Field @cappa.command class Foo(BaseModel): bar: int = Field(gt=0) print(cappa.parse(Foo)) ``` With that said, all 3 options produce `ValueError`-based exceptions on validation errors, and will do the correct thing with regards to cappa. ```` 1. Custom [Arg.action](arg-action) As usual, custom actions should probably only be considered as a last resort, because you're giving up most of the argument parsing infrastructure in exchange for control over how the data is written. With that said, anything you can do with a `Arg.parse` function, you can do with and `Arg.action`, and then some. cappa-0.32.2/docs/source/_static/.gitkeep0000644000000000000000000000000013615410400015077 0ustar00cappa-0.32.2/docs/source/_static/example.svg0000644000000000000000000004176313615410400015647 0ustar00 Usage: example [--flag][--optionOPTION][-mMANY_OPTIONS] [--three-valuesTHREE_VALUES THREE_VALUES THREE_VALUES][POSITIONAL] [-h][--completionCOMPLETION] Options                              [--flag]                        (Default: False)            [--optionOPTION]               A number      [-mMANY_OPTIONS]               Valid options: one, two, three.     [--three-valuesTHREE_VALUES  THREE_VALUES THREE_VALUES]                         Arguments                            [POSITIONAL]                    (Default: optional)     Help                                 [-h, --help]                    Show this message and exit.     [--completionCOMPLETION]       Use --completion generate to                                     print shell-specific completion                                      source. Valid options: generate,                                     complete. cappa-0.32.2/docs/source/examples/case_study.md0000644000000000000000000001627713615410400016352 0ustar00# Case Studies These are examples a meant to show a bit more of a stream of consciousness tutorial of sorts of how one might go from an empty python file to a CLI which does a thing, and design decisions or evolutions that happen along the way. ## To-Do List I am designing a CLI which manages a "To-Do List", a completely original idea that I indend to sell one day. In order to interact said To-Do list, I'm going to design a CLI interface with roughly the following command structure: ```bash todo add 'Buy bananas' todo remove 'Buy Bananas' todo complete 'Buy Bananas' ``` First i'll start by getting a basic no-op executable running. Dataclasses are the easiest way to define cappa-compatible classes (because they're built in), so I'll go with that. ```python from dataclasses import dataclass import cappa @dataclass class Todo: ... todo = cappa.parse(Todo) print(todo) ``` ```bash $ python todo.py --help Usage: todo [-h] [--completion COMPLETION] Help [-h, --help] Show this message and exit. [--completion COMPLETION] Use --completion generate to print shell-specific completion source. Valid options: generate, complete. $ python todo.py Todo() ``` Ok great, now let's add the "add" command ```python from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class Todo: command: cappa.Subcommands[AddCommand] @dataclass class AddCommand: todo: str todo = cappa.parse(Todo) print(todo) ``` Added `from __future__ import annotations` so that the classes could be defined top-down sequentially, which I think makes understanding the flow of the commands more intuitive. Oh but oops: ```bash $ python todo.py Usage: todo {add-command} [-h] [--completion COMPLETION] Error: A command is required: {add-command} ``` Ah yes, the name of my `AddCommand` class isn't the same as what I want the name of the literal CLI command to be. This should fix it: ```python @cappa.command(name="add") @dataclass class AddCommand: todo: str ``` ```bash $ python todo.py add 'buy bananas' Todo(command=AddCommand(todo='buy bananas')) ``` Now that the CLI has the shape I'd like it to have, time to implement the functionality. I have a [few options](../invoke.md) for how to attach behavior to a given command/subcommand, but in the interest of conciseness i'll simply my my dataclass callable. ```python import json from pathlib import Path @cappa.command(name="add") @dataclass class AddCommand: todo: str def __call__(self): path = Path("todo.json") data = [] if path.exists(): data = json.loads(path.read_text()) data.append(self.todo) path.write_text(json.dumps(data)) ``` We'll also need to replace `cappa.parse` with `cappa.invoke`. Let's also quickly add a `list` command to be able to actually test it out. ```python ... @dataclass class Todo: command: cappa.Subcommands[AddCommand | ListCommand] # <- gotta remember to # include the new command! ... @cappa.command(name="list") @dataclass class ListCommand: def __call__(self): path = Path("todo.json") data = [] if path.exists(): data = json.loads(path.read_text()) for item in data: print(f" - {item}") ``` Now we should be able to test it out: ```bash $ python todo.py add 'buy bananas' $ python todo.py add 'clean kitchen' $ python todo.py list - buy bananas - clean kitchen ``` That's about it! You could keep going and add more commands; or implement it with something fancier than a flat file. Here is the final program. ```python from __future__ import annotations import json from dataclasses import dataclass from pathlib import Path import cappa @dataclass class Todo: command: cappa.Subcommands[AddCommand | ListCommand] @cappa.command(name="add") @dataclass class AddCommand: todo: str def __call__(self): path = Path("todo.json") data = [] if path.exists(): data = json.loads(path.read_text()) data.append(self.todo) path.write_text(json.dumps(data)) @cappa.command(name="list") @dataclass class ListCommand: def __call__(self): path = Path("todo.json") data = [] if path.exists(): data = json.loads(path.read_text()) for item in data: print(f" - {item}") cappa.invoke(Todo) ``` ## Slow Startup ```{admonition} ...Context... Python CLIs can frequently have slow startup, the more imports and import-time ...stuff... that happens between the moment python starts up and the CLI parsing logic executes. This **can** be really annoying when all you're executing is `cli.py --help`, and it's taking all of a second or more to run! This is much _less_ of a problem for a library like `argparse`, whereas it can **quickly** become a problem with something like `click`, where you're forced to import the whole command tree preemptively in order to describe the CLI itself! (Either that or remember to inline all your imports yourself!). See [invoke](../invoke.md) docs for more detail! ``` I've got this really simply CLI program that's already taking an abnormally long time to return on a simple `--help`! What gives! ```python # cli.py from __future__ import annotations from dataclasses import dataclass import cappa from .mean import calculate_mean @dataclass class Slow: command: cappa.Subcommands[MeanCommand] @cappa.command(name='mean', invoke=calculate_mean) @dataclass class MeanCommand: numbers: list[int] cappa.invoke(Mean) # mean.py import pandas from .slow import MeanCommand def calculate_mean(command: MeanCommand): df = pandas.DataFrame(command.numbers) print(df.mean()[0]) ``` ```bash time slow/cli.py --help slow.py --help 0.82s user 0.07s system 165% cpu 0.541 total ``` 840ms to do nothing! You know, I recall importing pandas is reasonably slow maybe inline the import? ```python # mean.py def calculate_mean(command: MeanCommand): import pandas df = pandas.DataFrame(command.numbers) print(df.mean()[0]) ``` ```bash time slow/cli.py --help slow.py --help 0.20s user 0.07s system 165% cpu 0.541 total ``` Aha! That's a fair bit better. Although it's kind of annoying to have to remember to inline slow imports throughout my program. Instead, lets have cappa defer the import of the actual command for us! ```python # cli.py from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class DeferImport: command: cappa.Subcommands[MeanCommand] @cappa.command(name='mean', invoke='slow.mean.calculate_mean') @dataclass class MeanCommand: numbers: list[int] cappa.invoke(DeferImport) # mean.py import pandas from .slow import MeanCommand def calculate_mean(command: MeanCommand): df = pandas.DataFrame(command.numbers) print(df.mean()[0]) ``` Now, the CLI interface definition is nicely isolated from the actual implementation. I dont need to import every possible code path in order to execute the CLI in the first place, and we still have a nice fast CLI! ```bash time slow/cli.py --help slow.py --help 0.20s user 0.07s system 165% cpu 0.541 total ``` cappa-0.32.2/docs/source/examples/comparison.md0000644000000000000000000001700513615410400016347 0ustar00# Examples Below is a collection of standalone examples of specific argument/CLI behavior one might want, with comparison examples of how it might be done in each of Click/Typer/Argparse, where relevant. Please bring up any specific usage questions about comparisons between Cappa and other libraries as an [issue](https://github.com/dancardin/cappa/issues) or a [discussion](https://github.com/dancardin/cappa/discussion). The list below can and should grow in response to feature-comparisons which are not clear just from reading the cappa-specific documentation. ## Custom Option Value Name i.e. "FOO" in ``` $ prog --help Usage: prog --some-arg FOO ``` ````{admonition} Cappa :class: dropdown ```python from typing import Annotated from dataclasses import dataclass import cappa @dataclass class Args: some_arg: Annotated[str, cappa.Arg(value_name='FOO')] cappa.parse(Args) ``` ```` ````{admonition} Argparse :class: dropdown ```python import argparse parser = argparse.ArgumentParser() parser.add_argument("--some-arg", metavar="FOO") parser.parse_args() ``` ```` ````{admonition} Click :class: dropdown ```python import click @click.command() @click.option('--some-arg', metavar="FOO") def main(): ... main() ```` ````{admonition} Typer :class: dropdown ```python from typing_extensions import Annotated import typer def main(some_arg: Annotated[str, typer.Argument(metavar="FOO")]): ... typer.run(main) ``` ```` ## Unbounded option arguments i.e. `prog --foo 1 2 3 4 ...` ````{admonition} Cappa :class: dropdown ```python from typing import Annotated from dataclasses import dataclass import cappa @dataclass class Args: foo: Annotated[list[str], cappa.Arg(num_args=-1) print(cappa.parse(Args)) ``` ```` ````{admonition} Argparse :class: dropdown ```python import argparse parser = argparse.ArgumentParser() parser.add_argument("--foo", nargs="*") print(parser.parse_args()) ``` ```` ```{admonition} Click/Typer :class: dropdown Click's (and thus Typer) parser, at time of writing, appears to not support this feature ``` ## Interactive Prompt ``` $ prog What's your name?: Ron ``` ````{admonition} Cappa :class: dropdown ```python from typing import Annotated from dataclasses import dataclass from rich.prompt import Prompt import cappa @dataclass class Args: some_arg: Annotated[str, cappa.Arg(default=rich.Prompt("What's your name?"))] cappa.parse(Args) ``` ```` ```{admonition} Argparse :class: dropdown Argparse requires custom code or a [third party library](https://pypi.org/project/argparse-prompt/) to support this feature ``` ````{admonition} Click :class: dropdown ```python import click @click.command() @click.option('--name', prompt="What's your name?") def main(name): ... main() ```` ````{admonition} Typer :class: dropdown ```python import typer from typing_extensions import Annotated def main(name: str, lastname: Annotated[str, typer.Option(prompt="What's your name?")]): ... typer.run(main) ``` ```` ## Subcommands ``` $ prog double 123 246 $ prog print --title bar Bar ``` ````{admonition} Cappa :class: dropdown ```python from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class Args: command: cappa.Subcommands[Double | Print] @dataclass class Double: arg: int def __call__(self): print(self.arg * 2) @dataclass class Print: text: str title: bool = False def __call__(self): text = self.text.title() if self.title else self.text print(text) cappa.invoke(Args) ``` Not the shortest, but (I think) it's still very intelligble. Certainly, the extra line or two dedicated to turning each command into a type can add some extra heft. The mechanism by which the subcommands are declared is (I think) the clearest of the 4. ```` ````{admonition} Argparse :class: dropdown ```python import argparse def double(arg: int): print(arg * 2) def print_command(text: str, title: bool): text = text.title() if title else text print(text) parser = argparse.ArgumentParser() subcommand = parser.add_subparsers(dest="command") double_subcommand = subcommand.add_parser(name="double") double_subcommand.add_argument("arg", type=int) print_subcommand = subcommand.add_parser(name="print") print_subcommand.add_argument("text") print_subcommand.add_argument("--title", action="store_true", default=False) args = parser.parse_args() if args.command == "double": double(args.arg) elif args.command == "print": print_command(args.text, args.title) ``` I had to look at the docs to implement, because the `parser.add_subparsers` -> `subparser.add_parser()` API is non-intuitive. Obviously the programmer needs to implement the subcommand dispatch that's handled for you in all the other options. Finally the resultant `args` namespace is completely untyped and you need to hope that the shape you described matches how you access it. ```` ````{admonition} Click :class: dropdown ```python import click @click.group() def main(): ... @main.command() @click.argument("arg", type=int) def double(arg: int): print(arg * 2) @main.command(name="print") @click.argument("text") @click.option("--title", is_flag=True, default=False) def print_command(text: str, title: str): text = text.title() if title else text print(text) main() ``` Subcommands (particularly nested ones beyond the complexity of this example) are click's strong suit. The main annoyance is that it's fairly repetitive. You need to manually choose the correct mapper/type, match the argument names yourself, and repeat the types if you want a typed function. ```` ````{admonition} Typer :class: dropdown ```python import typer app = typer.Typer(name="example") @app.command() def double(arg: int): print(arg * 2) @app.command("print") def print_command(text: str, title: bool = False): text = text.title() if title else text print(text) app() ``` Typer is certainly the shortest in this example, and has the least repetition. This is probably Typer's clearest case. It very simply and directly solves the repetition/mapping problem of click. ```` ## Console Output ``` $ prog World Hello World ``` ````{admonition} Cappa :class: dropdown ```python from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class Args: text: str def __call__(self, output: cappa.Output): output(f"Hello {self.text}") cappa.invoke(Args) ``` In order to manually output text, depend on an `Output` instance to be provided a preconfigured output-able console object. Importantly, this will natively route output through rich, which gains easy styling. ```` ````{admonition} Argparse :class: dropdown ```python import argparse parser = argparse.ArgumentParser() parser.add_argument("text") args = parser.parse_args() print(f"Hello {args.text}") ``` Argparse just parses the arguments, so all choices of how to write output is left to the programmer. (Note, you can do this with cappa also, by using the `parse` interface) ```` ````{admonition} Click :class: dropdown ```python import click @main.command() @click.argument("text") def main(text: str): click.echo(f"Hello {text}) main() ``` ```` ````{admonition} Typer :class: dropdown ```python import typer def main(text: str): typer.echo(f"hello {text}") typer.run(app) ``` Typer is almost identical to click, because Typer is built on click. Typer also integrates well with rich, although it seems to encourage direct use of the rich Console-less API, which may(?) centralized control of styling within a CLI more challenging. ```` cappa-0.32.2/docs/source/examples/index.md0000644000000000000000000000153713615410400015307 0ustar00# Examples - [Examples by comparison to other libraries](./comparison.md). Since Cappa can be fairly different from other libraries, it might not be obvious how one might reproduce an existing CLI's feature using a different library within cappa. - [Examples by...Examples](https://github.com/DanCardin/cappa/tree/main/examples) This folder within the repo contains a number of completely standalone example CLIs exercising various notable cappa design patterns and features. - [Example by case study](./case_study.md) This is example a meant to show a bit more of a stream of consciousness tutorial of sorts of how one might go from an empty python file to a CLI which does a thing, and design decisions or evolutions that happen along the way. ```{toctree} :hidden: Comparison vs Other Libraries Case Study ``` cappa-0.32.2/examples/defer_import_slow_startup/README.md0000644000000000000000000000046713615410400020213 0ustar00# Defer Import Run `uv run defer-import --help` to run. Demonstrates how to reference the `invoke` target as a string. Note this is notably more complex than most other examples because this feature requires the modules be importable by one another and thus requires a proper package to be set up/installed. cappa-0.32.2/examples/defer_import_slow_startup/pyproject.toml0000644000000000000000000000065713615410400021651 0ustar00[project] name = "defer-import" version = "0.0.1" dependencies = [ "pandas", "cappa", ] requires-python = ">=3.8,<4" [project.scripts] defer-import = "defer_import.main:main" [tool.uv.sources] cappa = {path = "../.."} [dependency-groups] dev = [ "mypy >= 1.0.0", "pyright >= 1.1.398", "basedpyright >= 1.29.1", "pandas-stubs", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" cappa-0.32.2/examples/defer_import_slow_startup/uv.lock0000644000000000000000000032261413615410400020241 0ustar00version = 1 revision = 3 requires-python = ">=3.8, <4" resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version < '3.9'", ] [[package]] name = "basedpyright" version = "1.29.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodejs-wheel-binaries" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0c/db/fcfced6e89a49b52694d51078c2cf626b3fe9d097c1145bb8424337d7ae6/basedpyright-1.29.2.tar.gz", hash = "sha256:12c49186003b9f69a028615da883ef97035ea2119a9e3f93a00091b3a27088a6", size = 21966851, upload-time = "2025-05-21T11:45:43.03Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/98/b7/8307208ab517ac6e5d0331ad7347624fe8b250fb6e659ba3bd081d82c890/basedpyright-1.29.2-py3-none-any.whl", hash = "sha256:f389e2997de33d038c5065fd85bff351fbdc62fa6d6371c7b947fc3bce8d437d", size = 11472099, upload-time = "2025-05-21T11:45:38.785Z" }, ] [[package]] name = "cappa" version = "0.31.1" source = { directory = "../../" } dependencies = [ { name = "rich" }, { name = "type-lens" }, { name = "typing-extensions" }, ] [package.metadata] requires-dist = [ { name = "docstring-parser", marker = "python_full_version >= '3.14' and extra == 'docstring'", specifier = ">=0.17" }, { name = "docstring-parser", marker = "extra == 'docstring'", specifier = ">=0.15" }, { name = "rich", specifier = ">=12.1.0" }, { name = "type-lens", specifier = ">=0.2.5" }, { name = "typing-extensions", specifier = ">=4.8.0" }, { name = "typing-extensions", marker = "python_full_version >= '3.13'", specifier = ">=4.12.0" }, ] provides-extras = ["docstring"] [package.metadata.requires-dev] dev = [ { name = "attrs", specifier = ">=18.0" }, { name = "coverage", specifier = ">=7.3.0" }, { name = "docutils", specifier = ">=0.20.0" }, { name = "msgspec", marker = "python_full_version >= '3.9'", specifier = ">=0.19.0" }, { name = "mypy", specifier = ">=1.0.0" }, { name = "pydantic", marker = "python_full_version < '3.14'", specifier = ">=1.8.0" }, { name = "pyright", specifier = ">=1.1.398" }, { name = "pytest", specifier = ">=8.1.1,<9" }, { name = "ruff", specifier = ">=0.9.0" }, { name = "sphinx", specifier = ">=7.1.0" }, { name = "types-docutils", specifier = ">=0.20.0" }, ] [[package]] name = "defer-import" version = "0.0.1" source = { editable = "." } dependencies = [ { name = "cappa" }, { name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pandas", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] [package.dev-dependencies] dev = [ { name = "basedpyright" }, { name = "mypy", version = "1.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "mypy", version = "1.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pandas-stubs", version = "2.0.3.230814", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pandas-stubs", version = "2.2.2.240807", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "pandas-stubs", version = "2.2.3.250527", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pyright" }, ] [package.metadata] requires-dist = [ { name = "cappa", directory = "../../" }, { name = "pandas" }, ] [package.metadata.requires-dev] dev = [ { name = "basedpyright", specifier = ">=1.29.1" }, { name = "mypy", specifier = ">=1.0.0" }, { name = "pandas-stubs" }, { name = "pyright", specifier = ">=1.1.398" }, ] [[package]] name = "eval-type-backport" version = "0.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz", hash = "sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed", size = 9445, upload-time = "2025-12-02T11:51:42.987Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl", hash = "sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8", size = 6063, upload-time = "2025-12-02T11:51:41.665Z" }, ] [[package]] name = "markdown-it-py" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "mypy" version = "1.14.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "mypy-extensions", marker = "python_full_version < '3.9'" }, { name = "tomli", marker = "python_full_version < '3.9'" }, { name = "typing-extensions", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051, upload-time = "2024-12-30T16:39:07.335Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002, upload-time = "2024-12-30T16:37:22.435Z" }, { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400, upload-time = "2024-12-30T16:37:53.526Z" }, { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172, upload-time = "2024-12-30T16:37:50.332Z" }, { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732, upload-time = "2024-12-30T16:37:29.96Z" }, { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197, upload-time = "2024-12-30T16:38:05.037Z" }, { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836, upload-time = "2024-12-30T16:37:19.726Z" }, { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432, upload-time = "2024-12-30T16:37:11.533Z" }, { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515, upload-time = "2024-12-30T16:37:40.724Z" }, { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791, upload-time = "2024-12-30T16:36:58.73Z" }, { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203, upload-time = "2024-12-30T16:37:03.741Z" }, { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900, upload-time = "2024-12-30T16:37:57.948Z" }, { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869, upload-time = "2024-12-30T16:37:33.428Z" }, { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668, upload-time = "2024-12-30T16:38:02.211Z" }, { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060, upload-time = "2024-12-30T16:37:46.131Z" }, { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167, upload-time = "2024-12-30T16:37:43.534Z" }, { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341, upload-time = "2024-12-30T16:37:36.249Z" }, { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991, upload-time = "2024-12-30T16:37:06.743Z" }, { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016, upload-time = "2024-12-30T16:37:15.02Z" }, { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097, upload-time = "2024-12-30T16:37:25.144Z" }, { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728, upload-time = "2024-12-30T16:38:08.634Z" }, { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965, upload-time = "2024-12-30T16:38:12.132Z" }, { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660, upload-time = "2024-12-30T16:38:17.342Z" }, { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198, upload-time = "2024-12-30T16:38:32.839Z" }, { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276, upload-time = "2024-12-30T16:38:20.828Z" }, { url = "https://files.pythonhosted.org/packages/39/02/1817328c1372be57c16148ce7d2bfcfa4a796bedaed897381b1aad9b267c/mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31", size = 11143050, upload-time = "2024-12-30T16:38:29.743Z" }, { url = "https://files.pythonhosted.org/packages/b9/07/99db9a95ece5e58eee1dd87ca456a7e7b5ced6798fd78182c59c35a7587b/mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6", size = 10321087, upload-time = "2024-12-30T16:38:14.739Z" }, { url = "https://files.pythonhosted.org/packages/9a/eb/85ea6086227b84bce79b3baf7f465b4732e0785830726ce4a51528173b71/mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319", size = 12066766, upload-time = "2024-12-30T16:38:47.038Z" }, { url = "https://files.pythonhosted.org/packages/4b/bb/f01bebf76811475d66359c259eabe40766d2f8ac8b8250d4e224bb6df379/mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac", size = 12787111, upload-time = "2024-12-30T16:39:02.444Z" }, { url = "https://files.pythonhosted.org/packages/2f/c9/84837ff891edcb6dcc3c27d85ea52aab0c4a34740ff5f0ccc0eb87c56139/mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b", size = 12974331, upload-time = "2024-12-30T16:38:23.849Z" }, { url = "https://files.pythonhosted.org/packages/84/5f/901e18464e6a13f8949b4909535be3fa7f823291b8ab4e4b36cfe57d6769/mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837", size = 9763210, upload-time = "2024-12-30T16:38:36.299Z" }, { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493, upload-time = "2024-12-30T16:38:26.935Z" }, { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702, upload-time = "2024-12-30T16:38:50.623Z" }, { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104, upload-time = "2024-12-30T16:38:53.735Z" }, { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167, upload-time = "2024-12-30T16:38:56.437Z" }, { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834, upload-time = "2024-12-30T16:38:59.204Z" }, { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231, upload-time = "2024-12-30T16:39:05.124Z" }, { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905, upload-time = "2024-12-30T16:38:42.021Z" }, ] [[package]] name = "mypy" version = "1.16.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "mypy-extensions", marker = "python_full_version >= '3.9'" }, { name = "pathspec", marker = "python_full_version >= '3.9'" }, { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139, upload-time = "2025-05-29T13:46:12.532Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/64/5e/a0485f0608a3d67029d3d73cec209278b025e3493a3acfda3ef3a88540fd/mypy-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7909541fef256527e5ee9c0a7e2aeed78b6cda72ba44298d1334fe7881b05c5c", size = 10967416, upload-time = "2025-05-29T13:34:17.783Z" }, { url = "https://files.pythonhosted.org/packages/4b/53/5837c221f74c0d53a4bfc3003296f8179c3a2a7f336d7de7bbafbe96b688/mypy-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e71d6f0090c2256c713ed3d52711d01859c82608b5d68d4fa01a3fe30df95571", size = 10087654, upload-time = "2025-05-29T13:32:37.878Z" }, { url = "https://files.pythonhosted.org/packages/29/59/5fd2400352c3093bed4c09017fe671d26bc5bb7e6ef2d4bf85f2a2488104/mypy-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:936ccfdd749af4766be824268bfe22d1db9eb2f34a3ea1d00ffbe5b5265f5491", size = 11875192, upload-time = "2025-05-29T13:34:54.281Z" }, { url = "https://files.pythonhosted.org/packages/ad/3e/4bfec74663a64c2012f3e278dbc29ffe82b121bc551758590d1b6449ec0c/mypy-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4086883a73166631307fdd330c4a9080ce24913d4f4c5ec596c601b3a4bdd777", size = 12612939, upload-time = "2025-05-29T13:33:14.766Z" }, { url = "https://files.pythonhosted.org/packages/88/1f/fecbe3dcba4bf2ca34c26ca016383a9676711907f8db4da8354925cbb08f/mypy-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:feec38097f71797da0231997e0de3a58108c51845399669ebc532c815f93866b", size = 12874719, upload-time = "2025-05-29T13:21:52.09Z" }, { url = "https://files.pythonhosted.org/packages/f3/51/c2d280601cd816c43dfa512a759270d5a5ef638d7ac9bea9134c8305a12f/mypy-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:09a8da6a0ee9a9770b8ff61b39c0bb07971cda90e7297f4213741b48a0cc8d93", size = 9487053, upload-time = "2025-05-29T13:33:29.797Z" }, { url = "https://files.pythonhosted.org/packages/24/c4/ff2f79db7075c274fe85b5fff8797d29c6b61b8854c39e3b7feb556aa377/mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab", size = 10884498, upload-time = "2025-05-29T13:18:54.066Z" }, { url = "https://files.pythonhosted.org/packages/02/07/12198e83006235f10f6a7808917376b5d6240a2fd5dce740fe5d2ebf3247/mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2", size = 10011755, upload-time = "2025-05-29T13:34:00.851Z" }, { url = "https://files.pythonhosted.org/packages/f1/9b/5fd5801a72b5d6fb6ec0105ea1d0e01ab2d4971893076e558d4b6d6b5f80/mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff", size = 11800138, upload-time = "2025-05-29T13:32:55.082Z" }, { url = "https://files.pythonhosted.org/packages/2e/81/a117441ea5dfc3746431e51d78a4aca569c677aa225bca2cc05a7c239b61/mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666", size = 12533156, upload-time = "2025-05-29T13:19:12.963Z" }, { url = "https://files.pythonhosted.org/packages/3f/38/88ec57c6c86014d3f06251e00f397b5a7daa6888884d0abf187e4f5f587f/mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c", size = 12742426, upload-time = "2025-05-29T13:20:22.72Z" }, { url = "https://files.pythonhosted.org/packages/bd/53/7e9d528433d56e6f6f77ccf24af6ce570986c2d98a5839e4c2009ef47283/mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b", size = 9478319, upload-time = "2025-05-29T13:21:17.582Z" }, { url = "https://files.pythonhosted.org/packages/70/cf/158e5055e60ca2be23aec54a3010f89dcffd788732634b344fc9cb1e85a0/mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13", size = 11062927, upload-time = "2025-05-29T13:35:52.328Z" }, { url = "https://files.pythonhosted.org/packages/94/34/cfff7a56be1609f5d10ef386342ce3494158e4d506516890142007e6472c/mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090", size = 10083082, upload-time = "2025-05-29T13:35:33.378Z" }, { url = "https://files.pythonhosted.org/packages/b3/7f/7242062ec6288c33d8ad89574df87c3903d394870e5e6ba1699317a65075/mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1", size = 11828306, upload-time = "2025-05-29T13:21:02.164Z" }, { url = "https://files.pythonhosted.org/packages/6f/5f/b392f7b4f659f5b619ce5994c5c43caab3d80df2296ae54fa888b3d17f5a/mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8", size = 12702764, upload-time = "2025-05-29T13:20:42.826Z" }, { url = "https://files.pythonhosted.org/packages/9b/c0/7646ef3a00fa39ac9bc0938626d9ff29d19d733011be929cfea59d82d136/mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730", size = 12896233, upload-time = "2025-05-29T13:18:37.446Z" }, { url = "https://files.pythonhosted.org/packages/6d/38/52f4b808b3fef7f0ef840ee8ff6ce5b5d77381e65425758d515cdd4f5bb5/mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec", size = 9565547, upload-time = "2025-05-29T13:20:02.836Z" }, { url = "https://files.pythonhosted.org/packages/97/9c/ca03bdbefbaa03b264b9318a98950a9c683e06472226b55472f96ebbc53d/mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b", size = 11059753, upload-time = "2025-05-29T13:18:18.167Z" }, { url = "https://files.pythonhosted.org/packages/36/92/79a969b8302cfe316027c88f7dc6fee70129490a370b3f6eb11d777749d0/mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0", size = 10073338, upload-time = "2025-05-29T13:19:48.079Z" }, { url = "https://files.pythonhosted.org/packages/14/9b/a943f09319167da0552d5cd722104096a9c99270719b1afeea60d11610aa/mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b", size = 11827764, upload-time = "2025-05-29T13:46:04.47Z" }, { url = "https://files.pythonhosted.org/packages/ec/64/ff75e71c65a0cb6ee737287c7913ea155845a556c64144c65b811afdb9c7/mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d", size = 12701356, upload-time = "2025-05-29T13:35:13.553Z" }, { url = "https://files.pythonhosted.org/packages/0a/ad/0e93c18987a1182c350f7a5fab70550852f9fabe30ecb63bfbe51b602074/mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52", size = 12900745, upload-time = "2025-05-29T13:17:24.409Z" }, { url = "https://files.pythonhosted.org/packages/28/5d/036c278d7a013e97e33f08c047fe5583ab4f1fc47c9a49f985f1cdd2a2d7/mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb", size = 9572200, upload-time = "2025-05-29T13:33:44.92Z" }, { url = "https://files.pythonhosted.org/packages/bd/eb/c0759617fe2159aee7a653f13cceafbf7f0b6323b4197403f2e587ca947d/mypy-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f56236114c425620875c7cf71700e3d60004858da856c6fc78998ffe767b73d3", size = 10956081, upload-time = "2025-05-29T13:19:32.264Z" }, { url = "https://files.pythonhosted.org/packages/70/35/df3c74a2967bdf86edea58b265feeec181d693432faed1c3b688b7c231e3/mypy-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15486beea80be24ff067d7d0ede673b001d0d684d0095803b3e6e17a886a2a92", size = 10084422, upload-time = "2025-05-29T13:18:01.437Z" }, { url = "https://files.pythonhosted.org/packages/b3/07/145ffe29f4b577219943b7b1dc0a71df7ead3c5bed4898686bd87c5b5cc2/mypy-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ed0e0847a80655afa2c121835b848ed101cc7b8d8d6ecc5205aedc732b1436", size = 11879670, upload-time = "2025-05-29T13:17:45.971Z" }, { url = "https://files.pythonhosted.org/packages/c6/94/0421562d6b046e22986758c9ae31865d10ea0ba607ae99b32c9d18b16f66/mypy-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb5fbc8063cb4fde7787e4c0406aa63094a34a2daf4673f359a1fb64050e9cb2", size = 12610528, upload-time = "2025-05-29T13:34:36.983Z" }, { url = "https://files.pythonhosted.org/packages/1a/f1/39a22985b78c766a594ae1e0bbb6f8bdf5f31ea8d0c52291a3c211fd3cd5/mypy-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a5fcfdb7318c6a8dd127b14b1052743b83e97a970f0edb6c913211507a255e20", size = 12871923, upload-time = "2025-05-29T13:32:21.823Z" }, { url = "https://files.pythonhosted.org/packages/f3/8e/84db4fb0d01f43d2c82fa9072ca72a42c49e52d58f44307bbd747c977bc2/mypy-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e7e0ad35275e02797323a5aa1be0b14a4d03ffdb2e5f2b0489fa07b89c67b21", size = 9482931, upload-time = "2025-05-29T13:21:32.326Z" }, { url = "https://files.pythonhosted.org/packages/99/a3/6ed10530dec8e0fdc890d81361260c9ef1f5e5c217ad8c9b21ecb2b8366b/mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031", size = 2265773, upload-time = "2025-05-29T13:35:18.762Z" }, ] [[package]] name = "mypy-extensions" version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" }, ] [[package]] name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] [[package]] name = "nodejs-wheel-binaries" version = "22.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0f/c6/66f36b7b0d528660dfb4a59cb9b8dd6a3f4c0a3939cd49c404a775ea4a63/nodejs_wheel_binaries-22.16.0.tar.gz", hash = "sha256:d695832f026df3a0cf9a089d222225939de9d1b67f8f0a353b79f015aabbe7e2", size = 8061, upload-time = "2025-05-22T07:27:52.149Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d7/dc/417a5c5f99e53a5d2b3be122506312731eb90fb9630c248e327e2e38cc6b/nodejs_wheel_binaries-22.16.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:986b715a96ed703f8ce0c15712f76fc42895cf09067d72b6ef29e8b334eccf64", size = 50957501, upload-time = "2025-05-22T07:27:20.132Z" }, { url = "https://files.pythonhosted.org/packages/0e/dd/d6ce48209ed15f5d1fccb29eeaa111f962557123eaf4fd03a7316c42734c/nodejs_wheel_binaries-22.16.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:4ae3cf22138891cb44c3ee952862a257ce082b098b29024d7175684a9a77b0c0", size = 51891634, upload-time = "2025-05-22T07:27:24.029Z" }, { url = "https://files.pythonhosted.org/packages/80/fa/a07e622fd87717eec3e5cff41575f85ad62717e8698884d28ca809266ca1/nodejs_wheel_binaries-22.16.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f2de4dc0b64ae43e146897ce811f80ac4f9acfbae6ccf814226282bf4ef174", size = 57857862, upload-time = "2025-05-22T07:27:27.933Z" }, { url = "https://files.pythonhosted.org/packages/1f/80/52736f9570a93f8e6b7942981dc9770eca2bc7aa1d200c1d54198374a6ca/nodejs_wheel_binaries-22.16.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbfccbcd558d2f142ccf66d8c3a098022bf4436db9525b5b8d32169ce185d99e", size = 58395868, upload-time = "2025-05-22T07:27:32.088Z" }, { url = "https://files.pythonhosted.org/packages/0f/0e/53616a5ed8fc1fbe9e48bf132862da5a9abf5cc7f8483dab1722ec257187/nodejs_wheel_binaries-22.16.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:447ad796850eb52ca20356ad39b2d296ed8fef3f214921f84a1ccdad49f2eba1", size = 59712469, upload-time = "2025-05-22T07:27:37.193Z" }, { url = "https://files.pythonhosted.org/packages/4a/cd/e2b5083df581fc1d08eb93feb6f8fbd3d56b113cef9b59d8e0fb7d4dd4f3/nodejs_wheel_binaries-22.16.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7f526ca6a132b0caf633566a2a78c6985fe92857e7bfdb37380f76205a10b808", size = 60763005, upload-time = "2025-05-22T07:27:41.39Z" }, { url = "https://files.pythonhosted.org/packages/71/8d/57112b49214e8bd636f3cc3386eba6be4d23552ec8a0f6efbe814013caa7/nodejs_wheel_binaries-22.16.0-py2.py3-none-win_amd64.whl", hash = "sha256:2fffb4bf1066fb5f660da20819d754f1b424bca1b234ba0f4fa901c52e3975fb", size = 41313324, upload-time = "2025-05-22T07:27:45.293Z" }, { url = "https://files.pythonhosted.org/packages/91/03/a852711aec73dfb965844592dfe226024c0da28e37d1ee54083342e38f57/nodejs_wheel_binaries-22.16.0-py2.py3-none-win_arm64.whl", hash = "sha256:2728972d336d436d39ee45988978d8b5d963509e06f063e80fe41b203ee80b28", size = 38828154, upload-time = "2025-05-22T07:27:48.606Z" }, ] [[package]] name = "numpy" version = "1.24.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/2c/d4/590ae7df5044465cc9fa2db152ae12468694d62d952b1528ecff328ef7fc/numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155", size = 10909904, upload-time = "2023-04-22T21:44:47.789Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f3/23/7cc851bae09cf4db90d42a701dfe525780883ada86bece45e3da7a07e76b/numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570", size = 19788647, upload-time = "2023-04-22T21:29:36.764Z" }, { url = "https://files.pythonhosted.org/packages/fa/7d/8dfb40eecbb6bc83ca00ef979f5cdeca5909a250cb8b642dcf1fbd34c078/numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7", size = 13853918, upload-time = "2023-04-22T21:29:59.09Z" }, { url = "https://files.pythonhosted.org/packages/62/e4/cd77d5f3d02c30d9ca8f2995df3cb3974c75cf1cc777fad445753475c4e4/numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463", size = 13995215, upload-time = "2023-04-22T21:30:20.33Z" }, { url = "https://files.pythonhosted.org/packages/6f/72/38f9a536bdb5bfb1682f2520f133ec6e08dde8bcca1f632e347641d90763/numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6", size = 17281879, upload-time = "2023-04-22T21:30:46.087Z" }, { url = "https://files.pythonhosted.org/packages/89/e3/e2f478b2ff131e7c3171044a87e74df61db4b67fbcb90be479c07a44d0a7/numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b", size = 12428834, upload-time = "2023-04-22T21:31:05.215Z" }, { url = "https://files.pythonhosted.org/packages/65/5d/46da284b0bf6cfbf04082c3c5e84399664d69e41c11a33587ad49b0c64e5/numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7", size = 14841158, upload-time = "2023-04-22T21:31:28.341Z" }, { url = "https://files.pythonhosted.org/packages/ec/7d/f69c47ea3db0cd8ca444aec241a80b538eb176ae756820489a9d2946ec8c/numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3", size = 19775092, upload-time = "2023-04-22T21:31:57.734Z" }, { url = "https://files.pythonhosted.org/packages/ee/6c/7217a8844dfe22e349bccbecd35571fa72c5d7fe8b33d8c5540e8cc2535c/numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf", size = 13839692, upload-time = "2023-04-22T21:32:18.71Z" }, { url = "https://files.pythonhosted.org/packages/72/eb/9c77bbc4d2b4ca17ef253621794a2d42897d896f86cd493db3eabe1a7d25/numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385", size = 13991538, upload-time = "2023-04-22T21:32:40.184Z" }, { url = "https://files.pythonhosted.org/packages/82/19/321d369ede7458500f59151101470129d14f3b6768bb9b99bb7156f526b5/numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950", size = 17278482, upload-time = "2023-04-22T21:33:05.897Z" }, { url = "https://files.pythonhosted.org/packages/94/84/ed45416c8319c02348a5812d5647796a0833e3fb5576d01758f2a72e9200/numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096", size = 12422096, upload-time = "2023-04-22T21:33:24.783Z" }, { url = "https://files.pythonhosted.org/packages/f0/e8/1ea9adebdccaadfc208c7517e09f5145ed5a73069779ff436393085d47a2/numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80", size = 14833773, upload-time = "2023-04-22T21:33:47.309Z" }, { url = "https://files.pythonhosted.org/packages/79/4a/63a79242763edde0b5025d104cc2b78c44d89310b1bbc9b0f64a96b72ea0/numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078", size = 19780221, upload-time = "2023-04-22T21:34:16.111Z" }, { url = "https://files.pythonhosted.org/packages/ca/13/c5bc0100b425f007412c3ba5d71e5ae9c08260fecbffd620764a9df1f4de/numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c", size = 13842714, upload-time = "2023-04-22T21:34:37.582Z" }, { url = "https://files.pythonhosted.org/packages/a7/fe/72493149c65dcd39d8c8dc09870e242bd689d1db2bde3ec479807bf0d414/numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c", size = 14039218, upload-time = "2023-04-22T21:35:02.43Z" }, { url = "https://files.pythonhosted.org/packages/8b/d9/814a619ab84d8eb0d95e08d4c723e665f1e694b5a6068ca505a61bdc3745/numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f", size = 17315794, upload-time = "2023-04-22T21:35:28.399Z" }, { url = "https://files.pythonhosted.org/packages/53/f7/bf6e2b973c6d6a4c60f722dd95322d4997b4999347d67c5c74a4042a07b7/numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4", size = 12455309, upload-time = "2023-04-22T21:35:47.466Z" }, { url = "https://files.pythonhosted.org/packages/1a/62/af7e78a12207608b23e3b2e248fc823fbef75f17d5defc8a127c5661daca/numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289", size = 14867757, upload-time = "2023-04-22T21:36:09.8Z" }, { url = "https://files.pythonhosted.org/packages/0d/43/643629a4a278b4815541c7d69856c07ddb0e99bdc62b43538d3751eae2d8/numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4", size = 19816043, upload-time = "2023-04-22T21:36:39.961Z" }, { url = "https://files.pythonhosted.org/packages/54/41/fb17c1d48a574c50422ff3f1b17ed979b755adc6ed291c4a44a76e226c67/numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187", size = 13879548, upload-time = "2023-04-22T21:37:01.973Z" }, { url = "https://files.pythonhosted.org/packages/96/92/2a8c1356e226311cf885e04eff576df8c357b2626c47c9283024bc24e01e/numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02", size = 14029851, upload-time = "2023-04-22T21:37:23.135Z" }, { url = "https://files.pythonhosted.org/packages/83/be/de078ac5e4ff572b1bdac1808b77cea2013b2c6286282f89b1de3e951273/numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4", size = 17310746, upload-time = "2023-04-22T21:37:49.008Z" }, { url = "https://files.pythonhosted.org/packages/d5/d6/07b37e7fecad7d158aabb4782a1b941e10afe8b80ec24cd64285a5bbb81b/numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c", size = 12454561, upload-time = "2023-04-22T21:38:09.347Z" }, { url = "https://files.pythonhosted.org/packages/76/7c/cfb8ac4925defbe222aec15ac6b42b2a3d9bab7c9d13a2e767f534b35c2e/numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17", size = 14867092, upload-time = "2023-04-22T21:38:31.671Z" }, { url = "https://files.pythonhosted.org/packages/5a/ab/d0eff89e0c05cc86fa7955c5e54e8ed0957a8a97a2516384b9ffd82008cc/numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0", size = 19156116, upload-time = "2023-04-22T21:39:00.251Z" }, { url = "https://files.pythonhosted.org/packages/eb/10/2c3c672034d860bcca50b65d656e24c4e2ace9fb452fdd81da78cb7418a1/numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812", size = 16705327, upload-time = "2023-04-22T21:39:28.148Z" }, { url = "https://files.pythonhosted.org/packages/15/b8/cbe1750b9ec78062e5a00ef39ff8bdf189ce753b411b6b35931ababaee47/numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4", size = 14733920, upload-time = "2023-04-22T21:39:52.311Z" }, ] [[package]] name = "numpy" version = "2.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, ] [[package]] name = "pandas" version = "2.0.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "numpy", version = "1.24.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "python-dateutil", marker = "python_full_version < '3.9'" }, { name = "pytz", marker = "python_full_version < '3.9'" }, { name = "tzdata", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/a7/824332581e258b5aa4f3763ecb2a797e5f9a54269044ba2e50ac19936b32/pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c", size = 5284455, upload-time = "2023-06-28T23:19:33.371Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/b2/0d4a5729ce1ce11630c4fc5d5522a33b967b3ca146c210f58efde7c40e99/pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8", size = 11760908, upload-time = "2023-06-28T23:15:57.001Z" }, { url = "https://files.pythonhosted.org/packages/4a/f6/f620ca62365d83e663a255a41b08d2fc2eaf304e0b8b21bb6d62a7390fe3/pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f", size = 10823486, upload-time = "2023-06-28T23:16:06.863Z" }, { url = "https://files.pythonhosted.org/packages/c2/59/cb4234bc9b968c57e81861b306b10cd8170272c57b098b724d3de5eda124/pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183", size = 11571897, upload-time = "2023-06-28T23:16:14.208Z" }, { url = "https://files.pythonhosted.org/packages/e3/59/35a2892bf09ded9c1bf3804461efe772836a5261ef5dfb4e264ce813ff99/pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0", size = 12306421, upload-time = "2023-06-28T23:16:23.26Z" }, { url = "https://files.pythonhosted.org/packages/94/71/3a0c25433c54bb29b48e3155b959ac78f4c4f2f06f94d8318aac612cb80f/pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210", size = 9540792, upload-time = "2023-06-28T23:16:30.876Z" }, { url = "https://files.pythonhosted.org/packages/ed/30/b97456e7063edac0e5a405128065f0cd2033adfe3716fb2256c186bd41d0/pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e", size = 10664333, upload-time = "2023-06-28T23:16:39.209Z" }, { url = "https://files.pythonhosted.org/packages/b3/92/a5e5133421b49e901a12e02a6a7ef3a0130e10d13db8cb657fdd0cba3b90/pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8", size = 11645672, upload-time = "2023-06-28T23:16:47.601Z" }, { url = "https://files.pythonhosted.org/packages/8f/bb/aea1fbeed5b474cb8634364718abe9030d7cc7a30bf51f40bd494bbc89a2/pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26", size = 10693229, upload-time = "2023-06-28T23:16:56.397Z" }, { url = "https://files.pythonhosted.org/packages/d6/90/e7d387f1a416b14e59290baa7a454a90d719baebbf77433ff1bdcc727800/pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d", size = 11581591, upload-time = "2023-06-28T23:17:04.234Z" }, { url = "https://files.pythonhosted.org/packages/d0/28/88b81881c056376254618fad622a5e94b5126db8c61157ea1910cd1c040a/pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df", size = 12219370, upload-time = "2023-06-28T23:17:11.783Z" }, { url = "https://files.pythonhosted.org/packages/e4/a5/212b9039e25bf8ebb97e417a96660e3dc925dacd3f8653d531b8f7fd9be4/pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd", size = 9482935, upload-time = "2023-06-28T23:17:21.376Z" }, { url = "https://files.pythonhosted.org/packages/9e/71/756a1be6bee0209d8c0d8c5e3b9fc72c00373f384a4017095ec404aec3ad/pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b", size = 10607692, upload-time = "2023-06-28T23:17:28.824Z" }, { url = "https://files.pythonhosted.org/packages/78/a8/07dd10f90ca915ed914853cd57f79bfc22e1ef4384ab56cb4336d2fc1f2a/pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061", size = 11653303, upload-time = "2023-06-28T23:17:36.329Z" }, { url = "https://files.pythonhosted.org/packages/53/c3/f8e87361f7fdf42012def602bfa2a593423c729f5cb7c97aed7f51be66ac/pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5", size = 10710932, upload-time = "2023-06-28T23:17:49.875Z" }, { url = "https://files.pythonhosted.org/packages/a7/87/828d50c81ce0f434163bf70b925a0eec6076808e0bca312a79322b141f66/pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089", size = 11684018, upload-time = "2023-06-28T23:18:05.845Z" }, { url = "https://files.pythonhosted.org/packages/f8/7f/5b047effafbdd34e52c9e2d7e44f729a0655efafb22198c45cf692cdc157/pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0", size = 12353723, upload-time = "2023-06-28T23:18:17.631Z" }, { url = "https://files.pythonhosted.org/packages/ea/ae/26a2eda7fa581347d69e51f93892493b2074ef3352ac71033c9f32c52389/pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02", size = 9646403, upload-time = "2023-06-28T23:18:24.328Z" }, { url = "https://files.pythonhosted.org/packages/c3/6c/ea362eef61f05553aaf1a24b3e96b2d0603f5dc71a3bd35688a24ed88843/pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78", size = 10777638, upload-time = "2023-06-28T23:18:30.947Z" }, { url = "https://files.pythonhosted.org/packages/f8/c7/cfef920b7b457dff6928e824896cb82367650ea127d048ee0b820026db4f/pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b", size = 11834160, upload-time = "2023-06-28T23:18:40.332Z" }, { url = "https://files.pythonhosted.org/packages/6c/1c/689c9d99bc4e5d366a5fd871f0bcdee98a6581e240f96b78d2d08f103774/pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e", size = 10862752, upload-time = "2023-06-28T23:18:50.016Z" }, { url = "https://files.pythonhosted.org/packages/cc/b8/4d082f41c27c95bf90485d1447b647cc7e5680fea75e315669dc6e4cb398/pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b", size = 11715852, upload-time = "2023-06-28T23:19:00.594Z" }, { url = "https://files.pythonhosted.org/packages/9e/0d/91a9fd2c202f2b1d97a38ab591890f86480ecbb596cbc56d035f6f23fdcc/pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641", size = 12398496, upload-time = "2023-06-28T23:19:11.78Z" }, { url = "https://files.pythonhosted.org/packages/26/7d/d8aa0a2c4f3f5f8ea59fb946c8eafe8f508090ca73e2b08a9af853c1103e/pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", size = 9630766, upload-time = "2023-06-28T23:19:18.182Z" }, { url = "https://files.pythonhosted.org/packages/9a/f2/0ad053856debbe90c83de1b4f05915f85fd2146f20faf9daa3b320d36df3/pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc", size = 10755902, upload-time = "2023-06-28T23:19:25.151Z" }, ] [[package]] name = "pandas" version = "2.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "python-dateutil", marker = "python_full_version >= '3.9'" }, { name = "pytz", marker = "python_full_version >= '3.9'" }, { name = "tzdata", marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827, upload-time = "2024-09-20T13:08:42.347Z" }, { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897, upload-time = "2024-09-20T13:08:45.807Z" }, { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908, upload-time = "2024-09-20T18:37:13.513Z" }, { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210, upload-time = "2024-09-20T13:08:48.325Z" }, { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292, upload-time = "2024-09-20T19:01:54.443Z" }, { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379, upload-time = "2024-09-20T13:08:50.882Z" }, { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471, upload-time = "2024-09-20T13:08:53.332Z" }, { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222, upload-time = "2024-09-20T13:08:56.254Z" }, { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274, upload-time = "2024-09-20T13:08:58.645Z" }, { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836, upload-time = "2024-09-20T19:01:57.571Z" }, { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505, upload-time = "2024-09-20T13:09:01.501Z" }, { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420, upload-time = "2024-09-20T19:02:00.678Z" }, { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457, upload-time = "2024-09-20T13:09:04.105Z" }, { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166, upload-time = "2024-09-20T13:09:06.917Z" }, { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893, upload-time = "2024-09-20T13:09:09.655Z" }, { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475, upload-time = "2024-09-20T13:09:14.718Z" }, { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645, upload-time = "2024-09-20T19:02:03.88Z" }, { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445, upload-time = "2024-09-20T13:09:17.621Z" }, { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235, upload-time = "2024-09-20T19:02:07.094Z" }, { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756, upload-time = "2024-09-20T13:09:20.474Z" }, { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248, upload-time = "2024-09-20T13:09:23.137Z" }, { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643, upload-time = "2024-09-20T13:09:25.522Z" }, { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573, upload-time = "2024-09-20T13:09:28.012Z" }, { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085, upload-time = "2024-09-20T19:02:10.451Z" }, { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809, upload-time = "2024-09-20T13:09:30.814Z" }, { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316, upload-time = "2024-09-20T19:02:13.825Z" }, { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055, upload-time = "2024-09-20T13:09:33.462Z" }, { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175, upload-time = "2024-09-20T13:09:35.871Z" }, { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650, upload-time = "2024-09-20T13:09:38.685Z" }, { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177, upload-time = "2024-09-20T13:09:41.141Z" }, { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526, upload-time = "2024-09-20T19:02:16.905Z" }, { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013, upload-time = "2024-09-20T13:09:44.39Z" }, { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620, upload-time = "2024-09-20T19:02:20.639Z" }, { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" }, { url = "https://files.pythonhosted.org/packages/ca/8c/8848a4c9b8fdf5a534fe2077af948bf53cd713d77ffbcd7bd15710348fd7/pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39", size = 12595535, upload-time = "2024-09-20T13:09:51.339Z" }, { url = "https://files.pythonhosted.org/packages/9c/b9/5cead4f63b6d31bdefeb21a679bc5a7f4aaf262ca7e07e2bc1c341b68470/pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30", size = 11319822, upload-time = "2024-09-20T13:09:54.31Z" }, { url = "https://files.pythonhosted.org/packages/31/af/89e35619fb573366fa68dc26dad6ad2c08c17b8004aad6d98f1a31ce4bb3/pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c", size = 15625439, upload-time = "2024-09-20T19:02:23.689Z" }, { url = "https://files.pythonhosted.org/packages/3d/dd/bed19c2974296661493d7acc4407b1d2db4e2a482197df100f8f965b6225/pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c", size = 13068928, upload-time = "2024-09-20T13:09:56.746Z" }, { url = "https://files.pythonhosted.org/packages/31/a3/18508e10a31ea108d746c848b5a05c0711e0278fa0d6f1c52a8ec52b80a5/pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea", size = 16783266, upload-time = "2024-09-20T19:02:26.247Z" }, { url = "https://files.pythonhosted.org/packages/c4/a5/3429bd13d82bebc78f4d78c3945efedef63a7cd0c15c17b2eeb838d1121f/pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761", size = 14450871, upload-time = "2024-09-20T13:09:59.779Z" }, { url = "https://files.pythonhosted.org/packages/2f/49/5c30646e96c684570925b772eac4eb0a8cb0ca590fa978f56c5d3ae73ea1/pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e", size = 11618011, upload-time = "2024-09-20T13:10:02.351Z" }, ] [[package]] name = "pandas-stubs" version = "2.0.3.230814" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "numpy", version = "1.24.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.8'" }, { name = "types-pytz", version = "2024.2.0.20241221", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/73/852fec3fa5dd3072c261fb5153e82badcb6da63d8c3bbd4b386af0005379/pandas_stubs-2.0.3.230814.tar.gz", hash = "sha256:1d5cc09e36e3d9f9a1ed9dceae4e03eeb26d1b898dd769996925f784365c8769", size = 100183, upload-time = "2023-08-14T14:09:22.032Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/e9/e9550b05e262afc75b014b79224b6910f7cbc85cc40448b709ac90a58ecd/pandas_stubs-2.0.3.230814-py3-none-any.whl", hash = "sha256:4b3dfc027d49779176b7daa031a3405f7b839bcb6e312f4b9f29fea5feec5b4f", size = 153118, upload-time = "2023-08-14T14:09:20.613Z" }, ] [[package]] name = "pandas-stubs" version = "2.2.2.240807" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", ] dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "types-pytz", version = "2025.2.0.20250516", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1f/df/0da95bc75c76f1e012e0bc0b76da31faaf4254e94b9870f25e6311145e98/pandas_stubs-2.2.2.240807.tar.gz", hash = "sha256:64a559725a57a449f46225fbafc422520b7410bff9252b661a225b5559192a93", size = 103095, upload-time = "2024-08-07T12:30:54.538Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/f9/22c91632ea1b4c6165952f677bf9ad95f9ac36ffd7ef3e6450144e6d8b1a/pandas_stubs-2.2.2.240807-py3-none-any.whl", hash = "sha256:893919ad82be4275f0d07bb47a95d08bae580d3fdea308a7acfcb3f02e76186e", size = 157069, upload-time = "2024-08-07T12:30:51.868Z" }, ] [[package]] name = "pandas-stubs" version = "2.2.3.250527" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", ] dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "types-pytz", version = "2025.2.0.20250516", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5f/0d/5fe7f7f3596eb1c2526fea151e9470f86b379183d8b9debe44b2098651ca/pandas_stubs-2.2.3.250527.tar.gz", hash = "sha256:e2d694c4e72106055295ad143664e5c99e5815b07190d1ff85b73b13ff019e63", size = 106312, upload-time = "2025-05-27T15:24:29.716Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/f8/46141ba8c9d7064dc5008bfb4a6ae5bd3c30e4c61c28b5c5ed485bf358ba/pandas_stubs-2.2.3.250527-py3-none-any.whl", hash = "sha256:cd0a49a95b8c5f944e605be711042a4dd8550e2c559b43d70ba2c4b524b66163", size = 159683, upload-time = "2025-05-27T15:24:28.4Z" }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] name = "pygments" version = "2.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905, upload-time = "2024-05-04T13:42:02.013Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513, upload-time = "2024-05-04T13:41:57.345Z" }, ] [[package]] name = "pyright" version = "1.1.401" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/79/9a/7ab2b333b921b2d6bfcffe05a0e0a0bbeff884bd6fb5ed50cd68e2898e53/pyright-1.1.401.tar.gz", hash = "sha256:788a82b6611fa5e34a326a921d86d898768cddf59edde8e93e56087d277cc6f1", size = 3894193, upload-time = "2025-05-21T10:44:52.03Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0d/e6/1f908fce68b0401d41580e0f9acc4c3d1b248adcff00dfaad75cd21a1370/pyright-1.1.401-py3-none-any.whl", hash = "sha256:6fde30492ba5b0d7667c16ecaf6c699fab8d7a1263f6a18549e0b00bf7724c06", size = 5629193, upload-time = "2025-05-21T10:44:50.129Z" }, ] [[package]] name = "python-dateutil" version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "pytz" version = "2024.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/90/26/9f1f00a5d021fff16dee3de13d43e5e978f3d58928e129c3a62cf7eb9738/pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", size = 316214, upload-time = "2024-02-02T01:18:41.693Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9c/3d/a121f284241f08268b21359bd425f7d4825cffc5ac5cd0e1b3d82ffd2b10/pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319", size = 505474, upload-time = "2024-02-02T01:18:37.283Z" }, ] [[package]] name = "rich" version = "13.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, { name = "typing-extensions", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cf/60/5959113cae0ce512cf246a6871c623117330105a0d5f59b4e26138f2c9cc/rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4", size = 222072, upload-time = "2024-08-26T16:12:27.073Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c7/d9/c2a126eeae791e90ea099d05cb0515feea3688474b978343f3cdcfe04523/rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc", size = 241597, upload-time = "2024-08-26T16:12:25.449Z" }, ] [[package]] name = "six" version = "1.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, ] [[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" }, ] [[package]] name = "type-lens" version = "0.2.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/55/8846322acedf26cafb4a66dd23fdca6bc73cb3f8c0029040a9b96f5aa0dd/type_lens-0.2.6.tar.gz", hash = "sha256:5d46ab3bc2dfafc174cad702eccb4418c31e38e258e87945a19d86ebc204d08b", size = 362277, upload-time = "2025-10-20T20:08:28.07Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/c5/30de077e06f7ffc3fe21d45ce26b8088816227b678a7157f6b9e080ff2db/type_lens-0.2.6-py3-none-any.whl", hash = "sha256:aade6beb3eca337c90d1b627b509768473bbd591e233ed6b57fb93ec2a01bca6", size = 14451, upload-time = "2025-10-20T20:08:26.936Z" }, ] [[package]] name = "types-pytz" version = "2024.2.0.20241221" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/54/26/516311b02b5a215e721155fb65db8a965d061372e388d6125ebce8d674b0/types_pytz-2024.2.0.20241221.tar.gz", hash = "sha256:06d7cde9613e9f7504766a0554a270c369434b50e00975b3a4a0f6eed0f2c1a9", size = 10213, upload-time = "2024-12-21T02:40:48.654Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/74/db/c92ca6920cccd9c2998b013601542e2ac5e59bc805bcff94c94ad254b7df/types_pytz-2024.2.0.20241221-py3-none-any.whl", hash = "sha256:8fc03195329c43637ed4f593663df721fef919b60a969066e22606edf0b53ad5", size = 10008, upload-time = "2024-12-21T02:40:47.047Z" }, ] [[package]] name = "types-pytz" version = "2025.2.0.20250516" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/bd/72/b0e711fd90409f5a76c75349055d3eb19992c110f0d2d6aabbd6cfbc14bf/types_pytz-2025.2.0.20250516.tar.gz", hash = "sha256:e1216306f8c0d5da6dafd6492e72eb080c9a166171fa80dd7a1990fd8be7a7b3", size = 10940, upload-time = "2025-05-16T03:07:01.91Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ba/e205cd11c1c7183b23c97e4bcd1de7bc0633e2e867601c32ecfc6ad42675/types_pytz-2025.2.0.20250516-py3-none-any.whl", hash = "sha256:e0e0c8a57e2791c19f718ed99ab2ba623856b11620cb6b637e5f62ce285a7451", size = 10136, upload-time = "2025-05-16T03:07:01.075Z" }, ] [[package]] name = "typing-extensions" version = "4.12.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, ] [[package]] name = "tzdata" version = "2024.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/74/5b/e025d02cb3b66b7b76093404392d4b44343c69101cc85f4d180dd5784717/tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", size = 190559, upload-time = "2024-02-11T23:22:40.2Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/65/58/f9c9e6be752e9fcb8b6a0ee9fb87e6e7a1f6bcab2cdc73f02bb7ba91ada0/tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252", size = 345370, upload-time = "2024-02-11T23:22:38.223Z" }, ] cappa-0.32.2/examples/defer_import_slow_startup/defer_import/__init__.py0000644000000000000000000000000013615410400023503 0ustar00cappa-0.32.2/examples/defer_import_slow_startup/defer_import/main.py0000644000000000000000000000051213615410400022700 0ustar00from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class DeferImport: command: cappa.Subcommands[MeanCommand] @cappa.command(name="mean", invoke="defer_import.mean.calculate_mean") @dataclass class MeanCommand: numbers: list[int] def main(): cappa.invoke(DeferImport) cappa-0.32.2/examples/defer_import_slow_startup/defer_import/mean.py0000644000000000000000000000050613615410400022677 0ustar00import pandas from cappa.output import Output from defer_import.main import MeanCommand def calculate_mean(command: MeanCommand, output: Output): if not command.numbers: output.output("0.0") return series = pandas.Series(command.numbers) result = float(series.mean()) output.output(result) cappa-0.32.2/examples/docutils/conf.py0000644000000000000000000000040713615410400014546 0ustar00import sys sys.path.insert(0, ".") project = "docutils" copyright = "2025, docutils" author = "docutils" release = "docutils" extensions = [ "cappa.ext.docutils", ] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".venv"] html_theme = "alabaster" cappa-0.32.2/examples/docutils/example.py0000644000000000000000000000011113615410400015244 0ustar00from dataclasses import dataclass @dataclass() class Foo: bar: int cappa-0.32.2/examples/docutils/index.rst0000644000000000000000000000010113615410400015077 0ustar00.. cappa:: example.Foo :style: terminal :terminal-width: 0 cappa-0.32.2/examples/test_kitchen_sink/conftest.py0000644000000000000000000000016513615410400017331 0ustar00import example import pytest def test_help(): with pytest.raises(SystemExit): example.main(["--help"]) cappa-0.32.2/examples/test_kitchen_sink/example.py0000755000000000000000000000332513615410400017143 0ustar00#!/usr/bin/env python from __future__ import annotations import enum import logging import sqlite3 from dataclasses import dataclass, field from typing import Literal, Union from typing_extensions import Annotated import cappa log = logging.getLogger(__name__) class OtherOptions(enum.Enum): a = "a" b = "b" c = "c" @dataclass class Example: """Example program. Args: name: Required name argument flag: optional flag to enable """ name: str value: Annotated[int, cappa.Arg(short="-V", long="--val", default=0)] options: Annotated[ Union[Literal["one"], Literal["two"], Literal[3]], cappa.Arg(short=True) ] moptions: Annotated[OtherOptions, cappa.Arg(short=True)] subcommand: Annotated[Union[MeowCommand, BarkCommand], cappa.Subcommand] flags: Annotated[list[str], cappa.Arg(short=True, long=True)] = field( default_factory=lambda: [] ) flag: bool = False # --flag def db() -> sqlite3.Connection: return sqlite3.connect("sqlite:///") def meow( command: Example, meow: MeowCommand, db: Annotated[sqlite3.Connection, cappa.Dep(db)], ): result = db.execute(f"select '{command.name}', {meow.times} + 1").fetchone() log.info(result) @cappa.command(name="meow", invoke=meow) @dataclass class MeowCommand: times: int def bark(bark: BarkCommand): log.info(bark) @cappa.command(name="bark", invoke=bark) @dataclass class BarkCommand: ... # invoke cli parsing def main(argv: list[str] | None = None): logging.basicConfig() args: Example = cappa.parse(Example, argv=argv, version="1.2.3") log.info(args) cappa.invoke(Example, argv=argv, version="1.2.3") if __name__ == "__main__": main() cappa-0.32.2/examples/todo_list/todo.py0000644000000000000000000000151213615410400014736 0ustar00from __future__ import annotations import json from dataclasses import dataclass from pathlib import Path import cappa @dataclass class Todo: command: cappa.Subcommands[AddCommand | ListCommand] @cappa.command(name="add") @dataclass class AddCommand: todo: str def __call__(self): path = Path("todo.json") data: list[str] = [] if path.exists(): data = json.loads(path.read_text()) data.append(self.todo) path.write_text(json.dumps(data)) @cappa.command(name="list") @dataclass class ListCommand: def __call__(self, output: cappa.Output): path = Path("todo.json") data: list[str] = [] if path.exists(): data = json.loads(path.read_text()) for item in data: output.output(f" - {item}") cappa.invoke(Todo) cappa-0.32.2/src/cappa/__init__.py0000644000000000000000000000270013615410400013565 0ustar00from cappa.base import collect, command, invoke, invoke_async, parse, parse_async from cappa.command import Alias, Command, FinalCommand from cappa.completion.types import Completion from cappa.default import Confirm, Default, Env, Prompt, ValueFrom from cappa.file_io import FileMode from cappa.help import HelpFormattable, HelpFormatter from cappa.invoke.types import Dep, Self from cappa.output import Exit, HelpExit, Output from cappa.parse import default_parse, unpack_arguments from cappa.state import State from cappa.subcommand import FinalSubcommand, Subcommand, Subcommands from cappa.type_view import Empty, EmptyType # isort: split from cappa.arg import Arg, ArgAction, FinalArg, Group, NumArgs from cappa.destructure import Destructured # isort: split from cappa import argparse from cappa.parser import backend __all__ = [ "Alias", "Arg", "ArgAction", "Command", "Completion", "Confirm", "Default", "Dep", "Destructured", "Empty", "EmptyType", "Env", "Exit", "FileMode", "FinalArg", "FinalCommand", "FinalSubcommand", "Group", "HelpExit", "HelpFormattable", "HelpFormatter", "NumArgs", "Output", "Prompt", "Self", "State", "Subcommand", "Subcommands", "ValueFrom", "argparse", "backend", "collect", "command", "default_parse", "invoke", "invoke_async", "parse", "parse_async", "unpack_arguments", ] cappa-0.32.2/src/cappa/arg.py0000644000000000000000000010370713615410400012610 0ustar00"""Define the external facing argument definition types provided by a user.""" from __future__ import annotations import dataclasses import enum from functools import cached_property from typing import ( TYPE_CHECKING, Any, Callable, Generic, Iterable, Literal, Sequence, Set, TextIO, Union, cast, ) from typing_extensions import TypeAlias from cappa.class_inspect import Field, extract_dataclass_metadata from cappa.completion.completers import complete_choices from cappa.completion.types import Completion from cappa.default import Default, DefaultFormatter, ValueFrom from cappa.invoke.types import Resolved from cappa.parse import ( Parser, evaluate_parse, parse_handler, parse_literal, parse_value, ) from cappa.state import State from cappa.type_view import Empty, EmptyType, TypeView from cappa.typing import ( Doc, DocType, T, detect_choices, find_annotations, ) if TYPE_CHECKING: from cappa.destructure import Destructure @enum.unique class ArgAction(enum.Enum): """`Arg` action typee. Options: - set: Stores the given CLI value directly. - store_true: Stores a literal `True` value, causing options to not attempt to consume additional CLI arguments - store_false: Stores a literal `False` value, causing options to not attempt to consume additional CLI arguments - append: Produces a list, and accumulates the given value on top of prior values. - count: Increments an integer starting at 0 - help: Cancels argument parsing and prints the help text - version: Cancels argument parsing and prints the CLI version - completion: Cancels argument parsing and enters "completion mode" """ set = "store" store_true = "store_true" store_false = "store_false" append = "append" count = "count" help = "help" version = "version" completion = "completion" @classmethod def meta_actions(cls) -> Set[ArgAction]: return {cls.help, cls.version, cls.completion} @classmethod def is_custom(cls, action: ArgAction | Callable[..., Any] | None): return action is not None and not isinstance(action, ArgAction) @classmethod def is_non_value_consuming(cls, action: ArgAction | Callable[..., Any] | None): return action in { ArgAction.store_true, ArgAction.store_false, ArgAction.count, ArgAction.version, ArgAction.help, } @property def is_bool_action(self): return self in {self.store_true, self.store_false} ArgActionType: TypeAlias = Union[ArgAction, Callable[..., Any]] @dataclasses.dataclass(frozen=True) class NumArgs: """Control the number of values consumed for an argument, with optional-value support. Note: The vast majority of uses of `Arg.num_args` should be covered by supplying an integer value, or `NumArgs.unbounded()`. When `required=False`, the argument can be supplied without a value (e.g. `--foo` with no following value), yielding the `NumArgs.default` rather than the Arg's overall default. This maps to argparse's `nargs='?'` / `const` behaviour or click' s `is_flag=True` / `flag_value` behavior. Args: n: Number of values to consume. ``-1`` means unbounded. Defaults to ``1``. required: Whether a value is required when the option flag is present. When ``False``, ``--foo`` (no value) yields ``default`` and ``--foo VAL`` yields the parsed ``VAL``. Defaults to ``True``. default: Value used when the flag is present but no value follows. Only meaningful when ``required=False``. Defaults to ``None``. Example:: @dataclass class Args: # test.py -> Args(foo=5) (class default, flag not given) # test.py --foo -> Args(foo=None) (flag given, no value → NumArgs.default) # test.py --foo 4 -> Args(foo=4) (flag given with value) foo: Annotated[ int | None, Arg(long=True, num_args=NumArgs(required=False)) ] = 5 """ n: int = 1 required: bool = True default: Any = None @classmethod def unbounded(cls): return cls(n=-1) @classmethod def infer( cls, type_view: TypeView[Any], field_name: str, arg: Arg[Any] | None = None, action: ArgActionType | None = None, long: list[str] | Literal[False] = False, ) -> NumArgs: if arg: if arg.num_args is not None: if isinstance(arg.num_args, NumArgs): return arg.num_args return cls(n=arg.num_args) if arg.parse: return cls() if ArgAction.is_non_value_consuming(action): return cls(n=0) if type_view.is_union: # Recursively determine the `n` for each variant. Accept the # result only if all non-None variants agree on the same count. distinct_n: set[int] = set() n_variants: list[tuple[Any, int]] = [] for type_arg in type_view.inner_types: if type_arg.is_none_type: continue inferred = cls.infer( type_arg, field_name, arg=arg, action=action, long=long ) distinct_n.add(inferred.n) n_variants.append((type_arg.raw, inferred.n)) if len(distinct_n) == 1: return cls(n=distinct_n.pop()) invalid_kinds = ", ".join( [f"`{t}` produces `num_args={n}`" for t, n in n_variants] ) raise ValueError( f"On field '{field_name}', mismatch of arity between union variants. {invalid_kinds}." ) if type_view.is_tuple and not type_view.is_variadic_tuple: return cls(n=len(type_view.args)) is_positional = arg is None or (not arg.short and not long) is_sequence = type_view.is_variadic_tuple or type_view.is_subclass_of( ( list, set, ) ) if is_positional and is_sequence: return cls.unbounded() # Options whose outer type is a sequence derive count from the inner type. if type_view.is_subclass_of((list, set, tuple)): inner = cls.infer(type_view.inner_types[0], field_name) return cls(n=inner.n) return cls() @staticmethod def raw_n(num_args: int | NumArgs | None) -> int | None: """Extract the integer count from a raw (pre-normalization) num_args value.""" if isinstance(num_args, NumArgs): return num_args.n return num_args def __str__(self) -> str: return str(self.n) @dataclasses.dataclass(frozen=True) class Group: """Object used to control argument/subcommand grouping in generated help text. Note, when implicit group syntax is used, exclusive groups are automatically inferred, and the group identity is inferred to be the argument's `field_name`. E.g. `foo: Annotated[bool, Arg(long="--foo"), Arg(long="--bar")]`. Args: order: A number representing the relative ordering among different argument groups. Groups with the same order will be displayed alphabetically by name. name: The display name of the group in help text. exclusive: Used to control whether items in the group are mutually exclusive with each other. If `True`, the name of the group will be used to control group identity. If a string is provided, items with the same exclusive string will be considered part of the same exclusivity group. section: A secondary level of ordering. Sections have a higher order precedence than ``order``, in order to facilitate meta-ordering amongst kinds of groups (such as "meta" arguments (``--help``, ``--version``, etc) and subcommands). The default ``section`` for any normal argument/``Group`` is 0, for ``Subcommand`` it is 1, and for "meta" arguments it is 2. Examples: A non-exclusive group which just affects helptext display grouping >>> arg = Arg(group=Group(name="Colors")) An exclusive group, in which all "Colors"-named arguments are mutually exclusive >>> arg = Arg(group=Group(name="Colors", exclusive=True)) An exclusive group, in which all "colors"-identity arguments are mutually exclusive, but whose helptext is grouped alongside all other "Options". >>> arg = Arg(group=Group(name="Options", exclusive="colors")) """ order: int = dataclasses.field(default=0, compare=False) name: str = "" exclusive: bool | str | None = dataclasses.field(default=None, compare=True) section: int = 0 id: str = "" @cached_property def key(self): return (self.section, self.order, self.name, bool(self.exclusive)) def diff(self, other: Group) -> str: """Return a string showing only the attributes that differ between two Groups. Returns a string in the format: "Group(attr=value, ...) != Group(attr=other_value, ...)" """ differing_attrs: list[str] = ["id"] for field in dataclasses.fields(self): if not field.compare and field.name != "id": continue self_value = getattr(self, field.name) other_value = getattr(other, field.name) if self_value != other_value: differing_attrs.append(field.name) self_parts = [f"{attr}={getattr(self, attr)!r}" for attr in differing_attrs] other_parts = [f"{attr}={getattr(other, attr)!r}" for attr in differing_attrs] return f"Group({', '.join(self_parts)}) != Group({', '.join(other_parts)})" @classmethod def infer( cls, arg: Arg[Any], is_option: bool, exclusive: bool | str = False, ) -> Group: order = 0 name = None section = 0 if isinstance(arg.group, Group): order = arg.group.order section = arg.group.section if arg.group.name: name = arg.group.name if arg.group.exclusive is not None: exclusive = arg.group.exclusive if isinstance(arg.group, str): name = arg.group if isinstance(arg.group, tuple): order, name = arg.group if name is None: if is_option: name = "Options" else: name = "Arguments" order = 1 if isinstance(exclusive, str): identity = exclusive else: identity = name return cls( name=name, order=order, exclusive=bool(exclusive), section=section, id=identity, ) @dataclasses.dataclass(frozen=True) class Arg(Generic[T]): """Describe a CLI argument. Arguments: value_name: Placeholder for the argument's value in the help message / usage. Defaults to the name of the corresponding class field. short: If `True`, uses first letter of the name to infer a (ex. `-s`) short flag. If a string is supplied, that will be used instead. If a string is supplied, it is split on '/' (forward slash), to support multiple options. Additionally accepts a list of strings. long: If `True`, uses first letter of the name to infer a (ex. `--long`) long flag. If a string is supplied, that will be used instead. If a string is supplied, it is split on '/' (forward slash), to support multiple options. Additionally accepts a list of strings. count: If `True` the resultant argument will count instances and accept zero arguments. default: An explicit default CLI value. When left unspecified, the default is inferred from the class' default or the adapter default/default_factory. Note, if the default value is used, it will not be coerced into the annotated type through the `parse` method. help: By default, the help text will be inferred from the containing class' arguments' section, if it exists. Alternatively, you can directly supply the help text here. parse: Optional function(s) which accepts the raw argument as input and return the parsed value. This can be either a single function, or a sequence of functions. If a sequence is provided, they will be called (in order) with their return value chained into the next function in the sequence. parse_inference: Whether to include the default inferred parser for the annotated. group: Optional group names for the argument. This affects how they're displayed in the backend's help text. Note this can also be a `Group` instance in order to control group order and/or group exclusivity. hidden: Whether the argument should be hidden in help text. Defaults to False. action: Generally automatically inferred from the data type. This allows to override the default. num_args: Generally automatically inferred from the data type. This allows to override the default. choices: Generally automatically inferred from the data type. This allows to override the default. completion: Used to provide custom completions. If specified, should be a function which accepts a partial string value and returns a list of [cappa.Completion](cappa.Completion) objects. required: Defaults to automatically inferring requiredness, based on whether the class's value has a default. By setting this, you can force a particular value. field_name: The name of the class field to populate with this arg. In most usecases, this field should be left unspecified and automatically inferred. deprecated: If supplied, the argument will be marked as deprecated. If given `True`, a default message will be generated, otherwise a supplied string will be used as the deprecation message. show_default: Whether to show the default value in help text. Defaults to `True`. destructure: When set, destructures the annotated type into current-level arguments. Generally prefer to use `field: Destructured[T]` syntax to setting this directly. See `Arg.destructure`. has_value: Whether the argument has a value that should be saved back to the destination type. For most `Arg`, this will default to `True`, however `--help` is an example of an `Arg` for which it is false. propagate: Specifies that an argument can be matched to all child. Global arguments only propagate down. When used at the top-level, in effect it creates a "global" argument. """ def __hash__(self): return id(self) value_name: str | EmptyType = Empty short: bool | str | list[str] | None = False long: bool | str | list[str] | None = False count: bool = False default: Default | T | EmptyType | None = Empty help: str | None = None parse: Callable[..., T] | Sequence[Callable[..., Any]] | None = None parse_inference: bool = True group: str | tuple[int, str] | Group | EmptyType = Empty hidden: bool = False action: ArgActionType | None = None num_args: int | NumArgs | None = None choices: list[str] | None = None completion: Callable[..., list[Completion]] | None = None required: bool | None = None field_name: str | EmptyType = Empty deprecated: bool | str = False show_default: bool | str | DefaultFormatter = True propagate: bool = False destructure: Destructure | bool | None = None has_value: bool | None = None type_view: TypeView[Any] | None = None @classmethod def collect( cls, field: Field, type_view: TypeView[Any], fallback_help: str | None = None, default_short: bool = False, default_long: bool = False, state: State[Any] | None = None, ) -> list[FinalArg[Any] | FinalDestructure[Any]]: args: list[Arg[Any]] = find_annotations(type_view, cls) or [Arg()] exclusive = field.name if len(args) > 1 else False docs: list[DocType] = find_annotations(type_view, Doc) fallback_help = docs[0].documentation if docs else fallback_help destructure_: list[Destructure] = find_annotations(type_view, Destructure) destructure_annotation: Destructure | None = ( destructure_[0] if destructure_ else None ) # Dataclass field metadata takes precedence if it exists. field_metadata: list[Arg[Any]] = extract_dataclass_metadata(field, Arg) if field_metadata: args = field_metadata result: list[FinalArg[Any] | FinalDestructure[Any]] = [] for i, arg in enumerate(args, start=1): # field_name and default are evaluated outside `normalize` because they can # potentially depend on the type's dataclass-like field information, whereas # nothing else does. field_name = infer_field_name(arg, field) default = infer_default(arg, type_view, field, destructure_annotation) normalized_arg = arg.normalize( type_view, fallback_help=fallback_help, default_short=default_short, default_long=default_long, exclusive=exclusive, field_name=field_name, default=default, state=state, destructure=destructure_annotation, show_default=arg.show_default if i == len(args) else False, ) if normalized_arg.destructure: result.extend(normalized_arg.destructure.explode_args()) else: result.append(normalized_arg) return list(explode_negated_bool_args(result)) def normalize( self, type_view: TypeView[Any] | None = None, fallback_help: str | None = None, action: ArgActionType | None = None, default: Any = Empty, field_name: str | None = None, default_short: bool = False, default_long: bool = False, exclusive: bool | str = False, state: State[Any] | None = None, destructure: Destructure | bool | None = None, show_default: bool | str | DefaultFormatter | None = None, ) -> FinalArg[Any]: if type_view is None: type_view = TypeView(Any) default = Default.fallback_from(default, self.default) field_name = cast(str, field_name or self.field_name) short = infer_short(self, field_name, default_short) long = infer_long(self, type_view, field_name, default_long) choices = infer_choices(self, type_view) action = action or infer_action(self, type_view, long, default) num_args = NumArgs.infer( type_view, field_name, arg=self, action=action, long=long ) required = infer_required(self.required, num_args, self.num_args, default) parse = infer_parse(self, type_view, state=state) help = infer_help(self, fallback_help) completion = infer_completion(self, choices) group = Group.infer(self, bool(short or long), exclusive) value_name = infer_value_name(self, field_name, num_args) has_value = infer_has_value(self, action) if self.propagate and not short and not long: raise ValueError( "`Arg.propagate` requires a non-positional named option (`short` or `long`)." ) default_formatter: DefaultFormatter = DefaultFormatter.from_unknown( show_default if show_default is not None else self.show_default ) destructure = Destructure.collect( field_name, default, destructure or self.destructure, type_view, default_short=default_short, default_long=default_long, ) result: FinalArg[Any] = FinalArg( # preserved from self count=self.count, hidden=self.hidden, deprecated=self.deprecated, propagate=self.propagate, parse_inference=self.parse_inference, # computed/narrowed value_name=value_name, short=short, long=long, default=default, help=help, parse=parse, group=group, action=action, num_args=num_args, choices=choices, completion=completion, required=required, field_name=field_name, show_default=default_formatter, destructure=destructure, has_value=has_value, type_view=type_view, ) verify_type_compatibility( result, field_name, type_view, has_custom_parse=bool(self.parse) ) return result @classmethod def destructured(cls): """Mark a an argument as destructured. See also the shorter `Destructured` alias.""" return cls(destructure=Destructure()) @dataclasses.dataclass(frozen=True) class FinalArg(Arg[T]): """Post-normalization form of :class:`Arg` with narrowed field types. Produced exclusively by :meth:`Arg.normalize`. After normalization all previously-optional/Empty fields are guaranteed to be concrete values. """ value_name: str = "" short: list[str] | Literal[False] = False long: list[str] | Literal[False] = False default: Default = dataclasses.field(default_factory=Default) group: Group = dataclasses.field(default_factory=Group) action: ArgActionType = ArgAction.set num_args: NumArgs = dataclasses.field(default_factory=NumArgs) required: bool = False field_name: str = "" has_value: bool = True type_view: TypeView[Any] = dataclasses.field(default_factory=lambda: TypeView(Any)) parse: Callable[..., Any] = dataclasses.field(default=parse_value) show_default: DefaultFormatter = dataclasses.field(default_factory=DefaultFormatter) destructure: FinalDestructure[Any] | None = None def map_result( self, prog: str, parsed_args: dict[str, Any], state: State[Any] | None = None, input: TextIO | None = None, ) -> Resolved[T]: field_name = self.field_name if field_name in parsed_args: is_parsed, value = False, parsed_args[field_name] else: is_parsed, value = self.default(state=state, input=input) handler = parse_handler(self.parse, prog, value, self.names_str()) return Resolved(handler, args=(value, is_parsed)) def names(self, *, n: int = 0) -> list[str]: result = (self.short or []) + (self.long or []) return result[:n] if n else result def names_str(self, delimiter: str = ", ", *, n: int = 0) -> str: if self.short or self.long: return delimiter.join(self.names(n=n)) return self.value_name @cached_property def is_option(self) -> bool: return bool(self.short or self.long) def verify_type_compatibility( arg: FinalArg[Any], field_name: str, type_view: TypeView[Any], *, has_custom_parse: bool = False, ): """Verify classes of annotations are compatible with one another. Thus far: * Sequence and scalar types should not be mixed, unless an explicit `parse` is provided. Otherwise, they will always result in sequence-type results which will not map correctly to scalar types. * num_args!=1 should only be used with sequence types. * ArgAction.append should only be used with sequence types. * Non-required argument values with a None default must be Optional in the type annotation. """ action = arg.action if has_custom_parse or ArgAction.is_custom(action): return num_args = arg.num_args if not num_args.required and num_args.default is None and not type_view.is_optional: raise ValueError( f"On field '{field_name}', `NumArgs(required=False, default=None)` requires the " f"annotated type to include `None` (e.g. `{type_view.repr_type} | None`), " "since the absent-value default of `None` is not assignable to the field type." ) if type_view.is_union: all_same_arity = { ta.is_subclass_of((list, tuple, set)) for ta in type_view.strip_optional().inner_types } if len(all_same_arity) > 1: raise ValueError( f"On field '{field_name}', apparent mismatch of annotated type with `Arg` options. " 'Unioning "sequence" types with non-sequence types is not currently supported, ' "unless using `Arg(parse=...)` or `Arg(action=)`. " "See [documentation](https://cappa.readthedocs.io/en/latest/annotation.html) for more details." ) return if type_view.is_subclass_of((list, tuple, set)): if num_args.n in {0, 1} and action not in {ArgAction.append, None}: raise ValueError( f"On field '{field_name}', apparent mismatch of annotated type with `Arg` options. " f"'{type_view.repr_type}' type produces a sequence, whereas `num_args=1`/`action={action}` do not. " "See [documentation](https://cappa.readthedocs.io/en/latest/annotation.html) for more details." ) else: if num_args.n not in {None, 0, 1} or action is ArgAction.append: raise ValueError( f"On field '{field_name}', apparent mismatch of annotated type with `Arg` options. " f"'{type_view.repr_type}' type produces a scalar, whereas `num_args={num_args}`/`action={action}` do not. " "See [documentation](https://cappa.readthedocs.io/en/latest/annotation.html) for more details." ) def infer_field_name(arg: Arg[Any], field: Field) -> str: if arg.field_name is not Empty: raise ValueError("Arg 'name' cannot be set when using automatic inference.") return field.name def infer_default( arg: Arg[Any], type_view: TypeView[Any], field: Field | None = None, destructure: Destructure | None = None, ) -> Default: if field: inferred_default: Default = infer_default( arg, type_view, destructure=destructure ) if field.default is not Empty: return inferred_default | field.default if field.default_factory is not Empty: return inferred_default | ValueFrom(field.default_factory) return inferred_default default = arg.default if default is not Empty: return Default.from_value(default) if type_view.is_optional: return Default(default=None) if destructure: return Default(ValueFrom(type_view.annotation)) if type_view.is_subclass_of(bool): return Default(default=False) return Default() def infer_required( required: bool | None, num_args: NumArgs, raw_num_args: int | NumArgs | None, default: Default, ): if required is True: return True if default.has_value: if required is False: raise ValueError( "When specifying `required=False`, a default value must be supplied able to be " "supplied through type inference, `Arg(default=...)`, or through class-level default" ) # Only yield to "unbounded = optional" when the user *explicitly* set a negative # num_args (e.g. Arg(num_args=-1) or Arg(num_args=NumArgs(n=-1))). An inferred # -1 (e.g. from list[str]) keeps the arg required (at-least-one semantics). raw_n = NumArgs.raw_n(raw_num_args) if raw_n is not None and raw_n < 0: return False return True return False def infer_short( arg: Arg[Any], name: str, default: bool = False ) -> list[str] | Literal[False]: short = arg.short or default if not short: return False if isinstance(short, bool): short_name = name[0] return [f"-{short_name}"] if isinstance(short, str): short = short.split("/") else: short = short return [item if item.startswith("-") else f"-{item}" for item in short] def infer_long( arg: Arg[Any], type_view: TypeView[Any], name: str, default: bool ) -> list[str] | Literal[False]: long = arg.long or default if not long: # bools get automatically coerced into flags, otherwise stay off. if not type_view.is_subclass_of(bool): return False long = True if isinstance(long, bool): long = name.replace("_", "-") return [f"--{long}"] if isinstance(long, str): long = long.split("/") return [item if item.startswith("--") else f"--{item}" for item in long] def infer_choices(arg: Arg[Any], type_view: TypeView[Any]) -> list[str] | None: if arg.choices is None: choices = detect_choices(type_view) else: choices = arg.choices if not choices: return None return [str(c) for c in choices] def infer_action( arg: Arg[Any], type_view: TypeView[Any], long: list[str] | Literal[False], default: Default, ) -> ArgActionType: if arg.count: return ArgAction.count if arg.action is not None: return arg.action type_view = type_view.strip_optional() # Coerce raw `bool` into flags by default if type_view.is_subclass_of(bool): if default.default is not Empty and bool(default.default): return ArgAction.store_false return ArgAction.store_true has_custom_parse = arg.parse is not None has_custom_num_args = arg.num_args is not None is_positional = not arg.short and not long raw_n = NumArgs.raw_n(arg.num_args) is_unbounded = raw_n == -1 if ( has_custom_parse or is_unbounded or (is_positional and not has_custom_num_args) or (has_custom_num_args and raw_n != 1) ): return ArgAction.set if type_view.is_subtype_of((list, set)): return ArgAction.append if type_view.is_variadic_tuple: return ArgAction.append return ArgAction.set def infer_parse( arg: Arg[Any], type_view: TypeView[Any], state: State[Any] | None = None ) -> Callable[..., Any]: parse: Parser[Any] | Sequence[Parser[Any]] = arg.parse # type: ignore default_parse = parse_value(type_view) # This is the original arg choices, e.g. explicitly provided. Inferred choices # need to be handled internally to the parsers. parsers: Sequence[Parser[Any]] = [] if parse: if isinstance(parse, Sequence): parsers = [*cast(Sequence[Any], parse)] else: parsers = [parse] if arg.choices: literal_type = Literal[tuple(arg.choices)] # type: ignore literal_parse: Parser[Any] = parse_literal(literal_type) # type: ignore parsers = cast(Sequence[Parser[Any]], [*parsers, literal_parse]) if arg.parse_inference: parsers = [*parsers, default_parse] return evaluate_parse(parsers, type_view, state=state) def infer_help(arg: Arg[Any], fallback_help: str | None) -> str | None: help = arg.help if help is None: help = fallback_help return help def infer_completion( arg: Arg[Any], choices: list[str] | None ) -> Callable[[str], list[Completion]] | None: if arg.completion: return arg.completion if choices: return complete_choices(choices, help=arg.help) return None def infer_value_name(arg: Arg[Any], field_name: str, num_args: NumArgs) -> str: if arg.value_name is not Empty: return arg.value_name if num_args.n > 1: return " ".join([field_name] * num_args.n) return field_name def explode_negated_bool_args( args: Sequence[FinalArg[Any] | FinalDestructure[Any]], ) -> Iterable[FinalArg[Any] | FinalDestructure[Any]]: """Expand `--foo/--no-foo` solo arguments into dual-arguments. Acts as a transform from `Arg(long='--foo/--no-foo')` to `Annotated[Arg(long='--foo', action=ArgAction.store_true), Arg(long='--no-foo', action=ArgAction.store_false)]` """ for arg in args: # Only args can *be* bool negatable args. if not isinstance(arg, FinalArg): yield arg continue yielded = False if isinstance(arg.action, ArgAction) and arg.action.is_bool_action and arg.long: long = arg.long negatives = [item for item in long if "--no-" in item] positives = [item for item in long if "--no-" not in item] if negatives and positives: assert isinstance(arg.default, Default) disabled: DefaultFormatter = DefaultFormatter.disabled() group = dataclasses.replace( arg.group, exclusive=True, id=arg.field_name, ) positive_arg = dataclasses.replace( arg, long=positives, action=ArgAction.store_true, show_default=disabled, group=group, help=None, ) negative_arg = dataclasses.replace( arg, long=negatives, action=ArgAction.store_false, show_default=arg.show_default, group=group, ) yield positive_arg yield negative_arg yielded = True if not yielded: yield arg def infer_has_value(arg: Arg[Any], action: ArgActionType): if arg.has_value is not None: return arg.has_value if isinstance(action, ArgAction) and action in ArgAction.meta_actions(): return False return True from cappa.destructure import Destructure, FinalDestructure # noqa: E402 cappa-0.32.2/src/cappa/argparse.py0000644000000000000000000003201313615410400013632 0ustar00from __future__ import annotations import argparse import sys from typing import TYPE_CHECKING, Any, Callable, Hashable, TypeVar, cast from cappa.arg import Arg, ArgAction, FinalArg, NumArgs from cappa.command import Alias, Command, FinalCommand from cappa.help import ArgGroup from cappa.invoke.base import fulfill_deps from cappa.output import Exit, HelpExit, Output from cappa.parser import RawOption, Value from cappa.subcommand import FinalSubcommand if TYPE_CHECKING: from _typeshed import SupportsWrite if sys.version_info < (3, 9): # pragma: no cover # Backport https://github.com/python/cpython/pull/3680 original_get_action_name = argparse._get_action_name # pyright: ignore def _get_action_name( argument: argparse.Action | None, ) -> str | None: # pragma: no cover name = original_get_action_name(argument) assert argument if name is None and argument.choices: return "{" + ",".join(argument.choices) + "}" return name argparse._get_action_name = _get_action_name # pyright: ignore T = TypeVar("T") # Work around argparse's lack of `metavar` support on various built-in actions. class _HelpAction(argparse._HelpAction): # pyright: ignore def __init__(self, metavar: str | None = None, **kwargs: Any): self.metavar = metavar super().__init__(**kwargs) class _VersionAction(argparse._VersionAction): # pyright: ignore def __init__(self, metavar: str | None = None, **kwargs: Any): self.metavar = metavar super().__init__(**kwargs) class _StoreTrueAction(argparse._StoreTrueAction): # pyright: ignore def __init__(self, metavar: str | None = None, **kwargs: Any): self.metavar = metavar super().__init__(**kwargs) class _StoreFalseAction(argparse._StoreFalseAction): # pyright: ignore def __init__(self, metavar: str | None = None, **kwargs: Any): self.metavar = metavar super().__init__(**kwargs) class _CountAction(argparse._CountAction): # pyright: ignore def __init__(self, metavar: str | None = None, **kwargs: Any): self.metavar = metavar super().__init__(**kwargs) class ArgumentParser(argparse.ArgumentParser): def __init__( self, *args: Any, command: FinalCommand[Any], output: Output, **kwargs: Any ): super().__init__(*args, **kwargs) self.command = command self.output = output self.register("action", "store_true", _StoreTrueAction) self.register("action", "store_false", _StoreFalseAction) self.register("action", "help", _HelpAction) self.register("action", "version", _VersionAction) self.register("action", "count", _CountAction) def error(self, message: str): # Avoids argparse's error prefixing code, deferring it to Output self.exit(2, message) def exit(self, status: int = 0, message: str | None = None): if message: message = message.capitalize() raise Exit(message, code=status, prog=self.prog) def print_help(self, file: SupportsWrite[str] | None = None): raise HelpExit(self.command.help_formatter.long(self.command, self.prog)) def custom_action(arg: Arg[Any], action: Callable[..., Any]): class CustomAction(argparse.Action): def __call__( # type: ignore self, parser: ArgumentParser, namespace: argparse.Namespace, values: Any, option_string: str | None = None, ): # XXX: This should ideally be able to inject parser state, but here, we dont # have access to the same state as the native parser. fulfilled_deps: dict[Hashable, Any] = { Output: parser.output, Value: Value(values), Command: namespace.__command__, Arg: arg, } if option_string: fulfilled_deps[RawOption] = RawOption.from_str(option_string) deps = fulfill_deps(action, fulfilled_deps) result = action(**deps.kwargs) setattr(namespace, self.dest, result) return CustomAction class Nestedspace(argparse.Namespace): """Write each . separated section as a nested `Nestedspace` instance. By default, argparse write everything to a flat namespace so there's no obvious way to distinguish between multiple unrelated subcommands once once has been chosen. """ def __setattr__(self, name: str, value: Any): if "." in name: group, name = name.split(".", 1) ns = getattr(self, group, Nestedspace()) setattr(ns, name, value) self.__dict__[group] = ns else: self.__dict__[name] = value def backend( command: FinalCommand[T], argv: list[str], output: Output, prog: str, provide_completions: bool = False, ) -> tuple[Any, Command[T], dict[str, Any]]: parser = create_parser(command, output=output, prog=prog) try: version = next( a for a in command.arguments if isinstance(a, Arg) and a.action is ArgAction.version ) parser.version = version.value_name # type: ignore except StopIteration: pass ns = Nestedspace() try: result_namespace = parser.parse_args(argv, ns) except argparse.ArgumentError as e: raise Exit(str(e), code=2, prog=prog) result = to_dict(result_namespace) command = result.pop("__command__") return parser, command, result def create_parser( command: FinalCommand[Any], output: Output, prog: str ) -> argparse.ArgumentParser: kwargs: dict[str, Any] = {} if sys.version_info >= (3, 9): # pragma: no cover kwargs["exit_on_error"] = False parser = ArgumentParser( command=command, output=output, prog=prog, description=join_help(command.help, command.description), allow_abbrev=False, add_help=False, **kwargs, ) parser.set_defaults(__command__=command) add_arguments(parser, command, output=output) return parser def add_arguments( parser: ArgumentParser, command: FinalCommand[Any], output: Output, dest_prefix: str = "", ): exclusive_groups: dict[str, Any] = {} arg_groups = ArgGroup.collect(command, include_hidden=True) for group in arg_groups: argparse_group = parser.add_argument_group(title=group.name) for field_group in group.field_groups: if field_group.args: for arg in field_group.args: target_group: Any = argparse_group if arg.group.exclusive: target_group = exclusive_groups.get(arg.group.id) if target_group is None: target_group = exclusive_groups[arg.group.id] = ( argparse_group.add_mutually_exclusive_group() ) add_argument(parser, target_group, arg, dest_prefix=dest_prefix) else: assert field_group.subcommand add_subcommands( parser, group.name, field_group.subcommand, output=output, dest_prefix=dest_prefix, ) def add_argument( parser: ArgumentParser, # pyright: ignore subparser: argparse.ArgumentParser | argparse._ArgumentGroup, # pyright: ignore arg: FinalArg[Any], dest_prefix: str = "", **extra_kwargs: Any, ): if arg.propagate: raise RuntimeError("The argparse backend does not support the `Arg.propagate`.") names: list[str] = [] if arg.short: names.extend(arg.short) if arg.long: names.extend(arg.long) is_positional = not names num_args = backend_num_args(arg.num_args, arg.required) kwargs: dict[str, Any] = { "dest": dest_prefix + arg.field_name, "help": arg.help, "metavar": arg.value_name, "action": get_action(arg), "default": argparse.SUPPRESS, } if not is_positional and arg.required and arg.num_args.n >= 0: kwargs["required"] = arg.required is_optional_value = not is_positional and not arg.num_args.required if num_args is not None and not ArgAction.is_non_value_consuming(arg.action): kwargs["nargs"] = num_args elif is_optional_value: kwargs["nargs"] = "?" kwargs["const"] = arg.num_args.default elif is_positional and not arg.required: kwargs["nargs"] = "?" deprecated_kwarg = add_deprecated_kwarg(arg) kwargs.update(deprecated_kwarg) kwargs.update(extra_kwargs) try: subparser.add_argument(*names, **kwargs) except argparse.ArgumentError as e: raise Exit(str(e), code=2, prog=parser.prog) def add_subcommands( parser: argparse.ArgumentParser, group: str, subcommands: FinalSubcommand, output: Output, dest_prefix: str = "", ): subcommand_dest = subcommands.field_name visible_metavar = "{" + ",".join(subcommands.names()) + "}" subparsers = parser.add_subparsers( title=group, required=subcommands.required, parser_class=ArgumentParser, metavar=visible_metavar, ) for name, subcommand in subcommands.options.items(): deprecated_kwarg = add_deprecated_kwarg(subcommand) nested_dest_prefix = f"{dest_prefix}{subcommand_dest}." non_deprecated_visible_aliases = [ a.name for a in subcommand.resolved_aliases() if not a.hidden and not a.deprecated ] subparser = subparsers.add_parser( name=subcommand.real_name(), aliases=non_deprecated_visible_aliases, help=subcommand.help, description=subcommand.description, formatter_class=parser.formatter_class, add_help=False, command=subcommand, output=output, prog=f"{parser.prog} {subcommand.real_name()}", **deprecated_kwarg, ) subparser.set_defaults( __command__=subcommand, **{nested_dest_prefix + "__name__": name} ) # Aliases not registered via `aliases=` (hidden, or deprecated) get spliced # into the internal name map. Deprecated aliases get a wrapper that emits # the warning at dispatch time, then delegates parsing to the real subparser. for alias in subcommand.resolved_aliases(): if alias.name in non_deprecated_visible_aliases: continue if alias.deprecated: subparsers._name_parser_map[alias.name] = _DeprecatedAliasParser( subparser, alias, output ) else: subparsers._name_parser_map[alias.name] = subparser add_arguments( subparser, subcommand, output=output, dest_prefix=nested_dest_prefix, ) def backend_num_args(num_args: NumArgs, required: bool) -> int | str | None: if num_args.n == 1: return None if num_args.n == -1: if required: return "+" return "*" return num_args.n class _DeprecatedAliasParser(ArgumentParser): """Stand-in subparser used when a user invokes a deprecated alias. argparse dispatches into here via `_name_parser_map[alias_name]`. We emit the deprecation warning, then delegate parsing to the real subparser so the canonical `__name__` and arguments are populated as usual. """ def __init__( self, real_parser: ArgumentParser, alias: Alias, output: Output ) -> None: super().__init__( command=real_parser.command, output=output, add_help=False, allow_abbrev=False, ) self._real_parser = real_parser self._alias = alias def parse_known_args( # type: ignore[override] self, args: Any = None, namespace: Any = None, ) -> tuple[argparse.Namespace, list[str]]: message = f"Command alias `{self._alias.name}` is deprecated" if isinstance(self._alias.deprecated, str): message += f": {self._alias.deprecated}" self.output.error(message) return self._real_parser.parse_known_args(args, namespace) def to_dict(value: argparse.Namespace): result: dict[str, Any] = {} for k, v in value.__dict__.items(): if isinstance(v, argparse.Namespace): v = to_dict(v) result[k] = v return result def join_help(*segments: str | None): return " ".join([s for s in segments if s]) def get_action(arg: Arg[Any]) -> argparse.Action | type[argparse.Action] | str: action = arg.action if isinstance(action, ArgAction): return action.value action = cast(Callable[..., Any], action) return custom_action(arg, action) def add_deprecated_kwarg(arg: Arg[Any] | Command[Any]) -> dict[str, Any]: if sys.version_info < (3, 13) or not arg.deprecated: return {} return {"deprecated": arg.deprecated} # pragma: no cover cappa-0.32.2/src/cappa/base.py0000644000000000000000000006540713615410400012755 0ustar00from __future__ import annotations import contextlib import dataclasses import inspect from typing import ( TYPE_CHECKING, Any, Hashable, TextIO, cast, overload, ) from rich.theme import Theme from typing_extensions import dataclass_transform from cappa import argparse, parser from cappa.class_inspect import detect from cappa.command import Alias, Command, FinalCommand from cappa.help import HelpFormattable, HelpFormatter from cappa.invoke.base import resolve_callable from cappa.invoke.types import DepTypes, InvokeCallableSpec from cappa.output import Output from cappa.state import S, State from cappa.types import Backend, CappaCapable, FuncOrClassDecorator, ParseResult, T, U if TYPE_CHECKING: from cappa.arg import Arg, FinalArg def create_version_arg( version: str | Arg[Any] | None = None, ) -> FinalArg[Any] | None: from dataclasses import replace from cappa.arg import Arg, ArgAction, Empty, Group if not version: return None if isinstance(version, str): version = Arg( value_name=version, short=["-v"], long=["--version"], help="Show the version and exit.", group=Group(1, "Help", section=2), action=ArgAction.version, ) if version.value_name is Empty: raise ValueError( "Expected explicit version `Arg` to supply version number as its name, like `Arg('1.2.3', ...)`" ) if version.long is True: version = replace(version, long="--version") return version.normalize( action=ArgAction.version, field_name="version", default=None ) def create_help_arg(help: bool | Arg[bool] | None = True) -> FinalArg[bool] | None: from cappa.arg import Arg, ArgAction, Group if not help: return None if isinstance(help, bool): help = Arg( short=["-h"], long=["--help"], help="Show this message and exit.", group=Group(0, "Help", section=2), action=ArgAction.help, ) return help.normalize(action=ArgAction.help, field_name="help", default=None) def create_completion_arg( completion: bool | Arg[bool] = True, ) -> FinalArg[bool] | None: from cappa.arg import Arg, ArgAction, Group if not completion: return None if isinstance(completion, bool): completion = Arg( long=["--completion"], choices=["generate", "complete"], group=Group(2, "Help", section=2), help="Use `--completion generate` to print shell-specific completion source.", action=ArgAction.completion, ) return completion.normalize( field_name="completion", action=ArgAction.completion, default=None, ) def parse( obj: CappaCapable[T], *, argv: list[str] | None = None, input: TextIO | None = None, backend: Backend | None = None, color: bool = True, version: str | Arg[Any] | None = None, help: bool | Arg[Any] = True, completion: bool | Arg[Any] = True, theme: Theme | None = None, output: Output | None = None, help_formatter: HelpFormattable | None = None, state: State[Any] | None = None, exit_stack: contextlib.ExitStack | None = None, ) -> T: """Parse the command, returning an instance of `obj`. In the event that a subcommand is selected, only the selected subcommand function is invoked. Arguments: obj: A class which can represent a CLI command chain. argv: Defaults to the process argv. This command is generally only necessary when testing. input: Defaults to the process stdin. This command is generally only necessary when testing. backend: A function used to perform the underlying parsing and return a raw parsed state. This defaults to the native cappa parser, but can be changed to the argparse parser at `cappa.argparse.backend`. color: Whether to output in color. version: If a string is supplied, adds a -v/--version flag which returns the given string as the version. If an `Arg` is supplied, uses the `name`/`short`/`long`/`help` fields to add a corresponding version argument. Note the `name` is assumed to **be** the CLI's version, e.x. `Arg('1.2.3', help="Prints the version")`. help: If `True` (default to True), adds a -h/--help flag. If an `Arg` is supplied, uses the `short`/`long`/`help` fields to add a corresponding help argument. completion: Enables completion when using the cappa `backend` option. If `True` (default to True), adds a --completion flag. An `Arg` can be supplied to customize the argument's behavior. theme: Optional rich theme to customized output formatting. output: Optional `Output` instance. A default `Output` will constructed if one is not provided. Note the `color` and `theme` arguments take precedence over manually constructed `Output` attributes. help_formatter: Override the default help formatter. state: Optional initial State object. exit_stack: Optional ExitStack to use for managing context managers. If provided, the caller is responsible for closing the stack, allowing context to exceed the function call. If not provided, contexts are not entered (parse does not manage contexts by default). """ parse_result = parse_command( obj=obj, argv=argv, input=input, backend=backend, color=color, version=version, help=help, completion=completion, theme=theme, output=output, help_formatter=help_formatter, state=state, ) if exit_stack is not None: return exit_stack.enter_context( parse_result.instance.get(output=parse_result.output, managed=True) ) return parse_result.instance.call(output=parse_result.output, managed=False) async def parse_async( obj: CappaCapable[T], *, argv: list[str] | None = None, input: TextIO | None = None, backend: Backend | None = None, color: bool = True, version: str | Arg[Any] | None = None, help: bool | Arg[Any] = True, completion: bool | Arg[Any] = True, theme: Theme | None = None, output: Output | None = None, help_formatter: HelpFormattable | None = None, state: State[Any] | None = None, exit_stack: contextlib.AsyncExitStack | None = None, ) -> T: """Parse the command asynchronously, returning an instance of `obj`. This is the async version of `parse()`, necessary when using async parse functions. In the event that a subcommand is selected, only the selected subcommand function is invoked. Arguments: obj: A class which can represent a CLI command chain. argv: Defaults to the process argv. This command is generally only necessary when testing. input: Defaults to the process stdin. This command is generally only necessary when testing. backend: A function used to perform the underlying parsing and return a raw parsed state. This defaults to the native cappa parser, but can be changed to the argparse parser at `cappa.argparse.backend`. color: Whether to output in color. version: If a string is supplied, adds a -v/--version flag which returns the given string as the version. If an `Arg` is supplied, uses the `name`/`short`/`long`/`help` fields to add a corresponding version argument. Note the `name` is assumed to **be** the CLI's version, e.x. `Arg('1.2.3', help="Prints the version")`. help: If `True` (default to True), adds a -h/--help flag. If an `Arg` is supplied, uses the `short`/`long`/`help` fields to add a corresponding help argument. completion: Enables completion when using the cappa `backend` option. If `True` (default to True), adds a --completion flag. An `Arg` can be supplied to customize the argument's behavior. theme: Optional rich theme to customized output formatting. output: Optional `Output` instance. A default `Output` will constructed if one is not provided. Note the `color` and `theme` arguments take precedence over manually constructed `Output` attributes. help_formatter: Override the default help formatter. state: Optional initial State object. exit_stack: Optional AsyncExitStack to use for managing async context managers. If provided, the caller is responsible for closing the stack, allowing context to exceed the function call. If not provided, contexts are not entered (parse does not manage contexts by default). """ parse_result = parse_command( obj=obj, argv=argv, input=input, backend=backend, color=color, version=version, help=help, completion=completion, theme=theme, output=output, help_formatter=help_formatter, state=state, ) if exit_stack is not None: return await exit_stack.enter_async_context( parse_result.instance.get_async(output=parse_result.output, managed=True) ) return await parse_result.instance.call_async( output=parse_result.output, managed=False ) def invoke( obj: CappaCapable[T], *, deps: DepTypes = None, argv: list[str] | None = None, input: TextIO | None = None, backend: Backend | None = None, color: bool = True, version: str | Arg[Any] | None = None, help: bool | Arg[Any] = True, completion: bool | Arg[Any] = True, theme: Theme | None = None, output: Output | None = None, help_formatter: HelpFormattable | None = None, state: State[Any] | None = None, exit_stack: contextlib.ExitStack | None = None, ) -> Any: """Parse the command, and invoke the selected async command or subcommand. In the event that a subcommand is selected, only the selected subcommand function is invoked. Arguments: obj: A class which can represent a CLI command chain. deps: Optional extra depdnencies to load ahead of invoke processing. These deps are evaluated in order and unconditionally. argv: Defaults to the process argv. This command is generally only necessary when testing. input: Defaults to the process stdin. This command is generally only necessary when testing. backend: A function used to perform the underlying parsing and return a raw parsed state. This defaults to the native cappa parser, but can be changed to the argparse parser at `cappa.argparse.backend`. color: Whether to output in color. version: If a string is supplied, adds a -v/--version flag which returns the given string as the version. If an `Arg` is supplied, uses the `name`/`short`/`long`/`help` fields to add a corresponding version argument. Note the `name` is assumed to **be** the CLI's version, e.x. `Arg('1.2.3', help="Prints the version")`. help: If `True` (default to True), adds a -h/--help flag. If an `Arg` is supplied, uses the `short`/`long`/`help` fields to add a corresponding help argument. completion: Enables completion when using the cappa `backend` option. If `True` (default to True), adds a --completion flag. An `Arg` can be supplied to customize the argument's behavior. theme: Optional rich theme to customized output formatting. output: Optional `Output` instance. A default `Output` will constructed if one is not provided. Note the `color` and `theme` arguments take precedence over manually constructed `Output` attributes. help_formatter: Override the default help formatter. state: Optional initial State object. exit_stack: Optional ExitStack to use for managing context managers. If provided, the caller is responsible for closing the stack, allowing context to exceed the function call. If not provided, a new stack is created and automatically closed. """ parse_result = parse_command( obj=obj, argv=argv, input=input, backend=backend, color=color, version=version, help=help, completion=completion, theme=theme, output=output, help_formatter=help_formatter, state=state, ) def _invoke_with_stack(stack: contextlib.ExitStack): instance = stack.enter_context( parse_result.instance.get(output=parse_result.output) ) # Resolve all implicit deps resolved_implicit_deps: dict[Hashable, Any] = {} for key, resolved_dep in parse_result.implicit_deps.items(): resolved_implicit_deps[key] = stack.enter_context( resolved_dep.get(output=parse_result.output) ) resolved, global_deps = resolve_callable( parse_result.root_command, parse_result.parsed_command, instance, implicit_deps=resolved_implicit_deps, output=parse_result.output, state=parse_result.state, deps=deps, ) for dep in global_deps: stack.enter_context(dep.get(output=parse_result.output)) return stack.enter_context(resolved.get(output=parse_result.output)) if exit_stack is not None: return _invoke_with_stack(exit_stack) with contextlib.ExitStack() as stack: return _invoke_with_stack(stack) async def invoke_async( obj: CappaCapable[T], *, deps: DepTypes = None, argv: list[str] | None = None, input: TextIO | None = None, backend: Backend | None = None, color: bool = True, version: str | Arg[str] | None = None, help: bool | Arg[bool] = True, completion: bool | Arg[bool] = True, theme: Theme | None = None, output: Output | None = None, help_formatter: HelpFormattable | None = None, state: State[Any] | None = None, exit_stack: contextlib.AsyncExitStack | None = None, ) -> Any: """Parse the command, and invoke the selected command or subcommand. In the event that a subcommand is selected, only the selected subcommand function is invoked. Arguments: obj: A class which can represent a CLI command chain. deps: Optional extra depdnencies to load ahead of invoke processing. These deps are evaluated in order and unconditionally. argv: Defaults to the process argv. This command is generally only necessary when testing. input: Defaults to the process stdin. This command is generally only necessary when testing. backend: A function used to perform the underlying parsing and return a raw parsed state. This defaults to the native cappa parser, but can be changed to the argparse parser at `cappa.argparse.backend`. color: Whether to output in color. version: If a string is supplied, adds a -v/--version flag which returns the given string as the version. If an `Arg` is supplied, uses the `name`/`short`/`long`/`help` fields to add a corresponding version argument. Note the `name` is assumed to **be** the CLI's version, e.x. `Arg('1.2.3', help="Prints the version")`. help: If `True` (default to True), adds a -h/--help flag. If an `Arg` is supplied, uses the `short`/`long`/`help` fields to add a corresponding help argument. completion: Enables completion when using the cappa `backend` option. If `True` (default to True), adds a --completion flag. An `Arg` can be supplied to customize the argument's behavior. theme: Optional rich theme to customized output formatting. output: Optional `Output` instance. A default `Output` will constructed if one is not provided. Note the `color` and `theme` arguments take precedence over manually constructed `Output` attributes. help_formatter: Override the default help formatter. state: Optional initial State object. exit_stack: Optional AsyncExitStack to use for managing async context managers. If provided, the caller is responsible for closing the stack, allowing context to exceed the function call. If not provided, a new stack is created and automatically closed. """ parse_result = parse_command( obj=obj, argv=argv, input=input, backend=backend, color=color, version=version, help=help, completion=completion, theme=theme, output=output, help_formatter=help_formatter, state=state, ) async def _invoke_async_with_stack(stack: contextlib.AsyncExitStack): instance = await stack.enter_async_context( parse_result.instance.get_async(output=parse_result.output) ) # Resolve all implicit deps resolved_implicit_deps: dict[Hashable, Any] = {} for key, resolved_dep in parse_result.implicit_deps.items(): resolved_implicit_deps[key] = await stack.enter_async_context( resolved_dep.get_async(output=parse_result.output) ) resolved, global_deps = resolve_callable( parse_result.root_command, parse_result.parsed_command, instance, implicit_deps=resolved_implicit_deps, output=parse_result.output, state=parse_result.state, deps=deps, ) for dep in global_deps: await stack.enter_async_context(dep.get_async(output=parse_result.output)) return await stack.enter_async_context( resolved.get_async(output=parse_result.output) ) if exit_stack is not None: return await _invoke_async_with_stack(exit_stack) async with contextlib.AsyncExitStack() as stack: return await _invoke_async_with_stack(stack) def parse_command( obj: CappaCapable[T], *, argv: list[str] | None = None, input: TextIO | None = None, backend: Backend | None = None, color: bool = True, version: str | Arg[str] | None = None, help: bool | Arg[bool] = True, completion: bool | Arg[bool] = True, theme: Theme | None = None, output: Output | None = None, help_formatter: HelpFormattable | None = None, state: State[S] | None = None, ) -> ParseResult[T, S]: concrete_backend = _coalesce_backend(backend) concrete_output = _coalesce_output(output, theme, color) concrete_state: State[S] = State.ensure(state) # type: ignore command: FinalCommand[T] = collect( obj, help=help, version=version, completion=completion, backend=concrete_backend, help_formatter=help_formatter, state=concrete_state, ) return command.parse_command( argv=argv, input=input, backend=concrete_backend, output=concrete_output, state=concrete_state, ) @overload def command( _cls: type[T], *, name: str | None = None, aliases: list[str | Alias] | None = None, help: str | None = None, description: str | None = None, epilog: str | None = None, invoke: InvokeCallableSpec[Any] | None = None, hidden: bool = False, default_short: bool = False, default_long: bool = False, deprecated: bool = False, help_formatter: HelpFormattable = HelpFormatter.default, ) -> type[T]: ... @overload def command( *, name: str | None = None, aliases: list[str | Alias] | None = None, help: str | None = None, description: str | None = None, epilog: str | None = None, invoke: InvokeCallableSpec[Any] | None = None, hidden: bool = False, default_short: bool = False, default_long: bool = False, deprecated: bool = False, help_formatter: HelpFormattable = HelpFormatter.default, ) -> FuncOrClassDecorator: ... @overload def command( _cls: T, *, name: str | None = None, aliases: list[str | Alias] | None = None, help: str | None = None, description: str | None = None, epilog: str | None = None, invoke: InvokeCallableSpec[Any] | None = None, hidden: bool = False, default_short: bool = False, default_long: bool = False, deprecated: bool = False, help_formatter: HelpFormattable = HelpFormatter.default, ) -> T: ... @dataclass_transform() # type: ignore[misc] def command( _cls: type[T] | T | None = None, *, name: str | None = None, aliases: list[str | Alias] | None = None, help: str | None = None, description: str | None = None, epilog: str | None = None, invoke: InvokeCallableSpec[Any] | None = None, hidden: bool = False, default_short: bool = False, default_long: bool = False, deprecated: bool = False, help_formatter: HelpFormattable = HelpFormatter.default, ) -> type[T] | T | FuncOrClassDecorator: """Register a cappa CLI command/subcomment. Args: name: The name of the command. If omitted, the name of the command will be the name of the `cls`, converted to dash-case. aliases: Alternate names that may be used to invoke this command as a subcommand. Each entry is either a plain string (visible alias) or an :class:`Alias` instance (which can be marked `hidden=True` or `deprecated=...`). Only meaningful when the class is used as a subcommand. help: Optional help text. If omitted, the `cls` docstring will be parsed, and the headline section will be used to document the command itself, and the arguments section will become the default help text for any params/options. description: Optional extended help text. If omitted, the `cls` docstring will be parsed, and the extended long description section will be used. epilog: Optional text displayed after the argument list in the help output. invoke: Optional command to be called in the event parsing is successful. In the case of subcommands, it will only call the parsed/selected function to invoke. hidden: If `True`, the command will not be included in the help output. This option is only relevant to subcommands. default_short: If `True`, all arguments will be treated as though annotated with `Annotated[T, Arg(short=True)]`, unless otherwise annotated. default_long: If `True`, all arguments will be treated as though annotated with `Annotated[T, Arg(long=True)]`, unless otherwise annotated. deprecated: If supplied, the argument will be marked as deprecated. If given `True`, a default message will be generated, otherwise a supplied string will be used as the deprecation message. help_formatter: Override the default help formatter. """ def wrapper(_decorated_cls: U) -> U: if inspect.isclass(_decorated_cls) and not detect(_decorated_cls): _decorated_cls = dataclasses.dataclass(_decorated_cls) # type: ignore command: Command[T] = Command.get(_decorated_cls) # type: ignore instance = dataclasses.replace( command, invoke=invoke, name=name, aliases=list(aliases) if aliases is not None else command.aliases, help=help, description=description, epilog=epilog, hidden=hidden, default_short=default_short, default_long=default_long, deprecated=deprecated, help_formatter=help_formatter, ) _decorated_cls.__cappa__ = instance # type: ignore # Functions (and in particular class methods, must return a function object in order # to be attached as methods) cannot be nested, so we can just directly return it. if inspect.isfunction(_decorated_cls): return cast(U, _decorated_cls) # Whereas classes will **generally** be the **exact** object as `_decorated_cls` was, # except in the case of dynamically generated subclasses used for detecting methods. return instance.cmd_cls # type: ignore if _cls is not None: return wrapper(_cls) return wrapper def collect( obj: CappaCapable[T], *, backend: Backend | None = None, version: str | Arg[str] | None = None, help: bool | Arg[bool] = True, completion: bool | Arg[bool] = True, help_formatter: HelpFormattable | None = None, state: State[Any] | None = None, ) -> FinalCommand[T]: """Retrieve the `Command` object from a cappa-capable source class. Arguments: obj: A class which can represent a CLI command chain. backend: A function used to perform the underlying parsing and return a raw parsed state. This defaults to constructing built-in function using argparse. version: If a string is supplied, adds a -v/--version flag which returns the given string as the version. If an `Arg` is supplied, uses the `name`/`short`/`long`/`help` fields to add a corresponding version argument. Note the `name` is assumed to **be** the CLI's version, e.x. `Arg('1.2.3', help="Prints the version")`. help: If `True` (default to True), adds a -h/--help flag. If an `Arg` is supplied, uses the `short`/`long`/`help` fields to add a corresponding help argument. completion: Enables completion when using the cappa `backend` option. If `True` (default to True), adds a --completion flag. An `Arg` can be supplied to customize the argument's behavior. color: Whether to output in color. help_formatter: Override the default help formatter. state: Optional initial State object. """ state = State.ensure(state) # pyright: ignore command: FinalCommand[T] = Command.get( # pyright: ignore obj, help_formatter=help_formatter ).collect(state=state) concrete_backend = _coalesce_backend(backend) if concrete_backend is argparse.backend: # pyright: ignore completion = False help_arg = create_help_arg(help) version_arg = create_version_arg(version) completion_arg = create_completion_arg(completion) return command.add_meta_actions( help=help_arg, version=version_arg, completion=completion_arg ) def _coalesce_backend(backend: Backend | None = None) -> Backend: if backend is None: # pragma: no cover return parser.backend return backend def _coalesce_output( output: Output | None = None, theme: Theme | None = None, color: bool = True ): if output is None: output = Output() output.theme(theme) if not color: output.color(False) return output cappa-0.32.2/src/cappa/class_inspect.py0000644000000000000000000002412213615410400014662 0ustar00from __future__ import annotations import dataclasses import functools import inspect import sys import typing from enum import Enum from typing import TYPE_CHECKING, Any from typing_extensions import Annotated, Self from cappa.output import Output from cappa.state import State from cappa.type_view import CallableView, Empty, EmptyType from cappa.typing import T, find_annotations if typing.TYPE_CHECKING: from cappa.command import Command __all__ = [ "detect", "fields", ] def detect(cls: type) -> bool: return bool(ClassTypes.from_cls(cls)) @dataclasses.dataclass class Field: name: str default: typing.Any | EmptyType = Empty default_factory: typing.Any | EmptyType = Empty metadata: dict[str, Any] = dataclasses.field(default_factory=lambda: {}) @dataclasses.dataclass class DataclassField(Field): @classmethod def collect(cls, typ: type) -> list[Self]: fields: list[Self] = [] for f in dataclasses.fields(typ): # pyright: ignore if not f.init: continue field = cls( name=f.name, default=f.default if f.default is not dataclasses.MISSING else Empty, default_factory=f.default_factory if f.default_factory is not dataclasses.MISSING else Empty, metadata=dict(f.metadata), ) fields.append(field) return fields @dataclasses.dataclass class AttrsField(Field): @classmethod def collect(cls, typ: type) -> list[Self]: fields: list[Self] = [] for f in typ.__attrs_attrs__: # type: ignore if hasattr(f.default, "factory"): # pyright: ignore default = None default_factory: Any = f.default.factory # pyright: ignore else: default = f.default # pyright: ignore default_factory = None field = cls( name=f.name, # pyright: ignore default=default or Empty, # pyright: ignore default_factory=default_factory or Empty, # pyright: ignore metadata=f.metadata, # pyright: ignore ) fields.append(field) return fields if TYPE_CHECKING: @dataclasses.dataclass class MsgspecField(Field): @classmethod def collect(cls, typ: type) -> list[Self]: return [] else: @dataclasses.dataclass class MsgspecField(Field): @classmethod def collect(cls, typ: type) -> list[Self]: import msgspec # pyright: ignore fields: list[Self] = [] for f in msgspec.structs.fields(typ): default = f.default if f.default is not msgspec.NODEFAULT else Empty default_factory = ( f.default_factory if f.default_factory is not msgspec.NODEFAULT else Empty ) field = cls( name=f.name, default=default, default_factory=default_factory, ) fields.append(field) return fields @dataclasses.dataclass class PydanticV1Field(Field): @classmethod def collect(cls, typ: type) -> list[Self]: fields: list[Self] = [] callable_view = CallableView.from_callable(typ, include_extras=True) for param in callable_view.parameters: name = param.name f = typ.__fields__[name] # type: ignore field = cls( name=name, default=f.default # pyright: ignore if f.default.__repr__() != "PydanticUndefined" # pyright: ignore else Empty, default_factory=f.default_factory or Empty, # pyright: ignore ) fields.append(field) return fields @dataclasses.dataclass class PydanticV2Field(Field): @classmethod def collect(cls, typ: type) -> list[Self]: fields: list[Self] = [] for name, f in typ.model_fields.items(): # type: ignore field = cls( name=name, # pyright: ignore default=f.default # pyright: ignore if f.default.__repr__() != "PydanticUndefined" # pyright: ignore else Empty, default_factory=f.default_factory or Empty, # pyright: ignore ) fields.append(field) return fields @dataclasses.dataclass class PydanticV2DataclassField(Field): @classmethod def collect(cls, typ: type) -> list[Self]: fields: list[Self] = [] for name, f in typ.__pydantic_fields__.items(): # type: ignore field = cls( name=name, # pyright: ignore default=f.default or Empty, # pyright: ignore default_factory=f.default_factory or Empty, # pyright: ignore ) fields.append(field) return fields def fields(cls: type): class_type = ClassTypes.from_cls(cls) if class_type is None: raise ValueError( f"'{cls.__qualname__}' is not a currently supported kind of class. " "Must be one of: dataclass, pydantic, or attrs class." ) return class_type.value.collect(cls) # pyright: ignore class ClassTypes(Enum): attrs = AttrsField dataclass = DataclassField pydantic_v1 = PydanticV1Field pydantic_v2 = PydanticV2Field pydantic_v2_dataclass = PydanticV2DataclassField msgspec = MsgspecField @classmethod def from_cls(cls, obj: type) -> ClassTypes | None: if hasattr(obj, "__pydantic_fields__"): return cls.pydantic_v2_dataclass if dataclasses.is_dataclass(obj): return cls.dataclass if hasattr(obj, "__struct_config__"): # pyright: ignore assert obj.__struct_config__.__class__.__module__.startswith("msgspec") # pyright: ignore return cls.msgspec try: import pydantic # pyright: ignore from pydantic import BaseModel # pyright: ignore except ImportError: # pragma: no cover pass else: try: is_base_model = isinstance(obj, type) and issubclass(obj, BaseModel) except TypeError: # pragma: no cover is_base_model = False if is_base_model: if getattr(pydantic, "__version__", "1.0").startswith("1."): # pyright: ignore, pragma: no cover return cls.pydantic_v1 return cls.pydantic_v2 if hasattr(obj, "__attrs_attrs__"): # pyright: ignore return cls.attrs return None def extract_dataclass_metadata(field: Field, cls: type[T]) -> list[T]: field_metadata = field.metadata.get("cappa") if not field_metadata: return [] if not isinstance(field_metadata, cls): return [] return [field_metadata] def get_command_capable_object(obj: Any) -> type: """Convert raw functions into a stub class. Internally, a dataclass is constructed with a `__call__` method which **splats the arguments to the dataclass into the original callable. """ if inspect.isfunction(obj): from cappa import Dep from cappa.command import Command function_args: list[Any] = [] @functools.wraps(obj) def call(self: Any, *args: Any, **deps: Any): kwargs: dict[str, Any] = dataclasses.asdict(self) return obj(*args, **kwargs, **deps) callable_view = CallableView.from_callable(obj, include_extras=True) # We need to create a fake signature for the above callable, which does # not retain the `Arg` annotations signature = callable_view.signature sig_params: dict[str, inspect.Parameter] = dict(signature.parameters) signature._parameters = sig_params # type: ignore call.__signature__ = signature # type: ignore for param_view in callable_view.parameters: if not param_view.has_annotation: continue if find_annotations( param_view.type_view, Dep ) or param_view.type_view.is_subclass_of((Output, State, Command)): continue sig_params.pop(param_view.name, None) function_args.append( ( param_view.name, param_view.type_view.raw, dataclasses.field( default=param_view.default if param_view.has_default else dataclasses.MISSING ), ) ) result = dataclasses.make_dataclass( obj.__name__, function_args, namespace={"__call__": call}, ) result.__doc__ = obj.__doc__ result.__cappa__ = getattr(obj, "__cappa__", None) # type: ignore return result method_subcommands = collect_method_subcommands(obj) if method_subcommands: from cappa.subcommand import Subcommand kw_only: dict[str, typing.Any] = {} if sys.version_info >= (3, 10): kw_only["kw_only"] = True return dataclasses.make_dataclass( obj.__name__, [ ( "__cappa_subcommand__", Annotated[ typing.Union[method_subcommands], # pyright: ignore Subcommand("command", required=True), ], dataclasses.field( repr=False, compare=False, default=None, **kw_only ), ), ], bases=(obj,), ) return obj def collect_method_subcommands(cls: type) -> tuple[typing.Callable[..., Any], ...]: return tuple( method for _, method in inspect.getmembers(cls, callable) if hasattr(method, "__cappa__") ) def has_command(obj: object) -> bool: return hasattr(obj, "__cappa__") def get_command(obj: type) -> Command[Any] | None: return getattr(obj, "__cappa__", None) cappa-0.32.2/src/cappa/command.py0000644000000000000000000004075513615410400013460 0ustar00from __future__ import annotations import contextlib import dataclasses import sys from collections.abc import Callable from typing import ( TYPE_CHECKING, Any, ClassVar, Generator, Generic, Hashable, Iterable, Protocol, Sequence, TextIO, TypedDict, TypeVar, cast, ) from type_lens.type_view import TypeView from cappa.arg import Arg, FinalArg, Group from cappa.class_inspect import fields as get_fields from cappa.class_inspect import get_command, get_command_capable_object from cappa.docstring import ClassHelpText from cappa.help import HelpFormattable, HelpFormatter from cappa.invoke.types import Resolved from cappa.output import Exit, Output from cappa.state import S, State from cappa.subcommand import FinalSubcommand, Subcommand from cappa.type_view import CallableView from cappa.types import ParseResult from cappa.typing import assert_type if TYPE_CHECKING: from cappa.base import Backend, CappaCapable T = TypeVar("T") @dataclasses.dataclass(frozen=True) class Alias: """Describe an alternate name for a subcommand. Arguments: name: The alternate name a user may type to invoke the subcommand. hidden: If `True`, the alias is accepted but omitted from help output and from shell completion. Useful for supporting old names without advertising them. deprecated: If supplied, invoking the subcommand via this alias will emit a deprecation warning. If `True`, a default message is used; a string value is used as the deprecation message verbatim. """ name: str hidden: bool = False deprecated: bool | str = False @classmethod def coerce(cls, value: str | Alias) -> Alias: if isinstance(value, Alias): return value return cls(name=value) class CommandArgs(TypedDict, total=False): cmd_cls: type arguments: list[Arg[Any] | Subcommand] name: str | None aliases: list[str | Alias] help: str | None description: str | None invoke: Callable[..., Any] | str | None hidden: bool default_short: bool default_long: bool @dataclasses.dataclass class Command(Generic[T]): """Register a cappa CLI command/subcomment. Args: cmd_cls: The class representing the command/subcommand name: The name of the command. If omitted, the name of the command will be the name of the `cls`, converted to dash-case. help: Optional help text. If omitted, the `cls` docstring will be parsed, and the headline section will be used to document the command itself, and the arguments section will become the default help text for any params/options. description: Optional extended help text. If omitted, the `cls` docstring will be parsed, and the extended long description section will be used. epilog: Optional text displayed after the argument list in the help output. invoke: Optional command to be called in the event parsing is successful. In the case of subcommands, it will only call the parsed/selected function to invoke. The value can **either** be a callable object or a string. When the value is a string it will be interpreted as `module.submodule.function`; the module will be dynamically imported, and the referenced function invoked. hidden: If `True`, the command will not be included in the help output. This option is only relevant to subcommands. default_short: If `True`, all arguments will be treated as though annotated with `Annotated[T, Arg(short=True)]`, unless otherwise annotated. default_long: If `True`, all arguments will be treated as though annotated with `Annotated[T, Arg(long=True)]`, unless otherwise annotated. deprecated: If supplied, the argument will be marked as deprecated. If given `True`, a default message will be generated, otherwise a supplied string will be used as the deprecation message. """ cmd_cls: type[T] arguments: Sequence[Arg[Any] | Subcommand | FinalDestructure[Any]] = ( dataclasses.field(default_factory=lambda: []) ) propagated_arguments: list[FinalArg[Any]] = dataclasses.field( default_factory=lambda: [] ) name: str | None = None aliases: list[str | Alias] = dataclasses.field(default_factory=lambda: []) help: str | None = None description: str | None = None epilog: str | None = None invoke: Callable[..., Any] | str | None = None hidden: bool = False default_short: bool = False default_long: bool = False deprecated: bool | str = False help_formatter: HelpFormattable = HelpFormatter.default _collected: bool = False @classmethod def get( cls, obj: CappaCapable[T], help_formatter: HelpFormattable | None = None ) -> Command[T]: help_formatter = help_formatter or HelpFormatter.default instance = None if isinstance(obj, cls): instance = obj else: obj = get_command_capable_object(obj) instance = get_command(obj) if instance: return dataclasses.replace(instance, help_formatter=help_formatter) assert not isinstance(obj, Command) return cls( obj, # pyright: ignore help_formatter=help_formatter, ) def real_name(self) -> str: if self.name is not None: return self.name import re cls_name = self.cmd_cls.__name__ return re.sub(r"(? list[Alias]: return [Alias.coerce(a) for a in self.aliases] def collect( self, propagated_arguments: list[FinalArg[Any]] | None = None, state: State[Any] | None = None, ) -> FinalCommand[T]: kwargs: CommandArgs = CommandArgs() help_text = ClassHelpText.collect(self.cmd_cls) if not self.help: kwargs["help"] = help_text.summary if not self.description: kwargs["description"] = help_text.body fields = get_fields(self.cmd_cls) function_view = CallableView.from_callable(self.cmd_cls, include_extras=True) propagated_arguments = propagated_arguments or [] arguments: list[FinalArg[Any] | FinalDestructure[Any]] = [] raw_subcommands: list[tuple[Subcommand, TypeView[Any] | None, str | None]] = [] if self.arguments: param_by_name = {p.name: p for p in function_view.parameters} for arg in self.arguments: arg_help = help_text.args.get(assert_type(arg.field_name, str)) if isinstance(arg, Arg): type_view = ( param_by_name[cast(str, arg.field_name)].type_view if arg.field_name in param_by_name else None ) arguments.append( arg.normalize( type_view=type_view, default_short=self.default_short, default_long=self.default_long, fallback_help=arg_help, state=state, ) ) elif isinstance(arg, FinalDestructure): arguments.append(arg) else: raw_subcommands.append((arg, None, None)) else: param_by_name = {p.name: p for p in function_view.parameters} for field in fields: param_view = param_by_name[field.name] arg_help = help_text.args.get(param_view.name) maybe_subcommand = Subcommand.detect( field, param_view.type_view, ) if maybe_subcommand: raw_subcommands.append( ( maybe_subcommand, param_view.type_view, field.name, ) ) else: arg_defs = Arg.collect( field, param_view.type_view, fallback_help=arg_help, default_short=self.default_short, default_long=self.default_long, state=state, ) arguments.extend(arg_defs) propagating_arguments = [ *propagated_arguments, *(arg for arg in arguments if isinstance(arg, FinalArg) and arg.propagate), ] subcommands = [ subcommand.normalize( type_view, field_name, help_formatter=self.help_formatter, propagated_arguments=propagating_arguments, state=state, ) for subcommand, type_view, field_name in raw_subcommands ] check_group_identity([a for a in arguments if isinstance(a, FinalArg)]) final_arguments: list[ FinalArg[Any] | FinalSubcommand | FinalDestructure[Any] ] = [ *arguments, *subcommands, ] return FinalCommand( cmd_cls=self.cmd_cls, arguments=final_arguments, propagated_arguments=propagated_arguments, name=self.name, aliases=self.aliases, help=kwargs.get("help", self.help), description=kwargs.get("description", self.description), epilog=self.epilog, invoke=self.invoke, hidden=self.hidden, default_short=self.default_short, default_long=self.default_long, deprecated=self.deprecated, help_formatter=self.help_formatter, _collected=self._collected, ) @dataclasses.dataclass class FinalCommand(Command[T]): """Post-normalization form of :class:`Command` with narrowed field types. Produced exclusively by :meth:`Command.collect`. """ arguments: Sequence[FinalArg[Any] | FinalSubcommand | FinalDestructure[Any]] = ( # pyright: ignore dataclasses.field(default_factory=list) ) propagated_arguments: list[FinalArg[Any]] = dataclasses.field( # pyright: ignore default_factory=list ) @property def subcommand(self) -> FinalSubcommand | None: return next( (arg for arg in self.arguments if isinstance(arg, FinalSubcommand)), None, ) @property def value_arguments(self) -> Iterable[FinalArg[Any]]: for arg in self.arguments: if isinstance(arg, FinalArg) and arg.has_value: yield arg @property def destructured_arguments(self) -> Iterable[FinalDestructure[Any]]: for arg in self.arguments: if isinstance(arg, FinalDestructure): yield arg @property def all_arguments(self) -> Iterable[FinalArg[Any] | FinalSubcommand]: for arg in self.arguments: if not isinstance(arg, FinalDestructure): yield arg for arg in self.propagated_arguments: yield arg @property def options(self) -> Iterable[FinalArg[Any]]: for arg in self.arguments: if isinstance(arg, FinalArg) and arg.is_option: yield arg @property def positional_arguments( self, ) -> Iterable[FinalArg[Any] | FinalSubcommand]: for arg in self.arguments: if ( isinstance(arg, FinalArg) and not arg.short and not arg.long ) or isinstance(arg, FinalSubcommand): yield arg def add_meta_actions( self, help: FinalArg[bool] | None = None, version: FinalArg[str] | None = None, completion: FinalArg[bool] | None = None, ): if self._collected: return self arguments = [ dataclasses.replace( arg, options={ name: option.add_meta_actions(help) for name, option in arg.options.items() }, ) if help and isinstance(arg, FinalSubcommand) else arg for arg in self.arguments ] if help: arguments.append(help) if version: arguments.append(version) if completion: arguments.append(completion) return dataclasses.replace(self, arguments=arguments, _collected=True) def map_result( self, command: FinalCommand[T], prog: str, parsed_args: dict[str, Any], output: Output, state: State[Any] | None = None, input: TextIO | None = None, ) -> tuple[Resolved[T], dict[Hashable, Any]]: state = State.ensure(state) # pyright: ignore kwargs: dict[str, Any] = {} for arg in self.value_arguments: kwargs[arg.field_name] = arg.map_result( prog, parsed_args, state=state, input=input ) subcommand_deps: dict[Hashable, Any] = {} for destructure in self.destructured_arguments: fd_parsed = parsed_args.get(destructure.field_name, {}) fd_resolved = destructure.map_result( prog, fd_parsed, output, state=state, input=input ) kwargs[destructure.field_name] = fd_resolved subcommand = self.subcommand if subcommand: field_name = subcommand.field_name if field_name in parsed_args: value = parsed_args[field_name] value, subcommand_deps = subcommand.map_result( prog, value, output=output, state=state ) kwargs[field_name] = value def map_result(**kwargs: dict[str, Any]) -> T: with graceful_exit(command, prog, output): return command.cmd_cls(**kwargs) resolved = Resolved(map_result, kwargs=kwargs) key = cast(Hashable, command.cmd_cls) deps: dict[Hashable, Any] = {key: resolved, **subcommand_deps} return resolved, deps def parse_command( self, *, output: Output, backend: Backend, argv: list[str] | None = None, input: TextIO | None = None, state: State[S] | None = None, ) -> ParseResult[T, S]: if argv is None: # pragma: no cover argv = sys.argv[1:] prog = self.real_name() result_state: State[S] = State.ensure(state) # type: ignore with graceful_exit(self, prog, output): parser, parsed_command, parsed_args = backend( self, argv, output=output, prog=prog ) prog = parser.prog result, implicit_deps = self.map_result( self, prog, parsed_args, state=state, input=input, output=output ) return ParseResult( root_command=self, parsed_command=parsed_command, instance=result, implicit_deps=implicit_deps, output=output, state=result_state, ) H = TypeVar("H", covariant=True) class HasCommand(Generic[H], Protocol): __cappa__: ClassVar[Command[Any]] def check_group_identity(args: list[FinalArg[Any]]): group_identity: dict[str, Group] = {} for arg in args: identity = group_identity.get(arg.group.id) if identity and identity != arg.group: raise ValueError( f"Group details do not match among arguments: {arg.group.diff(identity)}." ) group_identity[arg.group.id] = arg.group @contextlib.contextmanager def graceful_exit( command: FinalCommand[T], prog: str, output: Output ) -> Generator[None, None, None]: try: yield except BaseException as e: if isinstance(e, Exit): command = e.command or command prog = e.prog or prog help = command.help_formatter.long(command, prog) short_help = command.help_formatter.short(command, prog) if isinstance(e, ValueError): exc = Exit(str(e), code=2, prog=prog, command=command) output.exit(exc, help=help, short_help=short_help) raise exc if isinstance(e, Exit): output.exit(e, help=help, short_help=short_help) raise raise from cappa.destructure import FinalDestructure # noqa: E402 cappa-0.32.2/src/cappa/default.py0000644000000000000000000002455513615410400013466 0ustar00from __future__ import annotations import os from dataclasses import dataclass from typing import ( Any, Callable, ClassVar, Hashable, Protocol, TextIO, Union, runtime_checkable, ) import rich.prompt from typing_extensions import Self, TypeAlias, TypeVar from cappa.state import State from cappa.type_view import Empty, EmptyType T = TypeVar("T") @dataclass(frozen=True) class Default: """Represents the sequence of fallback methods of default value retrieval for an `Arg`. **All** argument defaults (including static values) are coerced into `Default` instance. When evaluated for an argument, the provided sequence of default retrieval methods are evaluated in order, returning the value of the first option to return a non-None value. The `Default.default` value is returned if none of the provided methods return a value. Examples: - `Arg(default=4) == Arg(default=Default(default=4))` - `Arg(default=Env("FOO")) == Arg(default=Default(Env("FOO")))` - `Arg(default=Env("FOO") | Env("BAR")) == Arg(default=Default(Env("FOO"), Env("BAR")))` """ sequence: tuple[DefaultType, ...] = () default: EmptyType | Any = Empty def __init__(self, *sequence: DefaultTypes, default: Any = Empty): object.__setattr__(self, "sequence", sequence) object.__setattr__(self, "default", default) @classmethod def from_value(cls, default: Any) -> Default: """Produce a `Default` instance where the provided value is an explicit default. A default produced this way will resolve to a `Value` which gets appended to the fallback sequence rather than being overwriting the Default-level static fallback. Essentially `Default(..., Value(4), default=5)` will **always** evaluate to `4` if upstream fallbacks do not provide a value, and `5` will never be used. In practice this is utilized when `Arg(default=4)` is provided. This is distinct but similar to `Default() | 4 | 5` (which uses `fallback_to`), which would resolve to `Default((), default=5)`, the 4 being overwritten and never used. """ return cls().fallback_to(default, explicit=True) @classmethod def fallback_from(cls, *defaults: Default | EmptyType | Any | None) -> Default: """Return a `Default` instance from a sequence of potential defaults. This is used to compose different levels of default sources (Arg.default versus normalization time defaults). """ for default in defaults: if default is not Empty: return Default.from_value(default) return cls() def fallback_to(self, other: DefaultTypes | Any, explicit: bool = False) -> Default: """Produce a new Default from the current default and a supplied alternative. Examples: >>> Default(Env("FOO")).fallback_to(5) Default(sequence=(Env(env_vars=('FOO',), ...),), default=5) >>> Default(Env("FOO")).fallback_to(5, explicit=True) Default(sequence=(Env(env_vars=('FOO',), ...), Value(value=5)), default=<_EmptyEnum.EMPTY: 0>) """ cls = type(self) if isinstance(other, cls): return cls(*self.sequence, *other.sequence, default=other.default) if isinstance(other, default_types): default = self.default if self.default is Empty and isinstance(other, Env): default = other.default if isinstance(other, PromptType): other = Prompt.from_prompt(other) if isinstance(other, ConfirmType): other = Confirm.from_confirm(other) return cls(*self.sequence, other, default=default) if explicit: return cls(*self.sequence, Value(other)) return cls(*self.sequence, default=other) def __or__(self, other: Any) -> Default: """Compose two potential defaults, returning a new one. For example, `Default() | Prompt('...') | Env('FOO') | 5`. See `fallback_to` for details. """ return self.fallback_to(other) def __call__( self, state: State[Any] | None = None, input: TextIO | None = None ) -> tuple[bool, Any]: """Evaluate the default retrieval sequence, returning the first non-Empty value.""" for default in self.sequence: if isinstance(default, ValueFrom): value = default(state=state) elif isinstance(default, (Prompt, Confirm)): value = default(input=input) else: value = default() if value is not Empty: return default.is_parsed, value if self.default is Empty: return True, None return True, self.default @property def has_value(self) -> bool: """Whether the default instance **has** a default or if it's Empty.""" return not self.sequence and self.default is Empty @property def fallback_value(self) -> Any: return next( (d.value for d in self.sequence if isinstance(d, Value)), self.default ) @runtime_checkable class DefaultType(Protocol): is_parsed: ClassVar[bool] = False def __call__(self, *_: Any, **__: Any) -> Any: ... # pragma: no cover def __or__(self, other: DefaultTypes) -> Default: return Default(self) | other @dataclass(frozen=True) class Env(DefaultType): """Lazily evaluates environment variables in the order given. Argument value interpolation will handle and `Arg`'s default being an `Env` instance, by evaluating the `Env`, in the event the parser-level value fell back to the default. Examples: >>> from cappa import Arg, Env >>> arg = Arg(default=Env("HOST", "HOSTNAME", default="localhost")) """ env_vars: tuple[str, ...] default: str | EmptyType | None = Empty def __init__( self, env_var: str, *env_vars: str, default: str | EmptyType | None = Empty ): object.__setattr__(self, "env_vars", (env_var, *env_vars)) object.__setattr__(self, "default", default) def __call__(self) -> str | EmptyType | None: for env_var in self.env_vars: value = os.getenv(env_var) if value is not None: return value if self.default is None: return Empty return self.default class Prompt(rich.prompt.Prompt, DefaultType): """Prompt the user for a value, returning the response. Examples: >>> from cappa import Arg, Prompt >>> arg = Arg(default=Prompt("Ask user for value")) """ @classmethod def from_prompt(cls, prompt: Prompt | PromptType) -> Self: if isinstance(prompt, cls): return prompt return cls( console=prompt.console, prompt=prompt.prompt, password=prompt.password, choices=prompt.choices, show_default=prompt.show_default, show_choices=prompt.show_choices, ) def __call__(self, input: TextIO | None = None): # type: ignore return super().__call__(default=Empty, stream=input) class Confirm(rich.prompt.Confirm, DefaultType): """Prompt the user for a confirmation, returning `True`/`False`. Examples: >>> from cappa import Arg, Confirm >>> arg = Arg(default=Confirm("Confirm with user")) """ @classmethod def from_confirm(cls, confirm: Confirm | ConfirmType) -> Self: if isinstance(confirm, cls): return confirm return cls( console=confirm.console, prompt=confirm.prompt, password=confirm.password, choices=confirm.choices, show_default=confirm.show_default, show_choices=confirm.show_choices, ) def __call__(self, input: TextIO | None = None): # type: ignore return super().__call__(default=Empty, stream=input) @dataclass(frozen=True) class Value(DefaultType): value: Any is_parsed: ClassVar[bool] = True def __call__(self, state: State[Any] | None = None): return self.value @dataclass(frozen=True) class ValueFrom(DefaultType): """Retrieve the default value by calling the provided function. Optionally, `*args` and `**kwargs` to the supplied function can be provided ahead of time, to be supplied in the event the function is called. Examples: >>> from cappa import Arg, ValueFrom >>> def from_file(name): ... with open(name) as f: ... return f.read() >>> >>> arg = Arg(default=ValueFrom(from_file, name="config.json")) """ callable: Callable[..., Any] kwargs: dict[str, Any] is_parsed: ClassVar[bool] = True def __init__(self, callable: Callable[..., Any], **kwargs: Any): object.__setattr__(self, "callable", callable) object.__setattr__(self, "kwargs", kwargs) def __call__(self, state: State[Any] | None = None): from cappa.invoke.base import fulfill_deps kwargs = self.kwargs if state: deps: dict[Hashable, Any] = {State: state} resolved = fulfill_deps(self.callable, deps, allow_empty=True) kwargs = {**resolved.kwargs, **self.kwargs} return self.callable(**kwargs) @dataclass class DefaultFormatter: format: str = "{default}" show: bool | None = True @classmethod def disabled(cls) -> Self: return cls(show=False) @classmethod def from_unknown(cls, value: str | bool | Self) -> Self: if isinstance(value, cls): return value if isinstance(value, str): return cls(format=value) return cls(show=bool(value)) def format_default(self, default: Default, default_format: str = "") -> str: if self.show is False: return "" default_raw_value = default.fallback_value if default_raw_value in (None, Empty): default_raw_value = "" default_formatted_value = self.format.format(default=default_raw_value) if default_formatted_value == "": return default_formatted_value return default_format.format(default=default_formatted_value) PromptType = rich.prompt.Prompt ConfirmType = rich.prompt.Confirm default_types = (rich.prompt.Prompt, rich.prompt.Confirm, Env, ValueFrom) DefaultTypes: TypeAlias = Union[ Default, DefaultType, rich.prompt.Prompt, rich.prompt.Confirm ] cappa-0.32.2/src/cappa/destructure.py0000644000000000000000000000652513615410400014410 0ustar00from __future__ import annotations from dataclasses import dataclass, replace from typing import Any, Generic, TextIO, TypeVar from type_lens import TypeView from typing_extensions import Annotated from cappa.arg import FinalArg from cappa.default import Default from cappa.invoke.types import Resolved from cappa.output import Output from cappa.state import State @dataclass(frozen=True) class Destructure: """Collection for destructuring settings.""" @classmethod def collect( cls, field_name: str, default: Default, destructure: Destructure | bool | None, type_view: TypeView[Any], default_short: bool = False, default_long: bool = False, ) -> FinalDestructure[Any] | None: if not destructure: return None if destructure is True: destructure = Destructure() annotation = type_view.strip_optional().annotation if not isinstance(annotation, type): raise ValueError( "Destructured arguments currently only support singular concrete types." ) inner: Command[Any] = Command.get(annotation) # pyright: ignore inner = replace( inner, default_short=inner.default_short or default_short, default_long=inner.default_long or default_long, ) command: FinalCommand[Any] = inner.collect() return FinalDestructure( field_name=field_name, command=command, is_optional=type_view.is_optional, default=default, ) T = TypeVar("T") Destructured = Annotated[T, Destructure()] @dataclass(frozen=True) class FinalDestructure(Destructure, Generic[T]): """Post-normalization representation of a destructured argument. This is produced alongside FinalArg/FinalSubcommand in a `Command`'s set of arguments. The parser and mapper work together to produce the destructured value as a nested mapping that the contained virtual `command` can understand, and then the result is mapped to the final value of the field. """ field_name: str command: FinalCommand[T] is_optional: bool default: Default def map_result( self, prog: str, parsed_args: dict[str, Any], output: Output, state: State[Any] | None = None, input: TextIO | None = None, ) -> Resolved[T]: if self.is_optional and not parsed_args: _, value = self.default(state=state, input=input) return Resolved(lambda: value) return self.command.map_result( self.command, prog, parsed_args, output, state, input )[0] def explode_args(self) -> list[FinalArg[Any] | FinalDestructure[Any]]: virtual_args = self.command.arguments result: list[FinalArg[Any] | FinalDestructure[Any]] = [self] for virtual_arg in virtual_args: if not isinstance(virtual_arg, FinalArg): raise ValueError( "Only `Arg` is supported in the context of a destructured argument" ) virtual_arg = replace( virtual_arg, destructure=self, has_value=False, ) result.append(virtual_arg) return result from cappa.command import Command, FinalCommand # noqa: E402 cappa-0.32.2/src/cappa/docstring.py0000644000000000000000000000540713615410400014031 0ustar00from __future__ import annotations import ast import inspect import textwrap import typing from dataclasses import dataclass from inspect import cleandoc from types import ModuleType from typing_extensions import Self try: import docstring_parser as _docstring_parser docstring_parser: ModuleType | None = _docstring_parser except ImportError: # pragma: no cover docstring_parser = None @dataclass class ClassHelpText: summary: str | None body: str | None args: dict[str, str] @classmethod def collect(cls, command: type) -> Self: args: dict[str, str] = {} doc = get_doc(command) if docstring_parser: parsed_help = docstring_parser.parse(doc) for param in parsed_help.params: args[param.arg_name] = param.description summary = parsed_help.short_description body = parsed_help.long_description else: doc = inspect.cleandoc(doc).split("\n", 1) if len(doc) == 1: summary = doc[0] body = "" else: summary, body = doc body = body.strip() try: ast_args = get_attribute_docstrings(command) except Exception: ast_args = {} args.update(ast_args) return cls(summary=summary, body=body, args=args) def get_doc(cls: type): """Lifted from dataclasses source.""" doc = cls.__doc__ or "" # Dataclasses will set the doc attribute to the below value if there was no # explicit docstring. This is just annoying for us, so we treat that as though # there wasn't one. try: # In some cases fetching a signature is not possible. # But, we surely should not fail in this case. text_sig = str(inspect.signature(cls)).replace(" -> None", "") except (TypeError, ValueError): # pragma: no cover text_sig = "" dataclasses_docstring = cls.__name__ + text_sig if doc == dataclasses_docstring: return "" return doc def get_attribute_docstrings(command: type) -> dict[str, str]: result: dict[str, str] = {} raw_source = inspect.getsource(command) source = textwrap.dedent(raw_source) module = ast.parse(source) cls_node = module.body[-1] assert isinstance(cls_node, ast.ClassDef) last_assignment: ast.AnnAssign | None = None for node in cls_node.body: if isinstance(node, ast.Expr): if last_assignment: name = typing.cast(ast.Name, last_assignment.target).id value = typing.cast(ast.Constant, node.value).value result[name] = cleandoc(str(value)) continue last_assignment = node if isinstance(node, ast.AnnAssign) else None return result cappa-0.32.2/src/cappa/file_io.py0000644000000000000000000000406113615410400013436 0ustar00from __future__ import annotations import sys from dataclasses import dataclass from typing import IO, Any, BinaryIO, TextIO import cappa @dataclass class FileMode: """Factory for creating file object types. Instances of FileType are typically passed as type= arguments to the ArgumentParser add_argument() method. Arguments: mode: The file mode to use to open the file. Passes directly through to builtin `open()`. buffering: The file's desired buffer size. Passes directly through to builtin `open()`. encoding: The file's encoding. Passes directly through to builtin `open()`. errors: A string indicating how encoding and decoding errors are to be handled. Passes directly through to builtin `open()`. error_code: The exit code to use when an error occurs. Defaults to 1. Note this is **not** an `open()` argument. """ mode: str = "r" buffering: int = -1 encoding: str | None = None errors: str | None = None error_code: int = 1 def __call__(self, filename: str) -> IO[Any] | TextIO | BinaryIO: """Open the given `filename` and return the file handle. Supply "-" as the filename to read from stdin or write to stdout, depending on the chosen `mode`. """ # the special argument "-" means sys.std{in,out} if filename == "-": if "r" in self.mode: if "b" in self.mode: return sys.stdin.buffer return sys.stdin if any(c for c in self.mode if c in {"w", "a", "x"}): if "b" in self.mode: return sys.stdout.buffer return sys.stdout raise cappa.Exit( f"Invalid mode '{self.mode}' with supplied '-' file name.", code=self.error_code, ) try: return open(filename, self.mode, self.buffering, self.encoding, self.errors) except OSError as e: raise cappa.Exit(f"Cannot open {filename}: {e}", code=self.error_code) cappa-0.32.2/src/cappa/help.py0000644000000000000000000003350213615410400012762 0ustar00from __future__ import annotations import typing from collections.abc import Iterable from dataclasses import dataclass, replace from itertools import groupby from typing import Any, List, Sequence, Tuple, cast from rich.console import Console, NewLine from rich.markdown import Markdown from rich.padding import Padding from rich.table import Table from rich.text import Text from typing_extensions import Self, TypeAlias from cappa.arg import Arg, ArgAction, FinalArg from cappa.output import Displayable from cappa.subcommand import FinalSubcommand if typing.TYPE_CHECKING: from cappa.command import FinalCommand Dimension: TypeAlias = typing.Tuple[int, int, int, int] @dataclass(frozen=True) class FieldGroup: """A group of arguments or a subcommand sharing the same field_name. Either `args` or `subcommand` will be set, never both. """ field_name: str | None required: bool args: list[FinalArg[Any]] | None = None subcommand: FinalSubcommand | None = None @dataclass(frozen=True) class ArgGroup: """A group of arguments/subcommands organized by their Group.""" name: str field_groups: list[FieldGroup] @staticmethod def _by_group_key( arg: FinalArg[Any] | FinalSubcommand, ) -> tuple[int, int, str, bool]: return arg.group.key @staticmethod def _by_group(arg: FinalArg[Any] | FinalSubcommand) -> str: return arg.group.name @classmethod def collect( cls, command: FinalCommand[Any], include_hidden: bool = False ) -> list[ArgGroup]: """Collect and group arguments from a command by their Group.""" sorted_args = sorted(command.all_arguments, key=cls._by_group_key) result: list[ArgGroup] = [] for name, args in groupby(sorted_args, key=cls._by_group): items = [a for a in args if include_hidden or not a.hidden] field_grouped: dict[str | None, list[FinalArg[Any] | FinalSubcommand]] = {} for item in items: field_name = item.field_name or None if field_name not in field_grouped: field_grouped[field_name] = [] field_grouped[field_name].append(item) field_groups: list[FieldGroup] = [] for field_name, items in field_grouped.items(): # Validate that items are all Args or all Subcommands first_item = items[0] if isinstance(first_item, FinalArg): # Validate all are Args if not all(isinstance(item, FinalArg) for item in items): raise ValueError( # pragma: no cover f"FieldGroup with field_name={field_name!r} contains mixed types: " f"Args and Subcommands cannot share the same field_name" ) group_args = cast(List[FinalArg[Any]], items) # All items must have the same requiredness required_values = {arg.required for arg in group_args} assert len(required_values) == 1, ( f"All items with field_name={field_name!r} must have the same " f"required value, got: {required_values}" ) required = required_values.pop() field_groups.append( FieldGroup(field_name, required, args=group_args) ) else: assert len(items) == 1 assert isinstance(first_item, FinalSubcommand) field_groups.append( FieldGroup( field_name, first_item.required, subcommand=first_item, ) ) result.append(cls(name=name, field_groups=field_groups)) return result TextComponent = typing.Union[Text, Markdown, str] ArgFormat: TypeAlias = typing.Union[ TextComponent, typing.Callable[[Arg[Any]], typing.Union[TextComponent, None]], ] ArgFormats: TypeAlias = typing.Sequence[ArgFormat] class HelpFormattable(typing.Protocol): left_padding: Dimension arg_format: ArgFormat | ArgFormats default_format: str def long( self, command: FinalCommand[Any], prog: str ) -> list[Displayable]: ... # pragma: no cover def short( self, command: FinalCommand[Any], prog: str ) -> Displayable: ... # pragma: no cover @dataclass(frozen=True) class HelpFormatter(HelpFormattable): left_padding: Dimension = (0, 0, 0, 2) arg_format: ArgFormat | ArgFormats = ( Markdown("{help}"), Markdown("{choices}"), Markdown("{default}", style="dim italic"), ) default_format: str = "(Default: {default})" default: typing.ClassVar[HelpFormatter] def long(self, command: FinalCommand[Any], prog: str) -> list[Displayable]: arg_groups = ArgGroup.collect(command) lines: list[Displayable] = [] lines.append(add_short_args(prog, arg_groups)) if command.help: lines.append(NewLine()) lines.append(Padding(Markdown(f"**{command.help}**"), self.left_padding)) if command.description: lines.append(NewLine()) lines.append(Padding(Markdown(command.description), self.left_padding)) console = Console() lines.extend(add_long_args(console, self, arg_groups)) if command.epilog: lines.append(NewLine()) lines.append(Padding(Markdown(command.epilog), self.left_padding)) return lines def short(self, command: FinalCommand[Any], prog: str) -> Displayable: arg_groups = ArgGroup.collect(command) return add_short_args(prog, arg_groups) def with_arg_format( self, _format: ArgFormat | tuple[ArgFormat, ...], *formats: ArgFormat ) -> Self: format = cast( Tuple[ArgFormat, ...], _format if isinstance(_format, tuple) else (_format,) ) arg_format = (*format, *formats) return replace(self, arg_format=arg_format) def with_default_format(self, format: str) -> Self: return replace(self, default_format=format) HelpFormatter.default = HelpFormatter() def add_long_args( console: Console, help_formatter: HelpFormatter, arg_groups: list[ArgGroup] ) -> list[Table]: table = Table(box=None, expand=False, padding=help_formatter.left_padding) table.add_column(justify="left", ratio=1) table.add_column(style="cappa.help", ratio=2) for group in arg_groups: table.add_row( Text(group.name, style="cappa.group", justify="left"), Text(style="cappa.group"), ) for field_group in group.field_groups: if field_group.args: combined = format_arg_name(field_group, ", ") help_text = format_args(console, help_formatter, *field_group.args) table.add_row( Padding(combined, help_formatter.left_padding), help_text, ) else: assert field_group.subcommand subcommand = field_group.subcommand for option in subcommand.available_options(): table.add_row( *format_subcommand(help_formatter, option, subcommand) ) table.add_row() return [table] def format_args( console: Console, help_formatter: HelpFormattable, *args: FinalArg[Any] ) -> Displayable: """Format multiple args with the same field_name by concatenating their help texts.""" segments: list[TextComponent] = [] for arg in args: unknown_arg_format = help_formatter.arg_format if isinstance(unknown_arg_format, Iterable) and not isinstance( unknown_arg_format, str ): arg_format = cast(Sequence[ArgFormat], unknown_arg_format) else: arg_format = (unknown_arg_format,) for format_segment in arg_format: default = arg.show_default.format_default( arg.default, help_formatter.default_format ) choices = "" if arg.choices: choices = "Valid options: " + ", ".join(arg.choices) + "." context: dict[str, Any] = { "help": arg.help or "", "default": default, "choices": choices, "arg": arg, } if callable(format_segment): format_segment = cast(TextComponent, format_segment(arg)) format_segment_text = _get_text_component_text(format_segment) if not format_segment_text: continue formatted_text = format_segment_text.format(**context) segment = _replace_rich_text_component(format_segment, formatted_text) if segment: segments.append(segment) return _markdown_to_text(console, segments) def _markdown_to_text(console: Console, renderables: Sequence[TextComponent]) -> Text: # Render the text without line-wrapping because we're not yet printing it and it should reflow # until the point at which it's being printed to the user. render_console = Console(soft_wrap=True) render_options = render_console.options.update(no_wrap=True, overflow="ignore") result = Text() for renderable in renderables: if isinstance(renderable, Markdown): renderable_result = Text() for segment in render_console.render(renderable, render_options): text = segment.text if not text: continue if text.startswith(" "): # dedup leading spaces text = " " + text.lstrip() if text.endswith(" "): # dedup trailing spaces text = text.rstrip() + " " renderable_result.append(Text(text, style=segment.style or "", end="")) renderable_result.rstrip() if renderable_result: _append_to_text(result, renderable_result) else: if isinstance(renderable, str): renderable = Text.from_markup(renderable) _append_to_text(result, renderable) return result def _append_to_text(text: Text, component: Text) -> None: # When a component is multiline (like long, structured {help}), it looks weird when components # like {choices} or {default} are appended to the end of the last line. In such cases, add a newline. # Sort of a weird heuristic, we'll see if this holds well in practice. if text: text.append("\n" if "\n" in text.plain else " ") text.append(component) def _get_text_component_text(c: TextComponent) -> str: if isinstance(c, Text): return c.plain if isinstance(c, Markdown): return c.markup return c def _replace_rich_text_component(c: TextComponent, text: str) -> TextComponent: if isinstance(c, Text): return Text.from_markup( text, style=c.style, justify=c.justify, overflow=c.overflow, end=c.end, ) if isinstance(c, Markdown): return Markdown( text, code_theme=c.code_theme, justify=c.justify, style=c.style, hyperlinks=c.hyperlinks, inline_code_lexer=c.inline_code_lexer, inline_code_theme=c.inline_code_theme, ) return text def format_subcommand( help_formatter: HelpFormatter, command: FinalCommand[Any], subcommand: FinalSubcommand | None = None, ): canonical = command.real_name() parts = [f"[cappa.subcommand]{canonical}[/cappa.subcommand]"] if subcommand is not None: for alias in subcommand.visible_aliases_for(canonical): label = alias.name if alias.deprecated: label = f"{label} (deprecated)" parts.append(f"[cappa.subcommand]{label}[/cappa.subcommand]") return ( Padding(", ".join(parts), help_formatter.left_padding), command.help, ) def add_short_args(prog: str, arg_groups: list[ArgGroup]) -> str: segments: list[str] = [f"Usage: {prog}"] for group in arg_groups: for field_group in group.field_groups: segments.append(format_arg_name(field_group, ", ", n=1)) return " ".join(segments) def format_arg_name(item: FieldGroup, delimiter: str, *, n: int = 0) -> str: """Format the name(s) of args/subcommands for display. If given a FieldGroup, formats all items together with appropriate brackets. If given a single Arg/Subcommand, formats just that item. """ if item.args: parts: list[str] = [] for arg in item.args: num_args = arg.num_args has_value = ( not ArgAction.is_non_value_consuming(arg.action) and num_args.n != 0 ) arg_names = arg.names_str(delimiter, n=n) if not arg.is_option: arg_names = arg_names.upper() if num_args.n == -1: arg_names = f"{arg_names} ..." text = f"[cappa.arg]{arg_names}[/cappa.arg]" if arg.is_option and has_value: name = arg.value_name.upper() if num_args.n == -1: name = f"{name} ..." text = f"{text} [cappa.arg.name]{name}[/cappa.arg.name]" parts.append(text) combined = ", ".join(parts) if not item.required: return f"[{combined}]" return combined assert item.subcommand is not None arg_names = item.subcommand.names_str(",") return f"{{[cappa.subcommand]{arg_names}[/cappa.subcommand]}}" def format_subcommand_names(names: list[str]): return ", ".join(f"[cappa.subcommand]{a}[/cappa.subcommand]" for a in names) cappa-0.32.2/src/cappa/output.py0000644000000000000000000001507513615410400013377 0ustar00from __future__ import annotations import sys from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any, List, Union from rich.console import Console, RenderableType from rich.markup import escape from rich.text import Text from rich.theme import Theme from typing_extensions import TypeAlias if TYPE_CHECKING: from cappa.command import FinalCommand __all__ = [ "Displayable", "Exit", "HelpExit", "Output", "error_format", "error_format_without_short_help", "output_format", "theme", ] Displayable: TypeAlias = RenderableType Outputable: TypeAlias = Union[List[Displayable], Displayable, "Exit", str, Any, None] class Exit(SystemExit): def __init__( self, message: list[Displayable] | Displayable | None = None, *, command: FinalCommand[Any] | None = None, code: str | int | None = 0, prog: str | None = None, ): self.message = message self.prog = prog self.command = command super().__init__(code) class HelpExit(Exit): def __init__( self, message: list[Displayable] | Displayable, *, code: str | int | None = 0, prog: str | None = None, ): super().__init__(code=code, prog=prog) self.message = message theme: Theme = Theme( { "cappa.prog": "grey50", "cappa.group": "dark_orange bold", "cappa.arg": "cyan", "cappa.arg.name": "dark_cyan", "cappa.subcommand": "dark_cyan", "cappa.help": "", } ) output_format: str = "{message}" error_format: str = "{short_help}\n\n[red]Error[/red]: {message}" error_format_without_short_help: str = "[red]Error[/red]: {message}" @dataclass class Output: """Output sink for CLI std out and error streams. For simple customization (namely disabling color and overriding the theme), `invoke` and `parse` accept `color` and `theme` arguments, which internally configure the `output_console` and `error_console` fields. For more involved customization, an Output object can be supplied into either `invoke` or `parse` functions as well. Note, all input arguments to Output are optional. Arguments: output_console: Output sink, defaults to printing to stdout. error_console: Error sink, defaults to printing to stderr. output_format: Format string through which output_console output will be formatted. The following format string named format arguments can be used in `output_format`: prog, code, message. error_format: Format string through which output_console error will be formatted. The following format string named format arguments can be used in `error_format`: prog, code, message, help, short_help. Examples: >>> output = Output() >>> output = Output(error_format="{prog}: error: {message}") """ output_console: Console = field( default_factory=lambda: Console(file=sys.stdout, theme=theme) ) error_console: Console = field( default_factory=lambda: Console(file=sys.stderr, theme=theme) ) output_format: str = output_format error_format: str = error_format def color(self, value: bool = True): """Override the default `color` setting (None), to an explicit True/False value.""" self.output_console.no_color = not value self.error_console.no_color = not value return self def theme(self, t: Theme | None): """Override the default Theme, or reset the theme back to default (with `None`).""" self.output_console.push_theme(t or theme) self.error_console.push_theme(t or theme) def exit( self, e: Exit, help: list[Displayable] | None = None, short_help: Displayable | None = None, ): """Print a `cappa.Exit` object to the appropriate console.""" if e.code == 0: self.output(e, help=help, short_help=short_help) else: self.error(e, help=help, short_help=short_help) def __call__( self, message: Outputable, **context: Displayable | list[Displayable] | None, ): """Output a message to the `output_console`, exactly equivalent to `Output.output`.""" self.output(message, **context) def output( self, message: Outputable, **context: Displayable | list[Displayable] | None, ): """Output a message to the `output_console`. Additional `**context` can be supplied into the `output_format` string template. """ message = self._format_message( self.output_console, message, self.output_format, **context ) self.write(self.output_console, message) def error( self, message: Outputable, **context: Displayable | list[Displayable] | None, ): """Output a message to the `error_console`. Additional `**context` can be supplied into the `error_format` string template. """ message = self._format_message( self.error_console, message, self.error_format, **context ) self.write(self.error_console, message) def _format_message( self, console: Console, message: Outputable, format: str, **context: Displayable | list[Displayable] | None, ) -> Text | str | None: code: int | str | None = None prog = None if isinstance(message, Exit): code = message.code prog = message.prog message = message.message if message is None: return None text = rich_to_ansi(console, message) inner_context = { "code": code or 0, "prog": prog, "message": text, } context = {"short_help": None, "help": None, **context} rendered_context = { k: rich_to_ansi(console, v) if v else "" for k, v in context.items() } final_context = {**inner_context, **rendered_context} return Text.from_markup(format.format(**final_context).strip()) def write(self, console: Console, message: Text | str | None): if message is None: return console.print(message, overflow="ignore", crop=False) def rich_to_ansi(console: Console, message: Outputable) -> str: with console.capture() as capture: if isinstance(message, list): for m in message: # pyright: ignore console.print(m) else: console.print(message) return escape(capture.get().strip()) cappa-0.32.2/src/cappa/parse.py0000644000000000000000000002660213615410400013147 0ustar00from __future__ import annotations import contextlib import enum import functools import inspect import types from datetime import date, datetime, time from typing import ( Any, BinaryIO, Callable, Final, Generator, List, Sequence, TextIO, Type, Union, cast, ) from cappa.file_io import FileMode from cappa.output import Exit from cappa.state import S, State from cappa.type_view import TypeView from cappa.typing import T __all__ = [ "parse_list", "parse_literal", "parse_set", "parse_tuple", "parse_union", "parse_value", "unpack_arguments", ] type_priority: Final = types.MappingProxyType( { None: 0, ...: 1, float: 2, int: 3, bool: 4, str: 5, } ) Parser = Callable[..., T] MaybeTypeView = Union[Type[T], TypeView[Type[T]]] def unpack_arguments(value: object, type_view: TypeView[T]) -> Parser[T]: """`parse=` compatible function that splats values into a dataclass-like object constructor. For example, some `foo: Annotated[Object, Arg(parse[json.loads, splat_arguments])]` annotation would unpack the result of `json.loads` into the constructor of `Object`, like `Object(**data)`. """ origin = type_view.strip_optional().fallback_origin mapper = origin value_type_view = TypeView(type(value)) if value_type_view.is_mapping: return origin(**value) # pyright: ignore if value_type_view.is_collection and not value_type_view.is_subclass_of(str): return origin(*value) # pyright: ignore return mapper(value) def _as_type_view(typ: type[T] | TypeView[type[T]]) -> TypeView[type[T]]: if isinstance(typ, TypeView): return typ return TypeView(typ) def default_parse(value: str, type_view: TypeView[Any]) -> Any: """Perform the default inferred parse behavior of cappa's type inference system.""" parser: Parser[Any] = parse_value(type_view) return parser(value) def parse_value(typ: MaybeTypeView[T]) -> Parser[T]: """Create a value parser for the given annotation. Examples: >>> from typing import List, Literal, Tuple >>> list_parser = parse_value(List[int]) >>> tuple_parser = parse_value(Tuple[int, ...]) >>> literal_parser = parse_literal(Literal["foo"]) """ type_view = _as_type_view(typ) if type_view.is_type_alias: return parse_value(type_view.strip_type_alias()) if type_view.is_literal: return parse_literal(type_view) if type_view.is_union: return parse_union(type_view) if type_view.is_subclass_of((str, bool, int, float)): return type_view.annotation if type_view.is_none_type: return parse_none # type: ignore if type_view.is_subclass_of(enum.Enum): return parse_enum(type_view) if type_view.is_subclass_of(datetime): return datetime.fromisoformat # type: ignore if type_view.is_subclass_of(date): return date.fromisoformat # type: ignore if type_view.is_subclass_of(time): return time.fromisoformat # type: ignore if type_view.is_subclass_of(list): return parse_list(type_view) # type: ignore if type_view.is_subclass_of(set): return parse_set(type_view) # type: ignore if type_view.is_subclass_of(tuple): return parse_tuple(type_view) # type: ignore if type_view.is_subclass_of((TextIO, BinaryIO)): return parse_file_io(type_view) return parse_fallback(type_view.annotation) def parse_fallback(fallback: type[T]) -> Parser[T]: def fallback_mapper(v: Any) -> T: if isinstance(v, fallback): return v return fallback(v) # type: ignore return fallback_mapper def parse_literal(typ: MaybeTypeView[T]) -> Parser[T]: """Create a value parser for a given literal value.""" type_view = _as_type_view(typ) unique_type_args = set(type_view.args) def literal_mapper(value: T) -> T: if isinstance(value, (list, set, tuple)): for item in cast(Sequence[Any], value): if item not in unique_type_args: raise choices_error(type_view.args, item) return cast(T, value) if value in unique_type_args: return value for type_arg in unique_type_args: raw_value = str(type_arg) if raw_value == value: return type_arg raise choices_error(type_view.args, value) return literal_mapper def parse_enum(typ: MaybeTypeView[T]): type_view = _as_type_view(typ) choices: tuple[Any, ...] = tuple(v.value for v in type_view.annotation) def enum_mapper(value: Any) -> Any: try: return type_view.annotation(value) except ValueError: raise choices_error(choices, value) return enum_mapper def parse_list(typ: MaybeTypeView[list[T]]) -> Parser[list[T]]: """Create a value parser for a list of given type `of_type`.""" type_view = _as_type_view(typ) inner_mapper: Parser[T] = parse_value(type_view.inner_types[0]) def list_mapper(value: list[Any]) -> list[T]: return [inner_mapper(v) for v in value] return list_mapper def parse_set(typ: MaybeTypeView[list[T]]) -> Parser[set[T]]: """Create a value parser for a list of given type `of_type`.""" type_view = _as_type_view(typ) inner_mapper: Parser[T] = parse_value(type_view.inner_types[0]) def set_mapper(value: list[Any]) -> set[T]: return {inner_mapper(v) for v in value} return set_mapper def parse_tuple(typ: MaybeTypeView[T]) -> Parser[tuple[T]]: """Create a value parser for a tuple with type-args of given `type_args`.""" type_view = _as_type_view(typ) if type_view.is_variadic_tuple: assert type_view.args inner_type = type_view.args[0] list_mapper = parse_list(List[inner_type]) # type: ignore def unbounded_tuple_mapper(value: list[Any]) -> tuple[Any, ...]: return tuple(list_mapper(value)) return unbounded_tuple_mapper def tuple_mapper(value: list[Any]) -> tuple[Any]: result: list[Any] = [] for inner_type, inner_value in zip(type_view.inner_types, value): inner_mapper: Parser[T] = parse_value(inner_type) inner_value = inner_mapper(inner_value) result.append(inner_value) return tuple(result) return tuple_mapper def parse_union(typ: MaybeTypeView[T]) -> Parser[T]: """Create a value parser for a Union with type-args of given `type_args`.""" def type_priority_key(type_type_view: TypeView[Any]) -> int: return type_priority.get(type_type_view.annotation, 1) def union_mapper(value: Any) -> Any: exceptions: list[str] = [] for mapper_type_view, mapper in mappers: try: return mapper(value) except (ValueError, TypeError) as e: if mapper is parse_none: err = " - " else: err = f" - {mapper_type_view.repr_type}: {e}" exceptions.append(err) # Perhaps we should be showing all failed mappings at some point. As-is, # the preferred interpretation will be determined by order in the event # of all mappings failing reasons = "\n".join(exceptions) raise ValueError(f"Possible variants\n{reasons}") type_view = _as_type_view(typ) mappers: list[tuple[TypeView[T], Callable[..., Any]]] = [ (t, parse_value(t)) for t in sorted(type_view.inner_types, key=type_priority_key) ] return union_mapper def parse_none(value: Any) -> None: """Create a value parser for None.""" if value is None: return raise ValueError(value) def parse_file_io(typ: MaybeTypeView[T]) -> Parser[T]: type_view = _as_type_view(typ) def file_io_mapper(value: str) -> T: try: file_mode: FileMode = next( f for f in type_view.metadata if isinstance(f, FileMode) ) except StopIteration: file_mode = FileMode() if type_view.is_subclass_of(BinaryIO): file_mode.mode += "b" return file_mode(value) # type: ignore return file_io_mapper def evaluate_parse( parsers: Parser[T] | Sequence[Parser[Any]], type_view: TypeView[T], state: State[S] | None = None, ) -> Callable[..., Any]: import inspect from cappa.invoke.base import fulfill_deps state = State.ensure(state) # type: ignore if callable(parsers): parsers = [parsers] filled_parsers = [ functools.partial( parser, **fulfill_deps( parser, {TypeView: type_view, State: state}, allow_empty=True, ).kwargs, ) for parser in parsers ] if len(parsers) == 1: return filled_parsers[0] # Check if any parser is async has_async_parser = any(inspect.iscoroutinefunction(p.func) for p in filled_parsers) if has_async_parser: async def async_sequence_parsers(value: Any) -> T: result = value for parser in filled_parsers: result = parser(result) # If the result is a coroutine, await it if inspect.iscoroutine(result): result = await result return result # pyright: ignore[reportReturnType] return async_sequence_parsers def sequence_parsers(value: Any) -> T: result = value for parser in filled_parsers: result = parser(result) return result return sequence_parsers def choices_error(choices: Sequence[Any], value: Any) -> Exception: options = ", ".join(f"{t!r}" for t in choices) return ValueError(f"Invalid choice: '{value}' (choose from {options})") @contextlib.contextmanager def apply_parse( parse_fn: Callable[..., Any], prog: str, value: Any, is_parsed: bool, names_str: str, ) -> Generator[Any, None, None]: if not is_parsed: try: value = parse_fn(value) except Exception as e: raise Exit( f"Invalid value for '{names_str}': {e}", code=2, prog=prog, ) yield value def parse_handler( parse_fn: Callable[..., Any], prog: str, value: Any, names_str: str, ) -> Callable[[Any, bool], Any]: is_async_value = inspect.iscoroutine(value) is_async_parse = inspect.iscoroutinefunction( parse_fn ) or inspect.isasyncgenfunction(parse_fn) if is_async_value or is_async_parse: async def async_parse(raw_value: Any, is_parsed: bool) -> Any: if inspect.iscoroutine(raw_value): raw_value = await raw_value with apply_parse(parse_fn, prog, raw_value, is_parsed, names_str) as parsed: if inspect.iscoroutine(parsed): try: return await parsed except Exception as e: raise Exit( f"Invalid value for '{names_str}': {e}", code=2, prog=prog, ) return parsed return async_parse def sync_parse(raw_value: Any, is_parsed: bool) -> Any: with apply_parse(parse_fn, prog, raw_value, is_parsed, names_str) as parsed: return parsed return sync_parse cappa-0.32.2/src/cappa/parser.py0000644000000000000000000006241513615410400013333 0ustar00from __future__ import annotations import dataclasses import re from collections import deque from functools import cached_property from typing import ( Any, Callable, Generic, Hashable, Iterable, cast, ) from cappa.arg import Arg, ArgAction, ArgActionType, FinalArg from cappa.command import Alias, Command, FinalCommand from cappa.completion.types import Completion, FileCompletion from cappa.help import format_args, format_subcommand_names from cappa.invoke.base import fulfill_deps from cappa.output import Exit, HelpExit, Output from cappa.subcommand import FinalSubcommand from cappa.typing import T negative_number = re.compile(r"^-\d+$|^-\d*\.\d+$") class BadArgumentError(RuntimeError): def __init__( self, message: str, *, value: Any, command: FinalCommand[Any], arg: Arg[Any] | FinalSubcommand | None = None, ) -> None: super().__init__(message) self.value = value self.command = command self.arg = arg @dataclasses.dataclass class HelpAction(RuntimeError): command: FinalCommand[Any] command_name: str @classmethod def from_parse_state(cls, parse_state: ParseState, command: FinalCommand[Any]): raise cls(command, parse_state.prog) @dataclasses.dataclass class VersionAction(RuntimeError): version: Arg[Any] @classmethod def from_arg(cls, arg: Arg[Any]): raise cls(arg) class CompletionAction(RuntimeError): def __init__( self, *completions: Completion | FileCompletion, value: str = "complete", arg: Arg[Any] | None = None, ) -> None: self.completions = completions self.value = value self.arg = arg @classmethod def from_value(cls, value: Value[str], arg: Arg[Any]): raise cls(value=value.value, arg=arg) def backend( command: FinalCommand[T], argv: list[str], output: Output, prog: str, provide_completions: bool = False, ) -> tuple[Any, FinalCommand[T], dict[str, Any]]: context = ParseContext.from_command(command) parse_state = ParseState.from_command( argv, command, output=output, provide_completions=provide_completions ) try: try: parse(parse_state, context) except HelpAction as e: raise HelpExit( e.command.help_formatter.long(e.command, e.command_name), code=0, prog=parse_state.prog, ) except VersionAction as e: raise Exit(cast(str, e.version.value_name), code=0, prog=parse_state.prog) except BadArgumentError as e: if parse_state.provide_completions and e.arg: completions = e.arg.completion(e.value) if e.arg.completion else [] raise CompletionAction(*completions) raise Exit(str(e), code=2, prog=parse_state.prog, command=e.command) except CompletionAction as e: from cappa.completion.base import execute, format_completions if provide_completions: completions = format_completions(*e.completions) raise Exit(completions, code=0) execute(command, prog, e.value, cast(Arg[Any], e.arg), output=output) if provide_completions: raise Exit(code=0) return (parse_state, parse_state.current_command or command, context.result) @dataclasses.dataclass class ParseState: """The overall state of the argument parse.""" args: ArgCollection command_stack: list[FinalCommand[Any]] output: Output provide_completions: bool = False @classmethod def from_command( cls, argv: list[str], command: FinalCommand[Any], output: Output, provide_completions: bool = False, ): arg_stream = ArgCollection(deque(argv), provide_completions=provide_completions) return cls( arg_stream, command_stack=[command], output=output, provide_completions=provide_completions, ) @property def current_command(self): return self.command_stack[-1] @property def prog(self): return " ".join(c.real_name() for c in self.command_stack) def push_command(self, command: FinalCommand[Any]): self.command_stack.append(command) @dataclasses.dataclass class ParseContext: """The parsing context specific to a command.""" command: FinalCommand[Any] arguments: deque[FinalArg[Any] | FinalSubcommand] missing_options: set[str] arguments_by_field_name: dict[str, list[FinalArg[Any]]] arguments_by_value_name: dict[str, FinalArg[Any]] propagated_options: set[str] parent_context: ParseContext | None = None exclusive_args: dict[str, FinalArg[Any]] = dataclasses.field( default_factory=lambda: {} ) result: dict[str, Any] = dataclasses.field(default_factory=lambda: {}) @classmethod def from_command( cls, command: FinalCommand[Any], parent_context: ParseContext | None = None, ) -> ParseContext: arguments_by_field_name: dict[str, list[FinalArg[Any]]] = {} arguments_by_value_name: dict[str, FinalArg[Any]] = {} propagated_options: set[str] = set() unique_field_names: set[str] = set() def add_option_names(arg: FinalArg[Any]): for opts in (arg.short, arg.long): if not opts: continue for key in opts: if key in arguments_by_value_name: raise ValueError(f"Conflicting option string: {key}") arguments_by_value_name[key] = arg native_command_field_names: set[str] = set() for arg in command.options: field_name = arg.field_name if arg.action not in ArgAction.meta_actions(): unique_field_names.add(field_name) native_command_field_names.add(field_name) arguments_by_field_name.setdefault(field_name, []).append(arg) add_option_names(arg) for arg in command.propagated_arguments: field_name = arg.field_name if field_name in native_command_field_names: continue propagated_options.add(field_name) arguments_by_field_name.setdefault(field_name, []).append(arg) add_option_names(arg) arguments = deque(command.positional_arguments) return cls( command=command, parent_context=parent_context, arguments_by_field_name=arguments_by_field_name, arguments_by_value_name=arguments_by_value_name, propagated_options=propagated_options, arguments=arguments, missing_options=unique_field_names, ) @cached_property def propagated_context(self) -> dict[str, ParseContext]: parent_context = ( self.parent_context.propagated_context if self.parent_context else {} ) self_options = {o.field_name: o for o in self.command.options if o.propagate} self_context: dict[str, ParseContext] = dict.fromkeys(self_options, self) return {**parent_context, **self_context} def next_argument(self): return self.arguments.popleft() def resolve_context(self, field_name: str, option: RawOption | None = None): context = self if option and field_name in self.propagated_options: context = self.propagated_context[field_name] return context def set_result( self, field_name: str, value: Any, option: RawOption | None = None, has_value: bool = True, ): if option and field_name in self.missing_options: self.missing_options.remove(field_name) if has_value: self.result[field_name] = value def push(self, command: FinalCommand[Any], name: str) -> ParseContext: nested_context = ParseContext.from_command(command, parent_context=self) nested_context.result["__name__"] = name return nested_context @dataclasses.dataclass class ArgCollection: """Represent the pending parse state of remaining unparsed arguments. Whether a given fragment to be parsed is considered an Option or an Argument is context dependent (e.x. following `--`) and it simplifies code downstream if that decision is being made on-demand rather than eagerly in a way that needs to be corrected at the use-sites. """ argv: deque[str] provide_completions: bool = False encountered_double_dash: bool = False pending_args: deque[RawOption | RawArg] = dataclasses.field( default_factory=lambda: deque() ) def has_values(self) -> bool: return bool(self.argv) or bool(self.pending_args) def peek_value(self, context: ParseContext) -> RawArg | RawOption | None: next_value = self.next(context) if next_value is None: return None self.pending_args.appendleft(next_value) return next_value def next(self, context: ParseContext) -> RawArg | RawOption | None: if self.pending_args: return self.pending_args.popleft() if self.argv: next_value = self.argv.popleft() if self.encountered_double_dash: item: RawArg | RawOption | None = RawArg(next_value) else: item = RawArg.from_str( next_value, provide_completions=self.provide_completions ) if item is None: self.encountered_double_dash = True return None if isinstance(item, RawOption) and not item.is_long: item = self.generate_virtual_args(item, context.arguments_by_value_name) return item return None def generate_virtual_args( self, arg: RawOption, options: dict[str, Any] ) -> RawOption | RawArg: """Produce "virtual" options from short (potentially concatenated) options. Examples: -abc -> -a, -b, -c -c0 -> -c 0 -abc0 -> -a, -b, -c, 0 """ result: list[RawOption | RawArg] = [] partial_arg = "" remaining_arg = arg.name[1:] while remaining_arg: partial_arg += remaining_arg[0] remaining_arg = remaining_arg[1:] option_name = f"-{partial_arg}" option = options.get(option_name) if option: result.append(RawOption(option_name, value=arg.value)) partial_arg = "" # An option which requires consuming further arguments should consume # the rest of the concatenated character sequence as its value. if option.num_args.n: partial_arg = remaining_arg break if not result: # i.e. -p, where -p is not a real short option. It will get skipped above. option = RawOption(arg.name, value=arg.value) return option.to_negative_number() or option if partial_arg: result.append(RawArg(partial_arg)) option, *virtual_args = result self.pending_args.extend(virtual_args) return option @dataclasses.dataclass class RawArg: raw: str @classmethod def from_str( cls, arg: str, *, provide_completions: bool = False ) -> RawArg | RawOption | None: skip = arg == "--" and not provide_completions if skip: return None is_option = arg and arg[0] == "-" and (provide_completions or len(arg) > 1) if is_option: return RawOption.from_str(arg) return cls(arg) @dataclasses.dataclass class RawOption: name: str is_long: bool = True value: str | None = None @classmethod def from_str(cls, arg: str) -> RawOption: is_long = arg.startswith("--") is_explicit = "=" in arg name = arg value = None if is_explicit: name, value = arg.split("=", 1) return cls(name=name, is_long=is_long, value=value) def to_negative_number(self) -> RawArg | None: if re.match(negative_number, self.name): return RawArg(self.name) return None def parse(parse_state: ParseState, context: ParseContext) -> None: while True: while isinstance(parse_state.args.peek_value(context), RawOption): arg = cast(RawOption, parse_state.args.next(context)) parse_option(parse_state, context, arg) parse_args(parse_state, context) if not parse_state.args.has_values(): break # Options are not explicitly iterated over because they can occur multiple times non-contiguouesly. # So instead we check afterward, if there are any missing which we haven't yet fulfilled. required_missing_options = [ arg for opt_name in sorted(context.missing_options) for arg in context.arguments_by_field_name[opt_name] if arg.required ] if required_missing_options: names = ", ".join([opt.names_str("/") for opt in required_missing_options]) raise BadArgumentError( f"The following arguments are required: {names}", value="", command=parse_state.current_command, arg=required_missing_options[0], ) def parse_option( parse_state: ParseState, context: ParseContext, raw: RawOption ) -> None: if raw.name not in context.arguments_by_value_name: possible_options: dict[str, FinalArg[Any]] = { name: arg for name, arg in context.arguments_by_value_name.items() if name.startswith(raw.name) } if parse_state.provide_completions: options: list[Completion] = [] for name, option in possible_options.items(): rendered_help = str( format_args( parse_state.output.output_console, context.command.help_formatter, option, ) ).strip() completion = Completion(name, help=rendered_help, arg=option) options.append(completion) raise CompletionAction(*options) message = f"Unrecognized arguments: {raw.name}" if possible_options: message += f" (Did you mean: {', '.join(possible_options.keys())})" raise BadArgumentError( message, value=raw.name, command=parse_state.current_command ) arg = context.arguments_by_value_name[raw.name] consume_arg(parse_state, context, arg, raw) def parse_args(parse_state: ParseState, context: ParseContext) -> None: while context.arguments: peeked = parse_state.args.peek_value(context) if isinstance(peeked, RawOption): break arg = context.next_argument() if isinstance(arg, FinalSubcommand): consume_subcommand(parse_state, context, arg) else: consume_arg(parse_state, context, arg) else: value = parse_state.args.peek_value(context) if value is None or isinstance(value, RawOption): return raw_values: list[str] = [] while parse_state.args.peek_value(context): next_val = parse_state.args.next(context) if not isinstance(next_val, RawArg): break raw_values.append(next_val.raw) raise BadArgumentError( f"Unrecognized arguments: {', '.join(raw_values)}", value=raw_values, command=parse_state.current_command, ) def consume_subcommand( parse_state: ParseState, context: ParseContext, arg: FinalSubcommand ) -> Any: value = parse_state.args.next(context) if value is None: if not arg.required: return raise BadArgumentError( f"A command is required: {{{format_subcommand_names(arg.names())}}}", value="", command=parse_state.current_command, arg=arg, ) assert isinstance(value, RawArg), value canonical = arg.resolve_name(value.raw) if canonical is None: message = f"Invalid command '{value.raw}'" possible_values = [ name for name in arg.all_visible_names() if name.startswith(value.raw) ] if possible_values: message += f" (Did you mean: {format_subcommand_names(possible_values)})" raise BadArgumentError( message, value=value.raw, command=parse_state.current_command, arg=arg, ) command = arg.options[canonical] check_deprecated(parse_state, command) if value.raw != canonical: _, alias = arg.alias_map[value.raw] _warn_deprecated_alias(parse_state, alias) parse_state.push_command(command) nested_context = context.push(command, canonical) parse(parse_state, nested_context) context.result[arg.field_name] = nested_context.result def iter_arg_values( context: ParseContext, parse_state: ParseState, num_args: int, option: RawOption | None, ) -> Iterable[str]: """Collect argument values from the parse state.""" # If option has explicit value (e.g., --opt=val), yield it and stop if option and option.value: yield option.value return remaining = num_args while remaining: peeked_value = parse_state.args.peek_value(context) if peeked_value is None: break if isinstance(peeked_value, RawOption): break next_value = peeked_value.raw parse_state.args.next(context) yield next_value # num_args == -1 means unbounded, so remaining will always be truthy remaining -= 1 def arg_bypasses_action( arg: FinalArg[Any], values: list[str], expected_count: int, option: RawOption | None, parse_state: ParseState, ) -> bool: """Check whether the current argument is in a state that bypasses action processing. This is mainly to handle falling back to the default value when an expected one is missing. """ is_positional = not option is_option_collecting_values = option and not option.value is_single_value = expected_count == 1 is_fixed_count = expected_count > 0 is_unbounded = expected_count < 0 has_value = bool(values) has_expected_value = is_fixed_count and len(values) == expected_count expects_value = option or arg.required if has_expected_value: return False if is_single_value: if not has_value and expects_value: raise BadArgumentError( f"Option '{arg.value_name}' requires an argument", value="", command=parse_state.current_command, arg=arg, ) return not has_value is_unbounded_empty = is_unbounded and not has_value if not is_fixed_count and not is_unbounded_empty: return False is_optional_positional_without_value = ( is_positional and not arg.required and not has_value ) is_unbounded_optional_without_value = ( is_unbounded_empty and not arg.required and is_option_collecting_values ) if ( not is_optional_positional_without_value and not is_unbounded_optional_without_value ): names_str = arg.names_str("/") count_desc = "at least one" if is_unbounded else expected_count message = ( f"Argument '{names_str}' requires {count_desc} values, found {len(values)}" ) if values: quoted = [f"'{v}'" for v in values] message += f" ({', '.join(quoted)} so far)" raise BadArgumentError( message, value=values, command=parse_state.current_command, arg=arg, ) return is_optional_positional_without_value def check_exclusive_group( arg: FinalArg[Any], context: ParseContext, result: Any, parse_state: ParseState, ) -> None: """Check if arg violates exclusive group constraints.""" group = arg.group if not group.exclusive: return exclusive_arg = context.exclusive_args.get(group.id) if exclusive_arg and exclusive_arg != arg: raise BadArgumentError( f"Argument '{arg.names_str('/')}' is not allowed with argument" f" '{exclusive_arg.names_str('/')}'", value=result, command=parse_state.current_command, arg=arg, ) context.exclusive_args[group.id] = arg def _resolve_context( context: ParseContext, field_name: str, option: RawOption | None, arg: FinalArg[Any], ) -> tuple[ParseContext, bool]: resolved = context.resolve_context(field_name, option) if arg.destructure: nested = resolved.result.setdefault(arg.destructure.field_name, {}) resolved = dataclasses.replace(resolved, result=nested) return resolved, True return resolved, arg.has_value def consume_arg( parse_state: ParseState, context: ParseContext, arg: FinalArg[Any], option: RawOption | None = None, ) -> Any: field_name = arg.field_name # Determine how many values to collect normalized_num_args = arg.num_args expected_count = normalized_num_args.n if ArgAction.is_non_value_consuming(arg.action): expected_count = 0 values = list(iter_arg_values(context, parse_state, expected_count, option)) # Argument whose value is optional, without a value: falls back to NumArgs.default. Distinct from # an optional argument, who's value would be unrecorded and apply the default during mapping. if option is not None and not normalized_num_args.required and not values: check_exclusive_group(arg, context, normalized_num_args.default, parse_state) resolved_context, has_value = _resolve_context(context, field_name, option, arg) resolved_context.set_result( field_name, normalized_num_args.default, option, has_value ) check_deprecated(parse_state, arg, option) return if arg_bypasses_action(arg, values, expected_count, option, parse_state): return # Convert single-value args to scalar result: str | list[str] = values[0] if expected_count == 1 else values # Handle completions for args with values if parse_state.provide_completions and not parse_state.args.has_values() and values: if arg.completion: completions: list[Completion] | list[FileCompletion] = arg.completion( result ) else: completions = [FileCompletion(values[-1])] raise CompletionAction(*completions) check_exclusive_group(arg, context, result, parse_state) action_handler = determine_action_handler(arg.action) resolved_context, has_value = _resolve_context(context, field_name, option, arg) fulfilled_deps: dict[Hashable, Any] = { FinalCommand: parse_state.current_command, Command: parse_state.current_command, Output: parse_state.output, ParseContext: resolved_context, ParseState: parse_state, Arg: arg, FinalArg: arg, Value: Value(result), Any: result, } if option: fulfilled_deps[RawOption] = option kwargs = fulfill_deps(action_handler, fulfilled_deps).kwargs result = action_handler(**kwargs) resolved_context.set_result(field_name, result, option, has_value) check_deprecated(parse_state, arg, option) def check_deprecated( parse_state: ParseState, arg: FinalArg[Any] | FinalCommand[Any], option: RawOption | None = None, ) -> None: if not arg.deprecated: return if option: kind = "Option" name = option.name else: if isinstance(arg, Command): kind = "Command" name = arg.real_name() else: kind = "Argument" name = arg.names_str("/") message = f"{kind} `{name}` is deprecated" if isinstance(arg.deprecated, str): message += f": {arg.deprecated}" parse_state.output.error(message) def _warn_deprecated_alias(parse_state: ParseState, alias: Alias) -> None: if not alias.deprecated: return message = f"Command alias `{alias.name}` is deprecated" if isinstance(alias.deprecated, str): message += f": {alias.deprecated}" parse_state.output.error(message) @dataclasses.dataclass class Value(Generic[T]): value: T def store_true(): return True def store_false(): return False def store_count(context: ParseContext, arg: Arg[Any]): return context.result.get(cast(str, arg.field_name), 0) + 1 def store_set(value: Value[Any]): return value.value def store_append(context: ParseContext, arg: Arg[Any], value: Value[Any]): result = context.result.setdefault(cast(str, arg.field_name), []) result.append(value.value) return result def determine_action_handler(action: ArgActionType | None): assert action if isinstance(action, ArgAction): return process_options[action] return action process_options: dict[ArgAction, Callable[..., Any]] = { ArgAction.help: HelpAction.from_parse_state, ArgAction.version: VersionAction.from_arg, ArgAction.completion: CompletionAction.from_value, ArgAction.set: store_set, ArgAction.store_true: store_true, ArgAction.store_false: store_false, ArgAction.count: store_count, ArgAction.append: store_append, } cappa-0.32.2/src/cappa/py.typed0000644000000000000000000000000013615410400013142 0ustar00cappa-0.32.2/src/cappa/state.py0000644000000000000000000000316513615410400013154 0ustar00from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Dict, Generic, TypedDict, TypeVar, Union, overload class BaseTypedDict(TypedDict): ... S = TypeVar("S", bound=Union[Dict[str, Any], BaseTypedDict]) @dataclass class State(Generic[S]): """A store of arbitrary data. State can be used as arguments to `Arg.parse`, `Arg.action`, or `Arg.default=ValueFrom(...)` callables, as a way to propagate shared state between different parts of the overall parse execution. Example: >>> from typing_extensions import Annotated >>> import cappa >>> def parse_arg(state: State): ... state.state["arg"] = state.state.get("arg", 0) ... return state.state["arg"] >>> @dataclass ... class Command: ... arg1: Annotated[int, cappa.Arg(action=parse_arg)] ... arg2: Annotated[int, cappa.Arg(parse=parse_arg)] ... arg3: Annotated[int, cappa.Arg(default=cappa.ValueFrom(parse_arg))] """ state: S = field(default_factory=lambda: {}) # type: ignore def set(self, key: str, value: Any): self.state[key] = value # type: ignore def get(self, key: str, *, default: Any = None): return self.state.get(key, default) @overload @classmethod def ensure(cls, state: None) -> State[dict[str, Any]]: ... @overload @classmethod def ensure(cls, state: State[S]) -> State[S]: ... @classmethod def ensure(cls, state: State[S] | None) -> State[S] | State[dict[str, Any]]: if state is None: return State() return state cappa-0.32.2/src/cappa/subcommand.py0000644000000000000000000002257713615410400014174 0ustar00from __future__ import annotations import dataclasses from typing import TYPE_CHECKING, Any, Iterable, Mapping, TextIO from typing_extensions import Annotated, TypeAlias from cappa.arg import Group from cappa.class_inspect import Field, extract_dataclass_metadata from cappa.completion.types import Completion from cappa.invoke.types import Resolved from cappa.state import State from cappa.type_view import Empty, EmptyType, TypeView from cappa.typing import T, assert_type, find_annotations if TYPE_CHECKING: from cappa.arg import FinalArg from cappa.command import Alias, Command, FinalCommand from cappa.help import HelpFormattable from cappa.output import Output DEFAULT_SUBCOMMAND_GROUP = Group(3, "Subcommands", section=1) @dataclasses.dataclass class Subcommand: """Describe a CLI subcommand. Arguments: field_name: Defaults to the name of the class, converted to dash case, but can be overridden here. required: Defaults to automatically inferring requiredness, based on whether the class's value has a default. By setting this, you can force a particular value. group: The subcommand group, for use in controlling help text for the subcommand, and where it is displayed. This can be any of: the string name (``'Subcommands'``), a 2-tuple of the `order` and the name (``(3, "Subcommands")``), or a :class:`Group` instance (``Group(3, 'Subcommands')``) hidden: Whether the argument should be hidden in help text. Defaults to False. options: A mapping of the subcommand names to the corresponding `Command` to which the subcommands refer. Unless imperatively constructing the CLI structure, this field should generally always be inferred automatically. types: Defaults to the class's annotated types, but can be overridden here. """ field_name: str | EmptyType = Empty required: bool | None = None group: str | tuple[int, str] | Group = DEFAULT_SUBCOMMAND_GROUP hidden: bool = False options: Mapping[str, Command[Any]] = dataclasses.field(default_factory=lambda: {}) types: Iterable[type] | EmptyType = Empty # Mapping of alias name -> (canonical name in `options`, Alias metadata). # Populated during `normalize` from each Command's `aliases` field; collisions raise. alias_map: dict[str, tuple[str, Alias]] = dataclasses.field( default_factory=lambda: {} ) @classmethod def detect(cls, field: Field, type_view: TypeView[Any]) -> Subcommand | None: subcommands = find_annotations(type_view, Subcommand) or None field_metadata = extract_dataclass_metadata(field, Subcommand) if field_metadata: subcommands = field_metadata if not subcommands: return None assert len(subcommands) == 1 return subcommands[0] def normalize( self, type_view: TypeView[Any] | None = None, field_name: str | None = None, help_formatter: HelpFormattable | None = None, propagated_arguments: list[FinalArg[Any]] | None = None, state: State[Any] | None = None, ) -> FinalSubcommand: if type_view is None: type_view = TypeView(Any) field_name = field_name or assert_type(self.field_name, str) types = infer_types(self, type_view) required = infer_required(self, type_view) options = infer_options( self, types, help_formatter=help_formatter, propagated_arguments=propagated_arguments, state=state, ) alias_map = build_alias_map(options) group = infer_group(self) return FinalSubcommand( hidden=self.hidden, field_name=field_name, types=types, required=required, options=options, alias_map=alias_map, group=group, ) @dataclasses.dataclass class FinalSubcommand(Subcommand): """Post-normalization form of :class:`Subcommand` with narrowed field types. Produced exclusively by :meth:`Subcommand.normalize`. """ field_name: str = "" # pyright: ignore required: bool = False # pyright: ignore group: Group = dataclasses.field(default_factory=lambda: DEFAULT_SUBCOMMAND_GROUP) # pyright: ignore types: Iterable[type] = dataclasses.field(default_factory=tuple) # pyright: ignore options: Mapping[str, FinalCommand[Any]] = dataclasses.field( # pyright: ignore default_factory=dict ) def resolve_name(self, name: str) -> str | None: """Return the canonical name for a typed-in name (canonical or alias). Returns `None` when `name` matches neither. """ if name in self.options: return name if name in self.alias_map: return self.alias_map[name][0] return None def map_result( self, prog: str, parsed_args: dict[str, Any], *, output: Output, state: State[Any] | None = None, input: TextIO | None = None, ) -> tuple[Resolved[Any], dict[Any, Any]]: canonical = parsed_args.pop("__name__") option = self.options[canonical] return option.map_result( option, prog, parsed_args, output=output, state=state, input=input ) def available_options(self) -> list[FinalCommand[Any]]: return [o for o in self.options.values() if not o.hidden] def names(self) -> list[str]: return [n for n, o in self.options.items() if not o.hidden] def visible_aliases_for(self, canonical: str) -> list[Alias]: """Visible (non-hidden) aliases for the given canonical subcommand name.""" command = self.options.get(canonical) if command is None: return [] return [a for a in command.resolved_aliases() if not a.hidden] def all_visible_names(self) -> list[str]: """Canonical names plus visible aliases, in declaration order.""" result: list[str] = [] for name in self.names(): result.append(name) for alias in self.visible_aliases_for(name): result.append(alias.name) return result def names_str(self, delimiter: str = ", ") -> str: return delimiter.join(self.names()) def completion(self, partial: str): return [Completion(o) for o in self.all_visible_names() if partial in o] def infer_types(arg: Subcommand, type_view: TypeView[Any]) -> Iterable[type]: if arg.types is not Empty: return arg.types if type_view.is_union: return tuple(t.annotation for t in type_view.inner_types if not t.is_none_type) return (type_view.annotation,) def infer_required(arg: Subcommand, annotation: TypeView[Any]) -> bool: if arg.required is not None: return arg.required return not annotation.is_optional def infer_options( arg: Subcommand, types: Iterable[type], help_formatter: HelpFormattable | None = None, propagated_arguments: list[FinalArg[Any]] | None = None, state: State[Any] | None = None, ) -> dict[str, FinalCommand[Any]]: from cappa.command import Command if arg.options: return { name: type_command.collect( propagated_arguments=propagated_arguments, state=state, ) for name, type_command in arg.options.items() } options: dict[str, FinalCommand[Any]] = {} for type_ in types: type_command: Command[Any] = Command.get(type_, help_formatter=help_formatter) # pyright: ignore type_name = type_command.real_name() options[type_name] = type_command.collect( propagated_arguments=propagated_arguments ) return options def build_alias_map( options: dict[str, FinalCommand[Any]], ) -> dict[str, tuple[str, Alias]]: """Build alias name -> (canonical, Alias) for a set of subcommand options. Raises `ValueError` on collisions: an alias matching another command's canonical name, an alias colliding with another alias, or an alias that duplicates its own canonical name. """ alias_map: dict[str, tuple[str, Alias]] = {} for canonical, command in options.items(): for alias in command.resolved_aliases(): if alias.name == canonical: raise ValueError( f"Subcommand '{canonical}' has an alias matching its own name." ) if alias.name in options: raise ValueError( f"Subcommand alias '{alias.name}' (for '{canonical}') " f"collides with another subcommand's name." ) if alias.name in alias_map: other_canonical, _ = alias_map[alias.name] raise ValueError( f"Subcommand alias '{alias.name}' is declared on both " f"'{other_canonical}' and '{canonical}'." ) alias_map[alias.name] = (canonical, alias) return alias_map def infer_group(arg: Subcommand) -> Group: name = None order = 3 if isinstance(arg.group, Group): return arg.group if isinstance(arg.group, str): name = arg.group if isinstance(arg.group, tuple): order, name = arg.group assert name return Group(name=name, order=order) Subcommands: TypeAlias = Annotated[T, Subcommand] DEFAULT_SUBCOMMAND = Subcommand() cappa-0.32.2/src/cappa/testing.py0000644000000000000000000001136213615410400013507 0ustar00from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Generic, TextIO, TypedDict from typing_extensions import Unpack import cappa from cappa.base import Backend, CappaCapable, T from cappa.help import HelpFormattable from cappa.invoke.types import DepTypes from cappa.state import State __all__ = [ "CommandRunner", "RunnerArgs", ] class RunnerArgs(TypedDict, total=False): """Available kwargs for `parse` and `invoke` function, to match `CommandRunner` fields.""" argv: list[str] backend: Backend | None output: cappa.Output | None color: bool version: str | cappa.Arg[str] | None help: bool | cappa.Arg[bool] completion: bool | cappa.Arg[bool] help_formatter: HelpFormattable | None input: TextIO | None state: State[Any] | None @dataclass class CommandRunner(Generic[T]): """Object to hold common parse/invoke invocation state, for testing. Accepts almost identical inputs to that of `parse`/`invoke`. The notable deviation is `argv`. Whereas the original functions accept argv as a single-argument list; `CommandRunner` accepts `base_args` at the class-constructor level, which is concatenated with the `CommandRunner.parse` or `CommandRunner.invoke` `*args`, to arrive a the total set of input args. Example: Some base CLI object >>> from dataclasses import dataclass >>> >>> import cappa >>> >>> @dataclass ... class Obj: ... first: str ... second: str = "2" Create an instance with no arguments means there is no default state >>> runner = CommandRunner() >>> runner.parse(Obj, "one") Obj(first='one', second='2') Or create a runner that always uses the same base CLI object, and default base command >>> runner = CommandRunner(Obj, base_args=["first"]) Now each test, can customize the behavior to suit the test in question. >>> runner.parse(color=False) Obj(first='first', second='2') >>> runner.parse("two") Obj(first='first', second='two') """ obj: CappaCapable[T] | None = None deps: DepTypes = None backend: Backend | None = None output: cappa.Output | None = None color: bool = True version: str | cappa.Arg[str] | None = None help: bool | cappa.Arg[bool] = True completion: bool | cappa.Arg[bool] = True help_formatter: HelpFormattable | None = None input: TextIO | None = None state: State[Any] | None = None base_args: list[str] = field(default_factory=lambda: []) def coalesce_kwargs(self, *args: str, **kwargs: Unpack[RunnerArgs]) -> RunnerArgs: return RunnerArgs( argv=self.base_args + list(args), backend=kwargs.get("backend") or self.backend, output=kwargs.get("output") or self.output, color=kwargs.get("color") or self.color, version=kwargs.get("version") or self.version, help=kwargs["help"] if "help" in kwargs else self.help, completion=kwargs["completion"] if "completion" in kwargs else self.completion, help_formatter=kwargs.get("help_formatter") or self.help_formatter, input=kwargs.get("input") or self.input, state=kwargs.get("state") or self.state, ) def collect_args( self, obj: CappaCapable[T] | str | None = None, *args: str ) -> tuple[CappaCapable[T], tuple[str, ...]]: if isinstance(obj, str): assert self.obj return self.obj, (obj, *args) obj = obj or self.obj assert obj return obj, args def parse( self, obj: CappaCapable[T] | str | None = None, *args: str, **kwargs: Unpack[RunnerArgs], ) -> T: obj, args = self.collect_args(obj, *args) final_kwargs = self.coalesce_kwargs(*args, **kwargs) return cappa.parse(obj, **final_kwargs) def invoke( self, obj: CappaCapable[T] | str | None = None, *args: str, deps: DepTypes = None, **kwargs: Unpack[RunnerArgs], ) -> Any: obj, args = self.collect_args(obj, *args) final_kwargs = self.coalesce_kwargs(*args, **kwargs) deps = deps or self.deps return cappa.invoke(obj, **final_kwargs, deps=deps) async def invoke_async( self, obj: CappaCapable[T] | str | None = None, *args: str, deps: DepTypes = None, **kwargs: Unpack[RunnerArgs], ) -> Any: obj, args = self.collect_args(obj, *args) final_kwargs = self.coalesce_kwargs(*args, **kwargs) deps = deps or self.deps return await cappa.invoke_async(obj, **final_kwargs, deps=deps) cappa-0.32.2/src/cappa/themes.py0000644000000000000000000000043413615410400013315 0ustar00from rich.theme import Theme default_theme: Theme = Theme( { "cappa.prog": "grey50", "cappa.group": "dark_orange bold", "cappa.arg": "cyan", "cappa.arg.name": "dark_cyan", "cappa.subcommand": "dark_cyan", "cappa.help": "", } ) cappa-0.32.2/src/cappa/type_view.py0000644000000000000000000000022013615410400014034 0ustar00from type_lens import CallableView, Empty, EmptyType, TypeView __all__ = [ "CallableView", "Empty", "EmptyType", "TypeView", ] cappa-0.32.2/src/cappa/types.py0000644000000000000000000000320213615410400013170 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import ( TYPE_CHECKING, Any, Generic, Hashable, Protocol, Type, TypeVar, Union, overload, ) from cappa.state import S, State if TYPE_CHECKING: from cappa.command import Command, FinalCommand from cappa.invoke.types import InvokeCallable, Resolved from cappa.output import Output T = TypeVar("T") U = TypeVar("U") CappaCapable = Union["InvokeCallable[T]", Type[T], "Command[T]"] class Backend(Protocol): def __call__( self, command: FinalCommand[T], argv: list[str], output: Output, prog: str, provide_completions: bool = False, ) -> tuple[Any, Command[T], dict[str, Any]]: ... # pragma: no cover class FuncOrClassDecorator(Protocol): @overload def __call__(self, x: type[T], /) -> type[T]: ... @overload def __call__(self, x: T, /) -> T: ... @dataclass() class ParseResult(Generic[T, S]): """Result of parsing a command with all collected metadata. Attributes: root_command: The root command that was parsed. parsed_command: The selected command (may be a subcommand if one was invoked). instance: The deferred instantiated command object. implicit_deps: Mapping of command classes to their deferred instances, collected during parsing. output: The output handler for the command. state: The state object for the command. """ root_command: Command[T] parsed_command: Command[T] instance: Resolved[T] implicit_deps: dict[Hashable, Any] state: State[S] output: Output cappa-0.32.2/src/cappa/typing.py0000644000000000000000000000351013615410400013340 0ustar00from __future__ import annotations import enum import inspect from dataclasses import dataclass from types import MethodType from typing import Any, Protocol, TypeVar from typing_extensions import assert_never from cappa.type_view import TypeView class DocType(Protocol): documentation: str try: from typing_extensions import Doc as TypingExtensionsDoc Doc: type[DocType] = TypingExtensionsDoc except ImportError: # pragma: no cover @dataclass class InternalDoc: documentation: str Doc = InternalDoc __all__ = [ "Doc", "T", "assert_never", "assert_type", "detect_choices", "find_annotations", ] T = TypeVar("T") def find_annotations(type_view: TypeView[Any], kind: type[T]) -> list[T]: result: list[T] = [] for annotation in type_view.metadata: if isinstance(annotation, kind): result.append(annotation) if isinstance(annotation, type) and issubclass(annotation, kind): result.append(annotation()) return result def assert_type(value: Any, typ: type[T]) -> T: assert isinstance(value, typ), value return value def detect_choices(type_view: TypeView[Any]) -> list[str] | None: if type_view.is_optional: type_view = type_view.strip_optional() if type_view.is_subclass_of(enum.Enum): return [v.value for v in type_view.annotation] if type_view.is_subclass_of((list, set)) or type_view.is_variadic_tuple: type_view = type_view.inner_types[0] if type_view.is_union: if all(t.is_literal for t in type_view.inner_types): return [str(t.args[0]) for t in type_view.inner_types] if type_view.is_literal: return [str(t) for t in type_view.args] return None def get_method_class(fn: MethodType) -> type: return inspect._findclass(fn) # type: ignore cappa-0.32.2/src/cappa/completion/__init__.py0000644000000000000000000000000013615410400015725 0ustar00cappa-0.32.2/src/cappa/completion/base.py0000644000000000000000000000372313615410400015117 0ustar00from __future__ import annotations import os from pathlib import Path from typing import Any, Iterable, cast from cappa.arg import Arg from cappa.command import FinalCommand from cappa.completion.shells import available_shells from cappa.output import Exit, Output from cappa.parser import Completion, FileCompletion, backend def execute( command: FinalCommand[Any], prog: str, action: str, arg: Arg[Any], output: Output, ): shell_name = Path(os.environ.get("SHELL", "bash")).name shell = available_shells.get(shell_name) if shell is None: raise Exit("Unknown shell", code=1) if action == "generate": raise Exit(shell.backend_template(prog, arg), code=0) command_args = parse_incomplete_command() backend( command, command_args, output=output, prog=prog, provide_completions=True, ) def parse_incomplete_command() -> list[str]: raw_completion_line = os.environ.get("COMPLETION_LINE", "").replace("\n", " ") raw_completion_location = os.environ.get("COMPLETION_LOCATION") if not raw_completion_line or raw_completion_location is None: raise Exit(code=0) completion_line = list(split_incomplete_command(raw_completion_line)) completion_index = int(raw_completion_location) return completion_line[1:completion_index] def split_incomplete_command(string: str) -> Iterable[str]: import shlex lex = shlex.shlex(string, posix=True) lex.whitespace_split = True lex.commenters = "" try: yield from lex except ValueError: yield lex.token def format_completions(*completions: Completion | FileCompletion) -> str | None: if not completions: return None if isinstance(completions[0], FileCompletion): return "file" result: list[str] = [] for item in completions: item = cast(Completion, item) result.append(f"{item.value}:{item.description}") return "\n".join(result) cappa-0.32.2/src/cappa/completion/completers.py0000644000000000000000000000044013615410400016353 0ustar00from __future__ import annotations from cappa.completion.types import Completion def complete_choices(choices: list[str], help: str | None = None): def completion(partial: str = ""): return [Completion(c, help=help) for c in choices if partial in c] return completion cappa-0.32.2/src/cappa/completion/shells.py0000644000000000000000000000401513615410400015472 0ustar00from cappa.completion.types import ShellHandler shells = [ ShellHandler( "bash", template="""\ _%(safe_prog_name)s_completion() { local IFS=$'\\n' local response response=$(env COMPLETION_LINE="${COMP_WORDS[*]}" COMPLETION_LOCATION=$((COMP_CWORD+1)) $1 --completion=complete) for completion in $response; do IFS=':' read value help <<< "$completion" if [[ $type == 'file' ]]; then COMPREPLY=() compopt -o default else COMPREPLY+=($value) fi done return 0 } _%(safe_prog_name)s_completion_setup() { complete -o nosort -F _%(safe_prog_name)s_completion %(prog_name)s } _%(safe_prog_name)s_completion_setup; """, ), ShellHandler( "zsh", template="""\ #compdef %(prog_name)s _%(safe_prog_name)s_completion() { local -a completions (( ! $+commands[%(prog_name)s] )) && return 1 completions=("${(@f)$(env COMPLETION_LINE="${words[*]}" COMPLETION_LOCATION=$((CURRENT)) \ %(prog_name)s %(completion_arg)s=complete)}") if [[ -n "$completions" ]]; then if [[ "${completions[1]}" == "file" ]]; then _files else _describe 'options' completions fi fi } if [[ $zsh_eval_context[-1] == loadautofunc ]]; then _%(safe_prog_name)s_completion "$@" else compdef _%(safe_prog_name)s_completion %(prog_name)s fi """, ), ShellHandler( "fish", template="""\ function _%(safe_prog_name)s_completion set -l response (env COMPLETION_LINE=(commandline -cp) COMPLETION_LOCATION=(math (commandline -C) + 1) \ %(prog_name)s %(completion_arg)s=complete) for completion in $response set -l value (string split --max 1 ":" -- $completion) if test $value[1] = "file" __fish_complete_path else echo $value[2] end end end complete --no-files --command %(prog_name)s --arguments "(_%(safe_prog_name)s_completion)" """, ), ] available_shells = {s.name: s for s in shells} cappa-0.32.2/src/cappa/completion/types.py0000644000000000000000000000173613615410400015353 0ustar00from __future__ import annotations import dataclasses import re from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from cappa.arg import Arg @dataclasses.dataclass class ShellHandler: name: str template: str def backend_template(self, prog: str, arg: Arg[Any]) -> str: safe_name = re.sub(r"\W*", "", prog.replace("-", "_"), flags=re.ASCII) assert isinstance(arg.long, list) assert len(arg.long) > 0 return self.template % { "prog_name": prog, "safe_prog_name": safe_name, "completion_arg": arg.long[0], } @dataclasses.dataclass class Completion: value: str | None = None help: str | None = None arg: Arg[Any] | None = None @property def description(self) -> str: if not self.arg or self.arg.help: return self.help or "" return f"<{self.arg.value_name}> {self.help}".strip() @dataclasses.dataclass class FileCompletion: text: str cappa-0.32.2/src/cappa/ext/__init__.py0000644000000000000000000000000013615410400014354 0ustar00cappa-0.32.2/src/cappa/ext/docutils.py0000644000000000000000000001164613615410400014465 0ustar00from __future__ import annotations import importlib import importlib.util import io import logging from typing import TYPE_CHECKING, Any, ClassVar, Sequence from rich.console import Console import cappa from cappa.command import FinalCommand from cappa.default import Default from cappa.help import ArgGroup, HelpFormatter from cappa.output import Displayable, theme from cappa.type_view import Empty try: importlib.util.find_spec("docutils") except ImportError: # pragma: no cover raise ImportError( "The ext.docutils package can only be used if 'docutils' is installed." ) from docutils import nodes from docutils.parsers.rst import Directive, directives if TYPE_CHECKING: from sphinx.application import Sphinx log = logging.getLogger(__name__) FONT_FAMILY = ( """font-family:Menlo,"DejaVu Sans Mono",consolas,"Courier New",monospace""" ) def setup(app: Sphinx) -> dict[str, Any]: # pragma: no cover app.add_directive("cappa", CappaDirective) return {"parallel_read_safe": True} class CappaDirective(Directive): has_content = False required_arguments = 1 option_spec: ClassVar[dict[str, Any] | None] = { "style": directives.unchanged, "terminal-width": directives.unchanged, } def run(self) -> Sequence[nodes.Node]: path = self.arguments[0] style = self.options.get("style", "terminal") terminal_width = int(self.options.get("terminal-width", "0")) module_path, item_name = path.rsplit(".", 1) module = importlib.import_module(module_path) item = getattr(module, item_name) command: FinalCommand[Any] = cappa.collect(item) if style == "terminal": return render_to_terminal(command, terminal_width) if style == "native": return render_to_docutils(command, self.state.document) raise ValueError(f"Unrecognized style {style}") def render_to_terminal(command: cappa.FinalCommand[Any], terminal_width: int): raw_help: list[Displayable] = HelpFormatter.default.long( command, command.real_name() ) line_wrap = "" if terminal_width == 0: line_wrap = "white-space: pre-wrap;" terminal_width = 99999 console = Console( record=True, theme=theme, file=io.StringIO(), soft_wrap=True, width=terminal_width, ) console.print(*raw_help) style = " ".join([line_wrap, FONT_FAMILY]) code_format = f'
{{code}}
' html = console.export_html(inline_styles=True, code_format=code_format) return [nodes.raw(text=html, format="html")] def render_to_docutils(command: cappa.FinalCommand[Any], document: nodes.document): section = nodes.section() document.note_implicit_target(section) section += nodes.title(text=command.real_name()) command_content = nodes.paragraph() section += command_content description: list[str] = [] if command.help: description.append(command.help) if command.description: description.append(command.description) section += nodes.paragraph(text="\n\n".join(description)) for group in ArgGroup.collect(command): # Collect all Args and Subcommands from field_groups command_options: list[cappa.FinalArg[Any]] = [] command_subcommands: list[cappa.FinalSubcommand] = [] for field_group in group.field_groups: if field_group.args: command_options.extend(field_group.args) else: assert field_group.subcommand command_subcommands.append(field_group.subcommand) if command_options: section += nodes.subtitle(text=group.name) option = nodes.bullet_list() section += option for arg in command_options: option_content = nodes.list_item() option += option_content names = arg.names() if names: for name in names: option_content += nodes.literal(text=name) else: name = arg.field_name value_name = arg.value_name name += f" {value_name.upper()}" option_content += nodes.literal(text=name) assert isinstance(arg.default, Default) if arg.default.default not in (Empty, None): default = str(arg.default) option_content += nodes.inline(text=" (") option_content += nodes.literal(text=default) option_content += nodes.inline(text=")") if arg.help: option_content += nodes.inline(text=f": {arg.help}") if command_subcommands: for subcmd in command_subcommands: for o in subcmd.options.values(): section += render_to_docutils(o, document) return [section] cappa-0.32.2/src/cappa/invoke/__init__.py0000644000000000000000000000000013615410400015047 0ustar00cappa-0.32.2/src/cappa/invoke/base.py0000644000000000000000000002001313615410400014230 0ustar00from __future__ import annotations import contextlib import importlib import inspect from typing import ( Any, AsyncGenerator, Callable, Generator, Hashable, List, Sequence, cast, ) from typing_extensions import TypeGuard from cappa.class_inspect import has_command from cappa.command import Command from cappa.invoke.types import ( C, Dep, DepTypes, InvokeCallable, InvokeCallableSpec, InvokeResolutionError, Resolved, SelfType, ) from cappa.output import Output from cappa.state import State from cappa.type_view import CallableView, TypeView from cappa.typing import find_annotations, get_method_class def resolve_callable( command: Command[Any], parsed_command: Command[C], instance: C, *, implicit_deps: dict[Hashable, Any], output: Output, state: State[Any], deps: DepTypes = None, ) -> tuple[Resolved[C], Sequence[Resolved[Any]]]: try: fn: Callable[..., Any] = resolve_invoke_handler(parsed_command, implicit_deps) implicit_deps[Output] = output implicit_deps[Command] = parsed_command implicit_deps[State] = state implicit_deps[SelfType] = implicit_deps[cast(Hashable, parsed_command.cmd_cls)] global_deps = resolve_global_deps(deps, implicit_deps) fulfilled_deps: dict[Hashable, Any] = {**implicit_deps, **global_deps} resolved = fulfill_deps(fn, fulfilled_deps) except InvokeResolutionError as e: raise InvokeResolutionError( f"Failed to invoke {parsed_command.cmd_cls} due to resolution failure." ) from e return (resolved, tuple(global_deps.values())) def resolve_global_deps( deps: DepTypes, implicit_deps: dict[Hashable, Any] ) -> dict[Hashable, Any]: result: dict[Hashable, Any] = {} if not deps: return result # Coerce the sequence variant of input into the mapping equivalent. if isinstance(deps, Sequence): deps = {cast(InvokeCallable[Any], d): Dep(d) for d in deps} for source_function, dep in deps.items(): # Deps need to be fulfilled, whereas raw values are taken directly. if isinstance(dep, Dep): dep_callable = resolve_callable_reference(dep.callable) # pyright: ignore value = fulfill_deps(dep_callable, implicit_deps) else: value = Resolved(source_function, result=dep) # We map back to the source dep, i.e. the key in the input mapping. This # translates to the raw function in the sequence input, or the source # dep being overridden in the dict input. source_dep: Dep[Any] = Dep(source_function) result[source_dep] = value return result def resolve_invoke_handler( command: Command[C], implicit_deps: dict[Hashable, Any] ) -> Callable[..., C]: fn = command.invoke if not fn: command_type = cast(Hashable, command.cmd_cls) instance = implicit_deps.get(command_type) if callable(instance): return instance.__call__ # pyright: ignore raise InvokeResolutionError( f"Cannot call `invoke` for a command which does not have an invoke handler: {command.cmd_cls}." ) return resolve_callable_reference(fn) def resolve_callable_reference(fn: InvokeCallableSpec[C] | None) -> InvokeCallable[C]: if isinstance(fn, str): try: module_name, fn_name = fn.rsplit(".", 1) except ValueError: raise InvokeResolutionError( f"Invoke `{fn}` must be a fully qualified reference to a function in a module." ) # Let this bubble upwards if the modules doesn't exist. try: module = importlib.import_module(module_name) except ModuleNotFoundError as e: name = getattr(e, "name", None) or str(e) raise InvokeResolutionError( f"No module '{name}' when attempting to load '{fn}'." ) if not hasattr(module, fn_name): raise InvokeResolutionError( f"Module {module} does not have a function `{fn_name}`." ) fn = getattr(module, fn_name) if not callable(fn): raise InvokeResolutionError(f"`{fn}` does not reference a valid callable.") return cast(Callable[..., Any], fn) def fulfill_deps( fn: Callable[..., C], fulfilled_deps: dict[Hashable, Any], allow_empty: bool = False ) -> Resolved[C]: args: list[Any] = [] result: dict[str, Any] = {} try: callable_view = CallableView.from_callable(fn, include_extras=True) except NameError as e: # pragma: no cover name = getattr(e, "name", None) or str(e) raise InvokeResolutionError( f"Could not collect resolve reference to {name} for `{getattr(fn, '__name__', '')}`" ) except (ValueError, AttributeError): # ValueError is common amongst builtins. Perhaps TypeView ought to be handling this. # AttributeError is currently an issue with Enums, I think TypeView should **definitely** # handle this. return Resolved(fn, result) for index, param_view in enumerate(callable_view.parameters): type_view: TypeView[Any] = param_view.type_view # pyright: ignore # Unwrap TypeAliasType instances like `type Foo = Annotated[int, Dep(foo)]` type_view = type_view.strip_type_alias() # "Native" supported annotations if type_view.is_annotated and SelfType in type_view.metadata: result[param_view.name] = fulfilled_deps[SelfType] elif type_view.fallback_origin in fulfilled_deps: result[param_view.name] = fulfilled_deps[type_view.fallback_origin] # "Dep(foo)" annotations elif deps := cast(List[Dep[Any]], find_annotations(type_view, Dep)): assert len(deps) == 1 dep = deps[0] # Whereas everything else should be a resolvable explicit Dep, which might have either # already been fullfullfilled, or yet need to be. if dep not in fulfilled_deps: fulfilled_deps[dep] = fulfill_deps( cast(Callable[..., Any], dep.callable), fulfilled_deps ) result[param_view.name] = fulfilled_deps[dep] # Method `self` arguments can be assumed to be typed as the literal class they reside inside, # These classes should always be already fulfilled by the root command structure. elif index == 0 and inspect.ismethod(fn): cls = get_method_class(fn) if has_command(fn.__self__): value = fulfilled_deps[cls] args.append(value) # If there's a default, we can just skip it and let the default fulfill the value. # Alternatively, `allow_empty` might be True to indicate we shouldn't error. elif param_view.has_default or allow_empty: continue # Non-annotated args are either implicit dependencies (and thus already fulfilled), # or arguments that we cannot fulfill and should error. else: param_annotation = ( param_view.type_view.repr_type if param_view.has_annotation else "" ) raise InvokeResolutionError( f"`{param_view.name}: {param_annotation}` " f"is not a valid dependency for Dep({fn.__name__})." ) return Resolved(fn, kwargs=result, args=tuple(args)) def is_implicit_context_manager( value: Any, ) -> TypeGuard[Callable[..., Generator[Any, Any, Any]]]: return inspect.isgeneratorfunction(value) def is_context_manager(value: Any) -> TypeGuard[contextlib.AbstractContextManager[Any]]: return isinstance(value, contextlib.AbstractContextManager) def is_implicit_async_context_manager( value: Any, ) -> TypeGuard[Callable[..., AsyncGenerator[Any, Any]]]: return inspect.isasyncgenfunction(value) def is_async_context_manager( value: Any, ) -> TypeGuard[contextlib.AbstractAsyncContextManager[Any]]: return isinstance(value, contextlib.AbstractAsyncContextManager) cappa-0.32.2/src/cappa/invoke/types.py0000644000000000000000000001376413615410400014501 0ustar00from __future__ import annotations import contextlib import inspect from dataclasses import dataclass, field from typing import ( Any, AsyncGenerator, Callable, Coroutine, Generator, Generic, Mapping, Sequence, TypeVar, Union, cast, ) from typing_extensions import Annotated from cappa.output import Exit, Output from cappa.type_view import Empty, EmptyType class SelfType: ... C = TypeVar("C") T = TypeVar("T") InvokeCallable = Callable[..., T] InvokeCallableSpec = Union[InvokeCallable[T], str] @dataclass(frozen=True) class Dep(Generic[T]): """Describes the callable required to fulfill a given dependency.""" callable: InvokeCallableSpec[T] DepTypes = Union[ Sequence[InvokeCallableSpec[Any]], Mapping[InvokeCallableSpec[Any], Union[Dep[Any], InvokeCallableSpec[Any], Any]], None, ] Self = Annotated[T, SelfType] class InvokeResolutionError(RuntimeError): """Raised for errors encountered during evaluation of invoke dependencies.""" @dataclass class Resolved(Generic[C]): callable: InvokeCallableSpec[C] kwargs: dict[str, Any | Resolved[Any]] = field(default_factory=lambda: {}) args: tuple[Any, ...] = field(default=()) result: C | EmptyType = Empty def call(self, *args: Any, output: Output | None = None, managed: bool = True): with self.get(*args, output=output, managed=managed) as value: return value async def call_async( self, *args: Any, output: Output | None = None, managed: bool = True ): async with self.get_async(*args, output=output, managed=managed) as value: return value @contextlib.contextmanager def get( self, *args: Any, output: Output | None = None, managed: bool = True ) -> Generator[C, None, None]: """Get the resolved value. The value itself is cached in the event it's used as a dependency to more than one dependency. """ if self.result is not Empty: yield self.result return with contextlib.ExitStack() as stack: # Non-resolved values are literal values that can be recorded directly. finalized_kwargs = dict(self.iter_kwargs(is_resolved=False)) # Resolved values need to be recursed into. In order to handle the # wrapping context manager, we need to enter all contexts, and only # exit at the end. for k, v in self.iter_kwargs(is_resolved=True): finalized_kwargs[k] = stack.enter_context( v.get(output=output, managed=managed) ) with self.handle_exit(output): callable = cast(Callable[..., Any], self.callable) requires_management = inspect.isgeneratorfunction(callable) if requires_management: # Yield functions are assumed to be context-manager style generators # that we just need to wrap... callable = contextlib.contextmanager(callable) result: Any = callable(*args, *self.args, **finalized_kwargs) is_context_manager = isinstance( result, contextlib.AbstractContextManager ) # And then enter before producing the result. if requires_management or (managed and is_context_manager): result = stack.enter_context(result) # pyright: ignore self.result = result yield result @contextlib.asynccontextmanager async def get_async( self, *args: Any, output: Output | None = None, managed: bool = True ) -> AsyncGenerator[C, None]: """Get the resolved value, in an async context. Note, this is the exact same process as in `get`, except with `await`, `enter_async_context` and `async with`. There seems to be no way to share the logic between the two methods, so they just need to be kept in sync :shrug:. """ if self.result is not Empty: yield self.result return async with contextlib.AsyncExitStack() as stack: finalized_kwargs = dict(self.iter_kwargs(is_resolved=False)) for k, v in self.iter_kwargs(is_resolved=True): finalized_kwargs[k] = await stack.enter_async_context( v.get_async(output=output, managed=managed) ) with self.handle_exit(output): callable = cast(Callable[..., Any], self.callable) requires_management = inspect.isasyncgenfunction(callable) if requires_management: callable = contextlib.asynccontextmanager(callable) result: Any = callable(*args, *self.args, **finalized_kwargs) is_async_context_manager = isinstance( result, contextlib.AbstractAsyncContextManager ) is_sync_context_manager = isinstance( result, contextlib.AbstractContextManager ) if requires_management or (managed and is_async_context_manager): result = await stack.enter_async_context(result) # pyright: ignore elif managed and is_sync_context_manager: # Handle synchronous context managers in async context result = stack.enter_context(result) # pyright: ignore elif isinstance(result, Coroutine): result = await result # pyright: ignore self.result = result yield result def iter_kwargs(self, *, is_resolved: bool): for k, v in self.kwargs.items(): if is_resolved == isinstance(v, self.__class__): yield k, v @classmethod @contextlib.contextmanager def handle_exit(cls, output: Output | None = None): try: yield except Exit as e: if output: # pragma: no cover output.exit(e) raise e cappa-0.32.2/tests/__init__.py0000644000000000000000000000000013615410400013043 0ustar00cappa-0.32.2/tests/conftest.py0000644000000000000000000000003413615410400013140 0ustar00pytest_plugins = "pytester" cappa-0.32.2/tests/test_attrs.py0000644000000000000000000000113313615410400013510 0ustar00# type: ignore from __future__ import annotations import attr from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse factory = attr.Factory(lambda: [4]) @attr.s class Command: name: str = attr.ib() default: Annotated[int, cappa.Arg(long=True)] = attr.ib(default=4) default_factory: Annotated[list[int], cappa.Arg(long=True)] = attr.ib( default=factory ) @backends def test_attrs(backend: Backend): result = parse(Command, "meow", backend=backend) assert result == Command(name="meow", default=4, default_factory=[4]) cappa-0.32.2/tests/test_base.py0000644000000000000000000001200013615410400013260 0ustar00from dataclasses import dataclass, field from typing import Any, ClassVar import pytest import cappa from cappa.command import Command from tests.utils import Backend, backends, require_dataclass_kw_only @backends def test_color_off(backend: Backend): def no_op(): pass @cappa.command(invoke=no_op) @dataclass class Example: ... cappa.invoke(Example, color=False, argv=[], backend=backend) @backends def test_no_help(backend: Backend): @dataclass class Example: ... result = cappa.parse(Example, argv=[], help=False, backend=backend) assert result == Example() with pytest.raises(SystemExit) as e: cappa.parse(Example, argv=["-h"], help=False, backend=backend) assert e.value.code == 2 with pytest.raises(SystemExit) as e: cappa.parse(Example, argv=["--help"], help=False, backend=backend) assert e.value.code == 2 @pytest.mark.help @backends def test_arg_help(capsys: Any, backend: Backend): @dataclass class Example: ... help: cappa.Arg[str] = cappa.Arg(short="-p", long="--pelp") result = cappa.parse(Example, argv=[], help=help, backend=backend) assert result == Example() with pytest.raises(SystemExit) as e: cappa.parse(Example, argv=["-p"], help=help, backend=backend) assert e.value.code == 0 out = capsys.readouterr().out assert "-p, --pelp" in out with pytest.raises(SystemExit) as e: cappa.parse(Example, argv=["--pelp"], help=help, backend=backend) assert e.value.code == 0 out = capsys.readouterr().out assert "-p, --pelp" in out @backends def test_version_enabled(capsys: Any, backend: Backend): @dataclass class Example: ... with pytest.raises(SystemExit) as e: cappa.parse(Example, argv=["--version"], version="1.2.3", backend=backend) assert e.value.code == 0 out = capsys.readouterr().out assert "1.2.3" in out @backends def test_arg_version(capsys: Any, backend: Backend): @dataclass class Example: ... version: cappa.Arg[str] = cappa.Arg("1.2.3", short="-p", long="--persion") result = cappa.parse(Example, argv=[], version=version, backend=backend) assert result == Example() with pytest.raises(SystemExit) as e: cappa.parse(Example, argv=["-p"], version=version, backend=backend) assert e.value.code == 0 out = capsys.readouterr().out assert "1.2.3" in out with pytest.raises(SystemExit) as e: cappa.parse(Example, argv=["--persion"], version=version, backend=backend) assert e.value.code == 0 out = capsys.readouterr().out assert "1.2.3" in out @backends def test_version_without_help(backend: Backend): @dataclass class Example: ... result = cappa.parse(Example, argv=[], version="1.2.3", help=False, backend=backend) assert result == Example() @backends def test_arg_explicit_version_missing_name(backend: Backend): @dataclass class Example: ... version: cappa.Arg[str] = cappa.Arg(short="-p", long="--persion") with pytest.raises(ValueError) as e: cappa.parse(Example, argv=["-p"], version=version, backend=backend) assert ( str(e.value) == "Expected explicit version `Arg` to supply version number as its name, like `Arg('1.2.3', ...)`" ) @backends def test_arg_explicit_version_long_true_defaults_to_version( capsys: Any, backend: Backend ): @dataclass class Example: ... version: cappa.Arg[str] = cappa.Arg("1.2.3", short="-p", long=True) with pytest.raises(cappa.Exit): cappa.parse(Example, argv=["--version"], version=version, backend=backend) out = capsys.readouterr().out assert "1.2.3" in out @backends def test_prog_basename(capsys: Any, backend: Backend): @dataclass class Example: ... with pytest.raises(cappa.Exit): cappa.parse(Example, argv=["--help"], backend=backend) out = capsys.readouterr().out assert "usage: example" in out.lower() @backends def test_collect_composes_with_parse(backend: Backend): @dataclass class Example: ... command: Command[Example] = cappa.collect(Example, backend=backend) result = cappa.parse(command, argv=[], backend=backend) assert result == Example() @require_dataclass_kw_only @backends def test_kw_only_base_class_field_ordering(backend: Backend): kwargs = {"kw_only": True} @dataclass(**kwargs) class Base: name: str = "hello" @dataclass class Child(Base): number: int = 0 result = cappa.parse( cappa.Command(Child, default_long=True), argv=["--name", "world"], backend=backend, ) assert result == Child(number=0, name="world") @backends def test_collect_skips_non_init_fields(backend: Backend): @dataclass class Example: skipped1: ClassVar[str] = "skipped1" skipped2: str = field(default="", init=False) present: str = "" command: Command[Example] = cappa.collect(Example, backend=backend) result = cappa.parse(command, argv=[], backend=backend) assert result == Example() cappa-0.32.2/tests/test_class_inspect.py0000644000000000000000000000047113615410400015211 0ustar00import pytest from cappa.class_inspect import fields def test_invalid_class_base(): class Random: ... with pytest.raises(ValueError) as e: fields(Random) assert ( "'test_invalid_class_base..Random' is not a currently supported kind of class." in str(e.value) ) cappa-0.32.2/tests/test_default.py0000644000000000000000000000244413615410400014005 0ustar00from cappa.default import Confirm, Default, Env, Prompt, ValueFrom from cappa.type_view import Empty def test_default_combination(): assert Default().fallback_to(Default()) == Default() assert (Default() | Default()) == Default() assert Env("FOO") | Env("BAR") == Default(Env("FOO"), Env("BAR"), default=Empty) assert Default(default=4) | Env("FOO") == Default(Env("FOO"), default=4) assert Env("FOO") | ValueFrom(int) == Default(Env("FOO"), ValueFrom(int)) prompt = Prompt("FOO") confirm = Confirm("BAR") assert Default() | prompt | confirm == Default(prompt, confirm) def test_default_eval(): default = Default() assert default() == (True, None) default = Default(Env("FOO")) assert default() == (True, None) default = Default(Env("FOO", default="foo")) assert default() == (False, "foo") default = Default(Env("FOO"), Env("BAR"), Env("BAZ", default="okay")) assert default() == (False, "okay") def test_value_from(): def fn(): return 5 default = ValueFrom(fn) assert default() == 5 def fn2(value: int): return value + 5 default = ValueFrom(fn2, value=4) assert default() == 9 def fn3(*, value: int): return value + 5 default = ValueFrom(fn3, value=4) assert default() == 9 cappa-0.32.2/tests/test_docstring.py0000644000000000000000000000310313615410400014346 0ustar00from __future__ import annotations from dataclasses import dataclass from cappa.docstring import get_attribute_docstrings def test_attribute_docstring_single_line(): @dataclass class SingleLineArgs: foo: int """A simple description.""" result = get_attribute_docstrings(SingleLineArgs) assert result == {"foo": "A simple description."} def test_attribute_docstring_indented_multiline(): @dataclass class IndentedMultilineArgs: foo: int """First paragraph. Second paragraph. """ result = get_attribute_docstrings(IndentedMultilineArgs) assert result == {"foo": "First paragraph.\n\nSecond paragraph."} def test_attribute_docstring_indented_multiline_dedented(): """Indented attribute docstrings must not be treated as Markdown code blocks.""" @dataclass class IndentedDedentedArgs: foo: int """Summary line. Body paragraph that would have 8 spaces of indent before cleandoc, with a long enough body to wrap. """ result = get_attribute_docstrings(IndentedDedentedArgs) # No leading spaces on continuation lines assert not any(line.startswith(" ") for line in result["foo"].splitlines()) def test_attribute_docstring_multiple_fields(): @dataclass class MultipleFieldArgs: foo: int """Foo help. Foo body. """ bar: str """Bar help.""" result = get_attribute_docstrings(MultipleFieldArgs) assert result["foo"] == "Foo help.\n\nFoo body." assert result["bar"] == "Bar help." cappa-0.32.2/tests/test_e2e.py0000644000000000000000000000150713615410400013033 0ustar00from __future__ import annotations from pathlib import Path from typing import Any def successful_test_run( pytester: Any, *, count: int | None = None, skipped_count: int = 0 ): pytester.copy_example() result = pytester.runpytest_inprocess( "-vv", "--log-cli-level=WARNING", "--log-level=WARNING", "conftest.py" ) result.assert_outcomes(passed=count, skipped=skipped_count, failed=0) def test_kitchen_sink(pytester: Any): successful_test_run(pytester, count=1) def test_docutils(pytester: Any): build_path: Path = pytester.path / "_build" path = build_path / "index.html" path.unlink(missing_ok=True) pytester.copy_example(name="docutils") pytester.run("sphinx-build", "-W", "--jobs=2", pytester.path, build_path) output = path.read_text() assert "Usage: foo" in output cappa-0.32.2/tests/test_example.py0000644000000000000000000000265613615410400014021 0ustar00from __future__ import annotations import pathlib from dataclasses import dataclass, field import cappa from tests.utils import Backend, backends, parse @backends def test_positional_string(backend: Backend): @dataclass class ArgTest: name: str test = parse(ArgTest, "thename", backend=backend) assert test.name == "thename" @backends def test_path(backend: Backend): @dataclass class ArgTest: name: pathlib.PurePath test = parse(ArgTest, "./file.txt", backend=backend) assert test.name == pathlib.PurePath("./file.txt") @backends def test_bool(backend: Backend): @dataclass class ArgTest: flag: bool test = parse(ArgTest, "--flag", backend=backend) assert test.flag is True @backends def test_tuple(backend: Backend): @dataclass class ArgTest: exact_num_args: tuple[str, int] = field( default=("a", 0), metadata={"cappa": cappa.Arg(long=True)} ) test = parse( ArgTest, "--exact-num-args", "three", "4", backend=backend, ) assert test.exact_num_args == ("three", 4) @backends def test_missing_value_defaults(backend: Backend): @dataclass class ArgTest: exact_num_args: tuple[str, int] = field( default=("a", 0), metadata={"cappa": cappa.Arg(long=True)} ) test = parse(ArgTest, backend=backend) assert test.exact_num_args == ("a", 0) cappa-0.32.2/tests/test_implicit_dataclass.py0000644000000000000000000000112113615410400016201 0ustar00from __future__ import annotations import cappa from tests.utils import Backend, backends, parse @cappa.command(name="sub") class Example: bar: int name: str @backends def test_invoke_top_level_command(backend: Backend): result = parse(Example, "4", "foo", backend=backend) assert result == Example(bar=4, name="foo") # pyright: ignore @backends def test_no_args_command(backend: Backend): @cappa.command class Example: bar: int name: str result = parse(Example, "4", "foo", backend=backend) assert result == Example(bar=4, name="foo") cappa-0.32.2/tests/test_manually_built.py0000644000000000000000000000324413615410400015401 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any, List import pytest import cappa from cappa.output import Exit from cappa.parse import parse_list, parse_value from tests.utils import Backend, backends, parse @dataclass class Foo: bar: str baz: list[int] command = cappa.Command( Foo, arguments=[ cappa.Arg(field_name="bar"), cappa.Arg(field_name="baz", parse=parse_list(List[int]), num_args=-1), ], help="Short help.", description="Long description.", ) @backends def test_valid(backend: Backend): result = parse(command, "one", "2", "3", backend=backend) assert result == Foo(bar="one", baz=[2, 3]) @pytest.mark.help @backends def test_help(capsys: Any, backend: Backend): with pytest.raises(Exit): parse(command, "-h", backend=backend) out = capsys.readouterr().out assert "Short help." in out assert "Long description." in out @dataclass class Bar: bar: str @dataclass class Foo2: sub: Bar @backends def test_subcommand(backend: Backend): command = cappa.Command( Foo2, arguments=[ cappa.Subcommand( field_name="sub", options={ "bar": cappa.Command( Bar, arguments=[ cappa.Arg(field_name="bar", parse=parse_value(str)), ], ) }, ), ], help="Short help.", description="Long description.", ) result = parse(command, "bar", "one", backend=backend) assert result == Foo2(sub=Bar("one")) cappa-0.32.2/tests/test_msgspec.py0000644000000000000000000000274713615410400014030 0ustar00from __future__ import annotations import sys from typing import Optional import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse if sys.version_info >= (3, 9): try: import msgspec HAVE_MSGSPEC = True class PydanticCommand(msgspec.Struct): name: str foo: Annotated[int, cappa.Arg(short=True)] class OptSub(msgspec.Struct): name: Optional[str] = None class OptionalSubcommand(msgspec.Struct): sub: cappa.Subcommands[Optional[OptSub]] = None except ImportError: HAVE_MSGSPEC = False # pyright: ignore[reportConstantRedefinition] @pytest.mark.skipif(not HAVE_MSGSPEC, reason="msgspec not installed") @backends def test_base_model(backend: Backend): result = parse(PydanticCommand, "meow", "-f", "4", backend=backend) assert result == PydanticCommand(name="meow", foo=4) @pytest.mark.skipif(not HAVE_MSGSPEC, reason="msgspec not installed") @backends def test_optional_subcommand(backend: Backend): result = parse(OptionalSubcommand, backend=backend) assert result == OptionalSubcommand(sub=None) result = parse(OptionalSubcommand, "opt-sub", backend=backend) assert result == OptionalSubcommand(sub=OptSub(name=None)) result = parse(OptionalSubcommand, "opt-sub", "foo", backend=backend) assert result == OptionalSubcommand(sub=OptSub(name="foo")) cappa-0.32.2/tests/test_output.py0000644000000000000000000000620513615410400013720 0ustar00import textwrap from dataclasses import dataclass from typing import Any import pytest from rich.table import Table from rich.text import Text from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_invoke_exit_success_with_message(capsys: Any, backend: Backend): def fn(): raise cappa.Exit("With message") @cappa.command(invoke=fn) @dataclass class Example: ... with pytest.raises(cappa.Exit): cappa.invoke(Example, argv=[], backend=backend) out = capsys.readouterr().out assert out == "With message\n" @backends def test_invoke_exit_success_without_message(capsys: Any, backend: Backend): def fn(): raise cappa.Exit() @cappa.command(invoke=fn) @dataclass class Example: ... with pytest.raises(cappa.Exit): cappa.invoke(Example, argv=[], backend=backend) out = capsys.readouterr().out assert out == "" @backends def test_invoke_exit_error(capsys: Any, backend: Backend): def fn(): raise cappa.Exit("With message", code=1) @cappa.command(invoke=fn) @dataclass class Example: ... with pytest.raises(cappa.Exit) as e: cappa.invoke(Example, argv=[], backend=backend) assert e.value.code == 1 out = capsys.readouterr().err assert out == "Error: With message\n" @backends def test_error_output_rich_text(capsys: Any, backend: Backend): def fn(): raise cappa.Exit(Text("With message"), code=1) @cappa.command(invoke=fn) @dataclass class Example: ... with pytest.raises(cappa.Exit) as e: cappa.invoke(Example, argv=[], backend=backend) assert e.value.code == 1 out = capsys.readouterr().err assert out == "Error: With message\n" @backends def test_explicit_output_prefix(capsys: Any, backend: Backend): @cappa.command(name="asdf") @dataclass class Example: ... output = cappa.Output(error_format="{prog}: error({code}): {message}.") with pytest.raises(cappa.Exit) as e: parse(Example, "--fooooo", backend=backend, output=output) assert e.value.code == 2 out = capsys.readouterr().err assert out.lower() == "asdf: error(2): unrecognized arguments: --fooooo.\n" def _debug(output: cappa.Output): table = Table(style="blue") table.add_row("one", "two") output.error(table) raise cappa.Exit(code=0) @backends def test_output_formatting_complex_rich_object(capsys: Any, backend: Backend): @dataclass class Example: debug_info: Annotated[ bool, cappa.Arg(long=True, action=_debug, num_args=0), ] = False output = cappa.Output(error_format="[red]Error[/red]:\n{message}!") with pytest.raises(cappa.Exit) as e: parse(Example, "--debug-info", output=output, backend=backend) assert e.value.code == 0 out = capsys.readouterr().err assert out == textwrap.dedent( """\ Error: ┏━━━━━┳━━━━━┓ ┃ ┃ ┃ ┡━━━━━╇━━━━━┩ │ one │ two │ └─────┴─────┘! """ ) cappa-0.32.2/tests/test_pydantic.py0000644000000000000000000000362013615410400014171 0ustar00from __future__ import annotations import sys from typing import Optional import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse if sys.version_info < (3, 14): from pydantic import BaseModel, Field, dataclasses class PydanticCommand(BaseModel): name: str foo: Annotated[int, cappa.Arg(short=True)] @backends def test_base_model(backend: Backend): result = parse(PydanticCommand, "meow", "-f", "4", backend=backend) assert result == PydanticCommand(name="meow", foo=4) @dataclasses.dataclass class DataclassCommand: name: str foo: Annotated[int, cappa.Arg(short=True)] @backends def test_dataclass(backend: Backend): result = parse(DataclassCommand, "meow", "-f", "4", backend=backend) assert result == DataclassCommand(name="meow", foo=4) class OptSub(BaseModel): name: Optional[str] = None class OptionalSubcommand(BaseModel): sub: cappa.Subcommands[Optional[OptSub]] = None @backends def test_optional_subcommand(backend: Backend): result = parse(OptionalSubcommand, backend=backend) assert result == OptionalSubcommand(sub=None) result = parse(OptionalSubcommand, "opt-sub", backend=backend) assert result == OptionalSubcommand(sub=OptSub(name=None)) result = parse(OptionalSubcommand, "opt-sub", "foo", backend=backend) assert result == OptionalSubcommand(sub=OptSub(name="foo")) @backends def test_class_validation(backend: Backend): class Foo(BaseModel): bar: Optional[int] = Field(gt=0) result = parse(Foo, "3", backend=backend) assert result.bar == 3 with pytest.raises(cappa.Exit) as e: parse(Foo, "0", backend=backend) assert e.value.code == 2 assert "greater than 0" in str(e.value.message) cappa-0.32.2/tests/test_state.py0000644000000000000000000000323113615410400013474 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any, TypedDict from typing_extensions import Annotated import cappa from cappa.default import ValueFrom from cappa.state import State from tests.utils import Backend, backends, invoke, parse class Increment(TypedDict): value: int def woah(state: State[Increment]): state.state["value"] = state.state.get("value", 0) + 1 return state.state["value"] @dataclass class Command: foo: Annotated[int, cappa.Arg(default=ValueFrom(woah))] = 0 bar: Annotated[int, cappa.Arg(default=ValueFrom(woah))] = 0 def __call__(self, state: State[Increment]): return state.state @backends def test_shared_state(backend: Backend): result = parse(Command, "6", "7", backend=backend) assert result == Command(6, 7) result = parse(Command, backend=backend) assert result == Command(1, 2) @backends def test_dep_state(backend: Backend): result = invoke(Command, "6", "7", backend=backend) assert result == {} result = invoke(Command, backend=backend) assert result == {"value": 2} def parse_val(value: str, state: State[dict[str, Any]]): state.set("foo", int(value)) return state.get("foo") @backends def test_parse_state(backend: Backend): state: State[dict[str, Any]] = State() @dataclass class Command: foo: Annotated[int, cappa.Arg(parse=parse_val)] = 0 result = parse(Command, backend=backend, state=state) assert result == Command(0) assert state.get("foo") is None result = parse(Command, "6", backend=backend, state=state) assert result == Command(6) assert state.get("foo") == 6 cappa-0.32.2/tests/utils.py0000644000000000000000000000566413615410400012471 0ustar00from __future__ import annotations import contextlib import os import sys from dataclasses import dataclass from typing import Any, Union from unittest.mock import patch import pytest from typing_extensions import Unpack from cappa import argparse, parser from cappa.base import Backend from cappa.command import Command from cappa.invoke.types import DepTypes, InvokeCallable from cappa.output import Exit from cappa.testing import CommandRunner, RunnerArgs, T __all__ = [ "Backend", "CapsysOutput", "backends", "invoke", "invoke_async", "parse", "require_dataclass_kw_only", "runner", ] require_dataclass_kw_only = pytest.mark.skipif( sys.version_info < (3, 10), reason="kw_only requires Python 3.10+" ) backends = pytest.mark.parametrize( "backend", [ None, pytest.param(argparse.backend, marks=pytest.mark.argparse), ], ) runner: CommandRunner[Any] = CommandRunner(base_args=[]) def parse( cls: type[T] | InvokeCallable[T] | Command[T], *args: str, **kwargs: Unpack[RunnerArgs], ) -> T: return runner.parse(cls, *args, **kwargs) def invoke( cls: type[T] | InvokeCallable[T] | Command[T], *args: str, deps: DepTypes | None = None, **kwargs: Unpack[RunnerArgs], ): return runner.invoke(cls, *args, deps=deps, **kwargs) def invoke_async( cls: type[T] | InvokeCallable[T] | Command[T], *args: str, deps: DepTypes | None = None, **kwargs: Unpack[RunnerArgs], ): return runner.invoke_async(cls, *args, deps=deps, **kwargs) def parse_completion( cls: type, *args: str, location: str | None = None ) -> Union[str, None]: env = { "COMPLETION_LINE": " ".join(["test.py", *args]), "COMPLETION_LOCATION": location if location is not None else len(args) + 1, } with patch("os.environ", new=env): with pytest.raises(Exit) as e: parse(cls, "--completion", "complete", backend=parser.backend) assert e.value.code == 0 if e.value.message: return str(e.value.message) return None @contextlib.contextmanager def ignore_docstring_parser(monkeypatch: Any): import importlib cappa_command = importlib.import_module("cappa.docstring") with monkeypatch.context() as m: m.setattr(cappa_command, "docstring_parser", None) yield @contextlib.contextmanager def terminal_width(width: int = 1000): env = os.environ.copy() env["COLUMNS"] = str(width) with patch("os.environ", new=env), patch("rich.console.Console._environ", new=env): yield def strip_trailing_whitespace(text: str): return "\n".join([line.rstrip() for line in text.split("\n")]) @dataclass class CapsysOutput: stdout: str stderr: str @classmethod def from_capsys(cls, capsys: Any): outerr = capsys.readouterr() out = strip_trailing_whitespace(outerr.out) err = strip_trailing_whitespace(outerr.err) return cls(out, err) cappa-0.32.2/tests/arg/__init__.py0000644000000000000000000000000013615410400013614 0ustar00cappa-0.32.2/tests/arg/test_async_parse_basic.py0000644000000000000000000000123013615410400016572 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends async def async_parse_int(value: str) -> int: await asyncio.sleep(0.001) result = int(value) if result < 0: raise ValueError("Value must be non-negative") return result @dataclass class Command: value: Annotated[int, cappa.Arg(parse=async_parse_int)] def __call__(self): return self @backends def test(backend: Backend): result = asyncio.run(cappa.invoke_async(Command, argv=["42"], backend=backend)) assert result.value == 42 cappa-0.32.2/tests/arg/test_async_parse_optional.py0000644000000000000000000000137313615410400017346 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends async def async_parse_multiply(value: str) -> int: await asyncio.sleep(0.001) return int(value) * 2 @dataclass class Command: value: Annotated[int | None, cappa.Arg(long=True, parse=async_parse_multiply)] = ( None ) def __call__(self): return self @backends def test(backend: Backend): result = asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) assert result.value is None result = asyncio.run( cappa.invoke_async(Command, argv=["--value", "10"], backend=backend) ) assert result.value == 20 cappa-0.32.2/tests/arg/test_async_parse_with_default.py0000644000000000000000000000142513615410400020176 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends async def async_parse_int(value: str) -> int: await asyncio.sleep(0.001) result = int(value) if result < 0: raise ValueError("Value must be non-negative") return result @dataclass class Command: value: Annotated[int, cappa.Arg(parse=async_parse_int, default=100)] def __call__(self): return self @backends def test(backend: Backend): result = asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) assert result.value == 100 result = asyncio.run(cappa.invoke_async(Command, argv=["42"], backend=backend)) assert result.value == 42 cappa-0.32.2/tests/arg/test_async_parse_with_exception.py0000644000000000000000000000134013615410400020544 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends async def async_parse_that_fails(value: str) -> int: await asyncio.sleep(0.001) raise ValueError("Async parsing failed") @dataclass class Command: value: Annotated[int, cappa.Arg(parse=async_parse_that_fails)] def __call__(self): return self @backends def test(backend: Backend): with pytest.raises(cappa.Exit) as exc_info: asyncio.run(cappa.invoke_async(Command, argv=["42"], backend=backend)) assert exc_info.value.code == 2 assert "Async parsing failed" in str(exc_info.value.message) cappa-0.32.2/tests/arg/test_async_value_from_basic.py0000644000000000000000000000132413615410400017623 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from cappa.default import ValueFrom from tests.utils import Backend, backends async def async_value_provider() -> int: await asyncio.sleep(0.001) return 42 @dataclass class Command: value: Annotated[int, cappa.Arg(default=ValueFrom(async_value_provider))] def __call__(self): return self @backends def test(backend: Backend): result = asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) assert result.value == 42 result = asyncio.run(cappa.invoke_async(Command, argv=["123"], backend=backend)) assert result.value == 123 cappa-0.32.2/tests/arg/test_async_value_from_fallback_chain.py0000644000000000000000000000117413615410400021446 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from cappa.default import ValueFrom from tests.utils import Backend, backends async def async_fallback() -> int: await asyncio.sleep(0.001) return 99 @dataclass class Command: value: Annotated[ int, cappa.Arg(default=cappa.Env("MISSING_VAR") | ValueFrom(async_fallback)) ] def __call__(self): return self @backends def test(backend: Backend): result = asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) assert result.value == 99 cappa-0.32.2/tests/arg/test_async_value_from_list.py0000644000000000000000000000157413615410400017524 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from cappa.default import ValueFrom from tests.utils import Backend, backends async def async_list_provider() -> list[str]: await asyncio.sleep(0.001) return ["async", "list", "values"] @dataclass class Command: values: Annotated[ list[str], cappa.Arg( short=False, long=False, default=ValueFrom(async_list_provider), ), ] def __call__(self): return self @backends def test(backend: Backend): result = asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) assert result.values == ["async", "list", "values"] result = asyncio.run(cappa.invoke_async(Command, argv=["a", "b"], backend=backend)) assert result.values == ["a", "b"] cappa-0.32.2/tests/arg/test_async_value_from_with_async_parse.py0000644000000000000000000000175113615410400022110 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends async def async_fetch_raw_value() -> str: await asyncio.sleep(0.001) return "10" async def async_parse_multiply(value: str) -> int: await asyncio.sleep(0.001) return int(value) * 2 @dataclass class Command: value: Annotated[ int, cappa.Arg( default=cappa.ValueFrom(async_fetch_raw_value), parse=async_parse_multiply, ), ] def __call__(self): return self @backends def test(backend: Backend): # ValueFrom returns is_parsed=True, so parse is NOT applied result = asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) assert result.value == "10" # With explicit value: parse IS applied result = asyncio.run(cappa.invoke_async(Command, argv=["5"], backend=backend)) assert result.value == 10 cappa-0.32.2/tests/arg/test_async_value_from_with_exception.py0000644000000000000000000000125013615410400021571 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from cappa.default import ValueFrom from tests.utils import Backend, backends async def async_value_that_fails(): await asyncio.sleep(0.001) raise RuntimeError("Async fetch failed") @dataclass class Command: value: Annotated[int, cappa.Arg(default=ValueFrom(async_value_that_fails))] def __call__(self): return self @backends def test(backend: Backend): with pytest.raises(RuntimeError, match="Async fetch failed"): asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) cappa-0.32.2/tests/arg/test_async_value_from_with_kwargs.py0000644000000000000000000000123513615410400021074 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from cappa.default import ValueFrom from tests.utils import Backend, backends async def async_value_with_kwargs(multiplier: int) -> int: await asyncio.sleep(0.001) return 10 * multiplier @dataclass class Command: value: Annotated[ int, cappa.Arg(default=ValueFrom(async_value_with_kwargs, multiplier=5)) ] def __call__(self): return self @backends def test(backend: Backend): result = asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) assert result.value == 50 cappa-0.32.2/tests/arg/test_async_value_from_with_state.py0000644000000000000000000000147513615410400020724 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from cappa.default import ValueFrom from tests.utils import Backend, backends async def async_value_with_deps(state: cappa.State[dict[str, str]]) -> str: await asyncio.sleep(0.001) return f"state-value-{state.get('key', default='default')}" @dataclass class Command: value: Annotated[str, cappa.Arg(default=ValueFrom(async_value_with_deps))] def __call__(self): return self @backends def test(backend: Backend): state: cappa.State[dict[str, str]] = cappa.State() state.set("key", "injected") result = asyncio.run( cappa.invoke_async(Command, argv=[], backend=backend, state=state) ) assert result.value == "state-value-injected" cappa-0.32.2/tests/arg/test_bool.py0000644000000000000000000001201113615410400014054 0ustar00from __future__ import annotations from dataclasses import dataclass from unittest.mock import patch import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_missing_default(backend: Backend): @dataclass class ArgTest: default: bool test = parse(ArgTest, backend=backend) assert test.default is False test = parse(ArgTest, "--default", backend=backend) assert test.default is True @backends def test_default(backend: Backend): @dataclass class ArgTest: default: bool = False test = parse(ArgTest, backend=backend) assert test.default is False test = parse(ArgTest, "--default", backend=backend) assert test.default is True @backends def test_explicit_short(backend: Backend): @dataclass class ArgTest: explicit_short: Annotated[bool, cappa.Arg(short="-e")] = False test = parse(ArgTest, backend=backend) assert test.explicit_short is False test = parse(ArgTest, "-e", backend=backend) assert test.explicit_short is True @backends def test_explicit_long(backend: Backend): @dataclass class ArgTest: explicit_long: Annotated[bool, cappa.Arg(long="--meow")] = False test = parse(ArgTest, backend=backend) assert test.explicit_long is False test = parse(ArgTest, "--meow", backend=backend) assert test.explicit_long is True @backends def test_store_false(backend: Backend): @dataclass class ArgTest: default_true: Annotated[bool, cappa.Arg(long="--false")] = True test = parse(ArgTest, backend=backend) assert test.default_true is True test = parse(ArgTest, "--false", backend=backend) assert test.default_true is False @backends def test_true_false_option(backend: Backend): @dataclass class ArgTest: true_false: Annotated[bool, cappa.Arg(long="--true/--no-true")] = True test = parse(ArgTest, backend=backend) assert test.true_false is True test = parse(ArgTest, "--true", backend=backend) assert test.true_false is True test = parse(ArgTest, "--no-true", backend=backend) assert test.true_false is False with pytest.raises(cappa.Exit) as e: parse(ArgTest, "--garbage", backend=backend) assert e.value.code == 2 assert "unrecognized arguments: --garbage" in str(e.value.message).lower() @backends def test_optional_bool(backend: Backend): """Optional bool with default should still infer store_true/false action.""" @dataclass class ArgTest: true_false: Annotated[bool | None, cappa.Arg(long="--true/--no-true")] = None test = parse(ArgTest, backend=backend) assert test.true_false is None test = parse(ArgTest, "--true", backend=backend) assert test.true_false is True test = parse(ArgTest, "--no-true", backend=backend) assert test.true_false is False @backends def test_optional_bool_no_default(backend: Backend): """Optional bool should default to None rather than bool.""" @dataclass class ArgTest: true_false: Annotated[bool | None, cappa.Arg(long="--true/--no-true")] test = parse(ArgTest, backend=backend) assert test.true_false is None @backends def test_optional_bool_and_int(backend: Backend): """Union of multiple types, including bool does not infer bool action.""" @dataclass class ArgTest: true_false: Annotated[bool | int | None, cappa.Arg(short=True)] test = parse(ArgTest, backend=backend) assert test.true_false is None test = parse(ArgTest, "-t", "4", backend=backend) assert test.true_false == 4 test = parse(ArgTest, "-t", "asdf", backend=backend) assert test.true_false is True @backends def test_env_default_value_precedence(backend: Backend): """Assert a bool flag yields correct value given an env default.""" @dataclass class ArgTest: env_default: Annotated[ bool, cappa.Arg(long=True, default=cappa.Env("ENV_DEFAULT")) ] = False test = parse(ArgTest, backend=backend) assert test.env_default is False with patch("os.environ", new={"ENV_DEFAULT": "1"}): test = parse(ArgTest, backend=backend) assert test.env_default is True test = parse(ArgTest, "--env-default", backend=backend) assert test.env_default is True with patch("os.environ", new={"ENV_DEFAULT": "1"}): test = parse(ArgTest, "--env-default", backend=backend) assert test.env_default is True @backends def test_sole_no_arg(backend: Backend): @dataclass class ArgTest: no_dry_run: bool = False test = parse(ArgTest, backend=backend) assert test.no_dry_run is False test = parse(ArgTest, "--no-dry-run", backend=backend) assert test.no_dry_run is True @backends def test_sole_no_arg_inverted(backend: Backend): @dataclass class ArgTest: no_dry_run: bool = True test = parse(ArgTest, backend=backend) assert test.no_dry_run is True test = parse(ArgTest, "--no-dry-run", backend=backend) assert test.no_dry_run is False cappa-0.32.2/tests/arg/test_bool_both.py0000644000000000000000000000134713615410400015102 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_missing_default(backend: Backend): @dataclass class Args: foo: Annotated[bool, cappa.Arg(long="--foo/--no-foo")] = False test = parse(Args, backend=backend) assert test.foo is False test = parse(Args, "--foo", backend=backend) assert test.foo is True with pytest.raises(cappa.Exit) as e: parse(Args, "--foo", "--no-foo", backend=backend) message = str(e.value.message) assert "--no-foo" in message assert "--foo" in message assert "not allowed with argument" in message cappa-0.32.2/tests/arg/test_choices.py0000644000000000000000000000573213615410400014552 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any, Literal import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_manually_specified_choices(backend: Backend): @dataclass class ArgTest: choice: Annotated[str, cappa.Arg(choices=["a", "1"])] result = parse(ArgTest, "a") assert result.choice == "a" result = parse(ArgTest, "1") assert result.choice == "1" with pytest.raises(cappa.Exit) as e: parse(ArgTest, "two", backend=backend) message = str(e.value.message).lower() assert "invalid choice: 'two' (choose from 'a', '1')" in message @backends def test_optional_set_of_choices(backend: Backend, capsys: Any): @dataclass class ArgTest: choice: Annotated[set[Literal["one", "two"]] | None, cappa.Arg(short=True)] = ( None ) with pytest.raises(cappa.HelpExit): parse(ArgTest, "--help", backend=backend) result = capsys.readouterr().out assert "Valid options: one, two." in result @backends def test_variadic_tuple_choices(backend: Backend, capsys: Any): @dataclass class ArgTest: choice: Annotated[ tuple[Literal["one", "two"], ...] | None, cappa.Arg(short=True) ] = None with pytest.raises(cappa.HelpExit): parse(ArgTest, "--help", backend=backend) result = capsys.readouterr().out assert "Valid options: one, two." in result @backends def test_tuple_choices(backend: Backend, capsys: Any): @dataclass class ArgTest: choice: Annotated[ tuple[Literal["one", "two"], int] | None, cappa.Arg(short=True) ] = None with pytest.raises(cappa.HelpExit): parse(ArgTest, "--help", backend=backend) result = capsys.readouterr().out assert "Valid options: one, two." not in result @backends def test_literal_parse_sequence(backend: Backend): @dataclass class LiteralParse: log_level: Annotated[ str, cappa.Arg( short="-L", long=True, choices=["DEBUG"], parse=[str.upper, str.strip] ), ] = "DEBUG" result = parse(LiteralParse, "--log-level", " debug ", backend=backend) assert result == LiteralParse(log_level="DEBUG") explicit_choice_default = ["1s", "1m"] @backends def test_explicit_choice_sequence(backend: Backend): @dataclass class Example: interval: Annotated[ list[str] | None, cappa.Arg(choices=explicit_choice_default, default=["1m"]) ] result = parse(Example, backend=backend) assert result == Example(interval=["1m"]) result = parse(Example, "1s", backend=backend) assert result == Example(interval=["1s"]) with pytest.raises(cappa.Exit) as e: parse(Example, "2s", backend=backend) assert ( e.value.message == "Invalid value for 'interval': Invalid choice: '2s' (choose from '1s', '1m')" ) cappa-0.32.2/tests/arg/test_count.py0000644000000000000000000000145413615410400014262 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_count_action(backend: Backend): @dataclass class ArgTest: arg: Annotated[int, cappa.Arg(short=True, action=cappa.ArgAction.count)] result = parse(ArgTest, "-a", backend=backend) assert result.arg == 1 result = parse(ArgTest, "-aaa", backend=backend) assert result.arg == 3 @backends def test_count_option(backend: Backend): @dataclass class ArgTest: arg: Annotated[int, cappa.Arg(short=True, count=True)] result = parse(ArgTest, "-a", backend=backend) assert result.arg == 1 result = parse(ArgTest, "-aaa", backend=backend) assert result.arg == 3 cappa-0.32.2/tests/arg/test_dataclass_metadata_invalid.py0000644000000000000000000000120613615410400020432 0ustar00from __future__ import annotations from dataclasses import dataclass, field import pytest import cappa from tests.utils import Backend, backends, parse @dataclass class Command: name: str = field(metadata={"cappa": cappa.Arg(short=True)}) cmd: Invalid = field(metadata={"cappa": cappa.Subcommand()}) @dataclass class Invalid: a: int = field(metadata={"cappa": 4}) @backends def test_valid(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(Command, "-n", "foo", "subcommand", "4", backend=backend) assert "invalid" in str(e.value.message).lower() assert "'subcommand'" in str(e.value.message) cappa-0.32.2/tests/arg/test_date.py0000644000000000000000000000244213615410400014045 0ustar00from __future__ import annotations from dataclasses import dataclass from datetime import date, datetime, time from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_datetime(backend: Backend): @dataclass class ArgTest: datetime_arg: datetime datetime_opt: Annotated[datetime, cappa.Arg(long=True)] test = parse( ArgTest, "2020-01-01T01:02:03", "--datetime-opt=2021-02-03T04:05:06", backend=backend, ) assert test.datetime_arg == datetime(2020, 1, 1, 1, 2, 3) assert test.datetime_opt == datetime(2021, 2, 3, 4, 5, 6) @backends def test_date(backend: Backend): @dataclass class ArgTest: date_arg: date date_opt: Annotated[date, cappa.Arg(long=True)] test = parse(ArgTest, "2020-01-01", "--date-opt=2021-02-03", backend=backend) assert test.date_arg == date(2020, 1, 1) assert test.date_opt == date(2021, 2, 3) @backends def test_time(backend: Backend): @dataclass class ArgTest: date_arg: time date_opt: Annotated[time, cappa.Arg(long=True)] test = parse(ArgTest, "01:02:03", "--date-opt=04:05:06", backend=backend) assert test.date_arg == time(1, 2, 3) assert test.date_opt == time(4, 5, 6) cappa-0.32.2/tests/arg/test_default.py0000644000000000000000000000204113615410400014547 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Command: foo: Annotated[int, cappa.Arg(default=4)] bar: Annotated[int, cappa.Arg(default=5)] @backends def test_valid(backend: Backend): test = parse(Command, "1", "2", backend=backend) assert test == Command(1, 2) @backends def test_default_is_not_mapped(backend: Backend): @dataclass class Command: foo: int = "4" # type: ignore test = parse(Command, "1", backend=backend) assert test == Command(1) test = parse(Command, backend=backend) assert test == Command("4") # type: ignore @backends def test_explicit_arg_default_precedes_class_level(backend: Backend): @dataclass class Command: foo: Annotated[int, cappa.Arg(default=10)] = 4 test = parse(Command, "1", backend=backend) assert test == Command(1) test = parse(Command, backend=backend) assert test == Command(10) cappa-0.32.2/tests/arg/test_default_env_missing.py0000644000000000000000000000157313615410400017161 0ustar00from __future__ import annotations from dataclasses import dataclass from unittest.mock import patch from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Command: opt: Annotated[str | None, cappa.Arg(default=cappa.Env("DOESNT_EXIST"))] opt2: Annotated[ str | None, cappa.Arg(default=cappa.Env("DOESNT_EXIST", default=None)) ] opt3: Annotated[ str | None, cappa.Arg(default=cappa.Env("DOESNT_EXIST", default=None)) ] = None @backends def test_env_missing(backend: Backend): test = parse(Command, backend=backend) assert test == Command(None, None, None) @backends def test_default_is_not_mapped(backend: Backend): with patch("os.environ", new={"DOESNT_EXIST": "asdf"}): test = parse(Command, backend=backend) assert test == Command("asdf", "asdf", "asdf") cappa-0.32.2/tests/arg/test_default_long.py0000644000000000000000000000225613615410400015576 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from cappa.output import Exit from tests.utils import Backend, backends, parse @backends def test_valid(backend: Backend): @cappa.command(default_long=True) @dataclass class Command: foo: int = 4 bar: int = 5 test = parse(Command, backend=backend) assert test == Command(4, 5) test = parse(Command, "--foo", "1", "--bar", "2", backend=backend) assert test == Command(1, 2) @backends def test_override(backend: Backend): @cappa.command(default_long=True) @dataclass class Command: foo: int far: Annotated[int, cappa.Arg(long="--kar")] test = parse(Command, "--foo", "1", "--kar", "2", backend=backend) assert test == Command(1, 2) @backends def test_invalid(backend: Backend): @cappa.command(default_long=True) @dataclass class Command: foo: int far: Annotated[int, cappa.Arg(long="--foo")] with pytest.raises(Exit) as e: parse(Command, backend=backend) assert "conflicting option string: --f" in str(e.value.message).lower() cappa-0.32.2/tests/arg/test_default_prompt.py0000644000000000000000000000430013615410400016150 0ustar00from dataclasses import dataclass from io import StringIO from typing import Type import pytest from rich.prompt import Confirm as RichConfirm from rich.prompt import Prompt as RichPrompt from typing_extensions import Annotated import cappa from cappa import Confirm, Prompt from cappa.base import Backend from tests.utils import backends, parse prompt_types = pytest.mark.parametrize("prompt_type", (Prompt, RichPrompt)) confirm_types = pytest.mark.parametrize("confirm_type", (Confirm, RichConfirm)) @backends class TestPrompt: @prompt_types def test_value(self, backend: Backend, prompt_type: Type[Prompt]): @dataclass class Test: name: Annotated[str, cappa.Arg(default=prompt_type("gimme text number"))] result = parse(Test, backend=backend, input=StringIO("two")) assert result.name == "two" @prompt_types def test_default(self, backend: Backend, prompt_type: Type[Prompt]): @dataclass class Test: name: Annotated[ str, cappa.Arg(default=prompt_type("gimme text number")) ] = "one" result = parse(Test, backend=backend, input=StringIO("")) assert result.name == "one" @prompt_types def test_mapped(self, backend: Backend, prompt_type: Type[Prompt]): @dataclass class Test: num: Annotated[int, cappa.Arg(default=prompt_type("gimme text number"))] result = parse(Test, backend=backend, input=StringIO("5")) assert result.num == 5 @backends class TestConfirm: @confirm_types def test_value(self, backend: Backend, confirm_type: Type[Confirm]): @dataclass class Test: ok: Annotated[bool, cappa.Arg(default=confirm_type("gimme text number"))] result = parse(Test, backend=backend, input=StringIO("y")) assert result.ok is True @confirm_types def test_default(self, backend: Backend, confirm_type: Type[Confirm]): @dataclass class Test: ok: Annotated[ bool, cappa.Arg(default=confirm_type("gimme text number")) ] = False result = parse(Test, backend=backend, input=StringIO("")) assert result.ok is False cappa-0.32.2/tests/arg/test_default_short.py0000644000000000000000000000217613615410400015777 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from cappa.output import Exit from tests.utils import Backend, backends, parse @backends def test_valid(backend: Backend): @cappa.command(default_short=True) @dataclass class Command: foo: int = 4 bar: int = 5 test = parse(Command, backend=backend) assert test == Command(4, 5) test = parse(Command, "-f", "1", "-b", "2", backend=backend) assert test == Command(1, 2) @backends def test_override(backend: Backend): @cappa.command(default_short=True) @dataclass class Command: foo: int far: Annotated[int, cappa.Arg(short="-k")] test = parse(Command, "-f", "1", "-k", "2", backend=backend) assert test == Command(1, 2) @backends def test_invalid(backend: Backend): @cappa.command(default_short=True) @dataclass class Command: foo: int far: int with pytest.raises(Exit) as e: parse(Command, backend=backend) assert "conflicting option string: -f" in str(e.value.message).lower() cappa-0.32.2/tests/arg/test_default_value_from.py0000644000000000000000000000427213615410400016776 0ustar00from __future__ import annotations from dataclasses import dataclass from unittest.mock import patch import pytest from typing_extensions import Annotated import cappa from cappa.default import ValueFrom from tests.utils import Backend, backends, parse def woah(value: int) -> int: return 1 + value @dataclass class Command: foo: Annotated[int, cappa.Arg(default=cappa.Env("FOO"))] = 4 bar: Annotated[ int, cappa.Arg(default=cappa.Env("BAR") | ValueFrom(woah, value=4)) ] = 0 @backends def test_valid(backend: Backend): result = parse(Command, "6", "7", backend=backend) assert result == Command(6, 7) result = parse(Command, backend=backend) assert result == Command(4, 5) with patch("os.environ", new={"FOO": "3", "BAR": "9"}): result = parse(Command, backend=backend) assert result == Command(3, 9) def raises(): raise ValueError() @backends def test_not_parsed(backend: Backend): @dataclass class Command: foo: Annotated[int, cappa.Arg(parse=raises, default=ValueFrom(int))] with pytest.raises(cappa.Exit): parse(Command, "7", backend=backend) result = parse(Command, backend=backend) assert result == Command(0) def called(): return ["42"] @backends def test_positional_unbound_num_args(backend: Backend): @dataclass class Command: positional: Annotated[ list[str], cappa.Arg( short=False, long=False, default=cappa.ValueFrom(called), ), ] result = parse(Command, "7", backend=backend) assert result == Command(["7"]) result = parse(Command, backend=backend) assert result == Command(["42"]) def two_tuple(): return ("42", "0") def test_optional_positional_2_tuple(): @dataclass class Command: positional: Annotated[ tuple[str, str], cappa.Arg( short=False, long=False, default=cappa.ValueFrom(two_tuple), ), ] result = parse(Command, "7", "8") assert result == Command(("7", "8")) result = parse(Command) assert result == Command(("42", "0")) cappa-0.32.2/tests/arg/test_deprecated.py0000644000000000000000000000652513615410400015236 0ustar00from __future__ import annotations import sys import textwrap from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from tests.utils import parse def test_native_backend(capsys: Any): @dataclass class ArgTest: arg1: Annotated[str, cappa.Arg(deprecated=True)] = "default" arg2: Annotated[str, cappa.Arg(short="a", long="--aaaa", deprecated=True)] = ( "default" ) arg3: Annotated[str, cappa.Arg(short="b", deprecated="Use something else")] = ( "default" ) result = parse(ArgTest) assert result.arg1 == "default" assert result.arg2 == "default" assert result.arg3 == "default" err = capsys.readouterr().err assert err == "" result = parse(ArgTest, "a") assert result.arg1 == "a" err = capsys.readouterr().err assert err == "Error: Argument `arg1` is deprecated\n" result = parse(ArgTest, "-a", "1") assert result.arg2 == "1" err = capsys.readouterr().err assert err == "Error: Option `-a` is deprecated\n" result = parse(ArgTest, "-b", "1") assert result.arg3 == "1" err = capsys.readouterr().err assert err == "Error: Option `-b` is deprecated: Use something else\n" @pytest.mark.skipif(sys.version_info >= (3, 13), reason="requires python3.13 or higher") def test_argparse_le_313(capsys: Any): """Below 3.13, this option has no effect.""" @dataclass class ArgTest: arg1: Annotated[str, cappa.Arg(deprecated=True)] = "default" arg2: Annotated[str, cappa.Arg(short="a", long="--aaaa", deprecated=True)] = ( "default" ) arg3: Annotated[str, cappa.Arg(short="b", deprecated="Use something else")] = ( "default" ) result = parse(ArgTest, backend=cappa.argparse.backend) assert result.arg1 == "default" assert result.arg2 == "default" assert result.arg3 == "default" err = capsys.readouterr().err assert err == "" result = parse(ArgTest, "1", "-a", "1", "-b", "1", backend=cappa.argparse.backend) assert result.arg1 == "1" assert result.arg1 == "1" assert result.arg1 == "1" err = capsys.readouterr().err assert err == "" @pytest.mark.skipif( sys.version_info < (3, 13), reason="Below 3.13, the behavior is different" ) def test_argparse_ge_313(capsys: Any): @dataclass class ArgTest: arg1: Annotated[str, cappa.Arg(deprecated=True)] = "default" arg2: Annotated[str, cappa.Arg(short="a", long="--aaaa", deprecated=True)] = ( "default" ) arg3: Annotated[str, cappa.Arg(short="b", deprecated="Use something else")] = ( "default" ) result = parse(ArgTest, backend=cappa.argparse.backend) assert result.arg1 == "default" assert result.arg2 == "default" assert result.arg3 == "default" err = capsys.readouterr().err assert err == "" result = parse(ArgTest, "1", "-a", "1", "-b", "1", backend=cappa.argparse.backend) assert result.arg1 == "1" assert result.arg1 == "1" assert result.arg1 == "1" err = capsys.readouterr().err.replace("arg-test: ", "") assert err == textwrap.dedent( """\ warning: argument 'arg1' is deprecated warning: option '-a' is deprecated warning: option '-b' is deprecated """ ) cappa-0.32.2/tests/arg/test_destructured.py0000644000000000000000000000240013615410400015637 0ustar00from __future__ import annotations import enum from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse class Flavor(enum.Enum): vanilla = "vanilla" chocolate = "chocolate" @dataclass class Attributes: color: Annotated[str, cappa.Arg(long="attribute.color")] flavors: Annotated[list[Flavor], cappa.Arg(value_name="attribute.flavor")] something: Annotated[int, cappa.Arg(short="-p")] custom_parse: Annotated[str, cappa.Arg(long="foo", parse=lambda _: "always this")] # pyright: ignore custom_action: Annotated[int, cappa.Arg(long="bar", action=lambda: 42)] # pyright: ignore optional: Annotated[int, cappa.Arg(long="baz")] = 4 @dataclass class Args: attrs: Annotated[cappa.Destructured[Attributes], cappa.Arg(hidden=True)] def test_destructured(): test = parse( Args, "--attribute.color=red", "chocolate", "vanilla", "-p4", "--foo=whatever", "--bar=0", ) assert test == Args( attrs=Attributes( "red", [Flavor.chocolate, Flavor.vanilla], something=4, custom_parse="always this", custom_action=42, optional=4, ) ) cappa-0.32.2/tests/arg/test_destructured_append.py0000644000000000000000000000123013615410400017166 0ustar00from __future__ import annotations import dataclasses from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse @dataclass class Items: items: Annotated[list[str], cappa.Arg(long=True, action=cappa.ArgAction.append)] = ( dataclasses.field(default_factory=lambda: []) ) @dataclass class Args: w: cappa.Destructured[Items] def test_append_multiple(): result = parse(Args, "--items=a", "--items=b", "--items=c") assert result == Args(w=Items(items=["a", "b", "c"])) def test_append_default_empty(): result = parse(Args) assert result == Args(w=Items(items=[])) cappa-0.32.2/tests/arg/test_destructured_bool.py0000644000000000000000000000212013615410400016651 0ustar00from __future__ import annotations from dataclasses import dataclass from textwrap import dedent from typing import Any import pytest import cappa from tests.utils import ( Backend, backends, parse, strip_trailing_whitespace, terminal_width, ) @dataclass class Flags: flag: bool = False enabled: bool = True @dataclass class Args: flags: cappa.Destructured[Flags] @backends def test_bool_destructured(backend: Backend, capsys: Any): assert parse(Args, "--flag") == Args(flags=Flags(flag=True, enabled=True)) assert parse(Args, "--enabled") == Args(flags=Flags(flag=False, enabled=False)) with terminal_width(80), pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend, completion=False) result = strip_trailing_whitespace(capsys.readouterr().out) assert result == dedent( """\ Usage: args [--flag] [--enabled] [-h] Options [--flag] (Default: False) [--enabled] (Default: True) Help [-h, --help] Show this message and exit. """ ) cappa-0.32.2/tests/arg/test_destructured_collision.py0000644000000000000000000000137513615410400017724 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse @dataclass class CollisionSub: field1: str = "" field2: Annotated[str, cappa.Arg(long=True)] = "" @dataclass class Collision: sub: cappa.Destructured[CollisionSub] field1: int = 0 field2: int = 0 def test_destructured_collision(): result = parse(Collision, "1") assert result == Collision(sub=CollisionSub("1", ""), field1=0) result = parse(Collision, "1", "2") assert result == Collision(sub=CollisionSub("1", ""), field1=2) result = parse(Collision, "--field2=-1", "1", "2", "3") assert result == Collision(sub=CollisionSub("1", "-1"), field1=2, field2=3) cappa-0.32.2/tests/arg/test_destructured_count.py0000644000000000000000000000076113615410400017057 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse @dataclass class Config: verbose: Annotated[int, cappa.Arg(short=True, count=True)] @dataclass class Count: config: cappa.Destructured[Config] def test_count(): test = parse(Count, "-v") assert test == Count(config=Config(verbose=1)) test = parse(Count, "-vvvvv") assert test == Count(config=Config(verbose=5)) cappa-0.32.2/tests/arg/test_destructured_default_long_short.py0000644000000000000000000000164013615410400021606 0ustar00from __future__ import annotations from dataclasses import dataclass import cappa from tests.utils import parse @dataclass class Args: foo: int = 4 bar: int = 5 @cappa.command(default_long=True, default_short=True) @dataclass class Command: args: cappa.Destructured[Args] @cappa.command(default_long=True) @dataclass class InnerOverrideArgs: foo: int = 4 @cappa.command(default_long=False) @dataclass class InnerOverrideCommand: args: cappa.Destructured[InnerOverrideArgs] def test_default_long_propagates(): test = parse(Command, "--foo", "1", "--bar", "2") assert test == Command(Args(1, 2)) def test_default_short_propagates(): test = parse(Command, "-f", "1", "-b", "2") assert test == Command(Args(1, 2)) def test_inner_command_setting_takes_precedence(): test = parse(InnerOverrideCommand, "--foo", "1") assert test == InnerOverrideCommand(InnerOverrideArgs(1)) cappa-0.32.2/tests/arg/test_destructured_defaults.py0000644000000000000000000000313313615410400017532 0ustar00from __future__ import annotations from dataclasses import dataclass from unittest.mock import patch from typing_extensions import Annotated import cappa from cappa import Env, ValueFrom from tests.utils import parse @dataclass class Foo: color: Annotated[str, cappa.Arg(long=True, default=Env("FOO"))] @dataclass class Args: attrs: cappa.Destructured[Foo] def test_env_default_no_cli_arg(): with patch("os.environ", new={"FOO": "blue"}): result = parse(Args) assert result == Args(attrs=Foo("blue")) def test_env_default_cli_overrides(): with patch("os.environ", new={"FOO": "blue"}): result = parse(Args, "--color=red") assert result == Args(attrs=Foo("red")) @dataclass class Bar: x: Annotated[int, cappa.Arg(long=True, default=Env("BAR_X"))] y: Annotated[str, cappa.Arg(long=True, default="fallback")] @dataclass class PartialArgs: bar: cappa.Destructured[Bar] def test_partial_args_missing_field_uses_env(): with patch("os.environ", new={"BAR_X": "42"}): result = parse(PartialArgs, "--y=hello") assert result == PartialArgs(bar=Bar(x=42, y="hello")) def test_partial_args_all_from_env_and_default(): with patch("os.environ", new={"BAR_X": "7"}): result = parse(PartialArgs) assert result == PartialArgs(bar=Bar(x=7, y="fallback")) @dataclass class Baz: val: Annotated[int, cappa.Arg(long=True, default=ValueFrom(lambda: 99))] @dataclass class ValueFromArgs: baz: cappa.Destructured[Baz] def test_value_from_default(): result = parse(ValueFromArgs) assert result == ValueFromArgs(baz=Baz(val=99)) cappa-0.32.2/tests/arg/test_destructured_explicit.py0000644000000000000000000000117413615410400017547 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse @dataclass class Config: color: Annotated[str, cappa.Arg(long=True)] @dataclass class Args: config: Annotated[Config, cappa.Arg.destructured()] def test_destructured(): test = parse(Args, "--color=red") assert test == Args(config=Config(color="red")) @dataclass class Args2: config: Annotated[Config, cappa.Arg(destructure=True)] def test_destructured_alt(): test = parse(Args2, "--color=red") assert test == Args2(config=Config(color="red")) cappa-0.32.2/tests/arg/test_destructured_implicit_default.py0000644000000000000000000000124413615410400021242 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse @dataclass class Other: arg2: Annotated[int, cappa.Arg(long=True)] = 4 arg3: Annotated[str, cappa.Arg(long=True)] = "value3" @dataclass class CLI: other: cappa.Destructured[Other] def test_destructured_implicit_default(): result = parse(CLI) assert result == CLI(other=Other()) @dataclass class OptionalCLI: other: cappa.Destructured[Other | None] def test_destructured_implicit_default_optional_annotation(): result = parse(OptionalCLI) assert result == OptionalCLI(other=None) cappa-0.32.2/tests/arg/test_destructured_optional.py0000644000000000000000000000050513615410400017550 0ustar00from __future__ import annotations from dataclasses import dataclass import cappa from tests.utils import parse @dataclass class Foo: foo: int = 4 @dataclass class CLI: attrs: cappa.Destructured[Foo | None] = None def test_destructured_optional(): result = parse(CLI) assert result == CLI(attrs=None) cappa-0.32.2/tests/arg/test_destructured_subcommand.py0000644000000000000000000000121513615410400020052 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from cappa.subcommand import Subcommands from tests.utils import parse @dataclass class SubSub: foo: int @dataclass class InvalidSubcommandSub: sub: Subcommands[SubSub] @dataclass class InvalidSubcommand: attrs: Annotated[InvalidSubcommandSub, cappa.Arg.destructured()] def test_invalid_subcommand(): with pytest.raises(ValueError) as e: parse(InvalidSubcommand) assert ( str(e.value) == "Only `Arg` is supported in the context of a destructured argument" ) cappa-0.32.2/tests/arg/test_destructured_union.py0000644000000000000000000000070013615410400017050 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest import cappa from tests.utils import parse @dataclass class Foo: foo: int @dataclass class CLI: attrs: cappa.Destructured[Foo | int] def test_destructured_union(): with pytest.raises(ValueError) as e: parse(CLI) assert ( str(e.value) == "Destructured arguments currently only support singular concrete types." ) cappa-0.32.2/tests/arg/test_discriminated_union.py0000644000000000000000000000235113615410400017156 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Literal, Union import pytest from tests.utils import Backend, backends, parse @backends def test_valid_tagged_unions(backend: Backend): @dataclass class ArgTest: name: Union[ tuple[Literal["one"], str], tuple[Literal["two"], int], tuple[ Literal["three"], float, ], ] test = parse(ArgTest, "one", "string", backend=backend) assert test.name == ("one", "string") test = parse(ArgTest, "two", "4", backend=backend) assert test.name == ("two", 4) test = parse(ArgTest, "three", "1.4", backend=backend) assert test.name == ("three", 1.4) @backends def test_disallowed_different_arity_variants(backend: Backend): @dataclass class ArgTest: name: Union[tuple[str, str], tuple[str, str, str]] with pytest.raises(ValueError) as e: parse(ArgTest, "one", "string", backend=backend) assert ( str(e.value).lower().replace("typing.", "") == "on field 'name', mismatch of arity between union variants. `tuple[str, str]` produces `num_args=2`, `tuple[str, str, str]` produces `num_args=3`." ) cappa-0.32.2/tests/arg/test_enum.py0000644000000000000000000000126613615410400014077 0ustar00from __future__ import annotations import enum from dataclasses import dataclass import pytest import cappa from tests.utils import Backend, backends, parse class Options(enum.Enum): one = "one" two = "two" three = "three" @dataclass class ArgTest: options: Options @backends def test_valid(backend: Backend): test = parse(ArgTest, "two", backend=backend) assert test.options is Options.two @backends def test_invalid(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(ArgTest, "thename", backend=backend) message = str(e.value.message).lower() assert "invalid choice: 'thename' (choose from 'one', 'two', 'three')" in message cappa-0.32.2/tests/arg/test_env.py0000644000000000000000000000353113615410400013720 0ustar00from __future__ import annotations from dataclasses import dataclass from unittest.mock import patch from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_missing_fallback(backend: Backend): @dataclass class ArgTest: default: Annotated[str, cappa.Arg(default=cappa.Env("ASDF", default="wat"))] test = parse(ArgTest, backend=backend) assert test.default == "wat" @backends def test_has_env_var(backend: Backend): @dataclass class ArgTest: default: Annotated[str, cappa.Arg(default=cappa.Env("ASDF", default="wat"))] with patch("os.environ", new={"ASDF": "asdf!"}): test = parse(ArgTest, backend=backend) assert test.default == "asdf!" @backends def test_env_defers_to_real_value(backend: Backend): @dataclass class ArgTest: default: Annotated[str, cappa.Arg(default=cappa.Env("ASDF", default="wat"))] test = parse(ArgTest, "test", backend=backend) assert test.default == "test" @backends def test_mapping_is_applied(backend: Backend): @dataclass class ArgTest: default: Annotated[bool, cappa.Arg(default=cappa.Env("ASDF", default="wat"))] test = parse(ArgTest, backend=backend) assert test.default is True @backends def test_class_default_gets_applied_to_env_default(backend: Backend): @dataclass class ArgTest: default: Annotated[str, cappa.Arg(default=cappa.Env("ASDF"))] = "wat" test = parse(ArgTest, backend=backend) assert test.default == "wat" @backends def test_class_default_still_abides_env_var(backend: Backend): @dataclass class ArgTest: default: Annotated[str, cappa.Arg(default=cappa.Env("ASDF"))] = "wat" with patch("os.environ", new={"ASDF": "foo"}): test = parse(ArgTest, backend=backend) assert test.default == "foo" cappa-0.32.2/tests/arg/test_exclusive_group.py0000644000000000000000000000632213615410400016354 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from cappa.arg import Group from tests.utils import Backend, backends, parse @backends def test_implicit_syntax(backend: Backend | None): @dataclass class ArgTest: verbose: Annotated[ int, cappa.Arg(short="-v", action=cappa.ArgAction.count), cappa.Arg(long="--verbosity"), ] = 0 result = parse(ArgTest, backend=backend) assert result.verbose == 0 result = parse(ArgTest, "-vv", backend=backend) assert result.verbose == 2 result = parse(ArgTest, "--verbosity", "3", backend=backend) assert result.verbose == 3 with pytest.raises(cappa.Exit) as e: parse(ArgTest, "-vv", "--verbosity", "3", backend=backend) if backend is None: message = "Argument '--verbosity' is not allowed with argument '-v'" else: message = "argument --verbosity: not allowed with argument -v" assert message.lower() == str(e.value.message).lower() @backends def test_implicit_syntax_explicit_name(backend: Backend, capsys: Any): @dataclass class ArgTest: verbose: Annotated[ int, cappa.Arg(short="-v", action=cappa.ArgAction.count, group="Foo"), cappa.Arg(long="--verbosity", group="Foo"), ] = 0 with pytest.raises(cappa.Exit): parse(ArgTest, "-h") out = capsys.readouterr().out assert "[-v, --verbosity VERBOSE]" in out assert "(Default: 0)" in out @backends def test_explicit_groups(backend: Backend | None): @dataclass class ArgTest: verbose: Annotated[ int, cappa.Arg( short="-v", action=cappa.ArgAction.count, group=Group(exclusive=True), ), ] = 0 verbosity: Annotated[ int, cappa.Arg(long="--verbosity", group=Group(exclusive=True)), ] = 0 result = parse(ArgTest, backend=backend) assert result.verbose == 0 assert result.verbosity == 0 result = parse(ArgTest, "-vv", backend=backend) assert result.verbose == 2 assert result.verbosity == 0 result = parse(ArgTest, "--verbosity", "3", backend=backend) assert result.verbose == 0 assert result.verbosity == 3 with pytest.raises(cappa.Exit) as e: parse(ArgTest, "-vv", "--verbosity", "3", backend=backend) if backend is None: message = "Argument '--verbosity' is not allowed with argument '-v'" else: message = "argument --verbosity: not allowed with argument -v" assert message.lower() == str(e.value.message).lower() @backends def test_differing_group_identity(backend: Backend): @dataclass class ArgTest: one: Annotated[int, cappa.Arg(group=Group(name="Verbose", exclusive=False))] = 0 tw: Annotated[int, cappa.Arg(group=Group(name="Verbose", exclusive=True))] = 0 with pytest.raises(ValueError) as e: parse(ArgTest, backend=backend) assert str(e.value) == ( "Group details do not match among arguments: Group(id='Verbose', exclusive=True) != Group(id='Verbose', exclusive=False)." ) cappa-0.32.2/tests/arg/test_explicit_subcommand_types.py0000644000000000000000000000077113615410400020410 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Sc1: ... @dataclass class Sc2: ... @dataclass class Command: subcommand: Annotated[Union[Sc1, Sc2], cappa.Subcommand(types=[Sc1, Sc2])] @backends def test_required_missing(backend: Backend): parse(Command, "sc1", backend=backend) parse(Command, "sc2", backend=backend) cappa-0.32.2/tests/arg/test_file_io.py0000644000000000000000000001056113615410400014537 0ustar00from __future__ import annotations import io from contextlib import contextmanager from dataclasses import dataclass from typing import Any, BinaryIO, TextIO from unittest.mock import mock_open, patch import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @contextmanager def file_content(content: str, mode: str = "r"): if "b" in mode: content = content.encode("utf-8") # type: ignore mock = mock_open(read_data=content) with patch("builtins.open", new=mock): yield mock for call in mock.call_args[::2]: assert call[1] == mode @contextmanager def stdin(content: str): with patch("sys.stdin", new=io.TextIOWrapper(io.BytesIO(content.encode("utf-8")))): yield @backends def test_text_io_default(backend: Backend): @dataclass class Foo: bar: TextIO with file_content("wat"): test = parse(Foo, "foo.py", backend=backend) assert test.bar.read() == "wat" @backends def test_text_io(backend: Backend): @dataclass class Foo: bar: Annotated[TextIO, cappa.FileMode(mode="r")] with file_content("wat"): test = parse(Foo, "foo.py", backend=backend) assert test.bar.read() == "wat" @backends def test_text_io_write(backend: Backend): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="w")] with file_content("wat", mode="w"): test = parse(Foo, "foo.py", backend=backend) test.bar.write(b"wat") @backends def test_binary_io_default(backend: Backend): @dataclass class Foo: bar: BinaryIO with file_content("wat", mode="rb"): test = parse(Foo, "foo.py", backend=backend) assert test.bar.read() == b"wat" @backends def test_binary_io(backend: Backend): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="rb")] with file_content("wat", mode="rb"): test = parse(Foo, "foo.py", backend=backend) assert test.bar.read() == b"wat" @backends def test_binary_io_write(backend: Backend): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="wb")] with file_content("wat", mode="wb"): test = parse(Foo, "foo.py", backend=backend) test.bar.write(b"wat") @backends def test_stdin(backend: Backend): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="r")] with stdin("wat"): test = parse(Foo, "-", backend=backend) assert test.bar.read() == "wat" @backends def test_stdin_binary(backend: Backend): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="rb")] with stdin("wat"): test = parse(Foo, "-", backend=backend) assert test.bar.read() == b"wat" @backends def test_stdout(backend: Backend, capsys: Any): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="w")] test = parse(Foo, "-", backend=backend) test.bar.write("wat") # type: ignore out = capsys.readouterr().out assert out == "wat" @backends def test_stdout_binary(backend: Backend, capsys: Any): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="wb")] test = parse(Foo, "-", backend=backend) test.bar.write(b"wat") out = capsys.readouterr().out assert out == "wat" @backends def test_invalid_mode_dash(backend: Backend): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="tb")] with pytest.raises(cappa.Exit) as e: parse(Foo, "-", backend=backend) assert e.value.message == "Invalid mode 'tb' with supplied '-' file name." @backends def test_invalid_mode(backend: Backend): @dataclass class Foo: bar: Annotated[BinaryIO, cappa.FileMode(mode="tb")] with pytest.raises(cappa.Exit) as e: parse(Foo, "foo.py", backend=backend) assert str(e.value.message).startswith("Invalid value for 'bar'") assert str(e.value.message).endswith("can't have text and binary mode at once") @backends def test_open_oserror(backend: Backend): @dataclass class Foo: bar: BinaryIO with pytest.raises(cappa.Exit) as e: parse(Foo, "thisshouldneverexist.py", backend=backend) assert ( e.value.message == "Cannot open thisshouldneverexist.py: [Errno 2] No such file or directory: 'thisshouldneverexist.py'" ) cappa-0.32.2/tests/arg/test_help.py0000644000000000000000000000362413615410400014063 0ustar00from __future__ import annotations import re from dataclasses import dataclass from typing import Any, Literal, Union import pytest from typing_extensions import Annotated, Doc import cappa from cappa.output import Exit from tests.utils import Backend, backends, parse @pytest.mark.help @backends def test_explicit_parse_function(backend: Backend, capsys: Any): @dataclass class ArgTest: numbers: Annotated[int, cappa.Arg(parse=int, help="example")] with pytest.raises(Exit): parse(ArgTest, "--help", backend=backend) stdout = capsys.readouterr().out assert re.match(r".*NUMBERS\s+example.*", stdout, re.DOTALL) @pytest.mark.help @backends def test_choices_in_help(backend: Backend, capsys: Any): @dataclass class ArgTest: numbers: Annotated[ Union[Literal[1], Literal[2]], cappa.Arg(parse=int, help="example") ] result = parse(ArgTest, "1", backend=backend) assert result == ArgTest(1) with pytest.raises(Exit): parse(ArgTest, "--help", backend=backend) stdout = capsys.readouterr().out assert "Valid options: 1, 2" in stdout @pytest.mark.help @backends def test_pep_727_doc_annotated(backend: Backend, capsys: Any): @dataclass class ArgTest: """Test. Arguments: doc_beats_docstring: loses. """ only_doc: Annotated[int, cappa.Arg(parse=int), Doc("Use Doc if exists")] doc_beats_docstring: Annotated[ int, cappa.Arg(parse=int), Doc("Doc beats docstring") ] prefer_arg: Annotated[ int, cappa.Arg(parse=int, help="Arg wins"), Doc("Doc loses") ] with pytest.raises(Exit): parse(ArgTest, "--help", backend=backend) stdout = capsys.readouterr().out assert "Use Doc if exists" in stdout assert "Doc beats docstring" in stdout assert "Arg wins" in stdout assert "Doc loses" not in stdout cappa-0.32.2/tests/arg/test_invalid_annotation_combination.py0000644000000000000000000001240113615410400021366 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_sequence_unioned_with_scalar(backend: Backend): """Inferred num_args: union arity mismatch caught in NumArgs.infer.""" @dataclass class Args: foo: Union[list[str], str] with pytest.raises(ValueError) as e: parse(Args, "--help", backend=backend) exc = str(e.value).replace("typing.List", "list") assert exc == ( "On field 'foo', mismatch of arity between union variants. " "`list[str]` produces `num_args=-1`, `` produces `num_args=1`." ) @backends def test_sequence_unioned_with_scalar_explicit_num_args(backend: Backend): """Explicit num_args bypasses NumArgs.infer union check; verify_type_compatibility catches it.""" @dataclass class Args: foo: Annotated[Union[list[str], str], cappa.Arg(num_args=cappa.NumArgs(n=-1))] with pytest.raises(ValueError) as e: parse(Args, "--help", backend=backend) assert str(e.value) == ( "On field 'foo', apparent mismatch of annotated type with `Arg` options. " 'Unioning "sequence" types with non-sequence types is not currently supported, ' "unless using `Arg(parse=...)` or `Arg(action=)`. " "See [documentation](https://cappa.readthedocs.io/en/latest/annotation.html) for more details." ) @backends def test_sequence_with_scalar_action(backend: Backend): @dataclass class Args: foo: Annotated[list[str], cappa.Arg(action=cappa.ArgAction.set, num_args=1)] with pytest.raises(ValueError) as e: parse(Args, "--help", backend=backend) result = str(e.value).replace("List", "list") assert result == ( "On field 'foo', apparent mismatch of annotated type with `Arg` options. " "'list[str]' type produces a sequence, whereas `num_args=1`/`action=ArgAction.set` do not. " "See [documentation](https://cappa.readthedocs.io/en/latest/annotation.html) for more details." ) @backends def test_sequence_with_scalar_num_args(backend: Backend): @dataclass class Args: foo: Annotated[list[str], cappa.Arg(num_args=1, short=True)] args = parse(Args, "-f", "a", "-f", "b", backend=backend) assert args == Args(["a", "b"]) @dataclass class ArgsBad: foo: Annotated[ list[str], cappa.Arg(num_args=1, short=True, action=cappa.ArgAction.set) ] with pytest.raises(ValueError) as e: parse(ArgsBad, "--help", backend=backend) result = str(e.value).replace("List", "list") assert result == ( "On field 'foo', apparent mismatch of annotated type with `Arg` options. " "'list[str]' type produces a sequence, whereas `num_args=1`/`action=ArgAction.set` do not. " "See [documentation](https://cappa.readthedocs.io/en/latest/annotation.html) for more details." ) @backends def test_scalar_with_sequence_action(backend: Backend): @dataclass class Args: foo: Annotated[str, cappa.Arg(action=cappa.ArgAction.append)] with pytest.raises(ValueError) as e: parse(Args, "--help", backend=backend) assert str(e.value) == ( "On field 'foo', apparent mismatch of annotated type with `Arg` options. " "'str' type produces a scalar, whereas `num_args=1`/`action=ArgAction.append` do not. " "See [documentation](https://cappa.readthedocs.io/en/latest/annotation.html) for more details." ) @backends def test_scalar_with_sequence_num_args(backend: Backend): @dataclass class Args: foo: Annotated[str, cappa.Arg(num_args=5)] with pytest.raises(ValueError) as e: parse(Args, "--help", backend=backend) assert str(e.value) == ( "On field 'foo', apparent mismatch of annotated type with `Arg` options. " "'str' type produces a scalar, whereas `num_args=5`/`action=ArgAction.set` do not. " "See [documentation](https://cappa.readthedocs.io/en/latest/annotation.html) for more details." ) @backends def test_num_args_optional_value_requires_optional_type(backend: Backend): """NumArgs(required=False, default=None) must pair with an Optional type.""" @dataclass class Args: foo: Annotated[ int, cappa.Arg(long=True, num_args=cappa.NumArgs(required=False)) ] = 5 with pytest.raises(ValueError) as e: parse(Args, "--help", backend=backend) assert str(e.value) == ( "On field 'foo', `NumArgs(required=False, default=None)` requires the " "annotated type to include `None` (e.g. `int | None`), " "since the absent-value default of `None` is not assignable to the field type." ) @backends def test_num_args_optional_value_non_none_default_does_not_require_optional_type( backend: Backend, ): """NumArgs(required=False, default=0) is valid with a non-optional type.""" @dataclass class Args: foo: Annotated[ int, cappa.Arg(long=True, num_args=cappa.NumArgs(required=False, default=0)) ] = 5 # Should not raise — default is a valid int, type need not include None. args = parse(Args, "--foo", backend=backend) assert args == Args(foo=0) cappa-0.32.2/tests/arg/test_list.py0000644000000000000000000000254713615410400014111 0ustar00from __future__ import annotations from dataclasses import dataclass, field from typing import Union from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_list_option(backend: Backend): @dataclass class ArgTest: variable_number: Annotated[list[str], cappa.Arg(short=True, long=True)] = field( default_factory=lambda: [] ) test = parse( ArgTest, "-v", "one", "--variable-number", "two", backend=backend, ) assert test.variable_number == ["one", "two"] @backends def test_list_positional(backend: Backend): @dataclass class ArgTest: variable_number: list[str] = field(default_factory=lambda: []) test = parse( ArgTest, "one", "two", "three", backend=backend, ) assert test.variable_number == ["one", "two", "three"] @backends def test_optional_list(backend: Backend): @dataclass class ArgTest: value: Annotated[Union[list[str], None], cappa.Arg(short=True, long=True)] = ( None ) test = parse( ArgTest, "--value=one", "--value=two", backend=backend, ) assert test.value == ["one", "two"] test = parse(ArgTest, backend=backend) assert test.value is None cappa-0.32.2/tests/arg/test_literal.py0000644000000000000000000000522213615410400014563 0ustar00from __future__ import annotations import textwrap from dataclasses import dataclass from typing import Literal, Union import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_valid(backend: Backend): @dataclass class ArgTest: name: Literal["one", "two"] test = parse(ArgTest, "two", backend=backend) assert test.name == "two" @backends def test_valid_int(backend: Backend): @dataclass class ArgTest: name: Literal["one", 4] test = parse(ArgTest, "4", backend=backend) assert test.name == 4 @backends def test_invalid(backend: Backend): @dataclass class ArgTest: name: Literal["one", "two", "three", 4] with pytest.raises(cappa.Exit) as e: parse(ArgTest, "thename", backend=backend) message = str(e.value.message).lower() assert "invalid choice: 'thename' (choose from 'one', 'two', 'three', 4)" in message @backends def test_unioned_literals(backend: Backend): @dataclass class ArgTest: name: Union[Literal["one"], Literal["two"], Literal["three"], Literal[4]] with pytest.raises(cappa.Exit) as e: parse(ArgTest, "thename", backend=backend) message = str(e.value.message).lower() assert ( "invalid value for 'name': possible variants\n - literal['one']: invalid choice: 'thename' (choose from 'one')\n - literal['two']: invalid choice: 'thename' (choose from 'two')\n - literal['three']: invalid choice: 'thename' (choose from 'three')\n - literal[4]: invalid choice: 'thename' (choose from 4)" == message ) @backends def test_invalid_collection_of_literals(backend: Backend): @dataclass class Args: foo: Annotated[ set[Literal["one", "two"]] | list[int] | None, cappa.Arg(short=True) ] = None with pytest.raises(cappa.Exit) as e: parse(Args, "-f", "three", backend=backend) assert e.value.code == 2 err = textwrap.dedent( """\ Invalid value for '-f': Possible variants - set[Literal['one', 'two']]: Invalid choice: 'three' (choose from 'one', 'two') - list[int]: invalid literal for int() with base 10: 'three' - """ ) assert str(e.value.message).lower() == err.lower() @backends def test_literal_parse(backend: Backend): @dataclass class LiteralParse: log_level: Annotated[ Literal["TRACE", "DEBUG", "INFO"], cappa.Arg(short="-L", long=True, parse=str.upper), ] = "INFO" result = parse(LiteralParse, "--log-level=debug", backend=backend) assert result == LiteralParse(log_level="DEBUG") cappa-0.32.2/tests/arg/test_literal_intermixed_union.py0000644000000000000000000000172213615410400020224 0ustar00from __future__ import annotations import textwrap from dataclasses import dataclass from typing import Literal, Union import pytest import cappa from tests.utils import Backend, backends, parse @dataclass class ArgTest: name: Union[int, Literal["one"]] @backends def test_valid_int(backend: Backend): test = parse(ArgTest, "5", backend=backend) assert test.name == 5 @backends def test_valid_literal(backend: Backend): test = parse(ArgTest, "one", backend=backend) assert test.name == "one" @backends def test_invalid_string(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(ArgTest, "thename", backend=backend) assert e.value.code == 2 err = textwrap.dedent( """\ Invalid value for 'name': Possible variants - Literal['one']: Invalid choice: 'thename' (choose from 'one') - int: invalid literal for int() with base 10: 'thename'""" ) assert err in str(e.value.message) cappa-0.32.2/tests/arg/test_literal_multiple_variant.py0000644000000000000000000000124413615410400020222 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Literal import pytest import cappa from tests.utils import Backend, backends, parse @dataclass class ArgTest: name: Literal["one", "two", "three", 4] @backends def test_literal(backend: Backend): test = parse(ArgTest, "two", backend=backend) assert test.name == "two" test = parse(ArgTest, "4", backend=backend) assert test.name == 4 with pytest.raises(cappa.Exit) as e: parse(ArgTest, "thename", backend=backend) message = str(e.value.message).lower() assert "invalid choice: 'thename' (choose from 'one', 'two', 'three', 4)" in message cappa-0.32.2/tests/arg/test_literal_sequence.py0000644000000000000000000000251013615410400016450 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated, Literal import cappa from tests.utils import Backend, backends, parse @dataclass class ArgTest: list: Annotated[ list[Literal["one", "two", "three", 4]], cappa.Arg(short=True, default=[]), ] tuple: Annotated[ tuple[Literal["one", "two", "three", 4], ...], cappa.Arg(short=True, default=()), ] set: Annotated[ set[Literal["one", "two", "three", 4]], cappa.Arg(short=True, default=set()), # pyright: ignore ] @backends def test_list(backend: Backend): test = parse(ArgTest, "-l", "one", "-l", "two", backend=backend) assert test.list == ["one", "two"] @backends def test_tuple(backend: Backend): test = parse(ArgTest, "-t", "one", "-t", "two", backend=backend) assert test.tuple == ("one", "two") @backends def test_set(backend: Backend): test = parse(ArgTest, "-s", "one", "-s", "two", backend=backend) assert test.set == {"one", "two"} @backends def test_invalid(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(ArgTest, "-l", "one", "-l", "wat", backend=backend) message = str(e.value.message).lower() assert "invalid choice: 'wat' (choose from 'one', 'two', 'three', 4)" in message cappa-0.32.2/tests/arg/test_mapping_failure.py0000644000000000000000000000165313615410400016275 0ustar00from __future__ import annotations from dataclasses import dataclass from pathlib import Path from typing import Union import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_default(backend: Backend): @dataclass class ArgTest: default: int with pytest.raises(cappa.Exit) as e: parse(ArgTest, "foo", backend=backend) assert e.value.code == 2 assert ( e.value.message == "Invalid value for 'default': invalid literal for int() with base 10: 'foo'" ) @backends def test_other_exception_types(backend: Backend): @dataclass class ArgTest: path: Annotated[Union[Path, None], cappa.Arg(long=True)] = None result = parse(ArgTest, backend=backend) assert result.path is None result = parse(ArgTest, "--path", "asdf", backend=backend) assert result.path == Path("asdf") cappa-0.32.2/tests/arg/test_mixed_sync_async_parse.py0000644000000000000000000000145213615410400017661 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends def sync_parse_lowercase(value: str) -> str: return value.lower() async def async_parse_uppercase(value: str) -> str: await asyncio.sleep(0.001) return value.upper() @dataclass class Command: sync_value: Annotated[str, cappa.Arg(parse=sync_parse_lowercase)] async_value: Annotated[str, cappa.Arg(parse=async_parse_uppercase)] def __call__(self): return self @backends def test(backend: Backend): result = asyncio.run( cappa.invoke_async(Command, argv=["HELLO", "world"], backend=backend) ) assert result.sync_value == "hello" assert result.async_value == "WORLD" cappa-0.32.2/tests/arg/test_mutual_exclusion_two_or_more.py0000644000000000000000000000317213615410400021144 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from cappa.output import Exit from tests.utils import Backend, backends, parse @dataclass class Args: verbose: Annotated[ int, cappa.Arg(short="-v", action=cappa.ArgAction.count), cappa.Arg(long="--verbosity"), ] = 0 meow: Annotated[bool, cappa.Arg(long="--meow/--no-meow")] = False bar: Annotated[bool, cappa.Arg(long="--bar/--no-bar")] = False @backends def test_help(backend: Backend | None): result = parse(Args, "-vvv", "--meow", "--no-bar", backend=backend) assert result == Args(verbose=3, meow=True, bar=False) result = parse(Args, "--verbosity=4", "--no-meow", "--bar", backend=backend) assert result == Args(verbose=4, meow=False, bar=True) with pytest.raises(Exit) as e: parse(Args, "-vvv", "--verbosity=4", backend=backend) if backend is None: expected = "Argument '--verbosity' is not allowed with argument '-v'" else: expected = "argument --verbosity: not allowed with argument -v" assert expected.lower() in str(e.value.message).lower() with pytest.raises(Exit) as e: parse(Args, "--meow", "--no-meow", backend=backend) if backend is None: expected = "Argument '--no-meow' is not allowed with argument '--meow'" else: expected = "argument --no-meow: not allowed with argument --meow" assert expected.lower() in str(e.value.message).lower() args = parse(Args, "--no-bar", "--meow", backend=backend) assert args == Args(verbose=0, meow=True, bar=False) cappa-0.32.2/tests/arg/test_name_disallowed.py0000644000000000000000000000077213615410400016263 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_arg_name_disallowed(backend: Backend): @dataclass class ArgTest: bad: Annotated[bool, cappa.Arg(field_name="oops")] = False with pytest.raises( ValueError, match="Arg 'name' cannot be set when using automatic inference." ): parse(ArgTest, backend=backend) cappa-0.32.2/tests/arg/test_num_arg_unbounded_0.py0000644000000000000000000000142513615410400017042 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Args: numbers: Annotated[ list[int], cappa.Arg( long=True, default=[1, 2, 3], num_args=-1, ), ] @backends def test_num_args_unbounded_length_num_args(backend: Backend): result = parse(Args, backend=backend) assert result == Args([1, 2, 3]) result = parse(Args, "--numbers", backend=backend) assert result == Args([]) result = parse(Args, "--numbers", "2", backend=backend) assert result == Args([2]) result = parse(Args, "--numbers", "2", "5", backend=backend) assert result == Args([2, 5]) cappa-0.32.2/tests/arg/test_num_args.py0000644000000000000000000000176613615410400014753 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse def test_unbounded_list_option(): @dataclass class Args: unbounded: Annotated[list[list[int]], cappa.Arg(short=True)] result = parse(Args, "-u", "0") assert result == Args([[0]]) result = parse(Args, "-u", "0", "1", "2", "3", "4", "5") assert result == Args([[0, 1, 2, 3, 4, 5]]) def test_unbounded_set_option(): @dataclass class Args: unbounded: Annotated[set[tuple[int, int]], cappa.Arg(short=True)] result = parse(Args, "-u", "0", "1") assert result == Args({(0, 1)}) def test_unbounded_tuple_option(): @dataclass class Args: unbounded: Annotated[tuple[list[int], ...], cappa.Arg(short=True)] result = parse(Args, "-u", "0") assert result == Args(([0],)) result = parse(Args, "-u", "0", "1", "2", "3", "4", "5") assert result == Args(([0, 1, 2, 3, 4, 5],)) cappa-0.32.2/tests/arg/test_optional_positional.py0000644000000000000000000000117513615410400017220 0ustar00from __future__ import annotations from dataclasses import dataclass from tests.utils import Backend, backends, parse @backends def test_positional_with_default_is_optional(backend: Backend): @dataclass class ArgTest: arg: int = 0 result = parse(ArgTest, backend=backend) assert result.arg == 0 @backends def test_multiple_positionals_fill_in_order(backend: Backend): @dataclass class ArgTest: arg: int = 0 arg2: int = 0 arg3: int = 0 result = parse(ArgTest, "3", "5", backend=backend) assert result.arg == 3 assert result.arg2 == 5 assert result.arg3 == 0 cappa-0.32.2/tests/arg/test_parse.py0000644000000000000000000000771313615410400014250 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any, Union import pytest from type_lens.type_view import TypeView from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse def split(value: str): a, b = value.split(",", 1) return int(a), int(b) def parse_int(val: str) -> int: try: return int(val) except Exception: raise cappa.Exit("no go amego", code=2) @backends def test_explicit_parse_function(backend: Backend): @dataclass class ArgTest: numbers: Annotated[int, cappa.Arg(parse=int)] test = parse(ArgTest, "1", backend=backend) assert test.numbers == 1 @backends def test_not_typeable_parse_function(backend: Backend): @dataclass class ArgTest: numbers: Annotated[tuple[int, int], cappa.Arg(parse=split)] test = parse(ArgTest, "1,3", backend=backend) assert test.numbers == (1, 3) @backends def test_parse_failure(backend: Backend): """Optional annotations should take precedence over explicit parse.""" @dataclass class ArgTest: numbers: Annotated[int, cappa.Arg(parse=parse_int)] with pytest.raises(cappa.Exit) as e: parse(ArgTest, "one", backend=backend) assert e.value.code == 2 assert e.value.message == "no go amego" @backends def test_parse_optional(backend: Backend): """Optionals are handled by the given/inferred parse method.""" @dataclass class ArgTest: numbers: Annotated[Union[int, None], cappa.Arg(parse=float)] = None result = parse(ArgTest, backend=backend) assert result == ArgTest() with pytest.raises(cappa.Exit) as e: parse(ArgTest, "one", backend=backend) assert e.value.code == 2 assert ( e.value.message == "Invalid value for 'numbers': could not convert string to float: 'one'" ) def parse_test_parse_returns_none(value: str) -> bool | None: if int(value) % 2: return True return None @backends def test_parse_returns_none(backend: Backend): """Assert that a parser which returns None succeeds .""" @dataclass class ArgTest: numbers: Annotated[int | None, cappa.Arg(parse=parse_test_parse_returns_none)] result = parse(ArgTest, "1", backend=backend) assert result == ArgTest(True) result = parse(ArgTest, "2", backend=backend) assert result == ArgTest(None) def parser_test_typed_parse(value: str): return float(value) @backends def test_typed_parse(backend: Backend): """A parse function with a typed argument.""" @dataclass class ArgTest: num: Annotated[float, cappa.Arg(parse=parser_test_typed_parse)] result = parse(ArgTest, "4.1", backend=backend) assert result == ArgTest(num=4.1) def parser_test_type_aware_parse(value: str, type_view: TypeView[Any]) -> str: return str(type_view) @backends def test_type_aware_parse(backend: Backend): """A parse function that receives type information.""" @dataclass class ArgTest: num: Annotated[str, cappa.Arg(parse=parser_test_type_aware_parse)] result = parse(ArgTest, "4.1", backend=backend) assert result == ArgTest(num="TypeView(str)") def parse_exit(_: Any) -> Any: raise cappa.Exit("asdf") @backends def test_parse_exit(backend: Backend): @dataclass class ArgTest: num: Annotated[str, cappa.Arg(parse=parse_exit)] with pytest.raises(cappa.Exit): parse(ArgTest, "4.1", backend=backend) def min0(value: int) -> int: if not value > 0: raise ValueError(f"{value} > 0") return value @backends def test_default_parse(backend: Backend): @dataclass class ArgTest: num: Annotated[int, cappa.Arg(parse=[cappa.default_parse, min0])] result = parse(ArgTest, "4", backend=backend) assert result.num == 4 with pytest.raises(cappa.Exit) as e: parse(ArgTest, "--", "-4", backend=backend) assert e.value.message == "Invalid value for 'num': -4 > 0" cappa-0.32.2/tests/arg/test_parse_async_basic.py0000644000000000000000000000103113615410400016571 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends async def async_parse_multiply(value: str) -> int: await asyncio.sleep(0.001) return int(value) * 2 @dataclass class Command: value: Annotated[int, cappa.Arg(parse=async_parse_multiply)] @backends def test(backend: Backend): result = asyncio.run(cappa.parse_async(Command, argv=["5"], backend=backend)) assert result.value == 10 cappa-0.32.2/tests/arg/test_parse_dataclass_like.py0000644000000000000000000000304713615410400017267 0ustar00from __future__ import annotations import json from dataclasses import dataclass from typing import Union from typing_extensions import Annotated import cappa from cappa.parse import unpack_arguments from tests.utils import Backend, backends, parse @dataclass class Single: value: str @dataclass class Double: one: str two: str @dataclass class Mapping: foo: str bar: str @dataclass class Args: single: Annotated[ Union[Single, None], cappa.Arg(long=True, parse=unpack_arguments) ] = None double: Annotated[ Union[Double, None], cappa.Arg(long=True, num_args=2, parse=unpack_arguments), ] = None mapping: Annotated[ Union[Mapping, None], cappa.Arg(long=True, parse=[json.loads, unpack_arguments]), ] = None @backends def test_dataclass_annotation(backend: Backend): """Dataclass annotation will supply or splat arguments, depending on input shape.""" result = parse(Args, backend=backend) assert result == Args() result = parse(Args, "--single=3", backend=backend) assert result.single == Single("3") assert result.double is None assert result.mapping is None result = parse(Args, "--double", "5", "10", backend=backend) assert result.single is None assert result.double == Double(one="5", two="10") assert result.mapping is None result = parse(Args, '--mapping={"foo": "8", "bar": "3"}', backend=backend) assert result.single is None assert result.double is None assert result.mapping == Mapping(foo="8", bar="3") cappa-0.32.2/tests/arg/test_parse_inference.py0000644000000000000000000000063113615410400016256 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Args: numbers: Annotated[str, cappa.Arg(parse=int, parse_inference=False)] @backends def test_disable_default_parser(backend: Backend): test = parse(Args, "1", backend=backend) assert test.numbers == 1 cappa-0.32.2/tests/arg/test_parse_method.py0000644000000000000000000000174413615410400015606 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Config: path: str @classmethod def from_classmethod(cls, path: str): return cls(path) def from_method(self, path: str) -> str: return "/".join([self.path, path]) config = Config("foo") @backends def test_from_classmethod(backend: Backend): @cappa.command(name="command") @dataclass class Command: config: Annotated[Config, cappa.Arg(parse=Config.from_classmethod)] test = parse(Command, "foo", backend=backend) assert test == Command(config=Config("foo")) @backends def test_method(backend: Backend): @cappa.command(name="command") @dataclass class Command: config: Annotated[str, cappa.Arg(parse=config.from_method)] test = parse(Command, "bar", backend=backend) assert test == Command(config="foo/bar") cappa-0.32.2/tests/arg/test_pipe_union.py0000644000000000000000000000074413615410400015300 0ustar00from __future__ import annotations import sys from dataclasses import dataclass from typing import Literal import pytest from tests.utils import Backend, backends, parse @pytest.mark.skipif(sys.version_info < (3, 10), reason="requires 3.10") @backends def test_valid(backend: Backend): @dataclass class ArgTest: name: Literal["one"] | Literal["two"] | Literal["three"] | Literal[4] test = parse(ArgTest, "two", backend=backend) assert test.name == "two" cappa-0.32.2/tests/arg/test_propagate.py0000644000000000000000000001023313615410400015107 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from cappa.subcommand import Subcommands from tests.utils import CapsysOutput, parse @dataclass class Command: foo: Annotated[int, cappa.Arg(long=True, propagate=True, help="Everywhere")] = 1 sub: cappa.Subcommands[One | Two | None] = None @dataclass class One: ... @dataclass class Two: ... def test_propagated_arg(): result = parse(Command) assert result == Command(1, None) result = parse(Command, "--foo=4") assert result == Command(4, None) result = parse(Command, "one", "--foo=5") assert result == Command(5, One()) result = parse(Command, "two", "--foo=6") assert result == Command(6, Two()) def test_propagate_incompatible_with_argparse(): with pytest.raises(RuntimeError): parse(Command, backend=cappa.argparse.backend) @dataclass class Required: foo: Annotated[int, cappa.Arg(long=True, propagate=True)] sub: cappa.Subcommands[One | Two | None] = None def test_required_propagated_arg(): with pytest.raises(cappa.Exit): parse(Required) result = parse(Required, "--foo=4") assert result == Required(4, None) result = parse(Required, "one", "--foo=5") assert result == Required(5, One()) result = parse(Required, "two", "--foo=6") assert result == Required(6, Two()) @dataclass class ChildOverride: foo: Annotated[int, cappa.Arg(long=True, propagate=True)] = 1 sub: cappa.Subcommands[Child | None] = None @dataclass class Child: foo: Annotated[int, cappa.Arg(long=True)] = 2 def test_child_override(): result = parse(ChildOverride) assert result == ChildOverride(1, None) result = parse(ChildOverride, "--foo=4") assert result == ChildOverride(4, None) result = parse(ChildOverride, "--foo=4", "child") assert result == ChildOverride(4, Child(2)) result = parse(ChildOverride, "child") assert result == ChildOverride(1, Child(2)) result = parse(ChildOverride, "child", "--foo=5") assert result == ChildOverride(1, Child(5)) result = parse(ChildOverride, "--foo=3", "child", "--foo=6") assert result == ChildOverride(3, Child(6)) def test_propagate_requires_option(): @dataclass class ChildOverride: foo: Annotated[int, cappa.Arg(propagate=True)] with pytest.raises(ValueError) as e: parse(ChildOverride) assert ( str(e.value) == "`Arg.propagate` requires a non-positional named option (`short` or `long`)." ) def test_help_contains_propagated_arg(capsys: Any): @dataclass class Command: foo: Annotated[int, cappa.Arg(long=True, propagate=True, help="Everywhere")] = 1 bar: Annotated[int, cappa.Arg(long=True, help="Nowhere")] = 1 sub: cappa.Subcommands[One | Two | None] = None with pytest.raises(cappa.Exit): parse(Command, "--help") output = CapsysOutput.from_capsys(capsys) assert "foo" in output.stdout assert "Everywhere" in output.stdout assert "Nowhere" in output.stdout with pytest.raises(cappa.Exit): parse(Command, "one", "--help") output = CapsysOutput.from_capsys(capsys) assert "foo" in output.stdout assert "Everywhere" in output.stdout assert "Nowhere" not in output.stdout with pytest.raises(cappa.Exit): parse(Command, "two", "--help") output = CapsysOutput.from_capsys(capsys) assert "foo" in output.stdout assert "Everywhere" in output.stdout assert "Nowhere" not in output.stdout def test_count(): """Test that actions requiring accumulated prior state (e.g. count) **get** values from the correct context.""" @dataclass class Command: foo: Annotated[int, cappa.Arg(short=True, propagate=True, count=True)] = 1 subcommand: Subcommands[One | None] = None result = parse(Command, "-f") assert result == Command(1, None) result = parse(Command, "-fff") assert result == Command(3, None) result = parse(Command, "one", "-f") assert result == Command(1, One()) result = parse(Command, "one", "-fff") assert result == Command(3, One()) cappa-0.32.2/tests/arg/test_propagate_mutual_exclusive_arg.py0000644000000000000000000000170013615410400021415 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse @dataclass class Args: foo: Annotated[ bool, cappa.Arg( long=["--foo", "--no-foo"], propagate=True, default=False, help="Enable or disable foo feature", ), ] subcommand: cappa.Subcommands[Sub] @dataclass class Sub: name: str def test_propagated_arg(): result = parse(Args, "--foo", "sub", "bar") assert result == Args(foo=True, subcommand=Sub(name="bar")) result = parse(Args, "--no-foo", "sub", "bar") assert result == Args(foo=False, subcommand=Sub(name="bar")) result = parse(Args, "sub", "--foo", "bar") assert result == Args(foo=True, subcommand=Sub(name="bar")) result = parse(Args, "sub", "--no-foo", "bar") assert result == Args(foo=False, subcommand=Sub(name="bar")) cappa-0.32.2/tests/arg/test_required.py0000644000000000000000000000563213615410400014754 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Command: a: Annotated[str, cappa.Arg(short=True)] b: Annotated[str, cappa.Arg(short=True, required=True)] = "asdf" @backends def test_required_implicit(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(Command, "-b", "b", backend=backend) assert e.value.code == 2 assert "are required: -a" in str(e.value.message) result = parse(Command, "-a", "a", "-b", "b", backend=backend) assert result == Command(a="a", b="b") @backends def test_required_explicit(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(Command, "-a", "a", backend=backend) assert e.value.code == 2 assert "are required: -b" in str(e.value.message) result = parse(Command, "-a", "a", "-b", "b") assert result == Command(a="a", b="b") @backends def test_required_lists_all(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(Command, backend=backend) assert e.value.code == 2 assert "are required: -a, -b" in str(e.value.message) @backends def test_required_explicit_false(backend: Backend): @dataclass class Example: a: Annotated[str, cappa.Arg(short="-a", required=False)] c: Annotated[str, cappa.Arg(short="-c", required=False)] with pytest.raises(ValueError) as e: parse(Example, "-a", "a", backend=backend) assert ( "When specifying `required=False`, a default value must be supplied able to be " "supplied through type inference, `Arg(default=...)`, or through class-level default" ) == str(e.value) @backends def test_required_explicit_false_union_none_no_explicit_default(backend: Backend): @dataclass class Example: c: Annotated[Union[str, None], cappa.Arg(short="-c", required=False)] result = parse(Example, backend=backend) assert result == Example(c=None) result = parse(Example, "-c", "c", backend=backend) assert result == Example(c="c") @backends def test_required_unbounded_list(backend: Backend): @dataclass class Example: c: Annotated[list[str], cappa.Arg(required=True)] with pytest.raises(cappa.Exit) as e: parse(Example, backend=backend) assert "require" in str(e.value.message) result = parse(Example, "c", backend=backend) assert result == Example(["c"]) @backends def test_required_option(backend: Backend): @dataclass class Example: c: Annotated[list[str], cappa.Arg(short=True, required=True)] with pytest.raises(cappa.Exit) as e: parse(Example, backend=backend) assert "the following arguments are required: -c" == str(e.value.message).lower() result = parse(Example, "-c", "c", backend=backend) assert result == Example(["c"]) cappa-0.32.2/tests/arg/test_set.py0000644000000000000000000000177713615410400013735 0ustar00from __future__ import annotations from dataclasses import dataclass, field from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_list_option(backend: Backend): @dataclass class ArgTest: variable_number: Annotated[set[str], cappa.Arg(short=True, long=True)] = field( default_factory=lambda: set() ) test = parse( ArgTest, "-v", "one", "--variable-number", "one", "--variable-number", "two", "-v", "two", backend=backend, ) assert test.variable_number == {"one", "two"} @backends def test_list_positional(backend: Backend): @dataclass class ArgTest: variable_number: set[int] = field(default_factory=lambda: set()) test = parse( ArgTest, "1", "2", "3", "2", "3", "1", backend=backend, ) assert test.variable_number == {1, 2, 3} cappa-0.32.2/tests/arg/test_short_long.py0000644000000000000000000000242113615410400015303 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_short_missing_dash(backend: Backend): @dataclass class ArgTest: number: Annotated[int, cappa.Arg(short="n")] result = parse(ArgTest, "-n", "4", backend=backend) assert result.number == 4 @backends def test_multiple_shorts(backend: Backend): @dataclass class ArgTest: number: Annotated[int, cappa.Arg(short=["n", "o"])] result = parse(ArgTest, "-n", "4", backend=backend) assert result.number == 4 result = parse(ArgTest, "-o", "4", backend=backend) assert result.number == 4 @backends def test_long_missing_dash(backend: Backend): @dataclass class ArgTest: number: Annotated[int, cappa.Arg(long="nu")] result = parse(ArgTest, "--nu", "4", backend=backend) assert result.number == 4 @backends def test_multiple_longs(backend: Backend): @dataclass class ArgTest: number: Annotated[int, cappa.Arg(long=["so", "long"])] result = parse(ArgTest, "--so", "4", backend=backend) assert result.number == 4 result = parse(ArgTest, "--long", "4", backend=backend) assert result.number == 4 cappa-0.32.2/tests/arg/test_subcommand_required.py0000644000000000000000000000127713615410400017165 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Command: subcmd: Annotated[Union[A, None], cappa.Subcommand(required=True)] = None @dataclass class A: ... @backends def test_required_implicit(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(Command) assert e.value.code == 2 assert "A command is required: {[cappa.subcommand]a[/cappa.subcommand]}" in str( e.value.message ) result = parse(Command, "a", backend=backend) assert result == Command(subcmd=A()) cappa-0.32.2/tests/arg/test_tuple.py0000644000000000000000000000462613615410400014267 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class ArgTest: numbers: tuple[int, str, float] @backends def test_valid(backend: Backend): test = parse(ArgTest, "1", "2", "3.4", backend=backend) assert test.numbers == (1, "2", 3.4) @backends def test_tuple_option(backend: Backend): @dataclass class Example: start_project: Annotated[ tuple[int, float], cappa.Arg(short=True, default=(1, 9), required=False), ] test = parse(Example, backend=backend) assert test == Example(start_project=(1, 9)) test = parse(Example, "-s", "2", "2.4", backend=backend) assert test == Example(start_project=(2, 2.4)) # Missing values with pytest.raises(cappa.Exit) as e: parse(Example, "-s", "1", backend=backend) assert e.value.code == 2 if backend: assert str(e.value.message).lower() == "argument -s: expected 2 arguments" else: assert ( e.value.message == "Argument '-s' requires 2 values, found 1 ('1' so far)" ) # Extra values with pytest.raises(cappa.Exit) as e: parse(Example, "-s", "1", "2", "3", backend=backend) assert e.value.code == 2 assert str(e.value.message).lower() == "unrecognized arguments: 3" @backends def test_optional_tuple_option(backend: Backend): @dataclass class Example: start_project: Annotated[ tuple[int, float] | None, cappa.Arg(short=True, required=False), ] = None test = parse(Example, backend=backend) assert test == Example(start_project=None) test = parse(Example, "-s", "2", "2.4", backend=backend) assert test == Example(start_project=(2, 2.4)) # Missing values with pytest.raises(cappa.Exit) as e: parse(Example, "-s", "1", backend=backend) assert e.value.code == 2 if backend: assert str(e.value.message).lower() == "argument -s: expected 2 arguments" else: assert ( e.value.message == "Argument '-s' requires 2 values, found 1 ('1' so far)" ) # Extra values with pytest.raises(cappa.Exit) as e: parse(Example, "-s", "1", "2", "3", backend=backend) assert e.value.code == 2 assert str(e.value.message).lower() == "unrecognized arguments: 3" cappa-0.32.2/tests/arg/test_tuple_unbounded.py0000644000000000000000000000125113615410400016321 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_positional_arg(backend: Backend): @dataclass class ArgTest: numbers: tuple[int, ...] test = parse(ArgTest, "1", "2", "3", "4", backend=backend) assert test.numbers == (1, 2, 3, 4) @backends def test_option_flag(backend: Backend): @dataclass class ArgTest: numbers: Annotated[tuple[int, ...], cappa.Arg(short=True)] test = parse(ArgTest, "-n", "1", "-n", "2", "-n", "3", "-n", "4", backend=backend) assert test.numbers == (1, 2, 3, 4) cappa-0.32.2/tests/arg/test_type_alias.py0000644000000000000000000000067213615410400015265 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Literal from typing_extensions import TypeAliasType from tests.utils import Backend, backends, parse Numbers = TypeAliasType("Numbers", Literal[1, 2, 3]) @backends def test_manual_type_alias_type(backend: Backend): @dataclass class ArgTest: value: Numbers test = parse(ArgTest, "1", backend=backend) assert test.value == 1 cappa-0.32.2/tests/arg/test_value_name.py0000644000000000000000000000114113615410400015237 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union from typing_extensions import Annotated import cappa from cappa import Subcommands from tests.utils import Backend, backends, parse @dataclass class Example: subcommand: Subcommands[Union[A, None]] = None @cappa.command(name="a") @dataclass class A: a: Annotated[str, cappa.Arg(value_name="", short="-a")] @backends def test_value_name_uses_correct_value(backend: Backend): result = parse(Example, "a", "-a", "test", backend=backend) assert result == Example(subcommand=A(a="test")) cappa-0.32.2/tests/command/__init__.py0000644000000000000000000000000013615410400014461 0ustar00cappa-0.32.2/tests/command/test_function_command.py0000644000000000000000000000544613615410400017327 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any, TypedDict, Union from typing_extensions import Annotated import cappa from cappa.output import Output from cappa.state import State from tests.utils import Backend, CapsysOutput, backends, invoke, parse @backends def test_no_op(backend: Backend): """Base case, this doesnt really serve any purpose.""" def function(): return 4 parse(function, backend=backend) @backends def test_parse_one_arg(backend: Backend): """Parse with arguments. Again, not really a logical usecase for `parse`, but technically it ought to work. """ def function(foo: str): return foo result = parse(function, "meow", backend=backend) assert result.foo == "meow" # pyright: ignore @backends def test_invoke_base_case(backend: Backend): def function(foo: str): return foo result = invoke(function, "meow", backend=backend) assert result == "meow" @backends def test_invoke_optional_args(backend: Backend): def function(foo: Annotated[int, cappa.Arg(long=True)] = 15): return foo result = invoke(function, backend=backend) assert result == 15 result = invoke(function, "--foo", "53", backend=backend) assert result == 53 @dataclass class Sub: bar: Annotated[int, cappa.Arg(long=True)] def __call__(self): return self.bar + 1 @backends def test_subcommand(backend: Backend): def function(sub: cappa.Subcommands[Union[Sub, None]] = None): assert sub is None return 6 result = invoke(function, backend=backend) assert result == 6 result = invoke(function, "sub", "--bar", "34", backend=backend) assert result == 35 def foo(): return 5 @backends def test_invoke_partial_arg_partial_dep(backend: Backend): def function( dep: Annotated[int, cappa.Dep(foo)], foo: Annotated[int, cappa.Arg(long=True)] = 15, ): return dep + foo result = invoke(function, backend=backend) assert result == 20 result = invoke(function, "--foo", "53", backend=backend) assert result == 58 @backends def test_output(backend: Backend, capsys: Any): """Depends on Output DI.""" def function(foo: str, output: Output): output("hey") return foo result = invoke(function, "meow", backend=backend) assert result == "meow" out = CapsysOutput.from_capsys(capsys) assert out.stdout == "hey\n" class StateFoo(TypedDict): foo: int @backends def test_state(backend: Backend, capsys: Any): """Depends on Output DI.""" def function(foo: int, state: State[StateFoo]): return foo + state.state["foo"] state = State(StateFoo(foo=45)) result = invoke(function, "3", backend=backend, state=state) assert result == 48 cappa-0.32.2/tests/command/test_help.py0000644000000000000000000000432113615410400014723 0ustar00from __future__ import annotations import re from dataclasses import dataclass from textwrap import dedent from typing import Any import pytest import cappa from cappa.output import Exit from tests.utils import ( Backend, backends, parse, strip_trailing_whitespace, terminal_width, ) @pytest.mark.help @backends def test_default_help(backend: Backend, capsys: Any): @dataclass class Command: """Some help. More detail. """ ... with pytest.raises(Exit): parse(Command, "--help", backend=backend) stdout = capsys.readouterr().out assert re.match(r".*Some help\.\s+More detail\..*", stdout, re.DOTALL) @pytest.mark.help @backends def test_default_help_no_long_description(backend: Backend, capsys: Any): @dataclass class Command: """Some help.""" ... with pytest.raises(Exit): parse(Command, "--help", backend=backend) stdout = capsys.readouterr().out assert "Some help." in stdout @pytest.mark.help @backends def test_unannotated_argument(backend: Backend, capsys: Any): @cappa.command(help="All the help.") @dataclass class Command: ... with pytest.raises(Exit): parse(Command, "--help", backend=backend) stdout = capsys.readouterr().out assert "All the help." in stdout @pytest.mark.help @backends def test_description_without_help(backend: Backend, capsys: Any): @cappa.command(description="All the help.") @dataclass class Command: pass with pytest.raises(Exit): parse(Command, "--help", backend=backend) stdout = capsys.readouterr().out assert "All the help." in stdout @pytest.mark.help @backends def test_epilog(backend: Backend, capsys: Any): @cappa.command(epilog="See also: https://example.com") @dataclass class Command: pass with terminal_width(), pytest.raises(Exit): parse(Command, "--help", backend=backend, completion=False) result = strip_trailing_whitespace(capsys.readouterr().out) tail = result[result.index("[-h, --help]") :] assert tail == dedent( """\ [-h, --help] Show this message and exit. See also: https://example.com """ ) cappa-0.32.2/tests/completion/__init__.py0000644000000000000000000000000013615410400015214 0ustar00cappa-0.32.2/tests/completion/test_argument.py0000644000000000000000000000062713615410400016355 0ustar00from __future__ import annotations from dataclasses import dataclass from tests.utils import parse_completion def test_arg(): @dataclass class Args: value: str result = parse_completion(Args, "") assert not result def test_arg_partial(): @dataclass class Args: value: str result = parse_completion(Args, "m") assert result assert result == "file" cappa-0.32.2/tests/completion/test_base.py0000644000000000000000000000446113615410400015445 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any from unittest.mock import patch import pytest import cappa from cappa.parser import backend from tests.utils import parse def test_unknown_shell(): @dataclass class Args: value: str with patch("os.environ", new={"SHELL": "notbash"}): with pytest.raises(cappa.Exit) as e: parse(Args, "--completion", "complete", backend=backend) assert e.value.code == 1 assert e.value.message == "Unknown shell" def test_generate_completions(): @dataclass class Args: value: str with pytest.raises(cappa.Exit) as e: parse(Args, "--completion", "generate", backend=backend) assert e.value.code == 0 assert e.value.message assert "_args_completion" in str(e.value.message) def test_invalid_setup(): @dataclass class Args: value: str with pytest.raises(cappa.Exit) as e: parse(Args, "--completion", "complete", backend=backend) assert e.value.code == 0 assert not e.value.message def test_no_completion(): @dataclass class Example: ... result = cappa.parse(Example, argv=[], backend=backend, completion=False) assert result == Example() with pytest.raises(cappa.Exit) as e: cappa.parse( Example, argv=["--completion", "generate"], backend=backend, completion=False, ) assert e.value.code == 2 def test_arg_completion(capsys: Any): @dataclass class Example: ... completion: cappa.Arg[Any] = cappa.Arg(short="-p", long="--pompletion") result = cappa.parse(Example, argv=[], completion=completion, backend=backend) assert result == Example() with pytest.raises(SystemExit) as e: cappa.parse( Example, argv=["-p", "generate"], completion=completion, backend=backend, ) assert e.value.code == 0 out = capsys.readouterr().out assert "_example_completion" in out with pytest.raises(SystemExit) as e: cappa.parse( Example, argv=["--help"], completion=completion, backend=backend, ) assert e.value.code == 0 out = capsys.readouterr().out assert "-p, --pompletion" in out cappa-0.32.2/tests/completion/test_choices.py0000644000000000000000000000173113615410400016145 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union from typing_extensions import Annotated, Literal import cappa from tests.utils import parse_completion def test_choices_without_help(): @dataclass class Args: value: Union[Literal["one"], Literal["two"], Literal["three"]] result = parse_completion(Args, "") assert result assert result == "one:\ntwo:\nthree:" def test_choices_with_help(): @dataclass class Args: value: Annotated[ Union[Literal["one"], Literal["two"], Literal["three"]], cappa.Arg(help="helpful"), ] result = parse_completion(Args, "") assert result assert result == "one:helpful\ntwo:helpful\nthree:helpful" def test_half_string(): @dataclass class Args: value: Union[Literal["one"], Literal["two"], Literal["three"]] result = parse_completion(Args, "'on") assert result assert result == "one:" cappa-0.32.2/tests/completion/test_custom_completion.py0000644000000000000000000000072113615410400020271 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse_completion def foo(f: str): return [cappa.Completion(f"{f}ooooo", "ok")] def test_custom_completion(): @dataclass class Args: value: Annotated[str, cappa.Arg(short=True, completion=foo)] result = parse_completion(Args, "-v", "f") assert result assert result == "fooooo:ok" cappa-0.32.2/tests/completion/test_option.py0000644000000000000000000000242213615410400016036 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import parse_completion def test_long_option_name(): @dataclass class Args: default: bool = False result = parse_completion(Args, "--d") assert result == "--default: (Default: False)" def test_long_option_name_with_help(): @dataclass class Args: default: Annotated[bool, cappa.Arg(help="Enables default")] = False result = parse_completion(Args, "--d") assert result == "--default:Enables default (Default: False)" def test_multiple_matches(): @dataclass class Args: apple: Annotated[str, cappa.Arg(short=True, help="apple")] banana: Annotated[str, cappa.Arg(short=True, help="banana")] result = parse_completion(Args, "-") assert result assert "-a:apple\n-b:banana" in result def test_short_name(): @dataclass class Args: apple: Annotated[str, cappa.Arg(short=True)] result = parse_completion(Args, "-a") assert result is None def test_no_help_value_name(): @dataclass class Args: apple: Annotated[str, cappa.Arg(long=True)] result = parse_completion(Args, "--a") assert result == "--apple:" cappa-0.32.2/tests/completion/test_subcommand.py0000644000000000000000000000136713615410400016665 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union from typing_extensions import Annotated import cappa from tests.utils import parse_completion @dataclass class Foo: ... @dataclass class Bar: nested_opt: Annotated[str, cappa.Arg(long=True)] @dataclass class Args: value: cappa.Subcommands[Union[Foo, Bar]] def test_subcommand_name_no_partial(): result = parse_completion(Args, "") assert result == "foo:\nbar:" def test_subcommand_name_with_partial(): result = parse_completion(Args, "f") assert result assert result == "foo:" def test_subcommand_args(): result = parse_completion(Args, "bar", "--n") assert result assert result == "--nested-opt:" cappa-0.32.2/tests/completion/test_successful_parse.py0000644000000000000000000000043013615410400020074 0ustar00from __future__ import annotations from dataclasses import dataclass from tests.utils import parse_completion def test_long_option_name(): @dataclass class Args: default: bool = False result = parse_completion(Args, "--default") assert result is None cappa-0.32.2/tests/ext/__init__.py0000644000000000000000000000000013615410400013643 0ustar00cappa-0.32.2/tests/ext/test_docutils.py0000644000000000000000000000544313615410400015011 0ustar00from dataclasses import dataclass from typing import Sequence, Union from unittest.mock import Mock, patch import pytest from docutils import nodes from docutils.core import publish_from_doctree # pyright: ignore from docutils.parsers.rst.states import Body, RSTStateMachine from docutils.utils import new_document from typing_extensions import Annotated import cappa from cappa.ext.docutils import CappaDirective @dataclass class Foo: bar: int baz: Annotated[int, cappa.Arg(help="asdf")] @cappa.command(help="has help", description="has description") @dataclass class Bar: bar: int = 4 @dataclass class Subcommand: subcmd: cappa.Subcommands[Union[Bar, Foo]] def create_directive(*, style: str, cls_name: str = "Foo", terminal_width: int = 0): with patch("docutils.parsers.rst.states.Body.build_table"): state = Body(RSTStateMachine([], "")) state.document = new_document("", None) return CappaDirective( "cappa", [f"tests.ext.test_docutils.{cls_name}"], {"style": style, "terminal-width": terminal_width}, [], # type: ignore 0, 0, "", state, Mock(), ) def render(nodes: Sequence[nodes.Node]) -> str: doc = new_document("", None) doc += nodes return publish_from_doctree(doc, writer_name="html").decode() # pyright: ignore def test_invalid(): directive = create_directive(style="wat") with pytest.raises(ValueError): directive.run() def test_terminal(): directive = create_directive(style="terminal") nodes = directive.run() result = render(nodes) assert "Usage: foo" in result assert "BAR" in result assert "Show this message and exit." in result assert "asdf" in result def test_native(): directive = create_directive(style="native") nodes = directive.run() result = render(nodes) assert "

foo

" in result assert 'Arguments' in result assert 'bar BAR' in result assert 'Help' in result assert ( '--help: Show this message and exit.' in result ) assert "asdf" in result def test_has_help_desc(): directive = create_directive(style="native", cls_name="Bar") nodes = directive.run() result = render(nodes) assert "has help" in result assert "has description" in result def test_subcommand(): directive = create_directive(style="native", cls_name="Subcommand") nodes = directive.run() result = render(nodes) assert "subcommand" in result def test_terminal_width(): directive = create_directive(style="terminal", terminal_width=60) nodes = directive.run() render(nodes) cappa-0.32.2/tests/help/__init__.py0000644000000000000000000000000013615410400013773 0ustar00cappa-0.32.2/tests/help/test_bool_help.py0000644000000000000000000000232313615410400015250 0ustar00from __future__ import annotations import re from typing import Any import pytest from typing_extensions import Annotated import cappa from cappa.output import Exit from tests.utils import Backend, CapsysOutput, backends, parse @cappa.command class Args: flag: Annotated[bool, cappa.Arg(long=["--flag", "--no-flag"], help="meow.")] = False @pytest.mark.help @backends def test_default_help(backend: Backend, capsys: Any): with pytest.raises(Exit): parse(Args, "--help", backend=backend) out = CapsysOutput.from_capsys(capsys) assert re.match(r".*--no-flag]\s+meow.\s+\(Default", out.stdout, re.DOTALL) @pytest.mark.help @backends def test_default_help_true_default(backend: Backend, capsys: Any): """Default text must follow help text regardless of whether default is True or False.""" @cappa.command class TrueDefault: flag: Annotated[bool, cappa.Arg(long=["--flag", "--no-flag"], help="meow.")] = ( True ) with pytest.raises(Exit): parse(TrueDefault, "--help", backend=backend) out = CapsysOutput.from_capsys(capsys) assert re.match(r".*--no-flag]\s+meow.\s+\(Default", out.stdout, re.DOTALL) assert "(Default: True)" in out.stdout cappa-0.32.2/tests/help/test_completion_choices.py0000644000000000000000000000100513615410400017147 0ustar00from __future__ import annotations import re from dataclasses import dataclass from typing import Any import pytest import cappa from tests.utils import parse def test_string_group(capsys: Any): @dataclass class Args: ... with pytest.raises(cappa.HelpExit) as e: parse(Args, "--help", backend=cappa.backend) assert e.value.code == 0 out = capsys.readouterr().out options = re.findall(r"Valid\s+options:\s+generate,\s+complete", out, re.MULTILINE) assert len(options) == 1 cappa-0.32.2/tests/help/test_consistent_among_subcommands.py0000644000000000000000000000170013615410400021250 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest import cappa from tests.utils import parse @dataclass class SubTwo: ... @dataclass class SubOne: sub_two: cappa.Subcommands[SubTwo] @dataclass class Args: sub_one: cappa.Subcommands[SubOne] def test_all_help_options_share_defined_help_argument(capsys: Any): help: cappa.Arg[Any] = cappa.Arg(help="this is custom text", long=True) with pytest.raises(cappa.HelpExit): parse(Args, "--help", help=help) out = capsys.readouterr().out assert "this is custom text" in out with pytest.raises(cappa.HelpExit): parse(Args, "sub-one", "--help", help=help) out = capsys.readouterr().out assert "this is custom text" in out with pytest.raises(cappa.HelpExit): parse(Args, "sub-one", "sub-two", "--help", help=help) out = capsys.readouterr().out assert "this is custom text" in out cappa-0.32.2/tests/help/test_docstring.py0000644000000000000000000001364613615410400015313 0ustar00import re from dataclasses import dataclass from textwrap import dedent from typing import Any import pytest import cappa from tests.utils import ( Backend, backends, ignore_docstring_parser, parse, strip_trailing_whitespace, ) @dataclass class IncludesDocstring: """Does a thing. and does it really well! Args: foo (str): the value of foo bar (bool): whether to bar """ foo: str bar: bool = False @pytest.mark.help @backends def test_required_provided(backend: Backend, capsys: Any): with pytest.raises(cappa.Exit): parse(IncludesDocstring, "--help", backend=backend) result = capsys.readouterr().out assert "[--bar] FOO [-h]" in result assert re.match(r".*Does a thing\.\s+and does it really well!.*", result, re.DOTALL) assert re.match(r".*FOO\s+the value of foo.*", result, re.DOTALL) assert re.match(r".*\[--bar\]\s+whether to bar.*", result, re.DOTALL) @pytest.mark.help @backends def test_just_a_title(backend: Backend, capsys: Any): @dataclass class IncludesDocstring: """Just a title.""" with pytest.raises(cappa.Exit): parse(IncludesDocstring, "--help", backend=backend) result = capsys.readouterr().out assert "Just a title" in result @pytest.mark.help @backends def test_docstring_with_explicit_help(backend: Backend, capsys: Any): @cappa.command(help="help text") @dataclass class IncludesDocstring: """Just a title.""" with pytest.raises(cappa.Exit): parse(IncludesDocstring, "--help", backend=backend) result = capsys.readouterr().out assert "Just a title" not in result assert "help text" in result @pytest.mark.help @backends def test_docstring_with_explicit_description(backend: Backend, capsys: Any): @cappa.command(description="description") @dataclass class IncludesDocstring: """Just a title.""" with pytest.raises(cappa.Exit): parse(IncludesDocstring, "--help", backend=backend) result = capsys.readouterr().out assert "Just a title" in result assert "description" in result @pytest.mark.help @backends def test_docstring_being_used_but_not_parsed( backend: Backend, capsys: Any, monkeypatch: Any ): @dataclass class UnparsedDocstring: """Summary. Example: - one """ with pytest.raises(cappa.Exit), ignore_docstring_parser(monkeypatch): parse(UnparsedDocstring, "--help", backend=backend, completion=False) result = strip_trailing_whitespace(capsys.readouterr().out) assert result == dedent( """\ Usage: unparsed-docstring [-h] Summary. Example: • one Help [-h, --help] Show this message and exit. """ ) @pytest.mark.help @backends def test_docstring_being_used_but_not_parsed_one_line( backend: Backend, capsys: Any, monkeypatch: Any ): @dataclass class UnparsedDocstring: """Summary.""" with pytest.raises(cappa.Exit), ignore_docstring_parser(monkeypatch): parse(UnparsedDocstring, "--help", backend=backend, completion=False) result = strip_trailing_whitespace(capsys.readouterr().out) assert result == dedent( """\ Usage: unparsed-docstring [-h] Summary. Help [-h, --help] Show this message and exit. """ ) @pytest.mark.help @backends def test_escaped_markdown(backend: Backend, capsys: Any, monkeypatch: Any): @dataclass class Escaped: """Blabla. And configure `~/.pypirc`: ```ini [distutils] index-servers = pypi ``` """ with pytest.raises(cappa.Exit), ignore_docstring_parser(monkeypatch): parse(Escaped, "--help", backend=backend, completion=False) result = capsys.readouterr().out for char in "┌─┐│└┘": result = result.replace(char, "") result = strip_trailing_whitespace(result) assert result == dedent( """\ Usage: escaped [-h] Blabla. And configure ~/.pypirc: [distutils] index-servers = pypi Help [-h, --help] Show this message and exit. """ ) @pytest.mark.help @backends def test_attribute_docstring(backend: Backend, capsys: Any): @dataclass class Args: """Bah. Arguments: top_level: woo woo foo: this should get superseded """ top_level: int foo: int """This is a foo.""" bar: str """This is a bar.""" with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend, completion=False) result = strip_trailing_whitespace(capsys.readouterr().out) assert result == dedent( """\ Usage: args TOP_LEVEL FOO BAR [-h] Bah. Arguments TOP_LEVEL woo woo FOO This is a foo. BAR This is a bar. Help [-h, --help] Show this message and exit. """ ) @pytest.mark.help @backends def test_explicit_help_description_manual_args(backend: Backend, capsys: Any): @cappa.command(help="Title", description="longer description") @dataclass class Args2: """... Args: foo: nope bar: yep """ foo: int """this is a foo""" bar: int with pytest.raises(cappa.Exit): parse(Args2, "--help", backend=backend, completion=False) result = strip_trailing_whitespace(capsys.readouterr().out) assert result == dedent( """\ Usage: args2 FOO BAR [-h] Title longer description Arguments FOO this is a foo BAR yep Help [-h, --help] Show this message and exit. """ ) cappa-0.32.2/tests/help/test_field_grouping.py0000644000000000000000000001371313615410400016307 0ustar00"""Tests for field_name grouping in help text.""" from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, CapsysOutput, backends, parse @backends def test_basic_field_grouping(backend: Backend, capsys: Any): """Test that multiple args with same field_name are shown on same line.""" @dataclass class Args: verbose: Annotated[ bool, cappa.Arg(short="-v", long="--verbose", help="Enable verbose output."), cappa.Arg(long="--debug", help="Enable debug mode."), ] with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) # Bool flags combined: [-v, --verbose, --debug] assert "[-v, --verbose, --debug]" in output.stdout # Help texts should be concatenated (may wrap across lines) assert "Enable verbose output." in output.stdout assert "debug mode." in output.stdout @backends def test_single_arg_unaffected(backend: Backend, capsys: Any): """Test that single args without field_name grouping are unaffected.""" @dataclass class Args: name: Annotated[str, cappa.Arg(short="-n", help="Your name.")] with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) assert "-n NAME" in output.stdout assert "Your name." in output.stdout @backends def test_multiple_grouped_fields(backend: Backend, capsys: Any): """Test multiple different fields with grouping.""" @dataclass class Args: verbose: Annotated[ int, cappa.Arg( short="-v", help="Verbosity level (lowercase v).", group="Verbose" ), cappa.Arg( short="-V", help="Verbosity level (uppercase V).", group="Verbose" ), ] = 0 output: Annotated[ str, cappa.Arg(short="-o", help="Output file.", group="Output"), cappa.Arg(short="-O", help="Alternative output option.", group="Output"), ] = "out.txt" with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) # Check verbose group assert "[-v VERBOSE, -V VERBOSE]" in output.stdout assert "Verbosity level (lowercase v)." in output.stdout assert "(uppercase V)." in output.stdout # Check output group assert "[-o OUTPUT, -O OUTPUT]" in output.stdout assert "Output file." in output.stdout assert "Alternative" in output.stdout @backends def test_bool_pair_combined(backend: Backend, capsys: Any): """Test that --foo/--no-foo pairs are combined on the same line.""" @dataclass class Args: debug: Annotated[bool, cappa.Arg(long="--debug/--no-debug")] = False with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) # Bool flags with no value should be combined into single brackets assert "[--debug, --no-debug]" in output.stdout @backends def test_field_grouping_with_defaults(backend: Backend, capsys: Any): """Test that defaults are shown correctly for grouped fields.""" @dataclass class Args: level: Annotated[ int, cappa.Arg(short="-l", help="Level option 1."), cappa.Arg(short="-L", help="Level option 2."), ] = 5 with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) # Both args should be on the same line assert "[-l LEVEL, -L LEVEL]" in output.stdout # Default should appear assert "Default: 5" in output.stdout @backends def test_same_value_name_combined(backend: Backend, capsys: Any): """Test that args with same value_name show value once at end.""" @dataclass class Args: verbose: Annotated[ int, cappa.Arg(short="-v", help="Verbosity (short).", group="Options"), cappa.Arg(long="--verbose", help="Verbosity (long).", group="Options"), ] = 0 with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) # Format: [-v VERBOSE, --verbose VERBOSE] assert "[-v VERBOSE, --verbose VERBOSE]" in output.stdout @backends def test_different_value_names_each_shown(backend: Backend, capsys: Any): """Test that args with different value_names each show their own value.""" @dataclass class Args: output: Annotated[ str, cappa.Arg( short="-o", value_name="file", help="Output file.", group="Output" ), cappa.Arg( long="--output", value_name="path", help="Output path.", group="Output" ), ] = "out.txt" with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) # Different value_names: [-o FILE, --output PATH] assert "[-o FILE, --output PATH]" in output.stdout @backends def test_mixed_value_names_with_none(backend: Backend, capsys: Any): """Test args where some consume values and some don't.""" @dataclass class Args: # This creates exclusive args with different behaviors mode: Annotated[ int | bool, cappa.Arg(short="-m", value_name="mode", help="Mode value.", group="Mode"), cappa.Arg( long="--default", action=cappa.ArgAction.store_true, help="Use default.", group="Mode", ), ] = 0 with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) # Format: [-m MODE, --default] (--default is store_true so no value) assert "[-m MODE, --default]" in output.stdout cappa-0.32.2/tests/help/test_group.py0000644000000000000000000000172513615410400014446 0ustar00from __future__ import annotations import re from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_string_group(backend: Backend, capsys: Any): @dataclass class Args: name: Annotated[str, cappa.Arg(group="Strings")] with pytest.raises(cappa.HelpExit) as e: parse(Args, "--help", backend=backend) assert e.value.code == 0 out = capsys.readouterr().out assert re.match(r".*Strings:?\s*\n\s*NAME.*", out, re.DOTALL) @backends def test_tuple_group(backend: Backend, capsys: Any): @dataclass class Args: name: Annotated[str, cappa.Arg(group=(1, "Strings"))] with pytest.raises(cappa.HelpExit) as e: parse(Args, "--help", backend=backend) assert e.value.code == 0 out = capsys.readouterr().out assert re.match(r".*Strings:?\s*\n\s*NAME.*", out, re.DOTALL) cappa-0.32.2/tests/help/test_help_formatter.py0000644000000000000000000001306213615410400016322 0ustar00from __future__ import annotations import textwrap from dataclasses import dataclass from typing import Any, Literal import pytest from typing_extensions import Annotated import cappa from tests.utils import CapsysOutput, parse, strip_trailing_whitespace @dataclass class Args: required: Annotated[str, cappa.Arg(help="I'm required")] name: Annotated[str, cappa.Arg(help="I'm optional")] = "arg" short: Annotated[str, cappa.Arg(short=True, help="I'm an option")] = "opt" maybe: Annotated[str | None, cappa.Arg(short=True, help="maybe?")] = None def test_default_settings(capsys: Any): with pytest.raises(cappa.HelpExit) as e: parse(Args, "--help", completion=False) assert e.value.code == 0 out = strip_trailing_whitespace(capsys.readouterr().out) assert out == textwrap.dedent( """\ Usage: args [-s SHORT] [-m MAYBE] REQUIRED [NAME] [-h] Options [-s SHORT] I'm an option (Default: opt) [-m MAYBE] maybe? Arguments REQUIRED I'm required [NAME] I'm optional (Default: arg) Help [-h, --help] Show this message and exit. """ ) def test_override_default_format(capsys: Any): with pytest.raises(cappa.HelpExit) as e: parse( Args, "--help", completion=False, help_formatter=cappa.HelpFormatter.default.with_default_format( "[Default '{default}']" ), ) assert e.value.code == 0 out = strip_trailing_whitespace(capsys.readouterr().out) assert out == textwrap.dedent( """\ Usage: args [-s SHORT] [-m MAYBE] REQUIRED [NAME] [-h] Options [-s SHORT] I'm an option [Default 'opt'] [-m MAYBE] maybe? Arguments REQUIRED I'm required [NAME] I'm optional [Default 'arg'] Help [-h, --help] Show this message and exit. """ ) def test_override_help_format(capsys: Any): with pytest.raises(cappa.HelpExit) as e: parse( Args, "--help", completion=False, help_formatter=cappa.HelpFormatter.default.with_arg_format( ( "{default}", "{help}", ) ), ) assert e.value.code == 0 out = strip_trailing_whitespace(capsys.readouterr().out) assert out == textwrap.dedent( """\ Usage: args [-s SHORT] [-m MAYBE] REQUIRED [NAME] [-h] Options [-s SHORT] (Default: opt) I'm an option [-m MAYBE] maybe? Arguments REQUIRED I'm required [NAME] (Default: arg) I'm optional Help [-h, --help] Show this message and exit. """ ) def test_choice_options(capsys: Any): @dataclass class Args: required: Annotated[Literal["one", "two", "three"], cappa.Arg(help="Required.")] with pytest.raises(cappa.HelpExit) as e: parse( Args, "--help", completion=False, ) assert e.value.code == 0 out = strip_trailing_whitespace(capsys.readouterr().out) assert out == textwrap.dedent( """\ Usage: args REQUIRED [-h] Arguments REQUIRED Required. Valid options: one, two, three. Help [-h, --help] Show this message and exit. """ ) with pytest.raises(cappa.HelpExit) as e: parse( Args, "--help", completion=False, help_formatter=cappa.HelpFormatter().with_arg_format("{help}"), ) assert e.value.code == 0 out = strip_trailing_whitespace(capsys.readouterr().out) assert out == textwrap.dedent( """\ Usage: args REQUIRED [-h] Arguments REQUIRED Required. Help [-h, --help] Show this message and exit. """ ) def test_callable_help_formatter(capsys: Any): @dataclass class Args: required: Annotated[int, cappa.Arg(help="Required.")] not_required: Annotated[int | None, cappa.Arg(help="erm.")] = None def help_formatter(arg: cappa.Arg[Any]) -> str | None: if arg.field_name == "required": return f"Num args: {arg.num_args}" return None with pytest.raises(cappa.HelpExit) as e: parse( Args, "--help", completion=False, help_formatter=cappa.HelpFormatter().with_arg_format( ( "{help}", help_formatter, ) ), ) assert e.value.code == 0 out = strip_trailing_whitespace(capsys.readouterr().out) assert out == textwrap.dedent( """\ Usage: args REQUIRED [NOT_REQUIRED] [-h] Arguments REQUIRED Required. Num args: 1 [NOT_REQUIRED] erm. Help [-h, --help] Show this message and exit. """ ) def test_explicitly_wrapped_formatter(capsys: Any): @dataclass class Args: name: Annotated[str, cappa.Arg(help="Optional.")] = "arg" help_formatter = cappa.HelpFormatter(default_format="Default - {default}!") with pytest.raises(cappa.HelpExit) as e: parse(Args, "--help", help_formatter=help_formatter) assert e.value.code == 0 output = CapsysOutput.from_capsys(capsys) assert "Optional. Default - arg!\n" in output.stdout cappa-0.32.2/tests/help/test_line_wrapping.py0000644000000000000000000000250413615410400016144 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any from unittest.mock import patch import pytest from rich.console import Console from rich.text import Text from typing_extensions import Annotated import cappa from tests.utils import parse def test_line_wraps_correctly_with_terminal_escape_codes(capsys: Any): @dataclass class Args: a: Annotated[str, cappa.Arg(short=True)] b: Annotated[str, cappa.Arg(short=True)] c: Annotated[str, cappa.Arg(short=True)] d: Annotated[str, cappa.Arg(short=True)] e: Annotated[str, cappa.Arg(short=True)] f: Annotated[str, cappa.Arg(short=True)] g: Annotated[str, cappa.Arg(short=True)] i: Annotated[str, cappa.Arg(short=True)] columns = 80 env = { "FORCE_COLOR": "true", "COLUMNS": str(columns), } with patch("os.environ", new=env), pytest.raises(cappa.HelpExit) as e: output = cappa.Output(output_console=Console(force_terminal=True)) parse(Args, "--help", output=output) assert e.value.code == 0 out = capsys.readouterr().out plain, *_ = Text.from_ansi(out).plain.splitlines() expected = "Usage: args -a A -b B -c C -d D -e E -f F -g G -i I [-h] [--completion" assert len(expected) < columns assert plain == expected cappa-0.32.2/tests/help/test_markdown.py0000644000000000000000000000520113615410400015125 0ustar00import sys from dataclasses import dataclass from typing import Any import pytest from rich.console import Console from typing_extensions import Annotated import cappa from tests.utils import ( Backend, backends, parse, strip_trailing_whitespace, terminal_width, ) @pytest.mark.help @backends def test_arg_description_renders_markdown(backend: Backend, capsys: Any): @dataclass class Args: foo: str """`This` **is** _neat_""" with pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) result = capsys.readouterr() assert "This is neat" in result.out with pytest.raises(cappa.Exit): parse( Args, "--help", backend=backend, output=cappa.Output( output_console=Console(force_terminal=True, file=sys.stdout) ), ) result = capsys.readouterr() assert "This\x1b[0m" in result.out assert " \x1b[1mis\x1b[0m" in result.out # typos: ignore assert " \x1b[3mneat\x1b[0m" in result.out @pytest.mark.help @backends def test_multi_paragraph_arg_help_preserves_paragraph_breaks( backend: Backend, capsys: Any ): @dataclass class Args: foo: Annotated[ str, cappa.Arg( help="First paragraph summary.\n\nSecond paragraph body.", ), ] with terminal_width(80), pytest.raises(cappa.Exit): parse(Args, "--help", backend=backend) out = strip_trailing_whitespace(capsys.readouterr().out) assert "First paragraph summary." in out assert "Second paragraph body." in out summary_pos = out.index("First paragraph summary.") body_pos = out.index("Second paragraph body.") between = out[summary_pos + len("First paragraph summary.") : body_pos] assert "\n\n" in between @pytest.mark.help @backends def test_soft_line_breaks_fold_into_spaces(backend: Backend, capsys: Any): @dataclass class SoftBreakArgs: foo: str """ This is a long sentence that goes on and on. This second soft-break line continues the same paragraph. """ with terminal_width(80), pytest.raises(cappa.Exit): parse(SoftBreakArgs, "--help", backend=backend, completion=False) out = strip_trailing_whitespace(capsys.readouterr().out) # Words from adjacent soft-break lines must be separated by a space assert "on.This" not in out assert "on. This" in out # And must not have a blank line between them (same paragraph) first = out.index("goes on and on.") second = out.index("same paragraph.") assert "\n\n" not in out[first:second] cappa-0.32.2/tests/help/test_mutual_exclusion_helptext.py0000644000000000000000000000147613615410400020632 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from cappa.output import Exit from tests.utils import ( Backend, CapsysOutput, backends, parse, terminal_width, ) @dataclass class Args: verbose: Annotated[ int, cappa.Arg(short="-v", help="All help is included."), cappa.Arg(long="--verbosity", help="But the default is not duplicated"), ] = 0 @backends def test_help(backend: Backend, capsys: Any): with terminal_width(), pytest.raises(Exit): parse(Args, "--help", backend=backend) out = CapsysOutput.from_capsys(capsys) assert "All help is included. But the default is not duplicated" in out.stdout assert "(Default: 0)" in out.stdout cappa-0.32.2/tests/help/test_mutually_exclusive_no_arg.py0000644000000000000000000000176013615410400020601 0ustar00from __future__ import annotations import pytest from typing_extensions import Annotated import cappa from cappa.output import Exit, rich_to_ansi from tests.utils import Backend, backends, parse @cappa.command class Args: flag: Annotated[ str, cappa.Arg(long="--blue", action=lambda: "blue", num_args=0), cappa.Arg(long="--red", action=lambda: "red", num_args=0), cappa.Arg(long="--green", action=lambda: "green", num_args=0), ] = "blue" @backends def test_help(backend: Backend): output = cappa.Output() with pytest.raises(Exit) as e: parse(Args, "--help", backend=backend) assert "[--blue, --red, --green]" in rich_to_ansi( output.output_console, e.value.message ) result = parse(Args, "--blue", backend=backend) assert result.flag == "blue" result = parse(Args, "--red", backend=backend) assert result.flag == "red" with pytest.raises(Exit) as e: parse(Args, "--blue", "--red", backend=backend) cappa-0.32.2/tests/help/test_name.py0000644000000000000000000000261313615410400014227 0ustar00from __future__ import annotations import textwrap from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from tests.utils import parse, strip_trailing_whitespace def test_argument_name(capsys: Any): @dataclass class Args: name: Annotated[str, cappa.Arg(value_name="sname", help="more")] short: Annotated[str, cappa.Arg(short=True, value_name="ostr")] unbounded: Annotated[list[list[str]], cappa.Arg(short=True, value_name="UNB")] unbounded_pos: Annotated[list[str], cappa.Arg(value_name="upos", help="lots")] with pytest.raises(cappa.HelpExit) as e: parse(Args, "--help") assert e.value.code == 0 out = strip_trailing_whitespace(capsys.readouterr().out) assert out == textwrap.dedent( """\ Usage: args -s OSTR -u UNB ... SNAME UPOS ... [-h] [--completion COMPLETION] Options -s OSTR -u UNB ... Arguments SNAME more UPOS ... lots Help [-h, --help] Show this message and exit. [--completion COMPLETION] Use --completion generate to print shell-specific completion source. Valid options: generate, complete. """ ) cappa-0.32.2/tests/help/test_show_default.py0000644000000000000000000001023113615410400015766 0ustar00from __future__ import annotations import re from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from cappa.default import DefaultFormatter from tests.utils import Backend, CapsysOutput, backends, parse @backends def test_show_default_default_true(backend: Backend, capsys: Any): @dataclass class Command: foo: bool = False assert parse(Command, backend=backend).foo is False with pytest.raises(cappa.HelpExit): parse(Command, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) assert "(Default: False)" in output.stdout @backends def test_show_default_set_false(backend: Backend, capsys: Any): @dataclass class Command: foo: Annotated[bool, cappa.Arg(show_default=False)] = False assert parse(Command, backend=backend).foo is False with pytest.raises(cappa.HelpExit): parse(Command, "--help", backend=backend) output = CapsysOutput.from_capsys(capsys) assert "(Default: False)" not in output.stdout @backends def test_show_default_no_option_shows_for_false_default(backend: Backend, capsys: Any): @dataclass class Command: foo: Annotated[bool, cappa.Arg(long="--foo/--no-foo")] = False assert parse(Command, backend=backend).foo is False with pytest.raises(cappa.HelpExit): parse(Command, "--help", backend=backend) stdout = CapsysOutput.from_capsys(capsys).stdout # Bool flags are combined into single brackets assert "[--foo, --no-foo]" in stdout # Should show default False but not True (flexible whitespace between columns) assert re.search(r"\[--foo, --no-foo\]\s+\(Default:\s*False\)", stdout) assert "(Default: True)" not in stdout @backends def test_show_default_no_option_shows_for_true_default(backend: Backend, capsys: Any): @dataclass class Command: foo: Annotated[bool, cappa.Arg(long="--foo/--no-foo")] = True assert parse(Command, backend=backend).foo is True with pytest.raises(cappa.HelpExit): parse(Command, "--help", backend=backend) stdout = CapsysOutput.from_capsys(capsys).stdout # Bool flags with no value are combined into single brackets assert "[--foo, --no-foo]" in stdout # Should show default True but not False (flexible whitespace between columns) assert re.search(r"\[--foo, --no-foo\]\s+\(Default:\s*True\)", stdout) assert "(Default: False)" not in stdout @backends def test_explicit_arg_default(backend: Backend, capsys: Any): @dataclass class Command: foo: Annotated[bool, cappa.Arg(long="--foo/--no-foo", default=False)] with pytest.raises(cappa.HelpExit): parse(Command, "--help", backend=backend) stdout = CapsysOutput.from_capsys(capsys).stdout # Bool flags are combined into single brackets assert "[--foo, --no-foo]" in stdout # Flexible whitespace between columns assert re.search(r"\[--foo, --no-foo\]\s+\(Default:\s*False\)", stdout) @backends def test_show_default_string(backend: Backend, capsys: Any): @dataclass class Command: foo: Annotated[str, cappa.Arg(show_default="~{default}~")] = "asdf" with pytest.raises(cappa.HelpExit): parse(Command, "--help", backend=backend) stdout = CapsysOutput.from_capsys(capsys).stdout.replace(" ", "") assert "[FOO](Default:~asdf~)" in stdout @backends def test_show_default_explicit(backend: Backend, capsys: Any): @dataclass class Command: foo: Annotated[str, cappa.Arg(show_default=DefaultFormatter("!{default}!"))] = ( "asdf" ) with pytest.raises(cappa.HelpExit): parse(Command, "--help", backend=backend) stdout = CapsysOutput.from_capsys(capsys).stdout.replace(" ", "") assert "[FOO](Default:!asdf!)" in stdout @backends def test_static(backend: Backend, capsys: Any): @dataclass class Command: foo: Annotated[str | None, cappa.Arg(show_default="always show")] = None with pytest.raises(cappa.HelpExit): parse(Command, "--help", backend=backend) stdout = CapsysOutput.from_capsys(capsys).stdout assert re.search(r"\[FOO\]\s+\(Default: always show\)", stdout) cappa-0.32.2/tests/help/test_text_format.py0000644000000000000000000000342613615410400015646 0ustar00import sys from dataclasses import dataclass from typing import Any import pytest from rich.console import Console from rich.text import Text import cappa from tests.utils import Backend, backends, parse @pytest.mark.help @backends def test_arg_renders_text(backend: Backend, capsys: Any): @dataclass class Args: foo: str """[yellow]This is yellow[/]""" with pytest.raises(cappa.Exit): parse( Args, "--help", backend=backend, help_formatter=cappa.HelpFormatter().with_arg_format( Text("[red]Help:[/] {help}") ), output=cappa.Output( output_console=Console(force_terminal=True, file=sys.stdout) ), ) result = capsys.readouterr() red = "\x1b[31m" yellow = "\x1b[33m" end = "\x1b[0m" expected_result = f"{red}Help:{end} {yellow}This is yellow{end}" assert expected_result in result.out assert str(Text.from_ansi(expected_result)) == "Help: This is yellow" @pytest.mark.help @backends def test_arg_renders_string(backend: Backend, capsys: Any): @dataclass class Args: foo: str """[yellow]This is yellow[/]""" with pytest.raises(cappa.Exit): parse( Args, "--help", backend=backend, help_formatter=cappa.HelpFormatter(arg_format="Help: {help}"), output=cappa.Output( output_console=Console(force_terminal=True, file=sys.stdout) ), ) result = capsys.readouterr() yellow = "\x1b[33m" end = "\x1b[0m" expected_result = f"Help: {yellow}This is yellow{end}" assert expected_result in result.out assert str(Text.from_ansi(expected_result)) == "Help: This is yellow" cappa-0.32.2/tests/invoke/__init__.py0000644000000000000000000000000013615410400014336 0ustar00cappa-0.32.2/tests/invoke/test_async_context_manager_dependency.py0000644000000000000000000000147713615410400022432 0ustar00from __future__ import annotations import asyncio import contextlib import io import logging from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke_async log = logging.getLogger("test") @contextlib.asynccontextmanager async def binary_io(): with io.BytesIO() as buffer: yield buffer def command( binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)], ): assert not binary_io.closed binary_io.write(b"hello") return binary_io, binary_io.getvalue() @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_top_level_command(backend: Backend): buffer, result = asyncio.run(invoke_async(Command, backend=backend)) assert buffer.closed is True assert result == b"hello" cappa-0.32.2/tests/invoke/test_async_function.py0000644000000000000000000000130113615410400016665 0ustar00from __future__ import annotations import asyncio from dataclasses import dataclass from typing import Any from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke_async def bar(): return 7 async def foo(bar: Annotated[int, cappa.Dep(bar)]): return bar + 9 async def handler(foo: Annotated[int, cappa.Dep(foo)]): return foo + 1 @cappa.command(invoke=handler) @dataclass class Command: ... def idk(): print("wat") @backends def test_async_fn(backend: Backend, capsys: Any): result = asyncio.run(invoke_async(Command, backend=backend, deps=[idk])) assert result == 7 + 9 + 1 assert capsys.readouterr().out == "wat\n" cappa-0.32.2/tests/invoke/test_async_generator_dependency.py0000644000000000000000000000302313615410400021227 0ustar00from __future__ import annotations import asyncio import csv import io import logging from dataclasses import dataclass from typing import Any from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke_async log = logging.getLogger("test") async def binary_io(): print("binary before") with io.BytesIO() as buffer: yield buffer print("binary after") async def text_io(binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)]): print("text before") text_buffer = io.TextIOWrapper(binary_io) yield text_buffer print("text after") async def csv_file(text_io: Annotated[io.TextIOWrapper, cappa.Dep(text_io)]): print("csv before") writer = csv.DictWriter(text_io, ["foo", "bar"]) writer.writeheader() writer.writerow({"foo": 1, "bar": "woah"}) text_io.detach() yield writer print("csv after") def command( _: Annotated[dict[Any, Any], cappa.Dep(csv_file)], binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)], ): assert not binary_io.closed return binary_io, binary_io.getvalue() @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_top_level_command(backend: Backend, capsys: Any): buffer, result = asyncio.run(invoke_async(Command, backend=backend)) assert buffer.closed is True assert result == b"foo,bar\r\n1,woah\r\n" out = capsys.readouterr().out assert out == ( "binary before\ntext before\ncsv before\ncsv after\ntext after\nbinary after\n" ) cappa-0.32.2/tests/invoke/test_async_sync_context_manager.py0000644000000000000000000000127413615410400021263 0ustar00from __future__ import annotations import asyncio from contextlib import contextmanager from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke_async @contextmanager def sync_dep(): yield 42 async def async_handler(value: Annotated[int, cappa.Dep(sync_dep)]): return value * 2 @cappa.command(invoke=async_handler) @dataclass class AsyncCommandWithSyncDep: ... @backends def test_invoke_async_with_sync_dep(backend: Backend): """Test that invoke_async works with a synchronous dependency.""" result = asyncio.run(invoke_async(AsyncCommandWithSyncDep, backend=backend)) assert result == 84 cappa-0.32.2/tests/invoke/test_bad_handler.py0000644000000000000000000000444413615410400016101 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest import cappa from tests.utils import Backend, backends, invoke @backends def test_missing_invoke(backend: Backend): @cappa.command() @dataclass class Command: ... with pytest.raises(RuntimeError) as e: invoke(Command, backend=backend) exc = e.value cause = exc.__cause__ assert "due to resolution failure" in str(exc) assert ( f"Cannot call `invoke` for a command which does not have an invoke handler: {Command}." == str(cause) ) @backends def test_string_missing_function(backend: Backend): @cappa.command(invoke="sys") @dataclass class Command: ... with pytest.raises(RuntimeError) as e: invoke(Command, backend=backend) exc = e.value cause = exc.__cause__ assert "due to resolution failure" in str(exc) assert ( "Invoke `sys` must be a fully qualified reference to a function in a module." == str(cause) ) @backends def test_string_references_invalid_module(backend: Backend): @cappa.command(invoke="completely.wrong") @dataclass class Command: ... with pytest.raises(RuntimeError) as e: invoke(Command, backend=backend) exc = e.value cause = exc.__cause__ assert "due to resolution failure" in str(exc) assert "No module 'completely' when attempting to load 'completely.wrong'." == str( cause ) @backends def test_string_references_invalid_function(backend: Backend): @cappa.command(invoke="builtins.meow") @dataclass class Command: ... with pytest.raises(RuntimeError) as e: invoke(Command, backend=backend) exc = e.value cause = exc.__cause__ assert "due to resolution failure" in str(exc) assert ( "Module does not have a function `meow`." == str(cause) ) @backends def test_string_reference_not_callable(backend: Backend): @cappa.command(invoke="builtins.__name__") @dataclass class Command: ... with pytest.raises(RuntimeError) as e: invoke(Command, backend=backend) exc = e.value cause = exc.__cause__ assert "due to resolution failure" in str(exc) assert "`builtins` does not reference a valid callable." == str(cause) cappa-0.32.2/tests/invoke/test_callable_dataclass.py0000644000000000000000000000075313615410400017433 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke def dependency(): return 5 @dataclass class Command: foo: int def __call__(self, dep: Annotated[int, cappa.Dep(dependency)]): return self.foo + dep @backends def test_invoke_top_level_command(backend: Backend): result = invoke(Command, "7", backend=backend) assert result == 7 + 5 cappa-0.32.2/tests/invoke/test_command_dep.py0000644000000000000000000000134313615410400016117 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any, Union import cappa from tests.utils import Backend, backends, invoke @dataclass class Subcommand: ... def __call__(self, command: cappa.Command[Any]): return command.real_name() @dataclass class TopLevelCommand: subcmd: cappa.Subcommands[Union[Subcommand, None]] = None def __call__(self, command: cappa.Command[Any]): return command.real_name() @backends def test_command_as_dependency(backend: Backend): result = invoke(TopLevelCommand, backend=backend) assert result == "top-level-command" result = invoke(TopLevelCommand, "subcommand", backend=backend) assert result == "subcommand" cappa-0.32.2/tests/invoke/test_context_manager_dependency.py0000644000000000000000000000141413615410400021224 0ustar00from __future__ import annotations import contextlib import io import logging from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke log = logging.getLogger("test") @contextlib.contextmanager def binary_io(): with io.BytesIO() as buffer: yield buffer def command( binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)], ): assert not binary_io.closed binary_io.write(b"hello") return binary_io, binary_io.getvalue() @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_top_level_command(backend: Backend): buffer, result = invoke(Command, backend=backend) assert buffer.closed is True assert result == b"hello" cappa-0.32.2/tests/invoke/test_default_arg_non_dep.py0000644000000000000000000000114713615410400017632 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke def has_default(default: bool = True): return {"default": default} def command( default: Annotated[dict[str, Any], cappa.Dep(has_default)], ): return {"one": True, **default} @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_top_level_command(backend: Backend): result = invoke(Command, backend=backend) assert result == {"one": True, "default": True} cappa-0.32.2/tests/invoke/test_explicit_deps.py0000644000000000000000000000147513615410400016513 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke def level_three(): return {"level_three": True} def level_two(level_three: Annotated[dict[str, Any], cappa.Dep(level_three)]): return {"level_two": {**level_three}} def level_one(level_two: Annotated[dict[str, Any], cappa.Dep(level_two)]): return {"level_one": {**level_two}} def command(levels: Annotated[dict[str, Any], cappa.Dep(level_one)]): return levels @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_top_level_command(backend: Backend): result = invoke(Command, backend=backend) assert result == {"level_one": {"level_two": {"level_three": True}}} cappa-0.32.2/tests/invoke/test_explicit_deps_multiple_paths.py0000644000000000000000000000266113615410400021623 0ustar00from __future__ import annotations import logging from dataclasses import dataclass from typing import Any from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke log = logging.getLogger("test") def level_three(): log.debug("three") return {"three": True} def level_two(three: Annotated[dict[str, Any], cappa.Dep(level_three)]): log.debug("two") return {"two": {**three}} def level_one( two: Annotated[dict[str, Any], cappa.Dep(level_two)], three: Annotated[dict[str, Any], cappa.Dep(level_three)], ): log.debug("one") return {"one": {**two, **three}} def command( one: Annotated[dict[str, Any], cappa.Dep(level_one)], two: Annotated[dict[str, Any], cappa.Dep(level_two)], three: Annotated[dict[str, Any], cappa.Dep(level_three)], ): return {**one, **two, **three} @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_top_level_command(caplog: Any, backend: Backend): """Assert multiple paths to explicit dependencies are still fulfilled. * Ensure they are only called once overall, regardless of the number of downstream dependents. """ caplog.set_level(logging.DEBUG) result = invoke(Command, backend=backend) assert result == { "one": {"two": {"three": True}, "three": True}, "two": {"three": True}, "three": True, } assert len(caplog.records) == 3 cappa-0.32.2/tests/invoke/test_generator_dependency.py0000644000000000000000000000273113615410400020037 0ustar00from __future__ import annotations import csv import io import logging from dataclasses import dataclass from typing import Any from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke log = logging.getLogger("test") def binary_io(): print("binary before") with io.BytesIO() as buffer: yield buffer print("binary after") def text_io(binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)]): print("text before") text_buffer = io.TextIOWrapper(binary_io) yield text_buffer print("text after") def csv_file(text_io: Annotated[io.TextIOWrapper, cappa.Dep(text_io)]): print("csv before") writer = csv.DictWriter(text_io, ["foo", "bar"]) writer.writeheader() writer.writerow({"foo": 1, "bar": "woah"}) text_io.detach() yield writer print("csv after") def command( _: Annotated[dict[str, Any], cappa.Dep(csv_file)], binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)], ): assert not binary_io.closed return binary_io, binary_io.getvalue() @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_top_level_command(backend: Backend, capsys: Any): buffer, result = invoke(Command, backend=backend) assert buffer.closed is True assert result == b"foo,bar\r\n1,woah\r\n" out = capsys.readouterr().out assert out == ( "binary before\ntext before\ncsv before\ncsv after\ntext after\nbinary after\n" ) cappa-0.32.2/tests/invoke/test_global_dep.py0000644000000000000000000000111713615410400015740 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import cappa from tests.utils import Backend, backends, invoke def foo(command: Command): print("called", command) def bar(): print("two") def command(): return 1 @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_global_dep(capsys: Any, backend: Backend): result = invoke(Command, deps=[foo, bar], backend=backend) assert result == 1 out = capsys.readouterr().out assert "called Command()" in out assert "two" in out cappa-0.32.2/tests/invoke/test_global_dep_string_reference.py0000644000000000000000000000116413615410400021346 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import cappa from tests.utils import Backend, backends, invoke def foo(command: Command): print("called", command) def command(): return 1 @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_global_dep_string_reference(capsys: Any, backend: Backend): result = invoke( Command, deps=["tests.invoke.test_global_dep_string_reference.foo"], backend=backend, ) assert result == 1 out = capsys.readouterr().out assert "called Command()" in out cappa-0.32.2/tests/invoke/test_implicit_deps.py0000644000000000000000000000172713615410400016504 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke def top_level_command(tlc: TopLevelCommand): return ("tlc", tlc) def sub_command(sub: Subcommand): return ("sub", sub) @cappa.command(name="sub", invoke=sub_command) @dataclass class Subcommand: bar: int @cappa.command(invoke=top_level_command) @dataclass class TopLevelCommand: foo: Annotated[int, cappa.Arg(long=True)] = 4 subcommand: Annotated[Union[Subcommand, None], cappa.Subcommand] = None @backends def test_invoke_top_level_command(backend: Backend): result = invoke(TopLevelCommand, "--foo", "4", backend=backend) assert result == ("tlc", TopLevelCommand(foo=4)) @backends def test_invoke_subcommand(backend: Backend): result = invoke(TopLevelCommand, "sub", "4", backend=backend) assert result == ("sub", Subcommand(bar=4)) cappa-0.32.2/tests/invoke/test_invalid_dependency.py0000644000000000000000000000212513615410400017474 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest import cappa from tests.utils import Backend, backends, invoke @backends def test_invalid_dependency(backend: Backend): def command(levels: int): return levels @cappa.command(invoke=command) @dataclass class Command: ... with pytest.raises(RuntimeError) as e: invoke(Command, backend=backend) exc = e.value cause = exc.__cause__ assert "due to resolution failure" in str(exc) assert "`levels: int` is not a valid dependency for Dep(command)." == str(cause) @backends def test_unannotated_argument(backend: Backend): def command(levels): # pyright: ignore return levels # pyright: ignore @cappa.command(invoke=command) # pyright: ignore @dataclass class Command: ... with pytest.raises(RuntimeError) as e: invoke(Command, backend=backend) exc = e.value cause = exc.__cause__ assert "due to resolution failure" in str(exc) assert "`levels: ` is not a valid dependency for Dep(command)." == str(cause) cappa-0.32.2/tests/invoke/test_invoke_async_with_external_exit_stack.py0000644000000000000000000000174213615410400023517 0ustar00from __future__ import annotations import asyncio import contextlib import io from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends @contextlib.contextmanager def binary_io(): with io.BytesIO() as buffer: yield buffer def command( binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)], ): assert not binary_io.closed binary_io.write(b"hello from command") return binary_io @cappa.command(invoke=command) @dataclass class Command: pass @backends def test(backend: Backend): async def run_test(): async with contextlib.AsyncExitStack() as stack: buffer = await cappa.invoke_async( Command, argv=[], backend=backend, exit_stack=stack ) assert not buffer.closed assert buffer.getvalue() == b"hello from command" return buffer buffer = asyncio.run(run_test()) assert buffer.closed cappa-0.32.2/tests/invoke/test_invoke_async_with_internal_exit_stack_works.py0000644000000000000000000000127113615410400024733 0ustar00from __future__ import annotations import asyncio import contextlib import io from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends @contextlib.contextmanager def binary_io(): with io.BytesIO() as buffer: yield buffer def command( binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)], ): assert not binary_io.closed binary_io.write(b"hello from command") return binary_io @cappa.command(invoke=command) @dataclass class Command: pass @backends def test(backend: Backend): buffer = asyncio.run(cappa.invoke_async(Command, argv=[], backend=backend)) assert buffer.closed cappa-0.32.2/tests/invoke/test_invoke_with_external_exit_stack.py0000644000000000000000000000152613615410400022322 0ustar00from __future__ import annotations import contextlib import io from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends @contextlib.contextmanager def binary_io(): with io.BytesIO() as buffer: yield buffer def command( binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)], ): assert not binary_io.closed binary_io.write(b"hello from command") return binary_io @cappa.command(invoke=command) @dataclass class Command: pass @backends def test(backend: Backend): with contextlib.ExitStack() as stack: buffer: io.BytesIO = cappa.invoke( Command, argv=[], backend=backend, exit_stack=stack ) assert not buffer.closed assert buffer.getvalue() == b"hello from command" assert buffer.closed cappa-0.32.2/tests/invoke/test_invoke_with_internal_exit_stack_works.py0000644000000000000000000000122713615410400023537 0ustar00from __future__ import annotations import contextlib import io from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends @contextlib.contextmanager def binary_io(): with io.BytesIO() as buffer: yield buffer def command( binary_io: Annotated[io.BytesIO, cappa.Dep(binary_io)], ): assert not binary_io.closed binary_io.write(b"hello from command") return binary_io @cappa.command(invoke=command) @dataclass class Command: pass @backends def test(backend: Backend): buffer = cappa.invoke(Command, argv=[], backend=backend) assert buffer.closed cappa-0.32.2/tests/invoke/test_nested_implicit_dep.py0000644000000000000000000000167713615410400017667 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke def foo(tlc: TopLevelCommand, subcommand: Subcommand, foo: Foo): return (tlc, subcommand, foo) @cappa.command(invoke=foo) @dataclass class Foo: ... @dataclass class Subcommand: cmd: Annotated[Foo, cappa.Subcommand] @cappa.command(invoke=lambda: 4) @dataclass class TopLevelCommand: cmd: Annotated[Union[Subcommand, None], cappa.Subcommand] = None @backends def test_every_dependency_level(backend: Backend): result = invoke(TopLevelCommand, "subcommand", "foo", backend=backend) assert result == ( TopLevelCommand(cmd=Subcommand(cmd=Foo())), Subcommand(cmd=Foo()), Foo(), ) @backends def test_no_subcmd(backend: Backend): result = invoke(TopLevelCommand, backend=backend) assert result == 4 cappa-0.32.2/tests/invoke/test_only_requires_leaf_invoke_fn.py0000644000000000000000000000104713615410400021577 0ustar00from dataclasses import dataclass import cappa from tests.utils import Backend, backends, invoke @dataclass class SubSubCommand: """Sub-subcommand.""" def __call__(self) -> int: return 4 @dataclass class SubCommand: subcommand: cappa.Subcommands[SubSubCommand] @dataclass class MainCommand: subcommand: cappa.Subcommands[SubCommand] @backends def test_parent_commands_dont_require_invoke_fn(backend: Backend): result = invoke(MainCommand, "sub-command", "sub-sub-command", backend=backend) assert result == 4 cappa-0.32.2/tests/invoke/test_output.py0000644000000000000000000000367713615410400015225 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, CapsysOutput, backends, invoke @backends def test_outputs_output(backend: Backend, capsys: Any): @dataclass class TopLevelCommand: def __call__(self, out: cappa.Output): out.output("woah!") out.error("woops!") invoke(TopLevelCommand, backend=backend) outerr = capsys.readouterr() assert outerr.out == "woah!\n" assert outerr.err == "Error: woops!\n" @backends def test_output_callable(backend: Backend, capsys: Any): @dataclass class TopLevelCommand: def __call__(self, out: cappa.Output): out("woah!") invoke(TopLevelCommand, backend=backend) out = CapsysOutput.from_capsys(capsys) out.stdout = "woah!\n" out.stderr = "" def _raise_value_error(value: str) -> str: raise ValueError("nope") @backends def test_invoke_routes_parse_errors_through_output(backend: Backend, capsys: Any): """A parse= callback that raises should produce a visible error on stderr. Regression test: previously `invoke()` (and `invoke_async`/`parse`) did not pass `output=` into the resolution of `parse_result.instance`, so any `cappa.Exit` raised from an `Arg(parse=...)` callback would be caught by `Resolved.handle_exit` with `output=None` and silently re-raised, producing a non-zero exit with no message. """ @dataclass class Cmd: x: Annotated[ str, cappa.Arg(parse=_raise_value_error, parse_inference=False, long="--x"), ] = "default" def __call__(self) -> None: pass with pytest.raises(cappa.Exit) as e: invoke(Cmd, "--x", "foo", backend=backend) assert e.value.code == 2 out = CapsysOutput.from_capsys(capsys) assert "Invalid value for '--x': nope" in out.stderr cappa-0.32.2/tests/invoke/test_parse_async_with_external_exit_stack.py0000644000000000000000000000147213615410400023336 0ustar00from __future__ import annotations import asyncio import contextlib from dataclasses import dataclass from typing import Any import cappa from tests.utils import Backend, backends @dataclass class Command: value: str = "test" closed: bool = False async def __aenter__(self): return self async def __aexit__(self, *args: Any): object.__setattr__(self, "closed", True) @backends def test(backend: Backend): async def run_test(): async with contextlib.AsyncExitStack() as stack: result = await cappa.parse_async( Command, argv=[], backend=backend, exit_stack=stack ) assert not result.closed assert result.value == "test" return result result = asyncio.run(run_test()) assert result.closed cappa-0.32.2/tests/invoke/test_parse_with_external_exit_stack.py0000644000000000000000000000117613615410400022142 0ustar00from __future__ import annotations import contextlib from dataclasses import dataclass from typing import Any import cappa from tests.utils import Backend, backends @dataclass class Command: value: str = "test" closed: bool = False def __enter__(self): return self def __exit__(self, *args: Any): object.__setattr__(self, "closed", True) @backends def test(backend: Backend): with contextlib.ExitStack() as stack: result = cappa.parse(Command, argv=[], backend=backend, exit_stack=stack) assert not result.closed assert result.value == "test" assert result.closed cappa-0.32.2/tests/invoke/test_self.py0000644000000000000000000000214013615410400014576 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke def foo(command: cappa.Self[HasDefault]): print(f"called with {command.default}") @dataclass class HasDefault: default: Annotated[int, cappa.Arg(short=True)] = 1 def __call__(self): return 2 @dataclass class Command(HasDefault): sub: cappa.Subcommands[Foo | Bar | None] = None @dataclass class Foo(HasDefault): default: Annotated[int, cappa.Arg(short=True)] = 2 @dataclass class Bar(HasDefault): default: Annotated[int, cappa.Arg(short=True)] = 3 @backends def test_typing_self(capsys: Any, backend: Backend): invoke(Command, deps=[foo], backend=backend) out = capsys.readouterr().out assert "called with 1" in out invoke(Command, "foo", deps=[foo], backend=backend) out = capsys.readouterr().out assert "called with 2" in out invoke(Command, "bar", deps=[foo], backend=backend) out = capsys.readouterr().out assert "called with 3" in out cappa-0.32.2/tests/invoke/test_stubbing_deps.py0000644000000000000000000000127613615410400016506 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, invoke def two(): return 2 def three(_: Command): return 3 def one(two: Annotated[int, cappa.Dep(two)], three: Annotated[int, cappa.Dep(three)]): return (two, three) def command(one: Annotated[int, cappa.Dep(one)]): return one @cappa.command(invoke=command) @dataclass class Command: ... @backends def test_invoke_top_level_command(backend: Backend): def stub_two(): return 4 result = invoke(Command, deps={two: cappa.Dep(stub_two), three: 6}, backend=backend) assert result == (4, 6) cappa-0.32.2/tests/invoke/test_type_alias_dep.py0000644000000000000000000000076013615410400016635 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated, TypeAliasType import cappa from tests.utils import Backend, backends, invoke def foo(): return 45 Number = TypeAliasType("Number", Annotated[int, cappa.Dep(foo)]) @dataclass class ArgTest: def __call__(self, value: Number) -> int: return value + 1 @backends def test_scalar(backend: Backend): test = invoke(ArgTest, backend=backend) assert test == 46 cappa-0.32.2/tests/parser/__init__.py0000644000000000000000000000000013615410400014337 0ustar00cappa-0.32.2/tests/parser/test_bad_choice_assistance.py0000644000000000000000000000166013615410400020131 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from cappa.parser import backend from tests.utils import Backend, backends, parse @backends def test_invalid_choice_help(backend: Backend): @dataclass class Args: value: Annotated[str, cappa.Arg(long=True)] with pytest.raises(cappa.Exit) as e: parse(Args, "--value", "ok", "--meow", "wat", backend=backend) assert e.value.code == 2 assert "unrecognized arguments: --meow" in str(e.value.message).lower() def test_invalid_choice_help_possible_options(): @dataclass class Args: value: Annotated[str, cappa.Arg(long=True)] with pytest.raises(cappa.Exit) as e: parse(Args, "--val", "on", backend=backend) assert e.value.code == 2 assert "Unrecognized arguments: --val (Did you mean: --value)" in str( e.value.message ) cappa-0.32.2/tests/parser/test_concatenated_short_options.py0000644000000000000000000000264513615410400021302 0ustar00from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Args: arg: Annotated[int, cappa.Arg(short="-a", long=True)] foo: Annotated[bool, cappa.Arg(short="-f", long=True)] = False bar: Annotated[bool, cappa.Arg(short="-b", long=True)] = False @backends def test_single_option_value_no_space(backend: Backend): args = parse(Args, "-a0", backend=backend) assert args == Args(arg=0, foo=False, bar=False) @backends def test_single_option_value_equals(backend: Backend): args = parse(Args, "-a=0", backend=backend) assert args == Args(arg=0, foo=False, bar=False) @backends def test_multiple_option_no_space(backend: Backend): args = parse(Args, "-fba0", backend=backend) assert args == Args(arg=0, foo=True, bar=True) @backends def test_distinguish_short_args_with_args_from_without(backend: Backend): # Only short args which consume no additional values (i.e. flags) should # be captured as concatenated short args. With arg which consumes a value, # subsequent characters should be given as part of the "arg" portion of # the value @dataclass class Args: arg: Annotated[str, cappa.Arg(short="-a")] foo: Annotated[bool, cappa.Arg(short="-f")] = False args = parse(Args, "-fahello world", backend=backend) assert args == Args(arg="hello world", foo=True) cappa-0.32.2/tests/parser/test_custom_callable_action.py0000644000000000000000000000460613615410400020345 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any, Union import pytest from typing_extensions import Annotated import cappa from cappa.parser import Value from tests.utils import Backend, backends, parse ################################ def exit(): raise cappa.Exit("message") @backends def test_callable_action_fast_exits(backend: Backend): @dataclass class Args: value: Annotated[str, cappa.Arg(action=exit, short=True)] with pytest.raises(cappa.Exit) as e: parse(Args, "-v", "the", "rest", "is", "trash", backend=backend) assert e.value.message == "message" ################################ def truncate(value: Value[str]): return value.value[1:] @backends def test_uses_return_value(backend: Backend): @dataclass class Args: value: Annotated[str, cappa.Arg(action=truncate, short=True)] args = parse(Args, "-v", "okay", backend=backend) assert args.value == "kay" @backends def test_custom_arg(backend: Backend): @dataclass class Args: value: Annotated[str, cappa.Arg(action=truncate)] args = parse(Args, "okay", backend=backend) assert args.value == "kay" ################################ def command_name(command: cappa.Command[Any]): return command.real_name() @dataclass class SubBSub: value: Annotated[str, cappa.Arg(action=command_name, short=True)] @dataclass class SubA: value: Annotated[str, cappa.Arg(action=command_name, short=True)] @dataclass class SubB: cmd: cappa.Subcommands[SubBSub] @backends def test_subcommand_name(backend: Backend): @dataclass class Args: cmd: cappa.Subcommands[Union[SubA, SubB]] args = parse(Args, "sub-a", "-v", "one", backend=backend) assert isinstance(args.cmd, SubA) assert args.cmd.value == "sub-a" args = parse(Args, "sub-b", "sub-b-sub", "-v", "one", backend=backend) assert isinstance(args.cmd, SubB) assert args.cmd.cmd.value == "sub-b-sub" ################################ def custom_out(out: cappa.Output): out.output("woah") return 1 @backends def test_custom_action_output_dep(backend: Backend, capsys: Any): @dataclass class Args: value: Annotated[int, cappa.Arg(action=custom_out, short=True)] args = parse(Args, "-v", "one", backend=backend) assert args.value == 1 out = capsys.readouterr().out assert out == "woah\n" cappa-0.32.2/tests/parser/test_double_dash.py0000644000000000000000000000103513615410400016121 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_collect_post_dash_args(backend: Backend): @dataclass class Args: foo: str raw: Annotated[Union[list[str], None], cappa.Arg(num_args=-1)] = None test = parse(Args, "foovalue", "--", "--raw", "value", backend=backend) assert test.foo == "foovalue" assert test.raw == ["--raw", "value"] cappa-0.32.2/tests/parser/test_double_dash_following_invalid_flag.py0000644000000000000000000000127113615410400022702 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_(backend: Backend): @dataclass class Args: foo: Annotated[str, cappa.Arg(long=True)] raw: Annotated[Union[list[str], None], cappa.Arg(num_args=-1)] = None with pytest.raises(cappa.Exit) as e: parse(Args, "--foo", "--", "value", backend=backend) message = str(e.value.message) if backend: assert "--foo: expected one argument" in message else: assert "Option 'foo' requires an argument" in message cappa-0.32.2/tests/parser/test_double_dash_is_first_arg.py0000644000000000000000000000123613615410400020657 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_(backend: Backend): @dataclass class Args: foo: Annotated[bool, cappa.Arg(long=True)] = False raw: Annotated[Union[list[str], None], cappa.Arg(num_args=-1)] = None test = parse(Args, "--foo", "--", "value", backend=backend) assert test.foo is True assert test.raw == ["value"] test = parse(Args, "--", "--foo", "value", backend=backend) assert test.foo is False assert test.raw == ["--foo", "value"] cappa-0.32.2/tests/parser/test_explicit_equal_option.py0000644000000000000000000000125013615410400020247 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_unrecognized_post_dash_arg(backend: Backend): @dataclass class Args: value: Annotated[str, cappa.Arg(long=True)] result = parse(Args, "--value=val", backend=backend) assert result == Args(value="val") @backends def test_value_contains_equal(backend: Backend): @dataclass class Args: value: Annotated[str, cappa.Arg(long=True)] result = parse(Args, "--value='var == val'", backend=backend) assert result == Args(value="'var == val'") cappa-0.32.2/tests/parser/test_interspersed_options.py0000644000000000000000000000112113615410400020126 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_invalid_choice_help(backend: Backend): @dataclass class Args: arg: str option: Annotated[str, cappa.Arg(long=True)] arg2: str option2: Annotated[str, cappa.Arg(long=True)] result = parse( Args, "arg", "--option=opt", "arg2", "--option2=opt2", backend=backend ) assert result == Args(arg="arg", option="opt", arg2="arg2", option2="opt2") cappa-0.32.2/tests/parser/test_missing_num_args.py0000644000000000000000000000142313615410400017215 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from cappa import argparse from tests.utils import Backend, backends, parse @backends def test_invalid_choice_help(backend: Backend): @dataclass class Args: arg: tuple[str, str] option: Annotated[str, cappa.Arg(long=True)] with pytest.raises(cappa.Exit) as e: parse(Args, "arg", "--option=opt", backend=backend) assert e.value.code == 2 message = str(e.value.message) if backend is argparse.backend: # pyright: ignore assert "the following arguments are required: arg" in message.lower() else: assert message == "Argument 'arg arg' requires 2 values, found 1 ('arg' so far)" cappa-0.32.2/tests/parser/test_multicharacter_short.py0000644000000000000000000000067513615410400020107 0ustar00from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @cappa.command(name="insiders") @dataclass class Args: arg: Annotated[int, cappa.Arg(short="-ab")] foo: Annotated[bool, cappa.Arg(short="-bc")] = False @backends def test_single_opt(backend: Backend): args = parse(Args, "-ab", "1", backend=backend) assert args == Args(arg=1, foo=False) cappa-0.32.2/tests/parser/test_negative_int_option.py0000644000000000000000000000161113615410400017714 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_negative_int_option(backend: Backend): @dataclass class Args: count: Annotated[int, cappa.Arg(long=True)] result = parse(Args, "--count", "-5", backend=backend) assert result == Args(count=-5) @backends def test_negative_float_option(backend: Backend): @dataclass class Args: value: Annotated[float, cappa.Arg(long=True)] result = parse(Args, "--value", "-3.14", backend=backend) assert result == Args(value=-3.14) @backends def test_negative_int_short_option(backend: Backend): @dataclass class Args: count: Annotated[int, cappa.Arg(short=True)] result = parse(Args, "-c", "-10", backend=backend) assert result == Args(count=-10) cappa-0.32.2/tests/parser/test_negative_positional.py0000644000000000000000000000156413615410400017722 0ustar00from __future__ import annotations from dataclasses import dataclass from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_negative_int_positional(backend: Backend): @dataclass class Args: value: int result = parse(Args, "-5", backend=backend) assert result == Args(value=-5) @backends def test_negative_float_positional(backend: Backend): @dataclass class Args: value: float result = parse(Args, "-2.718", backend=backend) assert result == Args(value=-2.718) @backends def test_negative_positional_with_option(backend: Backend): @dataclass class Args: verbose: Annotated[bool, cappa.Arg(short=True, default=False)] value: int result = parse(Args, "-v", "-42", backend=backend) assert result == Args(verbose=True, value=-42) cappa-0.32.2/tests/parser/test_negative_value_name.py0000644000000000000000000000207513615410400017653 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Literal from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_negative_value_name_as_choice(backend: Backend): @dataclass class Args: level: Literal["-4", "-2", "0", "2", "4"] result = parse(Args, "-4", backend=backend) assert result == Args(level="-4") @backends def test_negative_value_name_with_option(backend: Backend): @dataclass class Args: verbose: Annotated[bool, cappa.Arg(short=True, default=False)] level: Literal["-4", "-2", "0", "2", "4"] result = parse(Args, "-v", "-4", backend=backend) assert result == Args(verbose=True, level="-4") @backends def test_negative_value_name_string_arg(backend: Backend): @dataclass class Args: option: Annotated[str, cappa.Arg(long=True)] level: Literal["-4", "-2", "0"] result = parse(Args, "--option", "foo", "-4", backend=backend) assert result == Args(option="foo", level="-4") cappa-0.32.2/tests/parser/test_num_args.py0000644000000000000000000000405413615410400015467 0ustar00from __future__ import annotations from dataclasses import dataclass import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse def action(): raise cappa.Exit() @backends def test_single_opt(backend: Backend): @dataclass class Args: kill_switch: Annotated[ bool, cappa.Arg(long="--kill", action=action, num_args=0) ] required: str with pytest.raises(cappa.Exit) as e: parse(Args, "--kill", backend=backend) assert e.value.message is None assert e.value.code == 0 @backends def test_num_args_list(backend: Backend): @dataclass class Args: foo: Annotated[ list[str], cappa.Arg( short=True, default=["", ""], required=False, num_args=2, ), ] args = parse(Args, backend=backend) assert args == Args(["", ""]) args = parse(Args, "-f", "2", "4", backend=backend) assert args == Args(["2", "4"]) with pytest.raises(cappa.Exit) as e: parse(Args, "-f", backend=backend) assert e.value.code == 2 if backend: assert str(e.value.message).lower() == "argument -f: expected 2 arguments" else: assert e.value.message == "Argument '-f' requires 2 values, found 0" @backends def test_num_args_optional_value(backend: Backend): """NumArgs(required=False) distinguishes absent flag, flag-no-value, flag-with-value.""" @dataclass class Args: foo: Annotated[ int | None, cappa.Arg(long=True, num_args=cappa.NumArgs(required=False, default=None)), ] = 5 # Flag not given → class default args = parse(Args, backend=backend) assert args == Args(foo=5) # Flag given with no value → NumArgs.default (None) args = parse(Args, "--foo", backend=backend) assert args == Args(foo=None) # Flag given with value → parsed value args = parse(Args, "--foo", "4", backend=backend) assert args == Args(foo=4) cappa-0.32.2/tests/parser/test_option_after_bad_arg.py0000644000000000000000000000062113615410400020000 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest import cappa from tests.utils import parse @dataclass class Args: arg: str def test_option_after_bad_arg(capsys: Any): with pytest.raises(cappa.Exit): parse(Args, "arg1", "arg2", "--help") err = capsys.readouterr().err assert "Unrecognized arguments: arg2" in err cappa-0.32.2/tests/parser/test_short_concat_following_unbounded_positional.py0000644000000000000000000000102113615410400024715 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Literal from typing_extensions import Annotated import cappa from tests.utils import parse @dataclass class Args: flavors: list[Literal["chocolate", "vanilla"]] something: Annotated[int, cappa.Arg(short="-p")] = 9 def test_destructured(): test = parse( Args, "chocolate", "vanilla", "-p4", ) assert test == Args( flavors=["chocolate", "vanilla"], something=4, ) cappa-0.32.2/tests/parser/test_unbounded_num_args.py0000644000000000000000000000317413615410400017534 0ustar00from __future__ import annotations from dataclasses import dataclass, field from typing import Optional import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @backends def test_num_args_unbounded_length_num_args(backend: Backend): @dataclass class Args: a: Annotated[list[str], cappa.Arg(short=True, num_args=-1)] = field( default_factory=lambda: [] ) b: Annotated[str, cappa.Arg(short=True)] = field(default="") foo: Optional[str] = None # Unbounded length by itself t1 = parse(Args, backend=backend) assert t1 == Args(a=[], foo=None) # Unbounded length by itself t1 = parse(Args, "-a", "1", "2", "3", backend=backend) assert t1 == Args(a=["1", "2", "3"], foo=None) # An option can follow to terminate the option t1 = parse(Args, "-a", "1", "2", "3", "-b", "b", "foo", backend=backend) assert t1 == Args(a=["1", "2", "3"], b="b", foo="foo") # Or the -- separator can be used to terminate it t1 = parse(Args, "-a", "1", "2", "3", "--", "foo", backend=backend) assert t1 == Args(a=["1", "2", "3"], foo="foo") @backends def test_unbounded_positional_args(backend: Backend): @dataclass class Args: a: list[str] with pytest.raises(cappa.Exit) as e: parse(Args, backend=backend) error = str(e.value.message).lower() if backend: assert error == "the following arguments are required: a" else: assert error == "argument 'a' requires at least one values, found 0" t1 = parse(Args, "a", backend=backend) assert t1 == Args(["a"]) cappa-0.32.2/tests/parser/test_unregonized_argument.py0000644000000000000000000000102713615410400020104 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union import pytest import cappa from tests.utils import Backend, backends, parse @backends def test_unrecognized_post_dash_arg(backend: Backend): @dataclass class Args: foo: str raw: Union[str, None] = None with pytest.raises(cappa.Exit) as e: parse(Args, "foovalue", "raw", "raw2", backend=backend) assert e.value.code == 2 assert "unrecognized arguments: raw2" in str(e.value.message).lower() cappa-0.32.2/tests/py312/test_nested_type_alias.py0000644000000000000000000000111713615410400016727 0ustar00from __future__ import annotations import sys from dataclasses import dataclass from typing import Annotated import cappa from tests.utils import Backend, backends, invoke if sys.version_info >= (3, 12): def foo(): return 45 type Int = int # pyright: ignore type Number = Annotated[Int, cappa.Dep(foo)] # pyright: ignore @dataclass class ArgTest: def __call__(self, value: Number) -> int: return value + 1 @backends def test_scalar(backend: Backend): test = invoke(ArgTest, backend=backend) assert test == 46 cappa-0.32.2/tests/py312/test_type_alias.py0000644000000000000000000000164513615410400015373 0ustar00from __future__ import annotations import sys from dataclasses import dataclass from typing import Annotated, Literal import cappa from tests.utils import Backend, backends, parse if sys.version_info >= (3, 12): type Numbers = Literal[1, 2, 3] # pyright: ignore @backends def test_scalar(backend: Backend): @dataclass class ArgTest: value: Numbers test = parse(ArgTest, "1", backend=backend) assert test.value == 1 @backends def test_list_numbers(backend: Backend): @dataclass class ArgTest: value: list[Numbers] test = parse(ArgTest, "1", backend=backend) assert test.value == [1] @backends def test_annotated(backend: Backend): @dataclass class ArgTest: value: Annotated[Numbers, cappa.Arg()] test = parse(ArgTest, "1", backend=backend) assert test.value == 1 cappa-0.32.2/tests/py312/test_type_alias_dep.py0000644000000000000000000000105013615410400016211 0ustar00from __future__ import annotations import sys from dataclasses import dataclass from typing import Annotated import cappa from tests.utils import Backend, backends, invoke if sys.version_info >= (3, 12): def foo(): return 45 type Number = Annotated[int, cappa.Dep(foo)] # pyright: ignore @dataclass class ArgTest: def __call__(self, value: Number) -> int: return value + 1 @backends def test_scalar(backend: Backend): test = invoke(ArgTest, backend=backend) assert test == 46 cappa-0.32.2/tests/subcommand/__init__.py0000644000000000000000000000000013615410400015173 0ustar00cappa-0.32.2/tests/subcommand/test_aliases.py0000644000000000000000000001360113615410400016127 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any, Union import pytest import cappa from cappa.help import HelpFormatter, format_subcommand from cappa.subcommand import FinalSubcommand from tests.utils import Backend, backends, parse, parse_completion @cappa.command(aliases=["ls"]) @dataclass class List: pass @cappa.command( aliases=[cappa.Alias("rm", deprecated="use remove")], ) @dataclass class Remove: pass @dataclass class Tool: cmd: cappa.Subcommands[Union[List, Remove]] @backends def test_visible_alias_dispatches(backend: Backend): result = parse(Tool, "list", backend=backend) assert isinstance(result.cmd, List) result = parse(Tool, "ls", backend=backend) assert isinstance(result.cmd, List) @backends def test_canonical_still_dispatches(backend: Backend): result = parse(Tool, "remove", backend=backend) assert isinstance(result.cmd, Remove) @backends def test_deprecated_alias_warns(backend: Backend, capsys: Any): result = parse(Tool, "rm", backend=backend) assert isinstance(result.cmd, Remove) err = capsys.readouterr().err assert "Command alias `rm` is deprecated: use remove" in err @backends def test_canonical_does_not_warn(backend: Backend, capsys: Any): parse(Tool, "remove", backend=backend) err = capsys.readouterr().err assert "deprecated" not in err @cappa.command(aliases=[cappa.Alias("del", deprecated=True)]) @dataclass class DeleteBool: pass @dataclass class DeleteBoolRoot: cmd: cappa.Subcommands[DeleteBool] @backends def test_deprecated_alias_bool_warns_without_suffix(backend: Backend, capsys: Any): """`deprecated=True` (bool) emits the default message with no `:` suffix.""" result = parse(DeleteBoolRoot, "del", backend=backend) assert isinstance(result.cmd, DeleteBool) err = capsys.readouterr().err assert "Command alias `del` is deprecated" in err assert "Command alias `del` is deprecated:" not in err # --- Hidden alias --- @cappa.command(aliases=[cappa.Alias("dir", hidden=True)]) @dataclass class HiddenList: pass @dataclass class HiddenTool: cmd: cappa.Subcommands[HiddenList] @backends def test_hidden_alias_dispatches(backend: Backend): result = parse(HiddenTool, "dir", backend=backend) assert isinstance(result.cmd, HiddenList) @backends def test_hidden_alias_not_in_help(backend: Backend, capsys: Any): with pytest.raises(cappa.Exit): parse(HiddenTool, "--help", backend=backend) out = capsys.readouterr().out assert "dir" not in out assert "hidden-list" in out # --- Completion --- def test_completion_includes_visible_alias(): result = parse_completion(Tool, "") assert result is not None assert "list" in result assert "ls" in result assert "remove" in result assert "rm" in result def test_completion_excludes_hidden_alias(): result = parse_completion(HiddenTool, "") assert result is not None assert "hidden-list" in result assert "dir" not in result # --- Help rendering --- @backends def test_visible_aliases_in_help(backend: Backend, capsys: Any): with pytest.raises(cappa.Exit): parse(Tool, "--help", backend=backend) out = capsys.readouterr().out assert "list, ls" in out assert "remove, rm (deprecated)" in out # --- Imperative construction --- @dataclass class ImpA: pass @dataclass class ImpB: pass @backends def test_imperative_aliases(backend: Backend): """Aliases declared on Command directly (not via decorator) work the same way.""" sub = cappa.Subcommand( field_name="cmd", options={ "imp-a": cappa.Command(ImpA, name="imp-a", aliases=["a"]), "imp-b": cappa.Command(ImpB, name="imp-b", aliases=["b"]), }, ) @dataclass class ImpRoot: cmd: cappa.Subcommands[Union[ImpA, ImpB]] cmd = cappa.Command(ImpRoot, arguments=[sub]) result = parse(cmd, "a", backend=backend) assert isinstance(result.cmd, ImpA) result = parse(cmd, "b", backend=backend) assert isinstance(result.cmd, ImpB) # --- Collisions --- @cappa.command(aliases=["self-collide"]) @dataclass class SelfCollide: pass @dataclass class SelfCollideRoot: cmd: cappa.Subcommands[SelfCollide] def test_alias_collides_with_own_canonical_name(): with pytest.raises(ValueError, match="matching its own name"): cappa.collect(SelfCollideRoot) @cappa.command(name="collide-x") @dataclass class CollideX: pass @cappa.command(aliases=["collide-x"]) @dataclass class CollideY: pass @dataclass class CollideXYRoot: cmd: cappa.Subcommands[Union[CollideX, CollideY]] def test_alias_collides_with_other_canonical_name(): with pytest.raises(ValueError, match="collides with another subcommand"): cappa.collect(CollideXYRoot) @cappa.command(aliases=["shared"]) @dataclass class CollideA: pass @cappa.command(aliases=["shared"]) @dataclass class CollideB: pass @dataclass class CollideABRoot: cmd: cappa.Subcommands[Union[CollideA, CollideB]] def test_alias_collides_with_other_alias(): with pytest.raises(ValueError, match="declared on both"): cappa.collect(CollideABRoot) # --- Defensive helpers (direct unit calls) --- def test_visible_aliases_for_unknown_canonical_returns_empty(): """`visible_aliases_for` returns [] when given a name that isn't a subcommand.""" sub = FinalSubcommand(field_name="cmd", options={}) assert sub.visible_aliases_for("not-a-subcommand") == [] def test_format_subcommand_without_subcommand_arg(): """`format_subcommand` works with the default `subcommand=None`.""" cmd = cappa.Command(List).collect() padding, help_text = format_subcommand(HelpFormatter(), cmd) rendered = str(padding.renderable) assert "list" in rendered assert "ls" not in rendered # aliases only appear when subcommand is provided assert help_text == cmd.help cappa-0.32.2/tests/subcommand/test_base.py0000644000000000000000000000433413615410400015423 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class RequiredMissingOne: foo: Annotated[Union[str, None], cappa.Arg(long=True)] = None @dataclass class RequiredMissing: subcommand: Annotated[RequiredMissingOne, cappa.Subcommand] @backends def test_required_missing(backend: Backend): with pytest.raises(cappa.Exit) as e: parse(RequiredMissing, backend=backend) assert isinstance(e.value.message, str) message = e.value.message.lower() if backend: assert "the following arguments are required: {required-missing-one}" in message else: assert ( "a command is required: {[cappa.subcommand]required-missing-one[/cappa.subcommand]}" in message ) @dataclass class RequiredProvidedOne: foo: Annotated[Union[str, None], cappa.Arg(long=True)] = None @dataclass class RequiredProvidedTwo: bar: Annotated[Union[str, None], cappa.Arg(long=True)] = None @dataclass class RequiredProvided: subcommand: Annotated[ Union[RequiredProvidedOne, RequiredProvidedTwo], cappa.Subcommand() ] @backends def test_required_provided(backend: Backend): test = parse( RequiredProvided, "required-provided-one", "--foo", "foo", backend=backend ) assert isinstance(test.subcommand, RequiredProvidedOne) assert test.subcommand.foo == "foo" test = parse(RequiredProvided, "required-provided-two", backend=backend) assert isinstance(test.subcommand, RequiredProvidedTwo) assert test.subcommand.bar is None test = parse( RequiredProvided, "required-provided-two", "--bar", "bar", backend=backend ) assert isinstance(test.subcommand, RequiredProvidedTwo) assert test.subcommand.bar == "bar" @cappa.command(name="one") @dataclass class NamedSubcommandOne: pass @dataclass class NamedSubcommand: subcommand: Annotated[NamedSubcommandOne, cappa.Subcommand()] @backends def test_named_subcommand(backend: Backend): test = parse(NamedSubcommand, "one", backend=backend) assert isinstance(test.subcommand, NamedSubcommandOne) cappa-0.32.2/tests/subcommand/test_deprecated.py0000644000000000000000000000320613615410400016606 0ustar00from __future__ import annotations import sys from dataclasses import dataclass from typing import Any import pytest import cappa from tests.utils import parse @cappa.command(deprecated=True) @dataclass class Sub: pass @dataclass class ArgTest: sub: cappa.Subcommands[Sub | None] = None def test_native_backend(capsys: Any): """Note, argparse only support deprecated at or above 3.13.""" result = parse(ArgTest) assert result.sub is None err = capsys.readouterr().err assert err == "" result = parse(ArgTest, "sub") assert result.sub == Sub() err = capsys.readouterr().err assert err == "Error: Command `sub` is deprecated\n" @pytest.mark.skipif(sys.version_info >= (3, 13), reason="requires python3.13 or higher") def test_argparse_le_313(capsys: Any): """Below 3.13, this option has no effect.""" result = parse(ArgTest, backend=cappa.argparse.backend) assert result.sub is None err = capsys.readouterr().err assert err == "" result = parse(ArgTest, "sub", backend=cappa.argparse.backend) assert result.sub == Sub() err = capsys.readouterr().err assert err == "" @pytest.mark.skipif( sys.version_info < (3, 13), reason="Below 3.13, the behavior is different" ) def test_argparse_ge_313(capsys: Any): result = parse(ArgTest, backend=cappa.argparse.backend) assert result.sub is None err = capsys.readouterr().err assert err == "" result = parse(ArgTest, "sub", backend=cappa.argparse.backend) assert result.sub == Sub() err = capsys.readouterr().err.replace("arg-test: ", "").strip() assert err == "warning: command 'sub' is deprecated" cappa-0.32.2/tests/subcommand/test_did_you_mean.py0000644000000000000000000000233713615410400017146 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Union import pytest from typing_extensions import Annotated import cappa from tests.utils import parse @dataclass class RequiredMissingOne: foo: Annotated[Union[str, None], cappa.Arg(long=True)] = None @dataclass class RequiredMissingTwo: foo: Annotated[Union[str, None], cappa.Arg(long=True)] = None @dataclass class RequiredMissing: subcommand: Annotated[ Union[RequiredMissingOne, RequiredMissingTwo], cappa.Subcommand ] def test_has_possible_values(): with pytest.raises(cappa.Exit) as e: parse(RequiredMissing, "req", backend=None) assert isinstance(e.value.message, str) expected_message = ( "Invalid command 'req' (Did you mean: " "[cappa.subcommand]required-missing-one[/cappa.subcommand], " "[cappa.subcommand]required-missing-two[/cappa.subcommand])" ) assert expected_message == e.value.message def test_no_possible_values(): with pytest.raises(cappa.Exit) as e: parse(RequiredMissing, "bad", backend=None) assert isinstance(e.value.message, str) expected_message = "Invalid command 'bad'" assert expected_message == e.value.message cappa-0.32.2/tests/subcommand/test_group.py0000644000000000000000000000260113615410400015640 0ustar00from __future__ import annotations import re from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from tests.utils import Backend, backends, parse @dataclass class Foo: ... @dataclass class Args: subcommand: Annotated[Foo, cappa.Subcommand(group="Yup")] @backends def test_required_missing(backend: Backend, capsys: Any): with pytest.raises(cappa.Exit) as e: parse(Args, "--help", backend=backend) assert e.value.code == 0 help = capsys.readouterr().out assert re.match(r".*Yup:?[\n\s]+\{?foo.*", help, re.DOTALL) @backends def test_explicit_group(backend: Backend, capsys: Any): @dataclass class Args: subcommand: Annotated[Foo, cappa.Subcommand(group=cappa.Group(name="Yup"))] with pytest.raises(cappa.Exit) as e: parse(Args, "--help", backend=backend) assert e.value.code == 0 help = capsys.readouterr().out assert re.match(r".*Yup:?[\n\s]+\{?foo.*", help, re.DOTALL) @backends def test_tuple_group(backend: Backend, capsys: Any): @dataclass class Args: subcommand: Annotated[Foo, cappa.Subcommand(group=(1, "Yup"))] with pytest.raises(cappa.Exit) as e: parse(Args, "--help", backend=backend) assert e.value.code == 0 help = capsys.readouterr().out assert re.match(r".*Yup:?[\n\s]+\{?foo.*", help, re.DOTALL) cappa-0.32.2/tests/subcommand/test_help.py0000644000000000000000000000136413615410400015441 0ustar00from __future__ import annotations import textwrap from dataclasses import dataclass from typing import Any import pytest import cappa from tests.utils import parse, strip_trailing_whitespace @dataclass class Sub: arg: str @dataclass class Args: command: cappa.Subcommands[Sub] def test_required_arg_subcommand_help(capsys: Any): """Assert short help is emitted for the subcommand (on bad input) rather than the root help.""" with pytest.raises(cappa.Exit) as e: parse(Args, "sub") assert e.value.code == 2 out = strip_trailing_whitespace(capsys.readouterr().err) assert out == textwrap.dedent( """\ Usage: args sub ARG [-h] Error: Option 'arg' requires an argument """ ) cappa-0.32.2/tests/subcommand/test_hidden.py0000644000000000000000000000134513615410400015743 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest import cappa from tests.utils import Backend, backends, parse @dataclass class One: ... @cappa.command(hidden=True) @dataclass class Two: ... @dataclass class Three: ... @dataclass class Command: sub: cappa.Subcommands[One | Two | Three] @backends def test_has_possible_values(capsys: Any, backend: Backend): with pytest.raises(cappa.Exit): parse(Command, "--help", backend=backend) out = capsys.readouterr().out assert "one" in out.lower() assert "two" not in out.lower() assert "three" in out.lower() cmd = parse(Command, "two", backend=backend) assert isinstance(cmd.sub, Two) cappa-0.32.2/tests/subcommand/test_method.py0000644000000000000000000000602613615410400015771 0ustar00from __future__ import annotations from dataclasses import dataclass from typing import Any import pytest from typing_extensions import Annotated import cappa from cappa.output import Exit from tests.utils import Backend, backends, invoke, parse, strip_trailing_whitespace def some_dep(): return 5 @cappa.command @dataclass class HasExecutableMethods: arg: int include_dep: bool = False @cappa.command def add(self, some_dep: Annotated[int, cappa.Dep(some_dep)]) -> int: """Add two numbers.""" if self.add: # type: ignore[truthy-function] return self.arg + some_dep return self.arg @cappa.command(help="Subtract two numbers") def subtract(self, other: int) -> int: if self.add: # type: ignore[truthy-function] return self.arg - other return self.arg @backends def test_parse(backend: Backend): result = parse(HasExecutableMethods, "10", "add", backend=backend) assert result == HasExecutableMethods(10, False) result = parse(HasExecutableMethods, "10", "subtract", "2", backend=backend) assert result == HasExecutableMethods(10, False) result = parse(HasExecutableMethods, "11", "--include-dep", "add", backend=backend) assert result == HasExecutableMethods(11, True) result = parse( HasExecutableMethods, "11", "--include-dep", "subtract", "2", backend=backend ) assert result == HasExecutableMethods(11, True) with pytest.raises(Exit) as e: parse(HasExecutableMethods, "10", backend=backend) message = str(e.value.message) assert "required" in message assert "add" in message assert "subtract" in message @backends def test_invoke_add(backend: Backend): result = invoke(HasExecutableMethods, "10", "add", backend=backend) assert result == 15 with pytest.raises(Exit) as e: invoke(HasExecutableMethods, "10", backend=backend) message = str(e.value.message) assert "required" in message assert "add" in message assert "subtract" in message @backends def test_invoke_subtract(backend: Backend): result = invoke(HasExecutableMethods, "10", "subtract", "7", backend=backend) assert result == 3 with pytest.raises(Exit) as e: invoke(HasExecutableMethods, "10", backend=backend) message = str(e.value.message) assert "required" in message assert "add" in message assert "subtract" in message @backends def test_help(backend: Backend, capsys: Any): with pytest.raises(Exit): invoke(HasExecutableMethods, "--help", backend=backend) out = strip_trailing_whitespace(capsys.readouterr().out) assert "Add two numbers" in out assert "Subtract two numbers" in out @backends def test_nested_method_invalid_ast_source(backend: Backend, capsys: Any): @cappa.command @dataclass class Example: @cappa.command def add(self): ... with pytest.raises(Exit): invoke(Example, "--help", backend=backend) out = strip_trailing_whitespace(capsys.readouterr().out) assert "add" in out cappa-0.32.2/tests/subcommand/test_subcommand_type.py0000644000000000000000000000106413615410400017677 0ustar00from __future__ import annotations import typing from dataclasses import dataclass import cappa from tests.utils import Backend, backends, parse @dataclass class Command: cmd: cappa.Subcommands[typing.Union[Foo, Bar]] @dataclass class Foo: f: int @dataclass class Bar: b: int @backends def test_subcommand_type_type_alias(backend: Backend): result = parse(Command, "foo", "4", backend=backend) assert result == Command(cmd=Foo(f=4)) result = parse(Command, "bar", "4", backend=backend) assert result == Command(cmd=Bar(b=4)) cappa-0.32.2/.gitignore0000644000000000000000000000011313615410400011565 0ustar00.coverage* coverage.xml *.pyc **/__pycache__/ docs/build/ **/_build dist/ cappa-0.32.2/LICENSE0000644000000000000000000002613513615410400010616 0ustar00 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. cappa-0.32.2/README.md0000644000000000000000000002444013615410400011065 0ustar00# Cappa [![Actions Status](https://github.com/DanCardin/cappa/actions/workflows/test.yml/badge.svg)](https://github.com/dancardin/cappa/actions) [![codecov](https://codecov.io/gh/DanCardin/cappa/graph/badge.svg?token=jCpAbqWQgU)](https://codecov.io/gh/DanCardin/cappa) [![Documentation Status](https://readthedocs.org/projects/cappa/badge/?version=latest)](https://cappa.readthedocs.io/en/latest/?badge=latest) - [Full Documentation](https://cappa.readthedocs.io/en/latest/) - [Comparison vs click/typer/argparse/etc](https://cappa.readthedocs.io/en/latest/comparison.html) - [Annotations Inference Docs](https://cappa.readthedocs.io/en/latest/annotation.html) - ["invoke" API Docs](https://cappa.readthedocs.io/en/latest/invoke.html) - [Class Compatibility (dataclasses/pydantic/etc)](https://cappa.readthedocs.io/en/latest/class_compatibility.html) Cappa is a declarative command line parsing library, which uses runtime type inspection to infer (default) CLI argument behavior, and provide automatic [help text](https://cappa.readthedocs.io/en/latest/help.html) generation and dynamic completion generation. It supports two different modes of execution: * `parse`: Argparse-like parsing of the arguments into an output structure * [invoke](https://cappa.readthedocs.io/en/latest/invoke.html): Click-like calling of functions based on the selected subcommand It also provides a [dependency injection system](https://cappa.readthedocs.io/en/latest/invoke.html#Invoke-Dependencies) for providing non-argument resources to the invoked commands. And, a number of different styles of CLI declaration (which can be mixed and matched within a given CLI): * Classes: The fields of the class correspond to CLI arguments/subcommands * Functions: The arguments of the function correspond to CLI arguments * Methods: The class fields correspond to CLI arguments, and the methods correspond to subcommands * Imperative Construction: The CLI structure can be manually/imperitavely constructed, rather than being inferred from the input structure

Class Based, parse

```python from __future__ import annotations from dataclasses import dataclass, field from typing import Literal from typing_extensions import Annotated import cappa @dataclass class Example: # Normal types are by default inferred as positional arguments positional: str = "optional" # Except boolean types, which are inferred as flags flag: bool = False # Option that only accepts the long name option: Annotated[int | None, cappa.Arg(long=True, help="A number")] = None # Option accepted many times many_options: Annotated[ list[Literal["one", "two", "three"]], cappa.Arg(short=True) ] = field(default_factory=list) # Single option, accepting many values three_values: Annotated[tuple[str, str, int] | None, cappa.Arg(long=True)] = None args = cappa.parse(Example, backend=cappa.backend) print(args) ``` Produces the following CLI: ![help text](./docs/source/_static/example.svg) With examples of its usage like: ```bash $ python example.py Example(positional='optional', flag=False, option=None, many_options=[], three_values=None) $ python example.py foo Example(positional='foo', flag=False, option=None, many_options=[], three_values=None) $ python example.py --flag Example(positional='optional', flag=True, option=None, many_options=[], three_values=None) $ python example.py --option 4 Example(positional="optional", flag=False, option=4, many_options=[], three_values=None) $ python example.py -m one -m three Example(positional='optional', flag=False, option=None, many_options=['one', 'three'], three_values=None) $ python example.py --three-values a b 4 Example(positional='optional', flag=False, option=None, many_options=[], three_values=('a', 'b', 4)) ``` In this way, you can turn any dataclass-like object (with some additional annotations, depending on what you're looking for) into a CLI. You'll note that `cappa.parse` returns an instance of the class. This API should feel very familiar to `argparse`, except that you get the fully typed dataclass instance back instead of a raw `Namespace`.

Class Based, invoke

["invoke" documentation](https://cappa.readthedocs.io/en/latest/invoke.html) The "invoke" API is meant to feel more like the experience you get when using `click` or `typer`. You can take the same dataclass, but register a function to be called on successful parsing of the command. ```python from dataclasses import dataclass import cappa from typing_extensions import Annotated def function(example: Example): print(example) @cappa.command(invoke=function) class Example: # identical to original class positional_arg: str boolean_flag: bool single_option: Annotated[int | None, cappa.Arg(long=True)] multiple_option: Annotated[list[str], cappa.Arg(short=True)] cappa.invoke(Example) ``` (Note the lack of the dataclass decorator. You can optionally omit or include it, and it will be automatically inferred). Alternatively you can make your dataclass callable, as a shorthand for an explicit invoke function: ```python @dataclass class Example: ... # identical to original class def __call__(self): print(self) ``` Note `invoke=function` can either be a reference to some callable, or a string module-reference to a function (which will get lazily imported and invoked). ## Subcommands With a single top-level command, the click-like API isn't particularly valuable by comparison. Click's command-centric API is primarily useful when composing a number of nested subcommands, and dispatching to functions based on the selected subcommand. ```python from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class Example: cmd: cappa.Subcommands[Print | Fail] @dataclass class Print: loudly: bool def __call__(self): # again, __call__ is shorthand for the above explicit `invoke=` form. if self.loudly: print("PRINTING!") else: print("printing!") def fail(): raise cappa.Exit(code=self.code) @cappa.command(invoke=fail) class Fail: code: int cappa.invoke(Example) ```

Functions, invoke

Purely function-based CLIs **can** reduce the ceremony required to define a given CLI command. Such a CLI is exactly equivalent to a CLI defined as a dataclass with the function's arguments as the dataclass's fields. ```python import cappa from typing_extensions import Annotated def function(foo: int, bar: bool, option: Annotated[str, cappa.Arg(long=True)] = "opt"): ... cappa.invoke(function) ``` There are, however, some downsides to using functions. Namely, that `function` has no nameable type! As such, a free function can not be easily named as a subcommand option (`Subcommand[Foo | Bar]`). You **can** define a root level function with class-based subcommands, but the reverse is not possible because there is no valid type you can supply in the subcommand union.

Methods, invoke

See also [Methods](https://cappa.readthedocs.io/en/latest/functions_and_methods.html#methods). ```python from __future__ import annotations from dataclasses import dataclass import cappa @cappa.command @dataclass class Example: arg: int @cappa.command def add(self, other: int) -> int: """Add two numbers.""" return self.arg + some_dep @cappa.command(help="Subtract two numbers") def subtract(self, other: int) -> int: return self.arg - other cappa.invoke(Example) ``` With methods, the enclosing class corresponds to the parent object CLI arguments, exactly like normal class based definition. Unlike with free functions, (explicitly annotated) methods are able to act as subcommands, who's arguments (similarly to free functions) act as the arguments for the subcommand. The above example produces a CLI like: ``` Usage: example ARG {add,subtract} [-h] [--completion COMPLETION] Arguments ARG Subcommands add Add two numbers. subtract Subtract two numbers. ```

Imperative Construction, parse/invoke

See also [Manual Construction](https://cappa.readthedocs.io/en/latest/manual_construction.html). ```python from dataclasses import dataclass import cappa @dataclass class Foo: bar: str baz: list[int] command = cappa.Command( Foo, arguments=[ cappa.Arg(field_name="bar"), cappa.Arg(field_name="baz", num_args=2), ], help="Short help.", description="Long description.", ) result = cappa.parse(command, argv=["one", "2", "3"]) ``` All other APIs of cappa amount to scanning the provided input structure, and producing a `cappa.Command` structure. As such, it's equally possible for users to manually construct the commands themselves. This could also be used to extend cappa, or design even more alternative interfaces ([Cleo](https://github.com/python-poetry/cleo) is another, fairly different, option that comes to mind).
## Inspirations Credit where credit is due * The "Derive" API of the Rust library [Clap](https://docs.rs/clap/latest/clap/_derive/index.html) directly inspired the concept of mapping a type's fields to the shape of the CLI, by inferring the default behavior from introspecting types. * Click's easy way of defining large graphs of subcommands and mapping them to functions, inspired the the "invoke" API of Cappa. The actual APIs dont particularly resemble one another, but subcommands directly triggering functions (in contrast to argparse/Clap) is a very nice, and natural seeming feature! * FastAPI's `Depends` system inspired Cappa's dependency injection system. This API is quite natural, and makes it very easy to define a complex system of ad-hoc dependencies **without** the upfront wiring cost of most DI frameworks. cappa-0.32.2/pyproject.toml0000644000000000000000000000473113615410400012523 0ustar00[project] name = "cappa" version = "0.32.2" description = "Declarative CLI argument parser." urls = { repository = "https://github.com/dancardin/cappa" } authors = [{ name = "Dan Cardin", email = "ddcardin@gmail.com" }] license = { file = "LICENSE" } readme = "README.md" keywords = ["CLI", "parser", "argparse", "click", "typer"] classifiers = ["Topic :: Software Development :: Libraries :: Python Modules"] requires-python = ">=3.8,<4" dependencies = [ "rich >= 12.1.0", "typing-extensions >= 4.8.0", "typing-extensions >= 4.12.0; python_version >= '3.13'", "type-lens >= 0.2.5", ] [project.optional-dependencies] docstring = ["docstring-parser >= 0.15", "docstring-parser >= 0.17; python_version >= '3.14'"] [dependency-groups] dev = [ "pytest >=8.1.1,<9", "coverage >= 7.3.0", "mypy >= 1.0.0", "pyright >= 1.1.398", "ruff >= 0.9.0", "docutils >= 0.20.0", "sphinx >= 7.1.0", "types-docutils >= 0.20.0", "attrs >= 18.0", "pydantic >= 1.8.0; python_version < '3.14'", "msgspec >= 0.19.0; python_version >= '3.9'", ] [tool.ruff] target-version = "py38" exclude = ["tests/py312"] [tool.ruff.lint] select = ["C", "D", "E", "F", "I", "N", "Q", "RET", "RUF", "S", "T", "UP", "YTT"] ignore = ["C901", "E501", "S101", "D1", "D203", "D213", "D406", "D407", "D408", "D409", "D413"] [tool.ruff.lint.per-file-ignores] "tests/*" = ["T201"] "src/cappa/parser.py" = ["N818"] [tool.ruff.lint.pyupgrade] keep-runtime-typing = true [tool.ruff.format] docstring-code-format = true [tool.coverage.report] show_missing = true skip_covered = true exclude_lines = [ "pragma: no cover", "if TYPE_CHECKING:", "if typing.TYPE_CHECKING:", "if __name__ == .__main__.:", "assert_never", "@overload", ] [tool.coverage.run] branch = true parallel = true include = ["src/*"] [tool.mypy] strict_optional = true ignore_missing_imports = true warn_unused_ignores = true incremental = true check_untyped_defs = true exclude = ["tests/py312"] [tool.pyright] typeCheckingMode = "strict" [tool.typos.default] extend-ignore-re = [".*#.*typos: ignore[^\\n]*\\n"] [tool.typos.default.extend-identifiers] typ = "typ" [tool.pytest.ini_options] doctest_optionflags = "NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL ELLIPSIS" addopts = "--doctest-modules -vv --ff --strict-markers" norecursedirs = ".* build dist *.egg bin" pytester_example_dir = "examples" markers = ["help", "argparse"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" cappa-0.32.2/PKG-INFO0000644000000000000000000005726013615410400010711 0ustar00Metadata-Version: 2.4 Name: cappa Version: 0.32.2 Summary: Declarative CLI argument parser. Project-URL: repository, https://github.com/dancardin/cappa Author-email: Dan Cardin License: Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. License-File: LICENSE Keywords: CLI,argparse,click,parser,typer Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: <4,>=3.8 Requires-Dist: rich>=12.1.0 Requires-Dist: type-lens>=0.2.5 Requires-Dist: typing-extensions>=4.12.0; python_version >= '3.13' Requires-Dist: typing-extensions>=4.8.0 Provides-Extra: docstring Requires-Dist: docstring-parser>=0.15; extra == 'docstring' Requires-Dist: docstring-parser>=0.17; (python_version >= '3.14') and extra == 'docstring' Description-Content-Type: text/markdown # Cappa [![Actions Status](https://github.com/DanCardin/cappa/actions/workflows/test.yml/badge.svg)](https://github.com/dancardin/cappa/actions) [![codecov](https://codecov.io/gh/DanCardin/cappa/graph/badge.svg?token=jCpAbqWQgU)](https://codecov.io/gh/DanCardin/cappa) [![Documentation Status](https://readthedocs.org/projects/cappa/badge/?version=latest)](https://cappa.readthedocs.io/en/latest/?badge=latest) - [Full Documentation](https://cappa.readthedocs.io/en/latest/) - [Comparison vs click/typer/argparse/etc](https://cappa.readthedocs.io/en/latest/comparison.html) - [Annotations Inference Docs](https://cappa.readthedocs.io/en/latest/annotation.html) - ["invoke" API Docs](https://cappa.readthedocs.io/en/latest/invoke.html) - [Class Compatibility (dataclasses/pydantic/etc)](https://cappa.readthedocs.io/en/latest/class_compatibility.html) Cappa is a declarative command line parsing library, which uses runtime type inspection to infer (default) CLI argument behavior, and provide automatic [help text](https://cappa.readthedocs.io/en/latest/help.html) generation and dynamic completion generation. It supports two different modes of execution: * `parse`: Argparse-like parsing of the arguments into an output structure * [invoke](https://cappa.readthedocs.io/en/latest/invoke.html): Click-like calling of functions based on the selected subcommand It also provides a [dependency injection system](https://cappa.readthedocs.io/en/latest/invoke.html#Invoke-Dependencies) for providing non-argument resources to the invoked commands. And, a number of different styles of CLI declaration (which can be mixed and matched within a given CLI): * Classes: The fields of the class correspond to CLI arguments/subcommands * Functions: The arguments of the function correspond to CLI arguments * Methods: The class fields correspond to CLI arguments, and the methods correspond to subcommands * Imperative Construction: The CLI structure can be manually/imperitavely constructed, rather than being inferred from the input structure

Class Based, parse

```python from __future__ import annotations from dataclasses import dataclass, field from typing import Literal from typing_extensions import Annotated import cappa @dataclass class Example: # Normal types are by default inferred as positional arguments positional: str = "optional" # Except boolean types, which are inferred as flags flag: bool = False # Option that only accepts the long name option: Annotated[int | None, cappa.Arg(long=True, help="A number")] = None # Option accepted many times many_options: Annotated[ list[Literal["one", "two", "three"]], cappa.Arg(short=True) ] = field(default_factory=list) # Single option, accepting many values three_values: Annotated[tuple[str, str, int] | None, cappa.Arg(long=True)] = None args = cappa.parse(Example, backend=cappa.backend) print(args) ``` Produces the following CLI: ![help text](./docs/source/_static/example.svg) With examples of its usage like: ```bash $ python example.py Example(positional='optional', flag=False, option=None, many_options=[], three_values=None) $ python example.py foo Example(positional='foo', flag=False, option=None, many_options=[], three_values=None) $ python example.py --flag Example(positional='optional', flag=True, option=None, many_options=[], three_values=None) $ python example.py --option 4 Example(positional="optional", flag=False, option=4, many_options=[], three_values=None) $ python example.py -m one -m three Example(positional='optional', flag=False, option=None, many_options=['one', 'three'], three_values=None) $ python example.py --three-values a b 4 Example(positional='optional', flag=False, option=None, many_options=[], three_values=('a', 'b', 4)) ``` In this way, you can turn any dataclass-like object (with some additional annotations, depending on what you're looking for) into a CLI. You'll note that `cappa.parse` returns an instance of the class. This API should feel very familiar to `argparse`, except that you get the fully typed dataclass instance back instead of a raw `Namespace`.

Class Based, invoke

["invoke" documentation](https://cappa.readthedocs.io/en/latest/invoke.html) The "invoke" API is meant to feel more like the experience you get when using `click` or `typer`. You can take the same dataclass, but register a function to be called on successful parsing of the command. ```python from dataclasses import dataclass import cappa from typing_extensions import Annotated def function(example: Example): print(example) @cappa.command(invoke=function) class Example: # identical to original class positional_arg: str boolean_flag: bool single_option: Annotated[int | None, cappa.Arg(long=True)] multiple_option: Annotated[list[str], cappa.Arg(short=True)] cappa.invoke(Example) ``` (Note the lack of the dataclass decorator. You can optionally omit or include it, and it will be automatically inferred). Alternatively you can make your dataclass callable, as a shorthand for an explicit invoke function: ```python @dataclass class Example: ... # identical to original class def __call__(self): print(self) ``` Note `invoke=function` can either be a reference to some callable, or a string module-reference to a function (which will get lazily imported and invoked). ## Subcommands With a single top-level command, the click-like API isn't particularly valuable by comparison. Click's command-centric API is primarily useful when composing a number of nested subcommands, and dispatching to functions based on the selected subcommand. ```python from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class Example: cmd: cappa.Subcommands[Print | Fail] @dataclass class Print: loudly: bool def __call__(self): # again, __call__ is shorthand for the above explicit `invoke=` form. if self.loudly: print("PRINTING!") else: print("printing!") def fail(): raise cappa.Exit(code=self.code) @cappa.command(invoke=fail) class Fail: code: int cappa.invoke(Example) ```

Functions, invoke

Purely function-based CLIs **can** reduce the ceremony required to define a given CLI command. Such a CLI is exactly equivalent to a CLI defined as a dataclass with the function's arguments as the dataclass's fields. ```python import cappa from typing_extensions import Annotated def function(foo: int, bar: bool, option: Annotated[str, cappa.Arg(long=True)] = "opt"): ... cappa.invoke(function) ``` There are, however, some downsides to using functions. Namely, that `function` has no nameable type! As such, a free function can not be easily named as a subcommand option (`Subcommand[Foo | Bar]`). You **can** define a root level function with class-based subcommands, but the reverse is not possible because there is no valid type you can supply in the subcommand union.

Methods, invoke

See also [Methods](https://cappa.readthedocs.io/en/latest/functions_and_methods.html#methods). ```python from __future__ import annotations from dataclasses import dataclass import cappa @cappa.command @dataclass class Example: arg: int @cappa.command def add(self, other: int) -> int: """Add two numbers.""" return self.arg + some_dep @cappa.command(help="Subtract two numbers") def subtract(self, other: int) -> int: return self.arg - other cappa.invoke(Example) ``` With methods, the enclosing class corresponds to the parent object CLI arguments, exactly like normal class based definition. Unlike with free functions, (explicitly annotated) methods are able to act as subcommands, who's arguments (similarly to free functions) act as the arguments for the subcommand. The above example produces a CLI like: ``` Usage: example ARG {add,subtract} [-h] [--completion COMPLETION] Arguments ARG Subcommands add Add two numbers. subtract Subtract two numbers. ```

Imperative Construction, parse/invoke

See also [Manual Construction](https://cappa.readthedocs.io/en/latest/manual_construction.html). ```python from dataclasses import dataclass import cappa @dataclass class Foo: bar: str baz: list[int] command = cappa.Command( Foo, arguments=[ cappa.Arg(field_name="bar"), cappa.Arg(field_name="baz", num_args=2), ], help="Short help.", description="Long description.", ) result = cappa.parse(command, argv=["one", "2", "3"]) ``` All other APIs of cappa amount to scanning the provided input structure, and producing a `cappa.Command` structure. As such, it's equally possible for users to manually construct the commands themselves. This could also be used to extend cappa, or design even more alternative interfaces ([Cleo](https://github.com/python-poetry/cleo) is another, fairly different, option that comes to mind).
## Inspirations Credit where credit is due * The "Derive" API of the Rust library [Clap](https://docs.rs/clap/latest/clap/_derive/index.html) directly inspired the concept of mapping a type's fields to the shape of the CLI, by inferring the default behavior from introspecting types. * Click's easy way of defining large graphs of subcommands and mapping them to functions, inspired the the "invoke" API of Cappa. The actual APIs dont particularly resemble one another, but subcommands directly triggering functions (in contrast to argparse/Clap) is a very nice, and natural seeming feature! * FastAPI's `Depends` system inspired Cappa's dependency injection system. This API is quite natural, and makes it very easy to define a complex system of ad-hoc dependencies **without** the upfront wiring cost of most DI frameworks.