From e7ed2e044cc7158a1a1c325dd10f498974cb6694 Mon Sep 17 00:00:00 2001 From: Stefan Tatschner Date: Thu, 4 Jan 2024 11:26:34 +0100 Subject: [PATCH] chore: Vendor pydantic-argparse for easier experiments https://raw.githubusercontent.com/cody-mar10/pydantic-argparse --- .../.github/workflows/linting.yml | 32 + .../.github/workflows/tests.yml | 60 + .../.github/workflows/typing.yml | 32 + vendor/pydantic-argparse/.gitignore | 85 ++ vendor/pydantic-argparse/LICENSE.md | 21 + vendor/pydantic-argparse/README.md | 173 +++ vendor/pydantic-argparse/docs/CNAME | 1 + .../docs/assets/images/logo.svg | 95 ++ .../docs/assets/images/showcase_01.png | Bin 0 -> 18144 bytes .../docs/assets/images/showcase_02.png | Bin 0 -> 6224 bytes .../docs/assets/images/showcase_03.png | Bin 0 -> 12391 bytes .../docs/assets/images/showcase_04.png | Bin 0 -> 8885 bytes .../docs/assets/images/showcase_05.png | Bin 0 -> 37119 bytes .../docs/assets/images/showcase_06.png | Bin 0 -> 12832 bytes .../docs/assets/stylesheets/reference.css | 6 + vendor/pydantic-argparse/docs/background.md | 137 ++ .../docs/examples/commands.md | 55 + .../pydantic-argparse/docs/examples/simple.md | 34 + vendor/pydantic-argparse/docs/index.md | 62 + .../docs/reference/reference.py | 81 ++ vendor/pydantic-argparse/docs/showcase.md | 130 ++ .../docs/usage/argument_parser.md | 60 + .../docs/usage/arguments/choices.md | 239 +++ .../docs/usage/arguments/commands.md | 100 ++ .../docs/usage/arguments/flags.md | 240 +++ .../docs/usage/arguments/index.md | 232 +++ .../docs/usage/arguments/regular.md | 154 ++ .../docs/usage/arguments/variadic.md | 126 ++ vendor/pydantic-argparse/examples/commands.py | 52 + vendor/pydantic-argparse/examples/simple.py | 38 + vendor/pydantic-argparse/mkdocs.yml | 92 ++ vendor/pydantic-argparse/poetry.lock | 1285 +++++++++++++++++ .../pydantic_argparse/__init__.py | 53 + .../pydantic_argparse/__metadata__.py | 28 + .../pydantic_argparse/argparse/__init__.py | 18 + .../pydantic_argparse/argparse/actions.py | 237 +++ .../pydantic_argparse/argparse/parser.py | 280 ++++ .../pydantic_argparse/argparse/patches.py | 46 + .../pydantic_argparse/parsers/__init__.py | 57 + .../pydantic_argparse/parsers/boolean.py | 67 + .../pydantic_argparse/parsers/command.py | 53 + .../pydantic_argparse/parsers/container.py | 60 + .../pydantic_argparse/parsers/enum.py | 75 + .../pydantic_argparse/parsers/literal.py | 80 + .../pydantic_argparse/parsers/mapping.py | 58 + .../pydantic_argparse/parsers/standard.py | 44 + .../pydantic_argparse/parsers/utils.py | 29 + .../pydantic_argparse/py.typed | 0 .../pydantic_argparse/utils/__init__.py | 13 + .../pydantic_argparse/utils/errors.py | 27 + .../pydantic_argparse/utils/namespaces.py | 30 + .../pydantic_argparse/utils/nesting.py | 93 ++ .../pydantic_argparse/utils/pydantic.py | 407 ++++++ .../pydantic_argparse/utils/types.py | 27 + vendor/pydantic-argparse/pyproject.toml | 98 ++ vendor/pydantic-argparse/tests/__init__.py | 5 + .../tests/argparse/__init__.py | 5 + .../tests/argparse/test_actions.py | 101 ++ .../tests/argparse/test_parser.py | 550 +++++++ vendor/pydantic-argparse/tests/conftest.py | 208 +++ .../tests/functional/__init__.py | 5 + .../functional/test_environment_variables.py | 340 +++++ .../tests/parsers/__init__.py | 5 + .../pydantic-argparse/tests/utils/__init__.py | 5 + .../tests/utils/test_arguments.py | 90 ++ .../tests/utils/test_errors.py | 83 ++ .../tests/utils/test_namespaces.py | 47 + .../tests/utils/test_types.py | 86 ++ 68 files changed, 7032 insertions(+) create mode 100644 vendor/pydantic-argparse/.github/workflows/linting.yml create mode 100644 vendor/pydantic-argparse/.github/workflows/tests.yml create mode 100644 vendor/pydantic-argparse/.github/workflows/typing.yml create mode 100644 vendor/pydantic-argparse/.gitignore create mode 100644 vendor/pydantic-argparse/LICENSE.md create mode 100644 vendor/pydantic-argparse/README.md create mode 100644 vendor/pydantic-argparse/docs/CNAME create mode 100644 vendor/pydantic-argparse/docs/assets/images/logo.svg create mode 100644 vendor/pydantic-argparse/docs/assets/images/showcase_01.png create mode 100644 vendor/pydantic-argparse/docs/assets/images/showcase_02.png create mode 100644 vendor/pydantic-argparse/docs/assets/images/showcase_03.png create mode 100644 vendor/pydantic-argparse/docs/assets/images/showcase_04.png create mode 100644 vendor/pydantic-argparse/docs/assets/images/showcase_05.png create mode 100644 vendor/pydantic-argparse/docs/assets/images/showcase_06.png create mode 100644 vendor/pydantic-argparse/docs/assets/stylesheets/reference.css create mode 100644 vendor/pydantic-argparse/docs/background.md create mode 100644 vendor/pydantic-argparse/docs/examples/commands.md create mode 100644 vendor/pydantic-argparse/docs/examples/simple.md create mode 100644 vendor/pydantic-argparse/docs/index.md create mode 100644 vendor/pydantic-argparse/docs/reference/reference.py create mode 100644 vendor/pydantic-argparse/docs/showcase.md create mode 100644 vendor/pydantic-argparse/docs/usage/argument_parser.md create mode 100644 vendor/pydantic-argparse/docs/usage/arguments/choices.md create mode 100644 vendor/pydantic-argparse/docs/usage/arguments/commands.md create mode 100644 vendor/pydantic-argparse/docs/usage/arguments/flags.md create mode 100644 vendor/pydantic-argparse/docs/usage/arguments/index.md create mode 100644 vendor/pydantic-argparse/docs/usage/arguments/regular.md create mode 100644 vendor/pydantic-argparse/docs/usage/arguments/variadic.md create mode 100644 vendor/pydantic-argparse/examples/commands.py create mode 100644 vendor/pydantic-argparse/examples/simple.py create mode 100644 vendor/pydantic-argparse/mkdocs.yml create mode 100644 vendor/pydantic-argparse/poetry.lock create mode 100644 vendor/pydantic-argparse/pydantic_argparse/__init__.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/__metadata__.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/argparse/__init__.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/argparse/actions.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/argparse/parser.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/argparse/patches.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/__init__.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/boolean.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/command.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/container.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/enum.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/literal.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/mapping.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/standard.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/parsers/utils.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/py.typed create mode 100644 vendor/pydantic-argparse/pydantic_argparse/utils/__init__.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/utils/errors.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/utils/namespaces.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/utils/nesting.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/utils/pydantic.py create mode 100644 vendor/pydantic-argparse/pydantic_argparse/utils/types.py create mode 100644 vendor/pydantic-argparse/pyproject.toml create mode 100644 vendor/pydantic-argparse/tests/__init__.py create mode 100644 vendor/pydantic-argparse/tests/argparse/__init__.py create mode 100644 vendor/pydantic-argparse/tests/argparse/test_actions.py create mode 100644 vendor/pydantic-argparse/tests/argparse/test_parser.py create mode 100644 vendor/pydantic-argparse/tests/conftest.py create mode 100644 vendor/pydantic-argparse/tests/functional/__init__.py create mode 100644 vendor/pydantic-argparse/tests/functional/test_environment_variables.py create mode 100644 vendor/pydantic-argparse/tests/parsers/__init__.py create mode 100644 vendor/pydantic-argparse/tests/utils/__init__.py create mode 100644 vendor/pydantic-argparse/tests/utils/test_arguments.py create mode 100644 vendor/pydantic-argparse/tests/utils/test_errors.py create mode 100644 vendor/pydantic-argparse/tests/utils/test_namespaces.py create mode 100644 vendor/pydantic-argparse/tests/utils/test_types.py diff --git a/vendor/pydantic-argparse/.github/workflows/linting.yml b/vendor/pydantic-argparse/.github/workflows/linting.yml new file mode 100644 index 000000000..2f94571fe --- /dev/null +++ b/vendor/pydantic-argparse/.github/workflows/linting.yml @@ -0,0 +1,32 @@ +name: Linting + +on: + pull_request: + branches: + - master + push: + branches: + - master + schedule: + - cron: "0 0 * * 0" + +jobs: + linting: + name: Linting + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + - name: Install Poetry + run: pipx install poetry + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + cache: poetry + - name: Install Dependencies + run: poetry install --no-interaction --no-root + - name: Install Package + run: poetry install --no-interaction + - name: Run Linting + run: poetry run poe lint diff --git a/vendor/pydantic-argparse/.github/workflows/tests.yml b/vendor/pydantic-argparse/.github/workflows/tests.yml new file mode 100644 index 000000000..3b5a46074 --- /dev/null +++ b/vendor/pydantic-argparse/.github/workflows/tests.yml @@ -0,0 +1,60 @@ +name: Tests + +on: + pull_request: + branches: + - master + push: + branches: + - master + schedule: + - cron: "0 0 * * 0" + +jobs: + tests: + name: Tests + strategy: + matrix: + os: [ "ubuntu-latest", "macos-latest", "windows-latest" ] + python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] + fail-fast: false + defaults: + run: + shell: bash + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + - name: Install Poetry + run: pipx install poetry + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: poetry + - name: Install Dependencies + run: poetry install --no-interaction --no-root + - name: Install Package + run: poetry install --no-interaction + - name: Run Tests + run: | + poetry run poe test + poetry run coverage lcov + - name: Coverage Results + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage.lcov + flag-name: ${{ matrix.os }}-${{ matrix.python-version }} + parallel: true + + coverage: + name: Coverage + needs: tests + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel-finished: true diff --git a/vendor/pydantic-argparse/.github/workflows/typing.yml b/vendor/pydantic-argparse/.github/workflows/typing.yml new file mode 100644 index 000000000..c1bb4fdba --- /dev/null +++ b/vendor/pydantic-argparse/.github/workflows/typing.yml @@ -0,0 +1,32 @@ +name: Typing + +on: + pull_request: + branches: + - master + push: + branches: + - master + schedule: + - cron: "0 0 * * 0" + +jobs: + typing: + name: Typing + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + - name: Install Poetry + run: pipx install poetry + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + cache: poetry + - name: Install Dependencies + run: poetry install --no-interaction --no-root + - name: Install Package + run: poetry install --no-interaction + - name: Run Type Checking + run: poetry run poe type diff --git a/vendor/pydantic-argparse/.gitignore b/vendor/pydantic-argparse/.gitignore new file mode 100644 index 000000000..9b1e530b8 --- /dev/null +++ b/vendor/pydantic-argparse/.gitignore @@ -0,0 +1,85 @@ +# IDE settings / virtual environment +.venv/ +.vscode/ + +# Caches +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Sphinx documentation +docs/_build/ + +# pyenv +.python-version + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# macOS +.DS_Store + +# Misc +/TODO.md +Dockerfile \ No newline at end of file diff --git a/vendor/pydantic-argparse/LICENSE.md b/vendor/pydantic-argparse/LICENSE.md new file mode 100644 index 000000000..ff06aaae1 --- /dev/null +++ b/vendor/pydantic-argparse/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Hayden Richards + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/pydantic-argparse/README.md b/vendor/pydantic-argparse/README.md new file mode 100644 index 000000000..7e8c70414 --- /dev/null +++ b/vendor/pydantic-argparse/README.md @@ -0,0 +1,173 @@ +
+ + + +

+ Pydantic Argparse +

+

+ Typed Argument Parsing with Pydantic +

+ + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ +## Fork major changes +1. Upgrade to only be compatible with `Pydantic` v2+ + - `Pydantic` recently released version 2, which heavily relies on a Rust backend for major speed improvements in data validation. + - However, there are many breaking changes that were introduced in the process. +2. Nested `Pydantic` models now default to argument **groups** instead of subcommands. This leads to large argument lists being much more composable for large applications since the arguments can be broken into smaller groups. + - Subcommands are now explicitly *opt-in* features. A convenience base class `pydantic_argparse.BaseCommand` has been provided that sets the queried configuration variable, which can then be used as a typical `pydantic.BaseModel` otherwise. +3. The `metavar` option for `argparser.ArgumentParser.add_argument` now (almost always) defaults to the type of the argument instead of the argument name. + + +### Argument Groups example +```python +from pydantic import Field, BaseModel +from pydantic_argparse import ArgumentParser, BaseArgument + +# BaseArgument is just a pydantic.BaseModel that explicitly opted out from subcommands +# however, pydantic.BaseModel classes have implicitly opted out as well + +class Group1(BaseArgument): + string: str = Field(description="a required string") + integer: int = Field(description="a required integer") + decimal: float = Field(description="a required float") + flag: bool = Field(False, description="a flag") + +class Group2(BaseArgument): + name: str = Field(description="your name") + age: int = Field(82, description="your age") + +class Arguments(BaseModel): + first: Group1 + second: Group2 + +if __name__ == "__main__": + parser = ArgumentParser(model=Arguments) + parser.parse_typed_args() +``` + +```console +$ python3 example_groups.py --help +usage: example_groups.py [-h] [-v] --string STR --integer INT [--flag] + --name STR [--age INT] + +FIRST: + --string STR a required string + --integer INT a required integer + --decimal FLOAT a required float + --flag a flag + +SECOND: + --name STR your name + --age INT you age (default: 82) + +help: + -h, --help show this help message and exit + -v, --version show program's version number and exit +``` + +### TODO + +- [ ] Look into short arg names at the command line. + - This may involve the use of the model field `.alias` option + +## Help +See [documentation](https://pydantic-argparse.supimdos.com) for help. + +## Installation +Installation with `pip` is simple: +```console +$ pip install pydantic-argparse +``` + +## Example +```py +import pydantic +import pydantic_argparse + + +class Arguments(pydantic.BaseModel): + # Required Args + string: str = pydantic.Field(description="a required string") + integer: int = pydantic.Field(description="a required integer") + flag: bool = pydantic.Field(description="a required flag") + + # Optional Args + second_flag: bool = pydantic.Field(False, description="an optional flag") + third_flag: bool = pydantic.Field(True, description="an optional flag") + + +def main() -> None: + # Create Parser and Parse Args + parser = pydantic_argparse.ArgumentParser( + model=Arguments, + prog="Example Program", + description="Example Description", + version="0.0.1", + epilog="Example Epilog", + ) + args = parser.parse_typed_args() + + # Print Args + print(args) + + +if __name__ == "__main__": + main() +``` + +```console +$ python3 example.py --help +usage: Example Program [-h] [-v] --string STRING --integer INTEGER --flag | + --no-flag [--second-flag] [--no-third-flag] + +Example Description + +required arguments: + --string STRING a required string + --integer INTEGER a required integer + --flag, --no-flag a required flag + +optional arguments: + --second-flag an optional flag (default: False) + --no-third-flag an optional flag (default: True) + +help: + -h, --help show this help message and exit + -v, --version show program's version number and exit + +Example Epilog +``` + +```console +$ python3 example.py --string hello --integer 42 --flag +string='hello' integer=42 flag=True second_flag=False third_flag=True +``` + +## License +This project is licensed under the terms of the MIT license. diff --git a/vendor/pydantic-argparse/docs/CNAME b/vendor/pydantic-argparse/docs/CNAME new file mode 100644 index 000000000..778b19381 --- /dev/null +++ b/vendor/pydantic-argparse/docs/CNAME @@ -0,0 +1 @@ +pydantic-argparse.supimdos.com \ No newline at end of file diff --git a/vendor/pydantic-argparse/docs/assets/images/logo.svg b/vendor/pydantic-argparse/docs/assets/images/logo.svg new file mode 100644 index 000000000..e2703d861 --- /dev/null +++ b/vendor/pydantic-argparse/docs/assets/images/logo.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/pydantic-argparse/docs/assets/images/showcase_01.png b/vendor/pydantic-argparse/docs/assets/images/showcase_01.png new file mode 100644 index 0000000000000000000000000000000000000000..117873c9aa9cc84edc6d6a87dcd1f2c5cbfb8e69 GIT binary patch literal 18144 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%0A`=H_6U zgUxj1c7gsh5h;u7OWhh|K}6S*kZt#cGG?rL#FF)_)J}6a<|fznEWMQUJ&c<&i1;T% zw85V;Vboc#GGy}7(_c(LPJGP@<}5~h3O|0lU8q+huOf!fD6fH2*wsG=Si)5GyA0EQ zuhRm(kx6I*R2KET=rkFzqMNewL^Bo?Vf}~wgr@y zmR7qQlfVGNA!Zv4z@u1?GHoUz&F=0h28D=j-*{o-E`XVty5MjFe@wgxdgNZ~xq&FD zs+Lyv@UWVuMpViKJUHX9Q}{jFB8m#2erxElt%pOZQA`&-b20JnpV{HlxQ_RvXM zq=Cmc=paH9A|b1YHFJ8vO5Q{qk8k^4Y{aW_`^q!Ot|AC~l>CT-ydDYdgdt!Za5)&!g+S^$e8D%1r1#e17egXG!CCj<4=6oD} zv8X@`T8y>4QWVB)JL$=kh;7ciDzV2ok68}ak8JhVhX#rwTFgbW_}hN|Tx-EH8%+?f z9Ayq6=Y#g|=c(IizI*r1-#H*4z_R@-*~Vy>r$xe0hV15Manra2YG2KNRXN_Nu-0W; zXZ5r@I{#=?T_x$r8;`;ztv!^AHD@I6-lL-7tXO?0h3)sp<&%1=9z15^1$_wuo>Vkk zhJM*9O^}wB7X6Om`U81DyQ=Hz2;wPif_8{c8u}<*J%z4Iv~7Mztc$BTPmEzxg|ubG+7h^JvzUbwlhVkY?QK3iVK>)^+zt_|ZX*<=%YDwGS@4)I}#G`r+R7(ZoQwd7| zM|ghU!$6N%vW(2`{^Ce@S~Ry#DXw5N^UQ?t{)@1wT}V{o^6l4TheHAp@8O& z8a!Ry1jDxTg&clCl`m>Pefs2qI$IT6Ut2R*;WAwKA+1wwzwmU7gp#r#luBfy1Hs1!mCt6Kp$u zDcDYH^XMBlsggsT-c%$^QV0jG3hLKUse!PHYYc2st2?bemnRcb`qtF|+lt$aUl#M; zYr)b7=9@S(Cj7cbolnA`_4PBX1IrE;z|8bJ zYMs{8`E91;BE-+vjEVtXv^{eciMoKh*DeG+RhqI@=vUKr2yHBV#fK6Ss{MMLiEQ+K z6@y6O_r8kW(r6Am>8d}=uf2F+txda_90E-~;vAqeJ1{IZl$4b83&6{=`UV^(pf0~Y z3qZjLx;7Fp0NSDX`G^7E{vr5PfA+**#}V|thGfolD|8Hab@~xYC7PD22kzYvUT}2V znf~&J$GZ8*lBYrDDJw$bWkxBco(4c<0`Kn6%!uNVa!I12qhsUa7x4(eHN=reBaw|- zVbrr?DPaVJl%-DV!;A!&yd*JOi*vZoNSFArI&F>tDT$20Eld-vfmQ4zFCTOZ1A_si z5Mw;*83L(uu>i8IyrSZg><|r-8hF)yq7Z4jAM=W=3VMEtM_%6IXt8;_&G<` z%(VEY4JDnOo&9^RC_QHkxe{Uge7t!EDa7nLauN8Hf`jHYhYX~uVq4;qSjuASyFE7e zNAiImEB3r>3sic#@eic_Zk68%kZt>>es{Fzj#PV&X77?ZQ+rmeR<~+pL0BbT?WEVR zaCT;7_+ZBNFx1WZ3@@`2E7-tA$l+hBva{P)FZzevGc7S^p~7#_JhQ$A9)m(HYfK_U zR=#Jw4`sFHrt%JMUU%a6Vh@aL`76==?JΝ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 literal 0 HcmV?d00001 diff --git a/vendor/pydantic-argparse/docs/assets/images/showcase_05.png b/vendor/pydantic-argparse/docs/assets/images/showcase_05.png new file mode 100644 index 0000000000000000000000000000000000000000..e7f340954bf541d76807837f20626c4becdf5d21 GIT binary patch literal 37119 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 WHJm-Wc5>*OO|EC%of}UF{`z0ryYYSi literal 0 HcmV?d00001 diff --git a/vendor/pydantic-argparse/docs/assets/images/showcase_06.png b/vendor/pydantic-argparse/docs/assets/images/showcase_06.png new file mode 100644 index 0000000000000000000000000000000000000000..dec909436405fe03f825ccb3dd5e7bd4e9d462d8 GIT binary patch literal 12832 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 literal 0 HcmV?d00001 diff --git a/vendor/pydantic-argparse/docs/assets/stylesheets/reference.css b/vendor/pydantic-argparse/docs/assets/stylesheets/reference.css new file mode 100644 index 000000000..27dd8cad9 --- /dev/null +++ b/vendor/pydantic-argparse/docs/assets/stylesheets/reference.css @@ -0,0 +1,6 @@ +/* Code Reference Indentation. */ +div.doc-contents:not(.first) { + padding-left: 25px; + border-left: 4px solid rgba(230, 230, 230); + margin-bottom: 80px; +} diff --git a/vendor/pydantic-argparse/docs/background.md b/vendor/pydantic-argparse/docs/background.md new file mode 100644 index 000000000..107d66f78 --- /dev/null +++ b/vendor/pydantic-argparse/docs/background.md @@ -0,0 +1,137 @@ +## Overview +Before delving into the documentation, examples and code reference, it is first +necessary to explore and understand why you may want to use this package. + +## Tenets +The design goals of `pydantic-argparse` are summarised by these core tenets. + +#### Simple +: `pydantic-argparse` has a simple API and code-base. + +#### Opinionated +: `pydantic-argparse` is deliberately limited with *one way* of doing things. + +#### Typed +: `pydantic-argparse` fully supports type-hinting and `mypy`. + +## Rationale +There are many benefits to using `pydantic-argparse` over a more traditional +argument parsing package that uses a functional api. Some of the most valuable +benefits are outlined below. + +#### Declarative Arguments +!!! success "" + Arguments are defined declaratively using `pydantic` models. This means the + command-line interface for your application has a strict schema, that is + easy to view, modify or even export to other formats such as `JSON Schema`. + +#### Familiar Syntax +!!! success "" + Due to the use of `pydantic` models and standard type-hinting, there is + almost no new syntax or API to learn. Just declare your interface with a + *dataclass-like* `pydantic` model, and let `pydantic-argparse` parse your + arguments. + +#### Type Hints +!!! success "" + Due to the use of `pydantic` models, your parsed command-line arguments are + just an instance of a type-hinted class. This means that your arguments can + support auto-completion, linting, mypy and other tools in your IDE. + +#### Pydantic Validation +!!! success "" + Due to the use of `pydantic` models, your command-line interface is able to + heavily leverage `pydantic`'s validation system to provide a *very* large + number of different types. + +#### Confidence +!!! success "" + As a result of type-hinting and `pydantic` validation, you can have the + confidence that once your command-line arguments have been parsed, their + type and validity have been confirmed - you don't have to check or worry + about them again. + +## Drawbacks +There are also some drawbacks to using `pydantic-argparse`, depending on the +size of your project, the features you require and the programming paradigms +that you agree with. Some of the possible drawbacks are outlined below. + +#### Extra Dependencies +!!! warning "" + While `pydantic-argparse` itself depends *only* on `pydantic`, it has a + number of transient dependencies due to the dependencies of `pydantic` + itself. If your application is small, it may not be suitable to pull in + `pydantic` and its dependencies for a simple command-line interface. + +#### Opinionated Design +!!! warning "" + `pydantic-argparse` is a very opinionated package by design. It aims for a + simple API, and to be both full featured while limiting excessive choices. + For example, there are no *positional* arguments in `pydantic-argparse`; + only *optional* and *required* arguments. If your opinions do not align + with these design choices, then you may not want to use the package. + +#### Nested Models +!!! warning "" + Sub-commands are supported by *nesting* `pydantic` models. This means that + for each sub-command, an additional model must be defined. If your + application requires many different sub-commands, it may result in a large + number of `pydantic` models. + +## Alternatives +There are many alternative argument parsing packages that already exist for +Python. Some of the most popular are outlined below. + +#### [Argparse][1] +> `argparse` is a standard-library module that makes it easy to write +> user-friendly command-line interfaces. The program defines what arguments it +> requires, and `argparse` will figure out how to parse those out of +> `sys.argv`. The `argparse` module also automatically generates help and usage +> messages and issues errors when users give the program invalid arguments. + +#### [Click][2] +> `click` is a Python package for creating beautiful command line interfaces in +> a composable way with as little code as necessary. It’s the “Command Line +> Interface Creation Kit”. It’s highly configurable but comes with sensible +> defaults out of the box. + +#### [Typer][3] +> `typer` is a library for building CLI applications that users will love using +> and developers will love creating. Based on Python 3.6+ type hints. The key +> features are that it is intuitive to write, easy to use, short and starts +> simple but can grow large. It aims to be the `fastapi` of command-line +> interfaces. + +## Comparison +A feature comparison matrix of the alternatives outlined above is shown below. + +| | `argparse` | `click` | `typer` | `pydantic-argparse` | +| ------------------------------: | :----------------: | :----------------: | :----------------: | :-----------------: | +| **Arguments** | +| *Optional Arguments* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| *Required Arguments* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| *Positional Arguments* | :white_check_mark: | :white_check_mark: | :white_check_mark: | | +| *Sub-Commands* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| **Argument Types** | +| *Regular Arguments* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| *Variadic Arguments* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| *Flag Arguments* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| *Choice Arguments* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| **Validation** | +| *Type Validation* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| *Automatic Validation* | | | :white_check_mark: | :white_check_mark: | +| *Pydantic Validation* | | | | :white_check_mark: | +| **Design Pattern** | +| *Functional Definition* | :white_check_mark: | :white_check_mark: | :white_check_mark: | | +| *Declarative Definition* | | | | :white_check_mark: | +| *Function Decorators* | | :white_check_mark: | :white_check_mark: | | +| *Function Signature Inspection* | | | :white_check_mark: | | +| **Extra Features** | +| *Typing Hinting* | | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| *Shell Completion* | | :white_check_mark: | :white_check_mark: | | +| *Environment Variables* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | + + +[1]: https://docs.python.org/3/library/argparse.html +[2]: https://click.palletsprojects.com/ +[3]: https://typer.tiangolo.com/ diff --git a/vendor/pydantic-argparse/docs/examples/commands.md b/vendor/pydantic-argparse/docs/examples/commands.md new file mode 100644 index 000000000..6ffd16c82 --- /dev/null +++ b/vendor/pydantic-argparse/docs/examples/commands.md @@ -0,0 +1,55 @@ +### Define Model +```python title="commands.py" +--8<-- "examples/commands.py" +``` + +### Check Help +```console +$ python3 examples/commands.py --help +usage: Example Program [-h] [-v] [--verbose] {build,serve} ... + +Example Description + +commands: + {build,serve} + build build command + serve serve command + +optional arguments: + --verbose verbose flag (default: False) + +help: + -h, --help show this help message and exit + -v, --version show program's version number and exit + +Example Epilog +``` + +### Check Commands Help +```console +$ python3 examples/commands.py build --help +usage: Example Program build [-h] --location LOCATION + +required arguments: + --location LOCATION build location + +help: + -h, --help show this help message and exit +``` +```console +$ python3 examples/commands.py serve --help +usage: Example Program serve [-h] --address ADDRESS --port PORT + +required arguments: + --address ADDRESS serve address + --port PORT serve port + +help: + -h, --help show this help message and exit +``` + +### Parse Arguments +```console +$ python3 examples/commands.py --verbose serve --address 127.0.0.1 --port 8080 +verbose=True build=None serve=ServeCommand(address=IPv4Address('127.0.0.1'), port=8080) +``` diff --git a/vendor/pydantic-argparse/docs/examples/simple.md b/vendor/pydantic-argparse/docs/examples/simple.md new file mode 100644 index 000000000..4811d84c0 --- /dev/null +++ b/vendor/pydantic-argparse/docs/examples/simple.md @@ -0,0 +1,34 @@ +### Define Model +```python title="simple.py" +--8<-- "examples/simple.py" +``` + +### Check Help +```console +$ python3 simple.py --help +usage: Example Program [-h] [-v] --string STRING --integer INTEGER --flag | + --no-flag [--second-flag] [--no-third-flag] + +Example Description + +required arguments: + --string STRING a required string + --integer INTEGER a required integer + --flag, --no-flag a required flag + +optional arguments: + --second-flag an optional flag (default: False) + --no-third-flag an optional flag (default: True) + +help: + -h, --help show this help message and exit + -v, --version show program's version number and exit + +Example Epilog +``` + +### Parse Arguments +```console +$ python3 simple.py --string hello --integer 42 --flag +string='hello' integer=42 flag=True second_flag=False third_flag=True +``` diff --git a/vendor/pydantic-argparse/docs/index.md b/vendor/pydantic-argparse/docs/index.md new file mode 100644 index 000000000..ade90f408 --- /dev/null +++ b/vendor/pydantic-argparse/docs/index.md @@ -0,0 +1,62 @@ +
+ + + +

+ Pydantic Argparse +

+

+ Typed Argument Parsing with Pydantic +

+ + + + + + + + + + + + +
+ + + + + + + + + + + + +
+--- + +## Overview +`pydantic-argparse` is a Python package built on top of [`pydantic`][1] which +provides declarative *typed* argument parsing using `pydantic` models. + +## Requirements +`pydantic-argparse` requires Python 3.7+ + +## Installation +Installation with `pip` is simple: +```console +$ pip install pydantic-argparse +``` + +## Quick Start +--8<-- "docs/examples/simple.md" + +## Credits +This project is made possible by [`pydantic`][1]. + +## License +This project is licensed under the terms of the MIT license. + + +[1]: https://docs.pydantic.dev/ diff --git a/vendor/pydantic-argparse/docs/reference/reference.py b/vendor/pydantic-argparse/docs/reference/reference.py new file mode 100644 index 000000000..9e8c22c25 --- /dev/null +++ b/vendor/pydantic-argparse/docs/reference/reference.py @@ -0,0 +1,81 @@ +"""Automatic Code Reference Documentation Generation.""" + + +# Standard +import pathlib + +# Third-Party +import mkdocs_gen_files + + +# Configuration +PACKAGE = pathlib.Path("pydantic_argparse") +DOCS = pathlib.Path("reference") + +# Constants +FILENAME_NAVIGATION = "SUMMARY.md" +FILENAME_INDEX = "index.md" +PYTHON_GLOB = "**/*.py" +DUNDER = "__" +DOT_MD = ".md" +PREFIX_H1 = "# " +PREFIX_H2 = "## " +PREFIX_CODE = "::: " +ESCAPE_MD = "_", "\\_" + + +def generate(package: pathlib.Path, docs: pathlib.Path) -> None: + """Generates the Code Reference Documentation. + + Args: + package (pathlib.Path): Location of the package to generate docs. + docs (pathlib.Path): Location to write out docs to. + """ + # Instantiate Documentation and Navigation Generators + files_editor = mkdocs_gen_files.FilesEditor.current() + nav = mkdocs_gen_files.Nav() + + # Loop through regular files in the package + for source in sorted(package.glob(PYTHON_GLOB)): + # Generate Reference + reference = PREFIX_CODE + ".".join(source.with_suffix("").parts) + + # Check if file is "dunder" module + if source.stem.startswith(DUNDER) and source.stem.endswith(DUNDER): + # Generate docs for dunder files + path = docs / source.with_name(FILENAME_INDEX) + heading = PREFIX_H1 + source.parent.stem + subheading = PREFIX_H2 + source.name.replace(*ESCAPE_MD) + titles = source.parent.parts + + else: + # Generate docs for regular files + path = docs / source.with_suffix(DOT_MD) + heading = PREFIX_H1 + source.stem + subheading = "" + titles = source.parts + + # Docs + with files_editor.open(str(path), "a") as file_object: + # Check if the file is empty + if not file_object.tell(): + # Heading + file_object.write(heading + "\n") + + # Sub Heading + file_object.write(subheading + "\n") + + # Code Reference + file_object.write(reference + "\n") + + # Build Nav + nav[titles] = str(path.relative_to(docs)) + + # Nav + with files_editor.open(str(docs / FILENAME_NAVIGATION), "w") as file_object: + # Write Nav + file_object.writelines(nav.build_literate_nav()) + + +# Run +generate(PACKAGE, DOCS) diff --git a/vendor/pydantic-argparse/docs/showcase.md b/vendor/pydantic-argparse/docs/showcase.md new file mode 100644 index 000000000..86b4cc325 --- /dev/null +++ b/vendor/pydantic-argparse/docs/showcase.md @@ -0,0 +1,130 @@ +## Feature Showcase +This showcase demonstrates how `pydantic-argparse` can be useful, by +highlighting some of its features and showing how they can be utilised. + +### CLI Construction +The `pydantic-argparse` command-line interface construction is simple. + +=== "Pydantic Argparse" + ```python + import pydantic + import pydantic_argparse + + # Declare Arguments + class Arguments(pydantic.BaseModel): + # Required Arguments + string: str = pydantic.Field(description="a required string") + integer: int = pydantic.Field(description="a required integer") + flag: bool = pydantic.Field(description="a required flag") + + # Optional Arguments + second_flag: bool = pydantic.Field(False, description="an optional flag") + third_flag: bool = pydantic.Field(True, description="an optional flag") + + # Create Parser + parser = pydantic_argparse.ArgumentParser( + model=Arguments, + prog="Example Program", + description="Example Description", + version="0.0.1", + epilog="Example Epilog", + ) + + # Parse Arguments + args = parser.parse_typed_args() + ``` + +=== "Argparse" + ```python + import argparse + + # Create Parser + parser = argparse.ArgumentParser( + prog="Example Program", + description="Example Description", + epilog="Example Epilog", + add_help=False, + ) + + # Functionally Add Argument Groups + required = parser.add_argument_group(title="required arguments") + optional = parser.add_argument_group(title="optional arguments") + help = parser.add_argument_group("help") + + # Add Help Actions + help.add_argument( + "-h", + "--help", + action="help", + help="show this help message and exit", + ) + help.add_argument( + "-v", + "--version", + action="version", + version="0.0.1", + help="show program's version number and exit", + ) + + # Add Required Arguments + required.add_argument( + "--string", + type=str, + required=True, + help="a required string", + ) + required.add_argument( + "--integer", + type=int, + required=True, + help="a required integer", + ) + required.add_argument( + "--flag", + action=argparse.BooleanOptionalAction, + required=True, + help="a required flag", + ) + + # Add Optional Arguments + optional.add_argument( + "--second-flag", + action="store_true", + help="an optional flag (default: False)", + ) + optional.add_argument( + "--third-flag", + action="store_false", + help="an optional flag (default: True)", + ) + + # Parse Arguments + args = parser.parse_args() + ``` + +### Auto Completion +The `pydantic-argparse` parsed `args` support auto-completion in your IDE. + +=== "Pydantic Argparse" + ![Pydantic Argparse - Auto Completion](assets/images/showcase_01.png) + +=== "Argparse" + ![Argparse - Auto Completion](assets/images/showcase_02.png) + +### Type Hints +The `pydantic-argparse` parsed `args` support type-hinting in your IDE. + +=== "Pydantic Argparse" + ![Pydantic Argparse - Type Hints](assets/images/showcase_03.png) + +=== "Argparse" + ![Argparse - Type Hints](assets/images/showcase_04.png) + +### Type Safety +The `pydantic-argparse` parsed `args` support type-safety with `mypy`. + +=== "Pydantic Argparse" + ![Pydantic Argparse - Type Safety](assets/images/showcase_05.png) + +=== "Argparse" + ![Argparse - Type Safety](assets/images/showcase_06.png) diff --git a/vendor/pydantic-argparse/docs/usage/argument_parser.md b/vendor/pydantic-argparse/docs/usage/argument_parser.md new file mode 100644 index 000000000..9db93ed3e --- /dev/null +++ b/vendor/pydantic-argparse/docs/usage/argument_parser.md @@ -0,0 +1,60 @@ +## Overview +The interface for `pydantic-argparse` is the custom typed +[`ArgumentParser`][pydantic_argparse.argparse.parser.ArgumentParser] class, +which provides declarative, typed argument parsing. + +This `ArgumentParser` class presents a very *similar* interface to the `python` +standard library `argparse.ArgumentParser`, in an attempt to provide as close +to a drop-in-replacement as possible. + +## Parser Instantiation +To create an instance of the `ArgumentParser`: +```python +parser = pydantic_argparse.ArgumentParser( + model=Arguments, + prog="Program Name", + description="Program Description", + version="1.2.3", + epilog="Program Epilog", + add_help=True, + exit_on_error=True, +) +``` + +### Required Parameters +The *required* parameters for the `ArgumentParser` are outlined below: + +* `model` (`Type[pydantic.BaseModel]`): + The model that defines the command-line arguments + +### Optional Parameters +The *optional* parameters for the `ArgumentParser` are outlined below: + +* `prog` (`Optional[str]`): + The program name that appears in the help message +* `description` (`Optional[str]`): + The program description that appears in the help message +* `version` (`Optional[str]`): + The program version that appears in the help message +* `epilog` (`Optional[str]`): + The program epilog that appears in the help message +* `add_help` (`bool`): + Whether to add the `-h / --help` help message action +* `exit_on_error` (`bool`): + Whether to exit, or raise an `ArgumentError` upon an error + +## Argument Parsing +To parse command-line arguments into the `model` using the `ArgumentParser`: +```python +args = parser.parse_typed_args() +``` + +!!! info + The `ArgumentParser` is *generic* over its `pydantic` `model`. This means + that the parsed `args` object is type-hinted as an instance of its `model`. + +### Optional Parameters +The *optional* parameters for the `parse_typed_args` method are outlined below: + +* `args` (`Optional[List[str]]`): + Optional list of arguments to parse *instead* of `sys.argv` diff --git a/vendor/pydantic-argparse/docs/usage/arguments/choices.md b/vendor/pydantic-argparse/docs/usage/arguments/choices.md new file mode 100644 index 000000000..9bb611ad6 --- /dev/null +++ b/vendor/pydantic-argparse/docs/usage/arguments/choices.md @@ -0,0 +1,239 @@ +## Overview +`pydantic-argparse` provides functionality for choice arguments. A choice is a +command-line argument that allows a restricted set of values. For example: +`--choice X` or `--choice Y`. + +This section covers the following standard `argparse` argument functionality: + +```python +# Enum Choices +parser.add_argument("--choice", choices=[Enum.A, Enum.B, Enum.B]) +# Literal Choices +parser.add_argument("--choice", choices=["A", "B", "C"]) +``` + +## Usage +The intended usage of choice arguments is to restrict the set of valid options +for the user. For example: + +```console +$ python3 example.py --choice PAPER +``` + +```python +if args.choice == "PAPER": + # Choice PAPER + ... +elif args.choice == "SCISSORS": + # Choice SCISSORS + ... +elif args.choice == "ROCK": + # Choice ROCK + ... +else: + # This cannot occur! + # Something must have gone wrong... + ... +``` + +## Enums +Enum choices can be created by adding a `pydantic` `Field` with the type of an +`:::python enum.Enum` class, which contains more than one enumeration. There +are different kinds of enum choice arguments, which are outlined below. + +### Required +A *required* enum choice argument is defined as follows: + +```python +class Choices(enum.Enum): + A = enum.auto() + B = enum.auto() + C = enum.auto() + +class Arguments(BaseModel): + # Required Choice + choice: Choices = Field(description="this is a required choice") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] --choice {A, B, C} + +required arguments: + --choice {A, B, C} this is a required choice + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--choice A` will set `args.choice` to `Choices.A`. +* Providing an argument of `--choice B` will set `args.choice` to `Choices.B`. +* Providing an argument of `--choice C` will set `args.choice` to `Choices.C`. +* This argument cannot be omitted. + +### Optional (Default `None`) +An *optional* enum choice argument with a default of `None` is defined as +follows: + +```python +class Choices(enum.Enum): + A = enum.auto() + B = enum.auto() + C = enum.auto() + +class Arguments(BaseModel): + # Optional Choice (Default None) + choice: Optional[Choices] = Field(description="this is an optional choice") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--choice {A, B, C}] + +optional arguments: + --choice {A, B, C} this is an optional choice (default: None) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--choice A` will set `args.choice` to `Choices.A`. +* Providing an argument of `--choice B` will set `args.choice` to `Choices.B`. +* Providing an argument of `--choice C` will set `args.choice` to `Choices.C`. +* Omitting this argument will set `args.choice` to `None` (the default). + +### Optional (Default `Value`) +An *optional* enum choice argument with a default choice is defined as follows: + +```python +class Choices(enum.Enum): + A = enum.auto() + B = enum.auto() + C = enum.auto() + +class Arguments(BaseModel): + # Optional Choice (Default Choices.A) + choice: Choices = Field(Choices.A, description="this is an optional choice") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--choice {A, B, C}] + +optional arguments: + --choice {A, B, C} this is an optional choice (default: Choices.A) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--choice A` will set `args.choice` to `Choices.A`. +* Providing an argument of `--choice B` will set `args.choice` to `Choices.B`. +* Providing an argument of `--choice C` will set `args.choice` to `Choices.C`. +* Omitting this argument will set `args.choice` to `Choices.A` (the default). + +## Literals +Literal choices can be created by adding a `pydantic` `Field` with the type of +`:::python typing.Literal`, which contains more than one literal value. There +are different kinds of literal flag arguments, which are outlined below. + +### Required +A *required* literal choice argument is defined as follows: + +```python +class Arguments(BaseModel): + # Required Choice + choice: Literal["A", "B", "C"] = Field(description="this is a required choice") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] --choice {A, B, C} + +required arguments: + --choice {A, B, C} this is a required choice + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--choice A` will set `args.choice` to `"A"`. +* Providing an argument of `--choice B` will set `args.choice` to `"B"`. +* Providing an argument of `--choice C` will set `args.choice` to `"C"`. +* This argument cannot be omitted. + +### Optional (Default `None`) +An *optional* literal choice argument with a default of `None` is defined as +follows: + +```python +class Arguments(BaseModel): + # Optional Choice (Default None) + choice: Optional[Literal["A", "B", "C"]] = Field(description="this is an optional choice") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--choice {A, B, C}] + +optional arguments: + --choice {A, B, C} this is an optional choice (default: None) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--choice A` will set `args.choice` to `"A"`. +* Providing an argument of `--choice B` will set `args.choice` to `"B"`. +* Providing an argument of `--choice C` will set `args.choice` to `"C"`. +* Omitting this argument will set `args.choice` to `None` (the default). + +### Optional (Default `Value`) +An *optional* literal choice argument with a default choice is defined as +follows: + +```python +class Arguments(BaseModel): + # Optional Choice (Default "A") + choice: Literal["A", "B", "C"] = Field("A", description="this is an optional choice") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--choice {A, B, C}] + +optional arguments: + --choice {A, B, C} this is an optional choice (default: A) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--choice A` will set `args.choice` to `"A"`. +* Providing an argument of `--choice B` will set `args.choice` to `"B"`. +* Providing an argument of `--choice C` will set `args.choice` to `"C"`. +* Omitting this argument will set `args.choice` to `"A"` (the default). diff --git a/vendor/pydantic-argparse/docs/usage/arguments/commands.md b/vendor/pydantic-argparse/docs/usage/arguments/commands.md new file mode 100644 index 000000000..0a6f6dab0 --- /dev/null +++ b/vendor/pydantic-argparse/docs/usage/arguments/commands.md @@ -0,0 +1,100 @@ +## Overview +`pydantic-argparse` provides functionality for commands. A command is a +positional command-line argument that can be followed by its own specific +subset of command-line arguments. For example: `command --arg abc`. + +This section covers the following standard `argparse` argument functionality: + +```python +# Subparser Commands +subparsers = parser.add_subparsers() +command = subparsers.add_parser("command") +command.add_argument(...) +``` + +## Usage +The intended usage of commands is to provide the user with different +application behaviours, each with their own subset of arguments. For example: + +```console +$ python3 example.py serve --address 127.0.0.1 --port 8080 +``` + +```python +if args.serve: + # The serve command was chosen + # We have typed access to any of the command model arguments we defined + # For example: `args.serve.address`, `args.serve.port`, etc. + ... +``` + +## Pydantic Models +Commands can be created by first defining a `pydantic` model for the command +(e.g., `Command`), containing its own subset of arguments. The command can then +be added to the command-line interface by adding a `pydantic` field with the +type of `Optional[Command]`. Despite each command itself being *optional*, +overall a command is *always* required, as outlined below. + +### Required +*Required* commands are defined as follows: + +```python +class Command1(BaseModel): + arg1: str = Field(description="this is sub-argument 1") + +class Command2(BaseModel): + arg2: str = Field(description="this is sub-argument 2") + +class Arguments(BaseModel): + # Commands + command1: Optional[Command1] = Field(description="this is command 1") + command2: Optional[Command2] = Field(description="this is command 2") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] {command1,command2} ... + +commands: + {command1,command2} + command1 this is command 1 + command2 this is command 2 + +help: + -h, --help show this help message and exit +``` + +This `Arguments` model also generates command-line interfaces for each of its +commands: + +```console +$ python3 example.py command1 --help +usage: example.py command1 [-h] --arg1 ARG1 + +required arguments: + --arg1 ARG1 this is sub-argument 1 + +help: + -h, --help show this help message and exit +``` + +```console +$ python3 example.py command2 --help +usage: example.py command2 [-h] --arg2 ARG2 + +required arguments: + --arg2 ARG2 this is sub-argument 2 + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing arguments of `command1 --arg1 abc` will set `args.command1` to + to `:::python Command1(arg1="abc")`, and `args.command2` to `None`. +* Providing arguments of `command2 --arg2 xyz` will set `args.command2` to + to `:::python Command2(arg2="xyz")`, and `args.command1` to `None`. +* Commands cannot be omitted. diff --git a/vendor/pydantic-argparse/docs/usage/arguments/flags.md b/vendor/pydantic-argparse/docs/usage/arguments/flags.md new file mode 100644 index 000000000..6040774b2 --- /dev/null +++ b/vendor/pydantic-argparse/docs/usage/arguments/flags.md @@ -0,0 +1,240 @@ +## Overview +`pydantic-argparse` provides functionality for flag arguments. A flag is a +command-line argument that has no following value. For example: `--flag` or +`--no-flag`. + +This section covers the following standard `argparse` argument functionality: + +```python +# Boolean Flags +parser.add_argument("--flag", action=argparse.BooleanOptionalAction) +parser.add_argument("--flag", action="store_true") +parser.add_argument("--no-flag", action="store_false") +# Constant Flags +parser.add_argument("--flag", action="store_const", const="A") +parser.add_argument("--flag", action="store_const", const=Enum.A) +``` + +## Usage +The intended usage of flags is to enable or disable features. For example: + +```console +$ python3 example.py --debug +``` + +```python +if args.debug: + # Set logging to DEBUG + ... +``` + +## Booleans +Boolean flags can be created by adding a `pydantic` `Field` with the type of +`:::python bool`. There are different kinds of boolean flag arguments, which +are outlined below. + +### Required +A *required* boolean flag is defined as follows: + +```python +class Arguments(BaseModel): + # Required Flag + flag: bool = Field(description="this is a required flag") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] --flag | --no-flag + +required arguments: + --flag, --no-flag this is a required flag + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--flag` will set `args.flag` to `True`. +* Providing an argument of `--no-flag` will set `args.flag` to `False`. +* This argument cannot be omitted. + +### Optional (Default `False`) +An *optional* boolean flag with a default of `False` is defined as follows: + +```python +class Arguments(BaseModel): + # Optional Flag (Default False) + flag: bool = Field(False, description="this is an optional flag") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--flag] + +optional arguments: + --flag this is an optional flag (default: False) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--flag` will set `args.flag` to `True`. +* Omitting this argument will set `args.flag` to `False` (the default). + +### Optional (Default `True`) +An *optional* boolean flag with a default of `True` is defined as follows: + +```python +class Arguments(BaseModel): + # Optional Flag (Default True) + flag: bool = Field(True, description="this is an optional flag") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--no-flag] + +optional arguments: + --no-flag this is an optional flag (default: True) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--no-flag` will set `args.flag` to `False`. +* Omitting this argument will set `args.flag` to `True` (the default). + +## Enums +Enum flags can be created by adding a `pydantic` `Field` with the type of an +`:::python enum.Enum` class, which contains only one enumeration. There are +different kinds of enum flag arguments, which are outlined below. + +### Optional (Default `None`) +An *optional* enum flag with a default of `None` is defined as follows: + +```python +class Constant(enum.Enum): + VALUE = enum.auto() + +class Arguments(BaseModel): + # Optional Flag (Default None) + constant: Optional[Constant] = Field(description="this is a constant flag") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--constant] + +optional arguments: + --constant this is a constant flag (default: None) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--constant` will set `args.constant` to `Constant.VALUE`. +* Omitting this argument will set `args.constant` to `None` (the default). + +### Optional (Default `Constant`) +An *optional* enum flag with a constant default value is defined as follows: + +```python +class Constant(enum.Enum): + VALUE = enum.auto() + +class Arguments(BaseModel): + # Optional Flag (Default Constant.VALUE) + constant: Optional[Constant] = Field(Constant.VALUE, description="this is a constant flag") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--no-constant] + +optional arguments: + --no-constant this is a constant flag (default: Constant.VALUE) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--no-constant` will set `args.constant` to `None`. +* Omitting this argument will set `args.constant` to `Constant.VALUE` (the default). + +## Literals +Literal flags can be created by adding a `pydantic` `Field` with the type of +`:::python typing.Literal`, which contains only one literal value. There are +different kinds of literal flag arguments, which are outlined below. + +### Optional (Default `None`) +An *optional* literal flag with a default of `None` is defined as follows: + +```python +class Arguments(BaseModel): + # Optional Flag (Default None) + constant: Optional[Literal["VALUE"]] = Field(description="this is a constant flag") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--constant] + +optional arguments: + --constant this is a constant flag (default: None) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--constant` will set `args.constant` to `"VALUE"`. +* Omitting this argument will set `args.constant` to `None` (the default). + +### Optional (Default `Constant`) +An *optional* literal flag with a constant default value is defined as follows: + +```python +class Arguments(BaseModel): + # Optional Flag (Default "VALUE") + constant: Optional[Literal["VALUE"]] = Field("VALUE", description="this is a constant flag") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--no-constant] + +optional arguments: + --no-constant this is a constant flag (default: VALUE) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--no-constant` will set `args.constant` to `None`. +* Omitting this argument will set `args.constant` to `"VALUE"` (the default). diff --git a/vendor/pydantic-argparse/docs/usage/arguments/index.md b/vendor/pydantic-argparse/docs/usage/arguments/index.md new file mode 100644 index 000000000..904c3199e --- /dev/null +++ b/vendor/pydantic-argparse/docs/usage/arguments/index.md @@ -0,0 +1,232 @@ +## Overview +At the core of `pydantic-argparse` is the `pydantic` *model*, in which +arguments are declared with `pydantic` *fields*. This combination of the +*model* and its *fields* defines the *schema* for your command-line arguments. + +## Pydantic +### Models +A `pydantic` model is simply a *dataclass-like* class that inherits from the +`pydantic.BaseModel` base class. In `pydantic-argparse`, this model is used to +declaratively define your command-line arguments. + +```python +class Arguments(BaseModel): + # Required + string: str + integer: int + number: float + + # Optional + boolean: bool = False +``` + +Arbitrary data, such as raw command-line arguments, can be passed to a model. +After parsing and validation `pydantic` guarantees that the fields of the +resultant model instance will conform to the field types defined on the model. + +!!! info + For more information about `pydantic` models, see the `pydantic` [docs][1]. + +### Fields +A `pydantic` model contains *fields*, which are the model class attributes. +These fields define each `pydantic-argparse` command-line argument, and they +can be declared either *implicitly* (as above), or *explicitly* (as below). + +```python +class Arguments(BaseModel): + # Required + string: str = Field(description="this argument is a string") + integer: int = Field(description="this argument is an integer") + number: float = Field(description="this argument is a number") + + # Optional + boolean: bool = Field(False, description="this argument is a boolean") +``` + +Explicitly defining fields can provide extra information about an argument, +either for the command-line interface, the model schema or features such as +complex validation. + +!!! info + For more information about `pydantic` fields, see the `pydantic` [docs][2]. + +## Arguments +### Required +A field defines a required argument if it has no default value, or a default +value of the `Ellipses` (`...`) singleton object. + +```python +class Arguments(BaseModel): + a: int + b: int = ... + c: int = Field() + d: int = Field(...) +``` + +### Optional +A field defines an optional argument if it has a default value. + +```python +class Arguments(BaseModel): + a: int = 42 + b: int = Field(42) +``` + +A field can also define an optional argument if it is type-hinted as +`Optional`. This type-hinting also allows the value of `None` for the field. + +```python +class Arguments(BaseModel): + a: Optional[int] + b: Optional[int] = None + c: Optional[int] = Field() + d: Optional[int] = Field(None) +``` + +### Descriptions +A field can be provided with a `description`, which will appear in the +command-line interface help message. + +```python +class Arguments(BaseModel): + a: int = Field(description="this is the command-line description!") +``` + +### Aliases +A field can be provided with an `alias`, which will change the argument name in +the command-line interface. + +```python +class Arguments(BaseModel): + # We want our argument to be named `class` (i.e., `--class`), but `class` + # is a reserved keyword in Python. To accomplish this, we can use the Field + # `alias` to override the argument name. + class_argument: int = Field(alias="class") +``` + +!!! tip + This feature allows you to define arguments that use a reserved python + keyword as the name. For example: `class`, `continue`, `async`. + + You can see the list of reserved keywords in Python at any time by typing + `:::python help("keywords")` into the Python interpreter. + +## Environment Variables +Functionality to parse both required and optional arguments from environment +variables is provided via the `pydantic.BaseSettings` base class. + +Simply inherit from `pydantic.BaseSettings` instead of `pydantic.BaseModel`: + +```python +class Arguments(BaseSettings): + integer: int +``` + +Arguments can then be provided via environment variables: + +```console +$ export INTEGER=123 +$ python3 example.py +Arguments(integer=123) + +$ INTEGER=456 python3 example.py +Arguments(integer=456) +``` + +Arguments supplied via the command-line take precedence over environment +variables: + +```console +$ export INTEGER=123 +$ python3 example.py --integer 42 +Arguments(integer=42) + +$ INTEGER=456 python3 example.py --integer 42 +Arguments(integer=42) +``` + +## Validation +When parsing command-line arguments with `parser.parse_typed_args()`, the raw +values are parsed and validated using `pydantic`. The parser has different +behaviours depending on whether the supplied command-line arguments are valid. + +Consider the following example model: + +```python +class Arguments(BaseModel): + integer: int +``` + +### Success +When the provided command-line arguments satisfy the `pydantic` model, a +populated instance of the model is returned + +```console +$ python3 example.py --integer 42 +Arguments(integer=42) +``` + +### Failure +When the provided command-line arguments do not satisfy the `pydantic` model, +the `ArgumentParser` will provide an error to the user. For example: + +```console +$ python3 example.py +usage: example.py [-h] --integer INTEGER +example.py: error: 1 validation error for Arguments +integer + field required (type=value_error.missing) + +$ python3 example.py --integer hello +usage: example.py [-h] --integer INTEGER +example.py: error: 1 validation error for Arguments +integer + value is not a valid integer (type=type_error.integer) +``` + +!!! note + The validation error shown to the user is the same as the error that + `pydantic` provides with its `pydantic.ValidationError`. + +### Under the Hood +Under the hood `pydantic-argparse` dynamically generates *extra* custom +`@pydantic.validator` class methods for each of your argument fields. + +These validators behave slightly differently for each argument type, but in +general they: + +* Parse empty `str` values to `None`. +* Parse *choices* (i.e., `Enum`s or `Literal`s) from `str`s to their respective + types (if applicable). +* Otherwise, pass values through unchanged. + +The validators are constructed with the `pre=True` argument, ensuring that they +are called *before* any of the user's `@pydantic.validator` class methods and +the built-in `pydantic` field validation. This means they are provided with the +most raw input data possible. + +After the generated validators have been called, the fields are parsed as per +usual by the built-in `pydantic` field validation for their respective types. + +!!! note + `pydantic-argparse` also enhances `pydantic`'s built-in + [environment variable parsing][3] capabilities. + + By default, `pydantic` attempts to parse complex types as `json` values. If + this parsing fails a `pydantic.env_settings.SettingsError` is raised and + the argument parsing fails immediately with an obscure error message. This + means the values never reach the generated `pydantic-argparse` validators, + the user's custom validators or the built-in `pydantic` field validation. + + As a solution, `pydantic-argparse` wraps the existing + `pydantic.BaseSettings.parse_env_var()` environment variable parsing class + method to handle this situation. The wrapped parser passes through raw + `str` values *unchanged* if the `json` parsing fails. This allows the raw + string values to be parsed, validated and handled by the generated + `pydantic-argparse` validators and the built-in `pydantic` field validators + if applicable. + + +[1]: https://docs.pydantic.dev/usage/models/ +[2]: https://docs.pydantic.dev/usage/schema/#field-customization +[3]: https://docs.pydantic.dev/usage/settings/#parsing-environment-variable-values diff --git a/vendor/pydantic-argparse/docs/usage/arguments/regular.md b/vendor/pydantic-argparse/docs/usage/arguments/regular.md new file mode 100644 index 000000000..3bc5c4e5c --- /dev/null +++ b/vendor/pydantic-argparse/docs/usage/arguments/regular.md @@ -0,0 +1,154 @@ +## Overview +`pydantic-argparse` provides functionality for regular arguments. A regular +argument is a command-line argument that is followed by *exactly* one value. +For example: `--arg hello`, `--arg 123` or `--arg 42.0`. + +This section covers the following standard `argparse` argument functionality: + +```python +parser.add_argument("--argument", type=T) +``` + +## Usage +The intended usage of regular arguments is to capture and validate a value from +the user for the application. For example: + +```console +$ python3 example.py --name SupImDos +``` + +```python +# We can use the validated command-line arguments in the application +print(f"Hello {args.name}!") +``` + +## Singular Types +Regular arguments can be created by adding a `pydantic` `Field` with any +type that takes "singular" values. + +Some examples of simple "singular" inbuilt types: + +* `str` +* `int` +* `float` +* `dict` + +!!! info + For more information about simple inbuilt types, see the `pydantic` + [docs][1] + +!!! note + `pydantic-argparse` handles some types *specially*, such as: + + * `collections.abc.Container` (e.g., `list`, `tuple`, `set`) + * `bool` + * `enum.Enum` + * `typing.Literal` + * `pydantic.BaseModel` + + The special behaviours of these types are addressed in the following + sections. + +Any type that is able to be validated by `pydantic` can be used. This allows +for advanced argument types, for example: + +* `pydantic.FilePath` +* `pydantic.EmailStr` +* `pydantic.AnyUrl` +* `pydantic.IPvAnyAddress` + +!!! info + For more information about advanced `pydantic` types, see the `pydantic` + [docs][2] + +There are different kinds of regular arguments, which are outlined below. + +### Required +A *required* regular singular argument is defined as follows: + +```python +class Arguments(BaseModel): + # Required Singular Argument + # Note: `int` is just an example, any singular type could be used + arg: int = Field(description="this is a required singular argument") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] --arg ARG + +required arguments: + --arg ARG this is a required singular argument + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--arg 42` will set `args.arg` to `42`. +* This argument cannot be omitted. + +### Optional (Default `None`) +An *optional* regular singular argument with a default of `None` is defined as +follows: + +```python +class Arguments(BaseModel): + # Optional Singular Argument + # Note: `int` is just an example, any singular type could be used + arg: Optional[int] = Field(description="this is an optional singular argument") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--arg ARG] + +optional arguments: + --arg ARG this is a required singular argument (default: None) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--arg 42` will set `args.arg` to `42`. +* Omitting this argument will set `args.arg` to `None` (the default). + +### Optional (Default `Value`) +An *optional* container variadic argument with a constant default value is +defined as follows: + +```python +class Arguments(BaseModel): + # Optional Singular Argument + # Note: `int` is just an example, any singular type could be used + arg: int = Field(42, description="this is an optional singular argument") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--arg ARG] + +optional arguments: + --arg ARG this is a required singular argument (default: 42) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--arg 7` will set `args.arg` to `7`. +* Omitting this argument will set `args.arg` to `42` (the default). + + +[1]: https://docs.pydantic.dev/usage/types/#standard-library-types +[2]: https://docs.pydantic.dev/usage/types/#pydantic-types diff --git a/vendor/pydantic-argparse/docs/usage/arguments/variadic.md b/vendor/pydantic-argparse/docs/usage/arguments/variadic.md new file mode 100644 index 000000000..7e74605e1 --- /dev/null +++ b/vendor/pydantic-argparse/docs/usage/arguments/variadic.md @@ -0,0 +1,126 @@ +## Overview +`pydantic-argparse` provides functionality for variadic arguments. A variadic +argument is a command-line argument that is followed by one *or more* values. +For example: `--variadic a b c` or `--variadic 1 2 3 4 5 6`. + +This section covers the following standard `argparse` argument functionality: + +```python +parser.add_argument("--variadic", nargs="+") +``` + +## Usage +The intended usage of variadic arguments is to capture multiple values for an +argument. For example: + +```console +$ python3 example.py --files a.txt b.txt c.txt +``` + +```python +for file in args.files: + # We can iterate through all of the values provided by the user + ... +``` + +## Container Types +Variadic arguments can be created by adding a `pydantic` `Field` with any +type that is a `:::python collections.abc.Container` type. For example: + +* `list[T]` +* `tuple[T]` +* `set[T]` +* `frozenset[T]` +* `deque[T]` + +There are different kinds of container variadic arguments, which are outlined +below. + +### Required +A *required* container variadic argument is defined as follows: + +```python +class Arguments(BaseModel): + # Required Container Argument + # Note: `list[int]` is just an example, any container type could be used + arg: list[int] = Field(description="this is a required variadic argument") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] --arg ARG [ARG ...] + +required arguments: + --arg ARG [ARG ...] this is a required variadic argument + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--arg 1` will set `args.arg` to `[1]`. +* Providing an argument of `--arg 1 2 3` will set `args.arg` to `[1, 2, 3]`. +* This argument cannot be omitted. + +### Optional (Default `None`) +An *optional* container variadic argument with a default of `None` is defined +as follows: + +```python +class Arguments(BaseModel): + # Optional Container Argument + # Note: `list[int]` is just an example, any container type could be used + arg: Optional[list[int]] = Field(description="this is an optional variadic argument") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--arg ARG [ARG ...]] + +optional arguments: + --arg ARG [ARG ...] this is a required variadic argument (default: None) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--arg 1` will set `args.arg` to `[1]`. +* Providing an argument of `--arg 1 2 3` will set `args.arg` to `[1, 2, 3]`. +* Omitting this argument will set `args.arg` to `None` (the default). + +### Optional (Default `Value`) +An *optional* container variadic argument with a constant default value is +defined as follows: + +```python +class Arguments(BaseModel): + # Optional Container Argument + # Note: `list[int]` is just an example, any container type could be used + arg: list[int] = Field([4, 5, 6], description="this is an optional variadic argument") +``` + +This `Arguments` model generates the following command-line interface: + +```console +$ python3 example.py --help +usage: example.py [-h] [--arg ARG [ARG ...]] + +optional arguments: + --arg ARG [ARG ...] this is an optional variadic argument (default: [4, 5, 6]) + +help: + -h, --help show this help message and exit +``` + +Outcomes: + +* Providing an argument of `--arg 1` will set `args.arg` to `[1]`. +* Providing an argument of `--arg 1 2 3` will set `args.arg` to `[1, 2, 3]`. +* Omitting this argument will set `args.arg` to `[4, 5, 6]` (the default). diff --git a/vendor/pydantic-argparse/examples/commands.py b/vendor/pydantic-argparse/examples/commands.py new file mode 100644 index 000000000..075a3835a --- /dev/null +++ b/vendor/pydantic-argparse/examples/commands.py @@ -0,0 +1,52 @@ +"""Commands Example.""" + + +# Third-Party +import pydantic +import pydantic_argparse + +# Typing +from typing import Optional + + +class BuildCommand(pydantic.BaseModel): + """Build Command Arguments.""" + # Required Args + location: pydantic.FilePath = pydantic.Field(description="build location") + + +class ServeCommand(pydantic.BaseModel): + """Serve Command Arguments.""" + # Required Args + address: pydantic.IPvAnyAddress = pydantic.Field(description="serve address") + port: int = pydantic.Field(description="serve port") + + +class Arguments(pydantic.BaseModel): + """Command-Line Arguments.""" + # Optional Args + verbose: bool = pydantic.Field(False, description="verbose flag") + + # Commands + build: Optional[BuildCommand] = pydantic.Field(description="build command") + serve: Optional[ServeCommand] = pydantic.Field(description="serve command") + + +def main() -> None: + """Main Function.""" + # Create Parser and Parse Args + parser = pydantic_argparse.ArgumentParser( + model=Arguments, + prog="Example Program", + description="Example Description", + version="0.0.1", + epilog="Example Epilog", + ) + args = parser.parse_typed_args() + + # Print Args + print(args) + + +if __name__ == "__main__": + main() diff --git a/vendor/pydantic-argparse/examples/simple.py b/vendor/pydantic-argparse/examples/simple.py new file mode 100644 index 000000000..cadf01563 --- /dev/null +++ b/vendor/pydantic-argparse/examples/simple.py @@ -0,0 +1,38 @@ +"""Simple Example.""" + + +# Third-Party +import pydantic +import pydantic_argparse + + +class Arguments(pydantic.BaseModel): + """Simple Command-Line Arguments.""" + # Required Args + string: str = pydantic.Field(description="a required string") + integer: int = pydantic.Field(description="a required integer") + flag: bool = pydantic.Field(description="a required flag") + + # Optional Args + second_flag: bool = pydantic.Field(False, description="an optional flag") + third_flag: bool = pydantic.Field(True, description="an optional flag") + + +def main() -> None: + """Simple Main Function.""" + # Create Parser and Parse Args + parser = pydantic_argparse.ArgumentParser( + model=Arguments, + prog="Example Program", + description="Example Description", + version="0.0.1", + epilog="Example Epilog", + ) + args = parser.parse_typed_args() + + # Print Args + print(args) + + +if __name__ == "__main__": + main() diff --git a/vendor/pydantic-argparse/mkdocs.yml b/vendor/pydantic-argparse/mkdocs.yml new file mode 100644 index 000000000..0a7bcb5a2 --- /dev/null +++ b/vendor/pydantic-argparse/mkdocs.yml @@ -0,0 +1,92 @@ +# Site +site_name: Pydantic Argparse +site_description: Typed Argument Parsing with Pydantic +site_url: https://pydantic-argparse.supimdos.com +site_author: SupImDos + +# Repository +repo_name: SupImDos/pydantic-argparse +repo_url: https://github.com/SupImDos/pydantic-argparse + +# Navigation +nav: + - Overview: index.md + - Background: background.md + - Showcase: showcase.md + - Usage: + - Argument Parser: usage/argument_parser.md + - Arguments: + - Arguments: usage/arguments/index.md + - Regular: usage/arguments/regular.md + - Variadic: usage/arguments/variadic.md + - Flags: usage/arguments/flags.md + - Choices: usage/arguments/choices.md + - Commands: usage/arguments/commands.md + - Examples: + - Simple: examples/simple.md + - Commands: examples/commands.md + - Reference: reference/ + +# Theme +theme: + name: material + icon: + logo: material/filter-plus + repo: fontawesome/brands/github + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: deep purple + accent: deep purple + toggle: + icon: material/toggle-switch + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: deep purple + accent: deep purple + toggle: + icon: material/toggle-switch-off-outline + name: Switch to light mode + features: + - content.code.copy + - navigation.footer + - navigation.instant + - navigation.top + - navigation.sections + - navigation.indexes + - search.suggest + - search.highlight + +# Extras +extra_css: + - assets/stylesheets/reference.css + +# Markdown Extensions +markdown_extensions: + - admonition + - def_list + - tables + - pymdownx.emoji + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + +# Plugins +plugins: + - search + - mkdocstrings: + handlers: + python: + options: + show_root_toc_entry: false + show_bases: false + members_order: source + - gen-files: + scripts: + - docs/reference/reference.py + - literate-nav + - autorefs diff --git a/vendor/pydantic-argparse/poetry.lock b/vendor/pydantic-argparse/poetry.lock new file mode 100644 index 000000000..5fedf6599 --- /dev/null +++ b/vendor/pydantic-argparse/poetry.lock @@ -0,0 +1,1285 @@ + +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "astunparse" +version = "1.6.3" +description = "An AST unparser for Python" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, + {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, +] + +[package.dependencies] +six = ">=1.6.1,<2.0" +wheel = ">=0.23.0,<1.0" + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.0.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, + {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +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 = "covdefaults" +version = "2.2.2" +description = "A coverage plugin to provide sensible default settings" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "covdefaults-2.2.2-py2.py3-none-any.whl", hash = "sha256:10c193cbf290675961a09166d7cdea8a783655e04009f5493d50685fe6ec82f3"}, + {file = "covdefaults-2.2.2.tar.gz", hash = "sha256:e543862ee0347769b47b27fa586d690e6b91587a3dcaaf8552fcfb1fac03d061"}, +] + +[package.dependencies] +coverage = ">=6.0.2" + +[[package]] +name = "coverage" +version = "7.1.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"}, + {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"}, + {file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"}, + {file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"}, + {file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"}, + {file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"}, + {file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"}, + {file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"}, + {file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"}, + {file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"}, + {file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"}, + {file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"}, + {file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"}, + {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"}, + {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "exceptiongroup" +version = "1.1.0" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, + {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.0.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, + {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown" +version = "3.3.4" +description = "Python implementation of Markdown." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, + {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, +] + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.4.2" +description = "Project documentation with Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, + {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.2.1,<3.4" +mergedeep = ">=1.3.4" +packaging = ">=20.5" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""} +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autorefs" +version = "0.4.1" +description = "Automatically link across pages in MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, + {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, +] + +[package.dependencies] +Markdown = ">=3.3" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-gen-files" +version = "0.4.0" +description = "MkDocs plugin to programmatically generate documentation pages during the build" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "mkdocs-gen-files-0.4.0.tar.gz", hash = "sha256:377bff8ee8e93515916689f483d971643f83a94eed7e92318854da8f344f0163"}, + {file = "mkdocs_gen_files-0.4.0-py3-none-any.whl", hash = "sha256:3241a4c947ecd11763ca77cc645015305bf71a0e1b9b886801c114fcf9971e71"}, +] + +[package.dependencies] +mkdocs = ">=1.0.3,<2.0.0" + +[[package]] +name = "mkdocs-literate-nav" +version = "0.6.0" +description = "MkDocs plugin to specify the navigation in Markdown instead of YAML" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_literate_nav-0.6.0-py3-none-any.whl", hash = "sha256:8c1b84714e5974da5e44e011ec0069275ae7647270c13a679662cf6ffce675a4"}, + {file = "mkdocs_literate_nav-0.6.0.tar.gz", hash = "sha256:81ccbea18163ae8e10bd0bd39237fe70c32a1f2dff6c170779f5d52dd98a0470"}, +] + +[package.dependencies] +mkdocs = ">=1.0.3" + +[[package]] +name = "mkdocs-material" +version = "9.0.13" +description = "Documentation that simply works" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material-9.0.13-py3-none-any.whl", hash = "sha256:06e51eba6a090de070a3489890cf1e491d52c04c6ff2b06dd4586c6cdd974a3f"}, + {file = "mkdocs_material-9.0.13.tar.gz", hash = "sha256:a62696610899d01df091b4d5ad23f9811f878a1f34307d7cea677baf4854c84f"}, +] + +[package.dependencies] +colorama = ">=0.4" +jinja2 = ">=3.0" +markdown = ">=3.2" +mkdocs = ">=1.4.2" +mkdocs-material-extensions = ">=1.1" +pygments = ">=2.14" +pymdown-extensions = ">=9.9.1" +regex = ">=2022.4.24" +requests = ">=2.26" + +[[package]] +name = "mkdocs-material-extensions" +version = "1.1.1" +description = "Extension pack for Python Markdown and MkDocs Material." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, + {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, +] + +[[package]] +name = "mkdocstrings" +version = "0.20.0" +description = "Automatic documentation from sources, for MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, + {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, +] + +[package.dependencies] +Jinja2 = ">=2.11.1" +Markdown = ">=3.3" +MarkupSafe = ">=1.1" +mkdocs = ">=1.2" +mkdocs-autorefs = ">=0.3.1" +mkdocstrings-python-legacy = {version = ">=0.2.1", optional = true, markers = "extra == \"python-legacy\""} +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python-legacy" +version = "0.2.3" +description = "A legacy Python handler for mkdocstrings." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocstrings-python-legacy-0.2.3.tar.gz", hash = "sha256:3fb58fdabe19c6b52b8bb1d3bb1540b1cd527b562865468d6754e8cd1201050c"}, + {file = "mkdocstrings_python_legacy-0.2.3-py3-none-any.whl", hash = "sha256:1b04d71a4064b0bb8ea9448debab89868a752c7e7bfdd11de480dfbcb9751a00"}, +] + +[package.dependencies] +mkdocstrings = ">=0.19" +pytkdocs = ">=0.14" + +[[package]] +name = "mypy" +version = "1.0.0" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0626db16705ab9f7fa6c249c017c887baf20738ce7f9129da162bb3075fc1af"}, + {file = "mypy-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ace23f6bb4aec4604b86c4843276e8fa548d667dbbd0cb83a3ae14b18b2db6c"}, + {file = "mypy-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87edfaf344c9401942883fad030909116aa77b0fa7e6e8e1c5407e14549afe9a"}, + {file = "mypy-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0ab090d9240d6b4e99e1fa998c2d0aa5b29fc0fb06bd30e7ad6183c95fa07593"}, + {file = "mypy-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:7cc2c01dfc5a3cbddfa6c13f530ef3b95292f926329929001d45e124342cd6b7"}, + {file = "mypy-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14d776869a3e6c89c17eb943100f7868f677703c8a4e00b3803918f86aafbc52"}, + {file = "mypy-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb2782a036d9eb6b5a6efcdda0986774bf798beef86a62da86cb73e2a10b423d"}, + {file = "mypy-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cfca124f0ac6707747544c127880893ad72a656e136adc935c8600740b21ff5"}, + {file = "mypy-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8845125d0b7c57838a10fd8925b0f5f709d0e08568ce587cc862aacce453e3dd"}, + {file = "mypy-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b1b9e1ed40544ef486fa8ac022232ccc57109f379611633ede8e71630d07d2"}, + {file = "mypy-1.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c7cf862aef988b5fbaa17764ad1d21b4831436701c7d2b653156a9497d92c83c"}, + {file = "mypy-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd187d92b6939617f1168a4fe68f68add749902c010e66fe574c165c742ed88"}, + {file = "mypy-1.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4e5175026618c178dfba6188228b845b64131034ab3ba52acaffa8f6c361f805"}, + {file = "mypy-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2f6ac8c87e046dc18c7d1d7f6653a66787a4555085b056fe2d599f1f1a2a2d21"}, + {file = "mypy-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7306edca1c6f1b5fa0bc9aa645e6ac8393014fa82d0fa180d0ebc990ebe15964"}, + {file = "mypy-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3cfad08f16a9c6611e6143485a93de0e1e13f48cfb90bcad7d5fde1c0cec3d36"}, + {file = "mypy-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67cced7f15654710386e5c10b96608f1ee3d5c94ca1da5a2aad5889793a824c1"}, + {file = "mypy-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a86b794e8a56ada65c573183756eac8ac5b8d3d59daf9d5ebd72ecdbb7867a43"}, + {file = "mypy-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:50979d5efff8d4135d9db293c6cb2c42260e70fb010cbc697b1311a4d7a39ddb"}, + {file = "mypy-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4c7a99e5153496243146a3baf33b9beff714464ca386b5f62daad601d87af"}, + {file = "mypy-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e398652d005a198a7f3c132426b33c6b85d98aa7dc852137a2a3be8890c4072"}, + {file = "mypy-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be78077064d016bc1b639c2cbcc5be945b47b4261a4f4b7d8923f6c69c5c9457"}, + {file = "mypy-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92024447a339400ea00ac228369cd242e988dd775640755fa4ac0c126e49bb74"}, + {file = "mypy-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:fe523fcbd52c05040c7bee370d66fee8373c5972171e4fbc323153433198592d"}, + {file = "mypy-1.0.0-py3-none-any.whl", hash = "sha256:2efa963bdddb27cb4a0d42545cd137a8d2b883bd181bbc4525b568ef6eca258f"}, + {file = "mypy-1.0.0.tar.gz", hash = "sha256:f34495079c8d9da05b183f9f7daec2878280c2ad7cc81da686ef0b484cea2ecf"}, +] + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.4" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = ">=2.7" +files = [ + {file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"}, +] + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] + +[[package]] +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, + {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, +] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poethepoet" +version = "0.18.1" +description = "A task runner that works well with poetry." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "poethepoet-0.18.1-py3-none-any.whl", hash = "sha256:e85727bf6f4a10bf6c1a43026bdeb40df689bea3c4682d03cbe531cabc8f2ba6"}, + {file = "poethepoet-0.18.1.tar.gz", hash = "sha256:5f3566b14c2f5dccdfbc3bb26f0096006b38dc0b9c74bd4f8dd1eba7b0e29f6a"}, +] + +[package.dependencies] +pastel = ">=0.2.1,<0.3.0" +tomli = ">=1.2.2" + +[package.extras] +poetry-plugin = ["poetry (>=1.0,<2.0)"] + +[[package]] +name = "pydantic" +version = "1.10.4" +description = "Data validation and settings management using python type hints" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854"}, + {file = "pydantic-1.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817"}, + {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c"}, + {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e"}, + {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85"}, + {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6"}, + {file = "pydantic-1.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d"}, + {file = "pydantic-1.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53"}, + {file = "pydantic-1.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3"}, + {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a"}, + {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"}, + {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72"}, + {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857"}, + {file = "pydantic-1.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3"}, + {file = "pydantic-1.10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00"}, + {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978"}, + {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e"}, + {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa"}, + {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8"}, + {file = "pydantic-1.10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903"}, + {file = "pydantic-1.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423"}, + {file = "pydantic-1.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f"}, + {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06"}, + {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15"}, + {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c"}, + {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416"}, + {file = "pydantic-1.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6"}, + {file = "pydantic-1.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d"}, + {file = "pydantic-1.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f"}, + {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024"}, + {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c"}, + {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28"}, + {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f"}, + {file = "pydantic-1.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4"}, + {file = "pydantic-1.10.4-py3-none-any.whl", hash = "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774"}, + {file = "pydantic-1.10.4.tar.gz", hash = "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pymdown-extensions" +version = "9.9.2" +description = "Extension pack for Python Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymdown_extensions-9.9.2-py3-none-any.whl", hash = "sha256:c3d804eb4a42b85bafb5f36436342a5ad38df03878bb24db8855a4aa8b08b765"}, + {file = "pymdown_extensions-9.9.2.tar.gz", hash = "sha256:ebb33069bafcb64d5f5988043331d4ea4929325dc678a6bcf247ddfcf96499f8"}, +] + +[package.dependencies] +markdown = ">=3.2" + +[[package]] +name = "pytest" +version = "7.2.1" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, + {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.10.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytkdocs" +version = "0.16.1" +description = "Load Python objects documentation." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytkdocs-0.16.1-py3-none-any.whl", hash = "sha256:a8c3f46ecef0b92864cc598e9101e9c4cf832ebbf228f50c84aa5dd850aac379"}, + {file = "pytkdocs-0.16.1.tar.gz", hash = "sha256:e2ccf6dfe9dbbceb09818673f040f1a7c32ed0bffb2d709b06be6453c4026045"}, +] + +[package.dependencies] +astunparse = {version = ">=1.6", markers = "python_version < \"3.9\""} +cached-property = {version = ">=1.5", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=3.7", markers = "python_version < \"3.8\""} + +[package.extras] +numpy-style = ["docstring_parser (>=0.7)"] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "regex" +version = "2022.10.31" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "regex-2022.10.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f"}, + {file = "regex-2022.10.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66"}, + {file = "regex-2022.10.31-cp310-cp310-win32.whl", hash = "sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1"}, + {file = "regex-2022.10.31-cp310-cp310-win_amd64.whl", hash = "sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5"}, + {file = "regex-2022.10.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe"}, + {file = "regex-2022.10.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7"}, + {file = "regex-2022.10.31-cp311-cp311-win32.whl", hash = "sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af"}, + {file = "regex-2022.10.31-cp311-cp311-win_amd64.whl", hash = "sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61"}, + {file = "regex-2022.10.31-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4"}, + {file = "regex-2022.10.31-cp36-cp36m-win32.whl", hash = "sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066"}, + {file = "regex-2022.10.31-cp36-cp36m-win_amd64.whl", hash = "sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6"}, + {file = "regex-2022.10.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95"}, + {file = "regex-2022.10.31-cp37-cp37m-win32.whl", hash = "sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394"}, + {file = "regex-2022.10.31-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0"}, + {file = "regex-2022.10.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d"}, + {file = "regex-2022.10.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c"}, + {file = "regex-2022.10.31-cp38-cp38-win32.whl", hash = "sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc"}, + {file = "regex-2022.10.31-cp38-cp38-win_amd64.whl", hash = "sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453"}, + {file = "regex-2022.10.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49"}, + {file = "regex-2022.10.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892"}, + {file = "regex-2022.10.31-cp39-cp39-win32.whl", hash = "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1"}, + {file = "regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692"}, + {file = "regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"}, +] + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "ruff" +version = "0.0.247" +description = "An extremely fast Python linter, written in Rust." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.247-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:0151face9ef0e09c0d09166eae5f6df9d61ed7b1686086092d56164b790d1adf"}, + {file = "ruff-0.0.247-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:0abffda0039dc0eec18d624a48a725c414587c816194d1c9889eceba82e87ad0"}, + {file = "ruff-0.0.247-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e34ce0a12a9c7ac25fcfd8a9a25ade778f4e54df37f7ce58c406c36f9d5a1e3"}, + {file = "ruff-0.0.247-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c31adc9f08e1652acb6c1b6d494a3e52895e341398b5dcaffe3325688f70de87"}, + {file = "ruff-0.0.247-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebc3b3077a880ea8af9f17c5614f606d6c1a15db6823501f4b8d3daf51f78782"}, + {file = "ruff-0.0.247-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:403f67452655923d0775c6c3854750e77c9c97eb875ea979ad515d3c75a45cff"}, + {file = "ruff-0.0.247-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53dd6124c6b822c27ee23965ce9d8c5fbc76a97ecc209daef0bbfbe8f905cb18"}, + {file = "ruff-0.0.247-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1483c7435db4926da3793a89f6bbb68dedf2990aeddef01407d8c47953403e0"}, + {file = "ruff-0.0.247-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ce619be01206ab71054c9f492a803cc81be678222379c69a0d60aa66c30e4a2"}, + {file = "ruff-0.0.247-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:172c0a8fb295259d9e12e43c39cf3bd006ae85eae89b8e9ca6ece7252241b603"}, + {file = "ruff-0.0.247-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0cda3a13e67adaf5198c69847a2f04011434bdfbfdca05ac32c101991dd56162"}, + {file = "ruff-0.0.247-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4481b5b6103dffc09156f2fea79a9a9282a72c0109ca4ab74828ae1089ec8c7e"}, + {file = "ruff-0.0.247-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8c835b703cebb0f23d59ec3d83ff498c5290fae51f98df548aacbb9ff85cc93c"}, + {file = "ruff-0.0.247-py3-none-win32.whl", hash = "sha256:3695f5fd2f4ad44030799a6021b2626442e8d92e432d646aadeefd4a1fceab12"}, + {file = "ruff-0.0.247-py3-none-win_amd64.whl", hash = "sha256:3e22f08bc403d3b4f32488ea52cd69fc3cb343b2c99431fd969cda1c83f4bc2f"}, + {file = "ruff-0.0.247-py3-none-win_arm64.whl", hash = "sha256:737b7fd25d2523b7c526830a3670364a953cb6c6bbf9912c78cba06bbf0ca125"}, + {file = "ruff-0.0.247.tar.gz", hash = "sha256:cce9566cea1cb348bb2dec99f810d846d112627fa52bf3a554773ce4737a061b"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, +] + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] + +[[package]] +name = "urllib3" +version = "1.26.14" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "watchdog" +version = "2.2.1" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"}, + {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"}, + {file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3"}, + {file = "watchdog-2.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7"}, + {file = "watchdog-2.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e"}, + {file = "watchdog-2.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd"}, + {file = "watchdog-2.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e"}, + {file = "watchdog-2.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_i686.whl", hash = "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e"}, + {file = "watchdog-2.2.1-py3-none-win32.whl", hash = "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73"}, + {file = "watchdog-2.2.1-py3-none-win_amd64.whl", hash = "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e"}, + {file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"}, + {file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wheel" +version = "0.38.4" +description = "A built-package format for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, + {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, +] + +[package.extras] +test = ["pytest (>=3.0.0)"] + +[[package]] +name = "zipp" +version = "3.12.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.12.1-py3-none-any.whl", hash = "sha256:6c4fe274b8f85ec73c37a8e4e3fa00df9fb9335da96fb789e3b96b318e5097b3"}, + {file = "zipp-3.12.1.tar.gz", hash = "sha256:a3cac813d40993596b39ea9e93a18e8a2076d5c378b8bc88ec32ab264e04ad02"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.7" +content-hash = "907ba36c3d14a18e35cdfadf827d82cbedc1ef8e294fed646f0ec1203f695b73" diff --git a/vendor/pydantic-argparse/pydantic_argparse/__init__.py b/vendor/pydantic-argparse/pydantic_argparse/__init__.py new file mode 100644 index 000000000..4612e66c3 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/__init__.py @@ -0,0 +1,53 @@ +"""Declarative Typed Argument Parsing with Pydantic Models. + +This is the `pydantic-argparse` package, which contains the classes, methods +and functions required for declarative and typed argument parsing with +`pydantic` models. + +The public interface exposed by this package is the declarative and typed +`ArgumentParser` class, as well as the package "dunder" metadata. +""" + +# Local +from pydantic import BaseModel, ConfigDict + +from pydantic_argparse.__metadata__ import ( + __author__, + __description__, + __license__, + __title__, + __version__, +) +from pydantic_argparse.argparse import ArgumentParser + +from . import argparse, parsers, utils + + +class BaseArgument(BaseModel): + """Base pydantic model for argument groups.""" + + model_config = ConfigDict(json_schema_extra=dict(subcommand=False)) + + +class BaseCommand(BaseModel): + """Base pydantic model for command groups. + + This class is only a convenience base class that sets the + `model_config` parameter to have the `json_schema_extra` parameter to + have `subcommand=True`. + """ + + model_config = ConfigDict(json_schema_extra=dict(subcommand=True)) + + +# Public Re-Exports +__all__ = ( + "ArgumentParser", + "BaseArgument", + "BaseCommand", + "__title__", + "__description__", + "__version__", + "__author__", + "__license__", +) diff --git a/vendor/pydantic-argparse/pydantic_argparse/__metadata__.py b/vendor/pydantic-argparse/pydantic_argparse/__metadata__.py new file mode 100644 index 000000000..ff9e598eb --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/__metadata__.py @@ -0,0 +1,28 @@ +"""Single-Source of Truth Package Versioning and Metadata. + +The `pydantic-argparse` package uses the `pyproject.toml` file as a +single-source of truth for the package metadata. As such, rather than +duplicating the metadata in code here, it is retrieved from the installed +package metadata at runtime. + +The metadata exported are the `title`, `description`, `version`, `author` and +`license` of the package +""" + + +# Standard +import sys + +# Version-Guarded +if sys.version_info < (3, 8): # pragma: <3.8 cover + import importlib_metadata as metadata +else: # pragma: >=3.8 cover + from importlib import metadata + + +# Retrieve Metadata from Package +__title__: str = metadata.metadata(__package__)["name"] +__description__: str = metadata.metadata(__package__)["summary"] +__version__: str = metadata.metadata(__package__)["version"] +__author__: str = metadata.metadata(__package__)["author"] +__license__: str = metadata.metadata(__package__)["license"] diff --git a/vendor/pydantic-argparse/pydantic_argparse/argparse/__init__.py b/vendor/pydantic-argparse/pydantic_argparse/argparse/__init__.py new file mode 100644 index 000000000..e69f7f384 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/argparse/__init__.py @@ -0,0 +1,18 @@ +"""Declarative and Typed Argument Parsing. + +This package contains the classes and methods required for declarative and +typed argument parsing. + +The public interface exposed by this package is the `ArgumentParser` class, +which is intended to be a *near* drop-in replacement for the Python standard +library `argparse.ArgumentParser` - while providing declarative and typed +argument parsing. +""" + +# Local +from pydantic_argparse.argparse.parser import ArgumentParser + +# Public Re-Exports +__all__ = ( + "ArgumentParser", +) diff --git a/vendor/pydantic-argparse/pydantic_argparse/argparse/actions.py b/vendor/pydantic-argparse/pydantic_argparse/argparse/actions.py new file mode 100644 index 000000000..91ec9f570 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/argparse/actions.py @@ -0,0 +1,237 @@ +"""Recursively Nesting Sub-Parsers Action for Typed Argument Parsing. + +The `actions` module contains the `SubParsersAction` class, which is an action +that provides recursive namespace nesting when parsing sub-commands. It also +contains the `BooleanOptionalAction` class, which is a direct backport of the +Python standard library `argparse` class of the same name. +""" + +import argparse +from typing import ( + Any, + Callable, + Iterable, + List, + Optional, + Sequence, + Tuple, + TypeVar, + Union, + cast, +) + +# Constants +T = TypeVar("T") + + +class SubParsersAction(argparse._SubParsersAction): + """Recursively Nesting Sub-Parsers Action for Typed Argument Parsing. + + This custom action differs in functionality from the existing standard + argparse SubParsersAction because it nests the resultant sub-namespace + directly into the supplied parent namespace, rather than iterating through + and updating the parent namespace object with each argument individually. + + Example: + Construct `ArgumentParser`: + ```python + # Create Argument Parser + parser = argparse.ArgumentParser() + + # Add Example Global Argument + parser.add_argument("--time") + + # Add SubParsersAction + subparsers = parser.add_subparsers() + + # Add Example 'walk' Command with Arguments + walk = subparsers.add_parser("walk") + walk.add_argument("--speed") + walk.add_argument("--distance") + + # Add Example 'talk' Command with Arguments + talk = subparsers.add_parser("talk") + talk.add_argument("--volume") + talk.add_argument("--topic") + ``` + + Parse the Arguments: + ```console + --time 3 walk --speed 7 --distance 42 + ``` + + Check Resultant Namespaces: + ```python + Original: Namespace(time=3, speed=7, distance=42) + Custom: Namespace(time=3, walk=Namespace(speed=7, distance=42)) + ``` + + This behaviour results in a final namespace structure which is much easier + to parse, where subcommands are easily identified and nested into their own + namespace recursively. + """ + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, + ) -> None: + """Parses arguments into a namespace with the specified subparser. + + This custom method parses arguments with the specified subparser, then + embeds the resultant sub-namespace into the supplied parent namespace. + + Args: + parser (argparse.ArgumentParser): Parent argument parser object. + namespace (argparse.Namespace): Parent namespace being parsed to. + values (Union[str, Sequence[Any], None]): Arguments to parse. + option_string (Optional[str]): Optional option string (not used). + + Raises: + argparse.ArgumentError: Raised if subparser name does not exist. + """ + # Check values object is a sequence + # In order to not violate the Liskov Substitution Principle (LSP), the + # function signature for __call__ must match the base Action class. As + # such, this function signature also accepts 'str' and 'None' types for + # the values argument. However, in reality, this should only ever be a + # list of strings here, so we just do a type cast. + values = cast(List[str], values) + + # Get Parser Name and Remaining Argument Strings + parser_name, *arg_strings = values + + # Try select the parser + try: + # Select the parser + parser = self._name_parser_map[parser_name] + + except KeyError as exc: + # Parser doesn't exist, raise an exception + raise argparse.ArgumentError( + self, + f"unknown parser {parser_name} (choices: {', '.join(self._name_parser_map)})", + ) from exc + + # Parse all the remaining options into a sub-namespace, then embed this + # sub-namespace into the parent namespace + subnamespace, arg_strings = parser.parse_known_args(arg_strings) + setattr(namespace, parser_name, subnamespace) + + # Store any unrecognized options on the parent namespace, so that the + # top level parser can decide what to do with them + if arg_strings: + vars(namespace).setdefault(argparse._UNRECOGNIZED_ARGS_ATTR, []) + getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) + + +class BooleanOptionalAction(argparse.Action): # pragma: no cover + """Action for parsing paired GNU-style boolean arguments. + + This backported action provides the functionality for parsing paired + GNU-style boolean arguments, such as "--foo/--no-foo". This style of + argument allows us to easily provide *required* boolean arguments. + + This action was added into the Python standard library `argparse` module + in [`BPO-8538`](https://bugs.python.org/issue8538) and is available in + Python 3.9 and above. In order to support Python 3.7 and 3.8 we directly + backport the class and make it available here. + + Source: + + """ + + def __init__( + self, + option_strings: Sequence[str], + dest: str, + default: Optional[Union[T, str]] = None, + type: Optional[ # noqa: A002] + Union[Callable[[str], T], argparse.FileType] + ] = None, + choices: Optional[Iterable[T]] = None, + required: bool = False, + help: Optional[str] = None, # noqa: A002 + metavar: Optional[Union[str, Tuple[str, ...]]] = None, + ) -> None: + """Instantiates the Boolean Optional Action. + + This creates the default provided "--" option strings which set + the argument to `True`. It also creates alternative pair "--no-" + option strings which set the argument to `False`. + + Args: + option_strings (Sequence[str]): Option strings. + dest (str): Destination variable to save the value to. + default (Optional[Union[T, str]]): Default value of the option. + type (Optional[Union[Callable[[str], T], argparse.FileType]]): Type + to cast the option to. + choices (Optional[Iterable[T]]): Allowed values for the option. + required (bool): Whether the option is required. + help (Optional[str]): Help string for the option. + metavar (Optional[Union[str, Tuple[str, ...]]]): Meta variable name + for the option. + """ + # Initialise intermediary option strings list + _option_strings = [] + + # Loop through passed in option strings + for option_string in option_strings: + # Append the option string to the new list + _option_strings.append(option_string) + + # Check if this option string is a "--" option string + if option_string.startswith("--"): + # Create a "--no-" negated option string + option_string = "--no-" + option_string[2:] + + # Append the negated option string to the new list as well + _option_strings.append(option_string) + + # Initialise Super Class + super().__init__( + option_strings=_option_strings, + dest=dest, + nargs=0, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar, + ) + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Optional[Union[str, Sequence[Any]]], + option_string: Optional[str] = None, + ) -> None: + """Parses the provided boolean arguments into a namespace. + + This custom method parses arguments as booleans, negating the values of + any arguments prepended with "--no-". + + Args: + parser (argparse.ArgumentParser): Parent argument parser object. + namespace (argparse.Namespace): Parent namespace being parsed to. + values (Optional[Union[str, Sequence[Any]]]): Arguments to parse. + option_string (Optional[str]): Optional option string. + """ + # Check if the passed in option string matches our option strings + if option_string in self.option_strings: + # Set a boolean value on the namespace + # If the option string starts with "--no-", then negate the value + setattr(namespace, self.dest, not option_string.startswith("--no-")) # type: ignore[union-attr] + + def format_usage(self) -> str: + """Formats the usage string. + + Returns: + str: Usage string for the option. + """ + # Format and return usage string + return " | ".join(self.option_strings) diff --git a/vendor/pydantic-argparse/pydantic_argparse/argparse/parser.py b/vendor/pydantic-argparse/pydantic_argparse/argparse/parser.py new file mode 100644 index 000000000..ec0c457fe --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/argparse/parser.py @@ -0,0 +1,280 @@ +"""Declarative and Typed Argument Parser. + +The `parser` module contains the `ArgumentParser` class, which provides a +declarative method of defining command-line interfaces. + +The procedure to declaratively define a typed command-line interface is: + +1. Define `pydantic` arguments model +2. Create typed `ArgumentParser` +3. Parse typed arguments + +The resultant arguments object returned is an instance of the defined +`pydantic` model. This means that the arguments object and its attributes will +be compatible with an IDE, linter or type checker. +""" + + +import argparse +import sys +from typing import Dict, Generic, List, NoReturn, Optional, Type, cast + +from pydantic import BaseModel, ValidationError + +from pydantic_argparse import parsers, utils +from pydantic_argparse.argparse import actions +from pydantic_argparse.utils.nesting import _NestedArgumentParser +from pydantic_argparse.utils.pydantic import PydanticField, PydanticModelT + + +class ArgumentParser(argparse.ArgumentParser, Generic[PydanticModelT]): + """Declarative and Typed Argument Parser. + + The `ArgumentParser` declaratively generates a command-line interface using + the `pydantic` model specified upon instantiation. + + The `ArgumentParser` provides the following `argparse` functionality: + + * Argument Groups + * Subcommands + + All arguments are *named*, and positional arguments are not supported. + + The `ArgumentParser` provides the method `parse_typed_args()` to parse + command line arguments and return an instance of its bound `pydantic` + model, populated with the parsed and validated user supplied command-line + arguments. + """ + + # Argument Group Names + COMMANDS = "commands" + HELP = "help" + + # Exit Codes + EXIT_ERROR = 2 + + def __init__( + self, + model: Type[PydanticModelT], + prog: Optional[str] = None, + description: Optional[str] = None, + version: Optional[str] = None, + epilog: Optional[str] = None, + add_help: bool = True, + exit_on_error: bool = True, + ) -> None: + """Instantiates the Typed Argument Parser with its `pydantic` model. + + Args: + model (Type[PydanticModelT]): Pydantic argument model class. + prog (Optional[str]): Program name for CLI. + description (Optional[str]): Program description for CLI. + version (Optional[str]): Program version string for CLI. + epilog (Optional[str]): Optional text following help message. + add_help (bool): Whether to add a `-h`/`--help` flag. + exit_on_error (bool): Whether to exit on error. + """ + # Initialise Super Class + if sys.version_info < (3, 9): # pragma: <3.9 cover + super().__init__( + prog=prog, + description=description, + epilog=epilog, + add_help=False, # Always disable the automatic help flag. + argument_default=argparse.SUPPRESS, # Allow `pydantic` to handle defaults. + ) + + else: # pragma: >=3.9 cover + super().__init__( + prog=prog, + description=description, + epilog=epilog, + exit_on_error=exit_on_error, + add_help=False, # Always disable the automatic help flag. + argument_default=argparse.SUPPRESS, # Allow `pydantic` to handle defaults. + ) + + # Set Version, Add Help and Exit on Error Flag + self.version = version + self.add_help = add_help + self.exit_on_error = exit_on_error + + # Add Arguments Groups + self._subcommands: Optional[argparse._SubParsersAction] = None + self._help_group = self.add_argument_group(ArgumentParser.HELP) + + # Add Help and Version Flags + if self.add_help: + self._add_help_flag() + if self.version: + self._add_version_flag() + + # Add Arguments from Model + self._submodels: dict[str, Type[BaseModel]] = dict() + self.model = self._add_model(model) + + @property + def has_submodels(self) -> bool: # noqa: D102 + # this is for simple nested models as arg groups + has_submodels = len(self._submodels) > 0 + + # this is for nested commands + if self._subcommands is not None: + has_submodels = has_submodels or any( + len(subparser._submodels) > 0 + for subparser in self._subcommands.choices.values() + ) + return has_submodels + + def parse_typed_args( + self, + args: Optional[List[str]] = None, + ) -> PydanticModelT: + """Parses command line arguments. + + If `args` are not supplied by the user, then they are automatically + retrieved from the `sys.argv` command-line arguments. + + Args: + args (Optional[List[str]]): Optional list of arguments to parse. + + Returns: + PydanticModelT: Populated instance of typed arguments model. + + Raises: + argparse.ArgumentError: Raised upon error, if not exiting on error. + SystemExit: Raised upon error, if exiting on error. + """ + # Call Super Class Method + namespace = self.parse_args(args) + + try: + nested_parser = _NestedArgumentParser(model=self.model, namespace=namespace) + return cast(PydanticModelT, nested_parser.validate()) + except ValidationError as exc: + # Catch exceptions, and use the ArgumentParser.error() method + # to report it to the user + self.error(utils.errors.format(exc)) + + def error(self, message: str) -> NoReturn: + """Prints a usage message to `stderr` and exits if required. + + Args: + message (str): Message to print to the user. + + Raises: + argparse.ArgumentError: Raised if not exiting on error. + SystemExit: Raised if exiting on error. + """ + # Print usage message + self.print_usage(sys.stderr) + + # Check whether parser should exit + if self.exit_on_error: + self.exit(ArgumentParser.EXIT_ERROR, f"{self.prog}: error: {message}\n") + + # Raise Error + raise argparse.ArgumentError(None, f"{self.prog}: error: {message}") + + def _commands(self) -> argparse._SubParsersAction: + """Creates and Retrieves Subcommands Action for the ArgumentParser. + + Returns: + argparse._SubParsersAction: SubParsersAction for the subcommands. + """ + # Check for Existing Sub-Commands Group + if self._subcommands is None: + # Add Sub-Commands Group + self._subcommands = self.add_subparsers( + title=ArgumentParser.COMMANDS, + action=actions.SubParsersAction, + required=True, + ) + + # Shuffle Group to the Top for Help Message + self._action_groups.insert(0, self._action_groups.pop()) + + # Return + return self._subcommands + + def _add_help_flag(self) -> None: + """Adds help flag to argparser.""" + # Add help flag + self._help_group.add_argument( + "-h", + "--help", + action=argparse._HelpAction, + help="show this help message and exit", + ) + + def _add_version_flag(self) -> None: + """Adds version flag to argparser.""" + # Add version flag + self._help_group.add_argument( + "-v", + "--version", + action=argparse._VersionAction, + help="show program's version number and exit", + ) + + def _add_model( + self, + model: Type[BaseModel], + arg_group: Optional[argparse._ArgumentGroup] = None, + ) -> Type[BaseModel]: + """Adds the `pydantic` model to the argument parser. + + This method also generates "validators" for the arguments derived from + the `pydantic` model, and generates a new subclass from the model + containing these validators. + + Args: + model (Type[PydanticModelT]): Pydantic model class to add to the + argument parser. + arg_group: (Optional[argparse._ArgumentGroup]): argparse ArgumentGroup. + This should not normally be passed manually, but only during + recursion if the original model is a nested pydantic model. These + nested models are then parsed as argument groups. + + Returns: + Type[PydanticModelT]: Pydantic model possibly with new validators. + """ + # Initialise validators dictionary + validators: Dict[str, utils.pydantic.PydanticValidator] = dict() + parser = self if arg_group is None else arg_group + + # Loop through fields in model + for field in PydanticField.parse_model(model): + if field.is_a(BaseModel): + if field.is_subcommand(): + validator = parsers.command.parse_field(self._commands(), field) + else: + # for any nested pydantic models, set default factory to model_construct + # method. This allows pydantic to handle if no arguments from a nested + # submodel are passed by creating the default submodel. + # This is not allowed for subcommands. + if field.info.default_factory is None: + field.info.default_factory = field.model_type.model_construct + + # create new arg group + group_name = str.upper(field.info.title or field.name) + arg_group = self.add_argument_group(group_name) + + # recurse and parse fields below this submodel + # TODO: storage of submodels not needed + self._submodels[field.name] = self._add_model( + model=field.model_type, + arg_group=arg_group, + ) + + validator = None + + else: + # Add field + validator = parsers.add_field(parser, field) + + # Update validators + utils.pydantic.update_validators(validators, validator) + + # Construct and return model with validators + return utils.pydantic.model_with_validators(model, validators) diff --git a/vendor/pydantic-argparse/pydantic_argparse/argparse/patches.py b/vendor/pydantic-argparse/pydantic_argparse/argparse/patches.py new file mode 100644 index 000000000..0424bb1ad --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/argparse/patches.py @@ -0,0 +1,46 @@ +"""Monkey patches for ArgumentParser. + +In order to support Python 3.7 and 3.8 while retaining the unit tests, we need +to backport the bugfix for [`BPO-29298`](https://bugs.python.org/issue29298). +""" +import argparse +import sys +from typing import Optional + + +# In Python versions before 3.9, using argparse with required subparsers will +# cause an unhelpful `TypeError` if the 'dest' parameter is not explicitly +# specified, and no arguments are provided. This bug was fixed in 3.11 and +# backported to 3.10 and 3.9. Here, we backport it to 3.7 and 3.8 as well, via +# monkey-patching. +# See: https://github.com/python/cpython/blob/v3.11.1/Lib/argparse.py#L739-L751 +if sys.version_info < (3, 9): # pragma: <3.9 cover + def _get_action_name(argument: Optional[argparse.Action]) -> Optional[str]: # pragma: no cover + """Generates the name for an argument action. + + The behaviour differs depending on what the action contains: + * `option_strings` are concatenated with a slash. + * `metavar` and `dest` are returned verbatim. + * `choices` are joined and represented as a comma-separated set. + + Args: + argument (Optional[argparse.Action]): Argument action. + + Returns: + Optional[str]: Generated action name. + """ + if argument is None: + return None + elif argument.option_strings: + return "/".join(argument.option_strings) + elif argument.metavar not in (None, argparse.SUPPRESS): + return argument.metavar + elif argument.dest not in (None, argparse.SUPPRESS): + return argument.dest + elif argument.choices: + return "{" + ",".join(argument.choices) + "}" + else: + return None + + # Monkey-Patch + argparse._get_action_name = _get_action_name diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/__init__.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/__init__.py new file mode 100644 index 000000000..263609ed1 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/__init__.py @@ -0,0 +1,57 @@ +"""Parses Pydantic Fields to Command-Line Arguments. + +This package contains the functions required for parsing `pydantic` model +fields to `ArgumentParser` command-line arguments. + +The public interface exposed by this package is the `parsing` modules, which +each contain the `should_parse()` and `parse_field()` functions. +""" + +from typing import Optional + +from pydantic_argparse.utils.pydantic import PydanticField, PydanticValidator + +from . import ( + boolean, + command, + container, + enum, + literal, + mapping, + standard, +) +from .utils import SupportsAddArgument + + +def add_field( + parser: SupportsAddArgument, + field: PydanticField, +) -> Optional[PydanticValidator]: + """Parses pydantic field type, and then adds it to argument parser. + + Args: + parser (argparse.ArgumentParser | argparse._ArgumentGroup): Sub-parser to add to. + field (pydantic.fields.ModelField): Field to be added to parser. + + Returns: + Optional[utils.pydantic.PydanticValidator]: Possible validator method. + """ + # Switch on Field Type -- for fields that are pydantic models + # this gets handled at the top level to distinguish + # subcommands from arg groups + if boolean.should_parse(field): + return boolean.parse_field(parser, field) + + if container.should_parse(field): + return container.parse_field(parser, field) + + if mapping.should_parse(field): + return mapping.parse_field(parser, field) + + if literal.should_parse(field): + return literal.parse_field(parser, field) + + if enum.should_parse(field): + return enum.parse_field(parser, field) + + return standard.parse_field(parser, field) diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/boolean.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/boolean.py new file mode 100644 index 000000000..04c8db457 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/boolean.py @@ -0,0 +1,67 @@ +"""Parses Boolean Pydantic Fields to Command-Line Arguments. + +The `boolean` module contains the `should_parse` function, which checks whether +this module should be used to parse the field, as well as the `parse_field` +function, which parses boolean `pydantic` model fields to `ArgumentParser` +command-line arguments. +""" + +import argparse +from typing import Optional + +from pydantic_argparse import utils +from pydantic_argparse.argparse import actions +from pydantic_argparse.utils.pydantic import PydanticField, PydanticValidator + +from .utils import SupportsAddArgument + + +def should_parse(field: PydanticField) -> bool: + """Checks whether the field should be parsed as a `boolean`. + + Args: + field (PydanticField): Field to check. + + Returns: + bool: Whether the field should be parsed as a `boolean`. + """ + # Check and Return + return field.is_a(bool) + + +def parse_field( + parser: SupportsAddArgument, + field: PydanticField, +) -> Optional[PydanticValidator]: + """Adds boolean pydantic field to argument parser. + + Args: + parser (argparse.ArgumentParser): Argument parser to add to. + field (PydanticField): Field to be added to parser. + + Returns: + Optional[PydanticValidator]: Possible validator method. + """ + # Compute Argument Intrinsics + is_inverted = not field.info.is_required() and bool(field.info.get_default()) + + # Determine Argument Properties + action = ( + actions.BooleanOptionalAction + if field.info.is_required() + else argparse._StoreFalseAction + if is_inverted + else argparse._StoreTrueAction + ) + + # Add Boolean Field + parser.add_argument( + field.argname(is_inverted), + action=action, + help=field.description(), + dest=field.name, + required=field.info.is_required(), + ) + + # Construct and Return Validator + return utils.pydantic.as_validator(field, lambda v: v) diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/command.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/command.py new file mode 100644 index 000000000..076f1bdf9 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/command.py @@ -0,0 +1,53 @@ +"""Parses Nested Pydantic Model Fields to Sub-Commands. + +The `command` module contains the `should_parse` function, which checks whether +this module should be used to parse the field, as well as the `parse_field` +function, which parses nested `pydantic` model fields to `ArgumentParser` +sub-commands. +""" + +import argparse +from typing import Optional + +from pydantic_argparse.utils.pydantic import ( + PydanticField, + PydanticValidator, +) + + +def should_parse(field: PydanticField) -> bool: + """Checks whether the field should be parsed as a `command`. + + Args: + field (PydanticField): Field to check. + + Returns: + bool: Whether the field should be parsed as a `command`. + """ + # Check and Return + return field.is_subcommand() + + +def parse_field( + subparser: argparse._SubParsersAction, + field: PydanticField, +) -> Optional[PydanticValidator]: + """Adds command pydantic field to argument parser. + + Args: + subparser (argparse._SubParsersAction): Sub-parser to add to. + field (PydanticField): Field to be added to parser. + + Returns: + Optional[PydanticValidator]: Possible validator method. + """ + # Add Command + subparser.add_parser( + field.info.title or field.info.alias or field.name, + help=field.info.description, + model=field.model_type, # type: ignore[call-arg] + exit_on_error=False, # Allow top level parser to handle exiting + ) + + # Return + return None diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/container.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/container.py new file mode 100644 index 000000000..163796476 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/container.py @@ -0,0 +1,60 @@ +"""Parses Container Pydantic Fields to Command-Line Arguments. + +The `container` module contains the `should_parse` function, which checks +whether this module should be used to parse the field, as well as the +`parse_field` function, which parses container `pydantic` model fields to +`ArgumentParser` command-line arguments. +""" + +import argparse +import collections.abc +import enum +from typing import Optional + +from pydantic_argparse import utils +from pydantic_argparse.utils.pydantic import PydanticField, PydanticValidator + +from .utils import SupportsAddArgument + + +def should_parse(field: PydanticField) -> bool: + """Checks whether the field should be parsed as a `container`. + + Args: + field (PydanticField): Field to check. + + Returns: + bool: Whether the field should be parsed as a `container`. + """ + # Check and Return + return field.is_a(collections.abc.Container) and not field.is_a( + (collections.abc.Mapping, enum.Enum, str, bytes) + ) + + +def parse_field( + parser: SupportsAddArgument, + field: PydanticField, +) -> Optional[PydanticValidator]: + """Adds container pydantic field to argument parser. + + Args: + parser (argparse.ArgumentParser): Argument parser to add to. + field (PydanticField): Field to be added to parser. + + Returns: + Optional[PydanticValidator]: Possible validator method. + """ + parser.add_argument( + field.argname(), + action=argparse._StoreAction, + nargs=argparse.ONE_OR_MORE, + help=field.description(), + dest=field.name, + metavar=field.metavar(), + required=field.info.is_required(), + ) + + # Construct and Return Validator + # TODO: this is basically useless? + return utils.pydantic.as_validator(field, lambda v: v) diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/enum.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/enum.py new file mode 100644 index 000000000..090a48ac6 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/enum.py @@ -0,0 +1,75 @@ +"""Parses Enum Pydantic Fields to Command-Line Arguments. + +The `enum` module contains the `should_parse` function, which checks whether +this module should be used to parse the field, as well as the `parse_field` +function, which parses enum `pydantic` model fields to `ArgumentParser` +command-line arguments. +""" + +import argparse +import enum +from typing import Optional, Type, cast + +from pydantic_argparse import utils +from pydantic_argparse.utils.pydantic import PydanticField, PydanticValidator + +from .utils import SupportsAddArgument + + +def should_parse(field: PydanticField) -> bool: + """Checks whether the field should be parsed as an `enum`. + + Args: + field (PydanticField): Field to check. + + Returns: + bool: Whether the field should be parsed as an `enum`. + """ + # Check and Return + return field.is_a(enum.Enum) + + +def parse_field( + parser: SupportsAddArgument, + field: PydanticField, +) -> Optional[PydanticValidator]: + """Adds enum pydantic field to argument parser. + + Args: + parser (argparse.ArgumentParser): Argument parser to add to. + field (PydanticField): Field to be added to parser. + + Returns: + Optional[PydanticValidator]: Possible validator method. + """ + # Extract Enum + enum_type = cast(Type[enum.Enum], field.info.annotation) + + # Compute Argument Intrinsics + is_flag = len(enum_type) == 1 and not field.info.is_required() + is_inverted = is_flag and field.info.get_default() is not None + + # Determine Argument Properties + metavar = f"{{{', '.join(e.name for e in enum_type)}}}" + action = argparse._StoreConstAction if is_flag else argparse._StoreAction + const = ( + {} + if not is_flag + else {"const": None} + if is_inverted + else {"const": list(enum_type)[0]} + ) + + # Add Enum Field + parser.add_argument( + field.argname(is_inverted), + action=action, + help=field.description(), + dest=field.name, + metavar=metavar, + required=field.info.is_required(), + **const, # type: ignore[arg-type] + ) + + # Construct and Return Validator + return utils.pydantic.as_validator(field, lambda v: enum_type[v]) diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/literal.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/literal.py new file mode 100644 index 000000000..2910f167a --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/literal.py @@ -0,0 +1,80 @@ +"""Parses Literal Pydantic Fields to Command-Line Arguments. + +The `literal` module contains the `should_parse` function, which checks whether +this module should be used to parse the field, as well as the `parse_field` +function, which parses literal `pydantic` model fields to `ArgumentParser` +command-line arguments. +""" + +import argparse +import sys +from typing import Optional + +from pydantic_argparse import utils +from pydantic_argparse.utils.pydantic import PydanticField, PydanticValidator + +from .utils import SupportsAddArgument + +if sys.version_info < (3, 8): # pragma: <3.8 cover + from typing_extensions import Literal, get_args +else: # pragma: >=3.8 cover + from typing import Literal, get_args + + +def should_parse(field: PydanticField) -> bool: + """Checks whether the field should be parsed as a `literal`. + + Args: + field (PydanticField): Field to check. + + Returns: + bool: Whether the field should be parsed as a `literal`. + """ + # Check and Return + return field.is_a(Literal) + + +def parse_field( + parser: SupportsAddArgument, + field: PydanticField, +) -> Optional[PydanticValidator]: + """Adds enum pydantic field to argument parser. + + Args: + parser (argparse.ArgumentParser): Argument parser to add to. + field (PydanticField): Field to be added to parser. + + Returns: + Optional[PydanticValidator]: Possible validator method. + """ + # Extract Choices + choices = get_args(field.info.annotation) + + # Compute Argument Intrinsics + is_flag = len(choices) == 1 and not field.info.is_required() + is_inverted = is_flag and field.info.get_default() is not None + + # Determine Argument Properties + metavar = f"{{{', '.join(str(c) for c in choices)}}}" + action = argparse._StoreConstAction if is_flag else argparse._StoreAction + const = ( + {} if not is_flag else {"const": None} if is_inverted else {"const": choices[0]} + ) + + # Add Literal Field + parser.add_argument( + field.argname(is_inverted), + action=action, + help=field.description(), + dest=field.name, + metavar=metavar, + required=field.info.is_required(), + **const, # type: ignore[arg-type] + ) + + # Construct String Representation Mapping of Choices + # This allows us O(1) parsing of choices from strings + mapping = {str(choice): choice for choice in choices} + + # Construct and Return Validator + return utils.pydantic.as_validator(field, lambda v: mapping[v]) diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/mapping.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/mapping.py new file mode 100644 index 000000000..513bf85aa --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/mapping.py @@ -0,0 +1,58 @@ +"""Parses Mapping Pydantic Fields to Command-Line Arguments. + +The `mapping` module contains the `should_parse` function, which checks whether +this module should be used to parse the field, as well as the `parse_field` +function, which parses mapping `pydantic` model fields to `ArgumentParser` +command-line arguments. +""" + +import argparse +import ast +import collections.abc +from typing import Optional + +from pydantic_argparse import utils +from pydantic_argparse.utils.pydantic import PydanticField, PydanticValidator + +from .utils import SupportsAddArgument + + +def should_parse(field: PydanticField) -> bool: + """Checks whether the field should be parsed as a `mapping`. + + Args: + field (PydanticField): Field to check. + + Returns: + bool: Whether the field should be parsed as a `mapping`. + """ + # Check and Return + return field.is_a(collections.abc.Mapping) + + +def parse_field( + parser: SupportsAddArgument, + field: PydanticField, +) -> Optional[PydanticValidator]: + """Adds mapping pydantic field to argument parser. + + Args: + parser (argparse.ArgumentParser): Argument parser to add to. + field (PydanticField): Field to be added to parser. + + Returns: + Optional[PydanticValidator]: Possible validator method. + """ + # Add Mapping Field + parser.add_argument( + field.argname(), + action=argparse._StoreAction, + help=field.description(), + dest=field.name, + metavar=field.metavar(), + required=field.info.is_required(), + ) + + # Construct and Return Validator + # TODO: this doesn't seem safe? + return utils.pydantic.as_validator(field, lambda v: ast.literal_eval(v)) diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/standard.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/standard.py new file mode 100644 index 000000000..07215c887 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/standard.py @@ -0,0 +1,44 @@ +"""Parses Standard Pydantic Fields to Command-Line Arguments. + +The `standard` module contains the `parse_field` function, which parses +standard `pydantic` model fields to `ArgumentParser` command-line arguments. + +Unlike the other `parser` modules, the `standard` module does not contain a +`should_parse` function. This is because it is the fallback case, where fields +that do not match any other types and require no special handling are parsed. +""" + +import argparse +from typing import Optional + +from pydantic_argparse import utils +from pydantic_argparse.utils.pydantic import PydanticField, PydanticValidator + +from .utils import SupportsAddArgument + + +def parse_field( + parser: SupportsAddArgument, + field: PydanticField, +) -> Optional[PydanticValidator]: + """Adds standard pydantic field to argument parser. + + Args: + parser (argparse.ArgumentParser): Argument parser to add to. + field (PydanticField): Field to be added to parser. + + Returns: + Optional[PydanticValidator]: Possible validator method. + """ + # Add Standard Field + parser.add_argument( + field.argname(), + action=argparse._StoreAction, + help=field.description(), + dest=field.name, + metavar=field.metavar(), + required=field.info.is_required(), + ) + + # Construct and Return Validator + return utils.pydantic.as_validator(field, lambda v: v) diff --git a/vendor/pydantic-argparse/pydantic_argparse/parsers/utils.py b/vendor/pydantic-argparse/pydantic_argparse/parsers/utils.py new file mode 100644 index 000000000..e4e8ec557 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/parsers/utils.py @@ -0,0 +1,29 @@ +"""Argument parser utilities.""" + +from argparse import Action, FileType +from collections.abc import Callable, Iterable +from typing import Any, Protocol, TypeVar, Union + +_T = TypeVar("_T") + + +class SupportsAddArgument(Protocol): + """ArgumentParser protocol that captures the base parser and argument groups.""" + + def add_argument( # noqa: D102 + self, + *name_or_flags: str, + action: Union[str, type[Action]] = ..., + nargs: Union[int, str] = ..., + const: Any = ..., + default: Any = ..., + type: Union[Callable[[str], _T], FileType] = ..., # noqa: A002 + choices: Iterable[_T] | None = ..., + required: bool = ..., + help: str | None = ..., # noqa: A002 + metavar: str | tuple[str, ...] | None = ..., + dest: str | None = ..., + version: str = ..., + **kwargs: Any + ) -> Action: + ... diff --git a/vendor/pydantic-argparse/pydantic_argparse/py.typed b/vendor/pydantic-argparse/pydantic_argparse/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/pydantic-argparse/pydantic_argparse/utils/__init__.py b/vendor/pydantic-argparse/pydantic_argparse/utils/__init__.py new file mode 100644 index 000000000..df91e22cc --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/utils/__init__.py @@ -0,0 +1,13 @@ +"""Utilities for Declarative Typed Argument Parsing. + +This package contains helper utility functions for the typed argument parsing +process, including formatting argument names and descriptions, formatting +errors, recursively parsing `argparse.Namespace` objects to `dict`s, +interacting with the internals of `pydantic` and determining the types of +`pydantic` fields. + +The public interface exposed by this package is the various described utility +modules each containing helper functions. +""" + +from . import errors, namespaces, pydantic, types diff --git a/vendor/pydantic-argparse/pydantic_argparse/utils/errors.py b/vendor/pydantic-argparse/pydantic_argparse/utils/errors.py new file mode 100644 index 000000000..71c4a1c02 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/utils/errors.py @@ -0,0 +1,27 @@ +"""Errors Utility Functions for Declarative Typed Argument Parsing. + +The `errors` module contains a utility function used for formatting `pydantic` +Validation Errors to human readable messages. +""" + + +# Third-Party +# Typing + +import pydantic + +# Constants +PydanticError = pydantic.ValidationError + + +def format(error: PydanticError) -> str: # noqa: A001 + """Formats a `pydantic` error into a human readable format. + + Args: + error (PydanticError): `pydantic` error to be formatted. + + Returns: + str: `pydantic` error in a human readable format. + """ + # Format and Return + return str(error) diff --git a/vendor/pydantic-argparse/pydantic_argparse/utils/namespaces.py b/vendor/pydantic-argparse/pydantic_argparse/utils/namespaces.py new file mode 100644 index 000000000..f1fa34dfa --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/utils/namespaces.py @@ -0,0 +1,30 @@ +"""Namespaces Utility Functions for Declarative Typed Argument Parsing. + +The `namespaces` module contains a utility function used for recursively +converting `argparse.Namespace`s to regular Python `dict`s. +""" + +import argparse +from typing import Any, Dict + + +def to_dict(namespace: argparse.Namespace) -> Dict[str, Any]: + """Converts a nested namespace to a dictionary recursively. + + Args: + namespace (argparse.Namespace): Namespace object to convert. + + Returns: + Dict[str, Any]: Nested dictionary generated from namespace. + """ + # Get Dictionary from Namespace Vars + dictionary = vars(namespace) + + # Loop Through Dictionary + for key, value in dictionary.items(): + # Check for Namespace Objects + if isinstance(value, argparse.Namespace): + # Recurse + dictionary[key] = to_dict(value) + + return dictionary diff --git a/vendor/pydantic-argparse/pydantic_argparse/utils/nesting.py b/vendor/pydantic-argparse/pydantic_argparse/utils/nesting.py new file mode 100644 index 000000000..954cf5f13 --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/utils/nesting.py @@ -0,0 +1,93 @@ +"""Utilities to help with parsing arbitrarily nested `pydantic` models.""" + +from argparse import Namespace +from typing import Any, Dict, Generic, Optional, Tuple, Type + +from boltons.iterutils import get_path, remap +from pydantic import BaseModel + +from .namespaces import to_dict +from .pydantic import PydanticField, PydanticModelT + +ModelT = PydanticModelT | Type[PydanticModelT] | BaseModel | Type[BaseModel] + + +class _NestedArgumentParser(Generic[PydanticModelT]): + """Parses arbitrarily nested `pydantic` models and inserts values passed at the command line.""" + + def __init__( + self, model: PydanticModelT | Type[PydanticModelT], namespace: Namespace + ) -> None: + self.model = model + self.args = to_dict(namespace) + self.subcommand = False + self.schema: Dict[str, Any] = self._get_nested_model_fields(self.model) + self.schema = self._remove_null_leaves(self.schema) + + if self.subcommand: + # if there are subcommands, they should only be in the topmost + # level, and the way that the unnesting works is + # that it will populate all subcommands, + # so we need to remove the subcommands that were + # not passed at cli + + # the command should be the very first argument + # after executable/file name + command = list(self.args.keys())[0] + self.schema = self._unset_subcommands(self.schema, command) + + def _get_nested_model_fields(self, model: ModelT, parent: Optional[Tuple] = None): + model_fields: Dict[str, Any] = dict() + + for field in PydanticField.parse_model(model): + key = field.name + + if field.is_a(BaseModel): + if field.is_subcommand(): + self.subcommand = True + + new_parent = (*parent, key) if parent is not None else (key,) + + # recursively build nestes pydantic models in dict, + # which matches the actual schema the nested + # schema pydantic will be expecting + model_fields[key] = self._get_nested_model_fields( + field.model_type, new_parent + ) + else: + # start with all leaves as None unless key is in top level + value = self.args.get(key, None) + if parent is not None: + # however, if travesing nested models, then the parent should + # not be None and then there is potentially a real value to get + + # check full path first + # TODO: this may not be needed depending on how nested namespaces work + # since the arg groups are not nested -- just flattened + full_path = (*parent, key) + value = get_path(self.args, full_path, value) + + if value is None: + short_path = (parent[0], key) + value = get_path(self.args, short_path, value) + + model_fields[key] = value + + return model_fields + + def _remove_null_leaves(self, schema: Dict[str, Any]): + # only remove None leaves + # actually CANNOT remove empty containers + # this causes problems with nested submodels that don't + # get any new args at command line, and therefore, are + # relying on the submodel defaults + # -> thus, the submodel name/key needs to be kept in + # the schema + return remap(schema, visit=lambda p, k, v: v is not None) + + def _unset_subcommands(self, schema: Dict[str, Any], command: str): + return {key: value for key, value in schema.items() if key == command} + + def validate(self): + """Return an instance of the `pydantic` modeled validated with data passed from the command line.""" + return self.model.model_validate(self.schema) diff --git a/vendor/pydantic-argparse/pydantic_argparse/utils/pydantic.py b/vendor/pydantic-argparse/pydantic_argparse/utils/pydantic.py new file mode 100644 index 000000000..41d3d30bd --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/utils/pydantic.py @@ -0,0 +1,407 @@ +"""Pydantic Utility Functions for Declarative Typed Argument Parsing. + +The `pydantic` module contains utility functions used for interacting with the +internals of `pydantic`, such as constructing field validators, updating +field validator dictionaries and constructing new model classes with +dynamically generated validators and environment variable parsers. +""" + +from collections.abc import Container, Mapping +from enum import Enum +from typing import ( + Any, + Callable, + Dict, + Iterator, + Literal, + NamedTuple, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, + get_args, + get_origin, +) + +import pydantic +from pydantic import BaseModel +from pydantic.fields import FieldInfo + +from .types import all_types + +# Constants +T = TypeVar("T") +PydanticModelT = TypeVar("PydanticModelT", bound=BaseModel) +PydanticValidator = classmethod +NoneType = type(None) + + +class PydanticField(NamedTuple): + """Simple Pydantic v2.0 field wrapper. + + Pydantic fields no longer store their name, so this named tuple + keeps the field name and field info together. + + The recommended entry point for an arbitrary `pydantic.BaseModel` is the classmethod `PydanticField.parse_model`. + """ + + name: str + info: FieldInfo + + @classmethod + def parse_model( + cls, model: BaseModel | Type[BaseModel] + ) -> Iterator["PydanticField"]: + """Iterator over the pydantic model fields, yielding this wrapper class. + + Yields: + Instances of self (`PydanticField`) + """ + for name, info in model.model_fields.items(): + yield cls(name, info) + + @property + def outer_type(self) -> Optional[Type]: + """Returns the outer type for nested types using `typing.get_origin`. + + This will return `None` for simple, unnested types. + """ + return get_origin(self.info.annotation) + + @property + def inner_type(self) -> Tuple[Type, ...]: + """Returns the inner type for nested types using `typing.get_args`. + + This will be an empty tuple for simple, unnested types. + """ + return get_args(self.info.annotation) + + @property + def main_type(self) -> Tuple[Type, ...]: + """Return the main inner types. + + This excludes the `NoneType` when dealing with `typing.Optional` types. + """ + return tuple(t for t in self.inner_type if t is not NoneType) + + def get_type(self) -> Union[Type, Tuple[Type, ...], None]: + """Return the type annotation for the `pydantic` field. + + Returns: + Union[Type, Tuple[Type, ...], None] + """ + field_type = self.info.annotation + main_type = self.main_type + outer_type = self.outer_type + outer_type_is_type = isinstance(outer_type, type) + if outer_type and ( + isinstance(outer_type, (Container, Mapping)) + or (outer_type_is_type and issubclass(outer_type, (Container, Mapping))) + or outer_type is Literal + ): + # only return if outer_type is a concrete type like list, dict, etc OR typing.Literal + # NOT if outer_type is typing.Union, etc + return outer_type + if main_type and all_types(main_type): + # the all type check is specifically for typing.Literal + return main_type + return field_type + + def is_a(self, types: Union[Any, Tuple[Any, ...]]) -> bool: + """Checks whether the subject *is* any of the supplied types. + + The checks are performed as follows: + + 1. `field` *is* one of the `types` + 2. `field` *is an instance* of one of the `types` + 3. `field` *is a subclass* of one of the `types` + + If any of these conditions are `True`, then the function returns `True`, + else `False`. + + Args: + types (Union[Any, Tuple[Any, ...]]): Type(s) to compare field against. + + Returns: + bool: Whether the field *is* considered one of the types. + """ + # Create tuple if only one type was provided + if not isinstance(types, tuple): + types = (types,) + + # Get field type, or origin if applicable + field_type = self.get_type() + if not isinstance(field_type, tuple): + field_type = (field_type,) + + # Check `isinstance` and `issubclass` validity + # In order for `isinstance` and `issubclass` to be valid, all arguments + # should be instances of `type`, otherwise `TypeError` *may* be raised. + + is_valid = all_types((*types, *field_type)) + + # Perform checks and return + is_type = False + for t in field_type: + is_type = ( + is_type + or t in types + or (is_valid and isinstance(t, types)) + or (is_valid and issubclass(t, types)) # type: ignore + ) + + return is_type + + @property + def model_type(self) -> Type[BaseModel]: + """Try to return the `pydantic.BaseModel` type. + + Raises: + TypeError: if this field is not a `pydantic.BaseModel` or if the model type cannot be found. + """ + if not self.is_a(BaseModel): + raise TypeError("This `pydantic` field is not a `pydantic.BaseModel`") + + types = self.get_type() + if not isinstance(types, tuple): + return cast(Type[BaseModel], types) + + for t in types: + if isinstance(t, type) and issubclass(t, BaseModel): + return t + else: + raise TypeError( + "No `pydantic.BaseModel`s were found associated with this field." + ) + + def is_subcommand(self) -> bool: + """Check whether the input pydantic Model is a subcommand. + + The default is that all pydantic Models are not subcommands, so this + has this featured has to be opted in by adding `subcommand=True` + to the `json_schema_extra` model config. A convenience class has been created + to provide this default for models that are meant to be command switches at + the command line: `pydantic_argparse.BaseCommand`. + + Returns: + bool: if the pydantic model is a subcommand. In all other cases, including when this field is not a + pydantic model, returns False. + """ + default = False + try: + model = self.model_type + value = model.model_config["json_schema_extra"].get("subcommand", default) # type: ignore + return cast(bool, value) + except (KeyError, AttributeError, TypeError): + # KeyError if: + # - subcommand key not in json_schema_extra + + # AttributeError if: + # - json_schema_extra not in the model_config, ie if using BaseModel + # just default to not being a subcommand + + # TypeError if + # - field is not a pydantic BaseModel or it can't be found + return default + + def argname(self, invert: bool = False) -> str: + """Standardises argument name when printing to command line. + + Args: + invert (bool): Whether to invert the name by prepending `--no-`. + + Returns: + str: Standardised name of the argument. Checks `pydantic.Field` title first, + but defaults to the field name. + """ + # TODO: this should return a tuple to allow short name args + # Construct Prefix + prefix = "--no-" if invert else "--" + name = self.info.title or self.name + + # Prepend prefix, replace '_' with '-' + return f"{prefix}{name.replace('_', '-')}" + + def description(self) -> str: + """Standardises argument description. + + Returns: + str: Standardised description of the argument. + """ + # Construct Default String + if self.info.is_required(): + default = None + required = "REQUIRED:" + else: + _default = self.info.get_default() + if isinstance(_default, Enum): + _default = _default.name + default = f"(default: {_default})" + required = None + + # Return Standardised Description String + return " ".join(filter(None, [required, self.info.description, default])) + + def metavar(self) -> Optional[str]: + """Generate the metavar name for the field. + + Returns: + Optional[str]: Field metavar if the `Field.info.alias` exists. + Otherwise, return constituent type names. + """ + # check alias first + if self.info.alias is not None: + return self.info.alias.upper() + + # otherwise default to the type + field_type = self.get_type() + if field_type: + if isinstance(field_type, tuple): + return "|".join(t.__name__.upper() for t in field_type) + return field_type.__name__.upper() + + +def as_validator( + field: PydanticField, + caster: Callable[[str], Any], +) -> PydanticValidator: + """Shortcut to wrap a caster and construct a validator for a given field. + + The provided caster function must cast from a string to the type required + by the field. Once wrapped, the constructed validator will pass through any + non-string values, or any values that cause the caster function to raise an + exception to let the built-in `pydantic` field validation handle them. The + validator will also cast empty strings to `None`. + + Args: + name (str): field name + field (pydantic.fields.FieldInfo): Field to construct validator for. + caster (Callable[[str], Any]): String to field type caster function. + + Returns: + PydanticValidator: Constructed field validator function. + """ + + # Dynamically construct a `pydantic` validator function for the supplied + # field. The constructed validator must be `pre=True` so that the validator + # is called before the built-in `pydantic` field validation occurs and is + # provided with the raw input data. The constructed validator must also be + # `allow_reuse=True` so the `__validator` function name can be reused + # multiple times when being decorated as a `pydantic` validator. Note that + # despite the `__validator` function *name* being reused, each instance of + # the validator function is uniquely constructed for the supplied field. + @pydantic.validator(field.name, pre=True, allow_reuse=True) + def __validator(cls: Type[Any], value: T) -> Optional[Union[T, Any]]: + if not isinstance(value, str): + return value + if not value: + return None + try: + return caster(value) + except Exception: + return value + + # Rename the validator uniquely for this field to avoid any collisions. The + # leading `__` and prefix of `pydantic_argparse` should guard against any + # potential collisions with user defined validators. + __validator.__name__ = f"__pydantic_argparse_{field.name}" # type: ignore + + # Return the constructed validator + return __validator # type: ignore + + +def update_validators( + validators: Dict[str, PydanticValidator], + validator: Optional[PydanticValidator], +) -> None: + """Updates a validators dictionary with a possible new field validator. + + Note that this function mutates the validators dictionary *in-place*, and + does not return the dictionary. + + Args: + validators (Dict[str, PydanticValidator]): Validators to update. + validator (Optional[PydanticValidator]): Possible field validator. + """ + # Check for Validator + if validator: + # Add Validator + validators[validator.__name__] = validator + + +def model_with_validators( + model: Type[BaseModel], + validators: Dict[str, PydanticValidator], +) -> Type[BaseModel]: + """Generates a new `pydantic` model class with the supplied validators. + + If the supplied base model is a subclass of `pydantic.BaseSettings`, then + the newly generated model will also have a new `parse_env_var` classmethod + monkeypatched onto it that suppresses any exceptions raised when initially + parsing the environment variables. This allows the raw values to still be + passed through to the `pydantic` field validators if initial parsing fails. + + Args: + model (Type[BaseModel]): Model type to use as base class. + validators (Dict[str, PydanticValidator]): Field validators to add. + + Returns: + Type[BaseModel]: New `pydantic` model type with field validators. + """ + # Construct New Model with Validators + model = pydantic.create_model( + model.__name__, + __base__=model, + __validators__=validators, + ) + + # Check if the model is a `BaseSettings` + # if issubclass(model, pydantic.BaseSettings): + # # Hold a reference to the current `parse_env_var` classmethod + # parse_env_var = model.__config__.parse_env_var + + # # Construct a new `parse_env_var` function which suppresses exceptions + # # raised by the current `parse_env_var` classmethod. This allows the + # # raw values to be passed through to the `pydantic` field validator + # # methods if they cannot be parsed initially. + # def __parse_env_var(field_name: str, raw_val: str) -> Any: + # with contextlib.suppress(Exception): + # return parse_env_var(field_name, raw_val) + # return raw_val + + # # Monkeypatch `parse_env_var` + # model.__config__.parse_env_var = __parse_env_var # type: ignore[assignment] + + # Return Constructed Model + return model + + +def is_subcommand(model: BaseModel | Type[BaseModel]) -> bool: + """Check whether the input pydantic Model is a subcommand. + + The default is that all pydantic Models are not subcommands, so this + has this featured has to be opted in by adding `subcommand=True` + to the `json_schema_extra` model config. A convenience class has been created + to provide this default for models that are meant to be command switches at + the command line: `pydantic_argparse.BaseCommand`. + + + Args: + model (BaseModel | Type[BaseModel]): a pydantic BaseModel subclass + + Returns: + bool: if the pydantic model is a subcommand + """ + default = False + try: + value = model.model_config["json_schema_extra"].get("subcommand", default) # type: ignore + return cast(bool, value) + except (KeyError, AttributeError): + # KeyError if: + # - subcommand key not in json_schema_extra + # AttributeError if: + # - json_schema_extra not in the model_config, ie if using BaseModel + # just default to not being a subcommand + return default diff --git a/vendor/pydantic-argparse/pydantic_argparse/utils/types.py b/vendor/pydantic-argparse/pydantic_argparse/utils/types.py new file mode 100644 index 000000000..6688c031b --- /dev/null +++ b/vendor/pydantic-argparse/pydantic_argparse/utils/types.py @@ -0,0 +1,27 @@ +"""Types Utility Functions for Declarative Typed Argument Parsing. + +The `types` module contains a utility function used for determining and +comparing the types of `pydantic fields. +""" + + +import sys +from typing import Iterable + +# Version-Guarded +if sys.version_info < (3, 8): # pragma: <3.8 cover + pass +else: # pragma: >=3.8 cover + pass + + +def all_types(types: Iterable) -> bool: + """Check if all inputs are `type`s and not instances. + + Args: + types (Iterable): an interable of putative `type` objects + + Returns: + bool: whether or not all inputs are `type`s + """ + return all(isinstance(t, type) for t in types) diff --git a/vendor/pydantic-argparse/pyproject.toml b/vendor/pydantic-argparse/pyproject.toml new file mode 100644 index 000000000..125152f6f --- /dev/null +++ b/vendor/pydantic-argparse/pyproject.toml @@ -0,0 +1,98 @@ +[tool.poetry] +name = "pydantic-argparse" +version = "1.0.0" +description = "Typed Argument Parsing with Pydantic" +authors = ["Hayden Richards "] +readme = "README.md" +license = "MIT" +homepage = "https://pydantic-argparse.supimdos.com" +repository = "https://github.com/SupImDos/pydantic-argparse" +keywords = ["python", "pydantic", "argparse", "typed", "validation"] +include = ["LICENSE.md"] + +[tool.poetry.dependencies] +python = "^3.7" +pydantic = "^2" +boltons = "*" +importlib_metadata = { version = ">=4", python = "<3.8" } +typing_extensions = { version = ">=4", python = "<3.8" } + +[tool.poetry.group.dev.dependencies] +poethepoet = "^0.18.1" +mypy = "^1.0.0" +ruff = "^0.0.247" +pytest = "^7.2.1" +pytest-cov = "^4.0.0" +pytest-mock = "^3.10.0" +covdefaults = "^2.2.2" +mkdocs-material = "^9.0.13" +mkdocstrings = { extras = ["python-legacy"], version = "^0.20.0" } +mkdocs-gen-files = "^0.4.0" +mkdocs-literate-nav = "^0.6.0" +mkdocs-autorefs = "^0.4.1" + +[tool.poe.tasks] +test = "pytest tests --cov=pydantic_argparse" +type = "mypy tests docs pydantic_argparse" +lint = "ruff tests docs pydantic_argparse" +clean = "rm -rf **/.coverage **/.mypy_cache **/.pytest_cache **/.ruff_cache **/__pycache__" +build = "poetry build" +publish = "poetry publish" +docs-serve = "mkdocs serve" +docs-publish = "mkdocs gh-deploy --force" + +[tool.ruff] +line-length = 120 +select = [ + "F", # flake8 + "E", # pycodestyle errors + "W", # pycodestyle warnings + "S", # flake8-bandit + "B", # flake8-bugbear + "A", # flake8-builtins + "D", # flake8-docstrings + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "RUF", # ruff +] +ignore = [ + "D401", # imperative mood - overly restrictive +] + +[tool.ruff.per-file-ignores] +"__init__.py" = ["F401"] # allow unused imports in `__init__.py` +"tests/*.py" = ["S101"] # allow asserts in unit tests + +[tool.ruff.pydocstyle] +convention = "google" + +[tool.pytest.ini_options] +addopts = "--verbose" +log_cli_level = "DEBUG" +log_cli_format = "%(asctime)s.%(msecs)03d [%(levelname)-8s] (%(name)-11s): %(message)s" +log_cli_date_format = "%Y%m%dT%H%M%S" + +[tool.coverage.run] +plugins = ["covdefaults"] + +[tool.mypy] +check_untyped_defs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +disallow_any_unimported = true +warn_return_any = true +warn_unused_ignores = true +no_implicit_optional = true +show_error_codes = true +plugins = ["pydantic.mypy"] + +[tool.pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true +warn_untyped_fields = true + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/vendor/pydantic-argparse/tests/__init__.py b/vendor/pydantic-argparse/tests/__init__.py new file mode 100644 index 000000000..8226739c6 --- /dev/null +++ b/vendor/pydantic-argparse/tests/__init__.py @@ -0,0 +1,5 @@ +"""Unit Tests for the `pydantic-argparse` Package. + +This file is required to mark the unit tests as a package, so they can resolve +and import the actual top level package code +""" diff --git a/vendor/pydantic-argparse/tests/argparse/__init__.py b/vendor/pydantic-argparse/tests/argparse/__init__.py new file mode 100644 index 000000000..40574a8f5 --- /dev/null +++ b/vendor/pydantic-argparse/tests/argparse/__init__.py @@ -0,0 +1,5 @@ +"""Unit Tests for the `argparse` Package. + +This file is required to mark the unit tests as a package, so they can resolve +and import the actual top level package code +""" diff --git a/vendor/pydantic-argparse/tests/argparse/test_actions.py b/vendor/pydantic-argparse/tests/argparse/test_actions.py new file mode 100644 index 000000000..68ed17249 --- /dev/null +++ b/vendor/pydantic-argparse/tests/argparse/test_actions.py @@ -0,0 +1,101 @@ +"""Tests the `actions` Module. + +This module provides full unit test coverage for the `actions` module, testing +all branches of all methods. These unit tests target the `SubParsersAction` +class by testing the expected nested namespace functionality. +""" + + +# Standard +import argparse + +# Third-Party +import pytest + +# Local +from pydantic_argparse.argparse import actions +from tests import conftest as conf + + +def test_invalid_command() -> None: + """Tests SubParsersAction with invalid command.""" + # Construct Subparser + subparser = conf.create_test_subparser() + + # Assert Raises + with pytest.raises(argparse.ArgumentError): + # Test Invalid Command + subparser( + parser=argparse.ArgumentParser(), + namespace=argparse.Namespace(), + values=["fake", "--not-real"], + ) + + +def test_valid_command() -> None: + """Tests SubParsersAction with valid command.""" + # Construct Subparser + subparser = conf.create_test_subparser() + + # Add Test Argument + subparser.add_parser("test") + + # Create Namespace + namespace = argparse.Namespace() + + # Test Valid Command + subparser( + parser=argparse.ArgumentParser(), + namespace=namespace, + values=["test"], + ) + + # Assert + assert getattr(namespace, "test") == argparse.Namespace() # noqa: B009 + + +def test_unrecognised_args() -> None: + """Tests SubParsersAction with unrecognised args.""" + # Construct Subparser + subparser = conf.create_test_subparser() + + # Add Test Argument + subparser.add_parser("test") + + # Create Namespace + namespace = argparse.Namespace() + + # Test Unrecognised Args + subparser( + parser=argparse.ArgumentParser(), + namespace=namespace, + values=["test", "--flag"], + ) + + # Assert + assert getattr(namespace, "test") == argparse.Namespace() # noqa: B009 + assert getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR) == ["--flag"] + + +def test_deep_unrecognised_args() -> None: + """Tests SubParsersAction with deeply nested unrecognised args.""" + # Construct Subparser + subparser = conf.create_test_subparser() + + # Add Test Argument + deep: argparse.ArgumentParser = subparser.add_parser("test") + deep.add_subparsers(action=actions.SubParsersAction).add_parser("deep") + + # Create Namespace + namespace = argparse.Namespace() + + # Test Deeply Nested Unrecognised Args + subparser( + parser=argparse.ArgumentParser(), + namespace=namespace, + values=["test", "--a", "deep", "--b"], + ) + + # Assert + assert getattr(namespace, "test") == argparse.Namespace(deep=argparse.Namespace()) # noqa: B009 + assert getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR) == ["--a", "--b"] diff --git a/vendor/pydantic-argparse/tests/argparse/test_parser.py b/vendor/pydantic-argparse/tests/argparse/test_parser.py new file mode 100644 index 000000000..5df328474 --- /dev/null +++ b/vendor/pydantic-argparse/tests/argparse/test_parser.py @@ -0,0 +1,550 @@ +"""Tests the `parser` Module. + +This module provides full unit test coverage for the `parser` module, testing +all branches of all methods. These unit tests target the typed `ArgumentParser` +class by testing a large number of expected use-cases. +""" + + +# Standard +import argparse +import collections as coll +import datetime as dt +import re +import sys +import textwrap + +# Third-Party +import pydantic +import pytest + +# Local +import pydantic_argparse +import tests.conftest as conf + +# Typing +from typing import Deque, Dict, FrozenSet, List, Optional, Set, Tuple, Type, TypeVar + +# Version-Guarded +if sys.version_info < (3, 8): # pragma: <3.8 cover + from typing_extensions import Literal +else: # pragma: >=3.8 cover + from typing import Literal + + +# Constants +ArgumentT = TypeVar("ArgumentT") + + +@pytest.mark.parametrize("prog", ["AA", None]) +@pytest.mark.parametrize("description", ["BB", None]) +@pytest.mark.parametrize("version", ["CC", None]) +@pytest.mark.parametrize("epilog", ["DD", None]) +@pytest.mark.parametrize("add_help", [True, False]) +@pytest.mark.parametrize("exit_on_error", [True, False]) +def test_create_argparser( + prog: Optional[str], + description: Optional[str], + version: Optional[str], + epilog: Optional[str], + add_help: bool, + exit_on_error: bool, +) -> None: + """Tests Constructing the ArgumentParser. + + Args: + prog (Optional[str]): Program name for testing. + description (Optional[str]): Program description for testing. + version (Optional[str]): Program version for testing. + epilog (Optional[str]): Program epilog for testing. + add_help (bool): Whether to add help flag for testing. + exit_on_error (bool): Whether to exit on error for testing. + """ + # Create ArgumentParser + parser = pydantic_argparse.ArgumentParser( + model=conf.TestModel, + prog=prog, + description=description, + version=version, + epilog=epilog, + add_help=add_help, + exit_on_error=exit_on_error, + ) + + # Asserts + assert isinstance(parser, pydantic_argparse.ArgumentParser) + + +@pytest.mark.parametrize( + ( + "argument_type", + "argument_default", + "arguments", + "result", + ), + [ + # Required Arguments + (int, ..., "--test 123", 123), + (float, ..., "--test 4.56", 4.56), + (str, ..., "--test hello", "hello"), + (bytes, ..., "--test bytes", b"bytes"), + (List[str], ..., "--test a b c", list(("a", "b", "c"))), + (Tuple[str, str, str], ..., "--test a b c", tuple(("a", "b", "c"))), + (Set[str], ..., "--test a b c", set(("a", "b", "c"))), + (FrozenSet[str], ..., "--test a b c", frozenset(("a", "b", "c"))), + (Deque[str], ..., "--test a b c", coll.deque(("a", "b", "c"))), + (Dict[str, int], ..., "--test {'a':2}", dict(a=2)), + (dt.date, ..., "--test 2021-12-25", dt.date(2021, 12, 25)), + (dt.datetime, ..., "--test 2021-12-25T12:34", dt.datetime(2021, 12, 25, 12, 34)), + (dt.time, ..., "--test 12:34", dt.time(12, 34)), + (dt.timedelta, ..., "--test PT12H", dt.timedelta(hours=12)), + (bool, ..., "--test", True), + (bool, ..., "--no-test", False), + (Literal["A"], ..., "--test A", "A"), + (Literal["A", 1], ..., "--test 1", 1), + (conf.TestEnumSingle, ..., "--test D", conf.TestEnumSingle.D), + (conf.TestEnum, ..., "--test C", conf.TestEnum.C), + + # Optional Arguments (With Default) + (int, 456, "--test 123", 123), + (float, 1.23, "--test 4.56", 4.56), + (str, "world", "--test hello", "hello"), + (bytes, b"bits", "--test bytes", b"bytes"), + (List[str], list(("d", "e", "f")), "--test a b c", list(("a", "b", "c"))), + (Tuple[str, str, str], tuple(("d", "e", "f")), "--test a b c", tuple(("a", "b", "c"))), + (Set[str], set(("d", "e", "f")), "--test a b c", set(("a", "b", "c"))), + (FrozenSet[str], frozenset(("d", "e", "f")), "--test a b c", frozenset(("a", "b", "c"))), + (Deque[str], coll.deque(("d", "e", "f")), "--test a b c", coll.deque(("a", "b", "c"))), + (Dict[str, int], dict(b=3), "--test {'a':2}", dict(a=2)), + (dt.date, dt.date(2021, 7, 21), "--test 2021-12-25", dt.date(2021, 12, 25)), + (dt.datetime, dt.datetime(2021, 7, 21, 3), "--test 2021-04-03T02:00", dt.datetime(2021, 4, 3, 2)), + (dt.time, dt.time(3, 21), "--test 12:34", dt.time(12, 34)), + (dt.timedelta, dt.timedelta(hours=6), "--test PT12H", dt.timedelta(hours=12)), + (bool, False, "--test", True), + (bool, True, "--no-test", False), + (Literal["A"], "A", "--test", "A"), + (Literal["A", 1], "A", "--test 1", 1), + (conf.TestEnumSingle, conf.TestEnumSingle.D, "--test", conf.TestEnumSingle.D), + (conf.TestEnum, conf.TestEnum.B, "--test C", conf.TestEnum.C), + + # Optional Arguments (With Default) (No Value Given) + (int, 456, "", 456), + (float, 1.23, "", 1.23), + (str, "world", "", "world"), + (bytes, b"bits", "", b"bits"), + (List[str], list(("d", "e", "f")), "", list(("d", "e", "f"))), + (Tuple[str, str, str], tuple(("d", "e", "f")), "", tuple(("d", "e", "f"))), + (Set[str], set(("d", "e", "f")), "", set(("d", "e", "f"))), + (FrozenSet[str], frozenset(("d", "e", "f")), "", frozenset(("d", "e", "f"))), + (Deque[str], coll.deque(("d", "e", "f")), "", coll.deque(("d", "e", "f"))), + (Dict[str, int], dict(b=3), "", dict(b=3)), + (dt.date, dt.date(2021, 7, 21), "", dt.date(2021, 7, 21)), + (dt.datetime, dt.datetime(2021, 7, 21, 3, 7), "", dt.datetime(2021, 7, 21, 3, 7)), + (dt.time, dt.time(3, 21), "", dt.time(3, 21)), + (dt.timedelta, dt.timedelta(hours=6), "", dt.timedelta(hours=6)), + (bool, False, "", False), + (bool, True, "", True), + (Literal["A"], "A", "", "A"), + (Literal["A", 1], "A", "", "A"), + (conf.TestEnumSingle, conf.TestEnumSingle.D, "", conf.TestEnumSingle.D), + (conf.TestEnum, conf.TestEnum.B, "", conf.TestEnum.B), + + # Optional Arguments (No Default) + (Optional[int], None, "--test 123", 123), + (Optional[float], None, "--test 4.56", 4.56), + (Optional[str], None, "--test hello", "hello"), + (Optional[bytes], None, "--test bytes", b"bytes"), + (Optional[List[str]], None, "--test a b c", list(("a", "b", "c"))), + (Optional[Tuple[str, str, str]], None, "--test a b c", tuple(("a", "b", "c"))), + (Optional[Set[str]], None, "--test a b c", set(("a", "b", "c"))), + (Optional[FrozenSet[str]], None, "--test a b c", frozenset(("a", "b", "c"))), + (Optional[Deque[str]], None, "--test a b c", coll.deque(("a", "b", "c"))), + (Optional[Dict[str, int]], None, "--test {'a':2}", dict(a=2)), + (Optional[dt.date], None, "--test 2021-12-25", dt.date(2021, 12, 25)), + (Optional[dt.datetime], None, "--test 2021-12-25T12:34", dt.datetime(2021, 12, 25, 12, 34)), + (Optional[dt.time], None, "--test 12:34", dt.time(12, 34)), + (Optional[dt.timedelta], None, "--test PT12H", dt.timedelta(hours=12)), + (Optional[bool], None, "--test", True), + (Optional[Literal["A"]], None, "--test", "A"), + (Optional[Literal["A", 1]], None, "--test 1", 1), + (Optional[conf.TestEnumSingle], None, "--test", conf.TestEnumSingle.D), + (Optional[conf.TestEnum], None, "--test C", conf.TestEnum.C), + + # Optional Arguments (No Default) (No Value Given) + (Optional[int], None, "", None), + (Optional[float], None, "", None), + (Optional[str], None, "", None), + (Optional[bytes], None, "", None), + (Optional[List[str]], None, "", None), + (Optional[Tuple[str, str, str]], None, "", None), + (Optional[Set[str]], None, "", None), + (Optional[FrozenSet[str]], None, "", None), + (Optional[Deque[str]], None, "", None), + (Optional[Dict[str, int]], None, "", None), + (Optional[dt.date], None, "", None), + (Optional[dt.datetime], None, "", None), + (Optional[dt.time], None, "", None), + (Optional[dt.timedelta], None, "", None), + (Optional[bool], None, "", None), + (Optional[Literal["A"]], None, "", None), + (Optional[Literal["A", 1]], None, "", None), + (Optional[conf.TestEnumSingle], None, "", None), + (Optional[conf.TestEnum], None, "", None), + + # Special Enums and Literals Optional Flag Behaviour + (Optional[Literal["A"]], "A", "--no-test", None), + (Optional[Literal["A"]], "A", "", "A"), + (Optional[conf.TestEnumSingle], conf.TestEnumSingle.D, "--no-test", None), + (Optional[conf.TestEnumSingle], conf.TestEnumSingle.D, "", conf.TestEnumSingle.D), + + # Commands + (conf.TestCommand, ..., "test", conf.TestCommand()), + (conf.TestCommands, ..., "test cmd_01", conf.TestCommands(cmd_01=conf.TestCommand())), + (conf.TestCommands, ..., "test cmd_02", conf.TestCommands(cmd_02=conf.TestCommand())), + (conf.TestCommands, ..., "test cmd_03", conf.TestCommands(cmd_03=conf.TestCommand())), + (conf.TestCommands, ..., "test cmd_01 --flag", conf.TestCommands(cmd_01=conf.TestCommand(flag=True))), + (conf.TestCommands, ..., "test cmd_02 --flag", conf.TestCommands(cmd_02=conf.TestCommand(flag=True))), + (conf.TestCommands, ..., "test cmd_03 --flag", conf.TestCommands(cmd_03=conf.TestCommand(flag=True))), + (Optional[conf.TestCommand], ..., "test", conf.TestCommand()), + (Optional[conf.TestCommands], ..., "test cmd_01", conf.TestCommands(cmd_01=conf.TestCommand())), + (Optional[conf.TestCommands], ..., "test cmd_02", conf.TestCommands(cmd_02=conf.TestCommand())), + (Optional[conf.TestCommands], ..., "test cmd_03", conf.TestCommands(cmd_03=conf.TestCommand())), + (Optional[conf.TestCommands], ..., "test cmd_01 --flag", conf.TestCommands(cmd_01=conf.TestCommand(flag=True))), + (Optional[conf.TestCommands], ..., "test cmd_02 --flag", conf.TestCommands(cmd_02=conf.TestCommand(flag=True))), + (Optional[conf.TestCommands], ..., "test cmd_03 --flag", conf.TestCommands(cmd_03=conf.TestCommand(flag=True))), + ], +) +def test_valid_arguments( + argument_type: Type[ArgumentT], + argument_default: ArgumentT, + arguments: str, + result: ArgumentT, +) -> None: + """Tests ArgumentParser Valid Arguments. + + Args: + argument_type (Type[ArgumentT]): Type of the argument. + argument_default (ArgumentT): Default for the argument. + arguments (str): An example string of arguments for testing. + result (ArgumentT): Result from parsing the argument. + """ + # Construct Pydantic Model + model = conf.create_test_model(test=(argument_type, argument_default)) + + # Create ArgumentParser + parser = pydantic_argparse.ArgumentParser(model) + + # Parse + args = parser.parse_typed_args(arguments.split()) + + # Asserts + assert isinstance(args.test, type(result)) + assert args.test == result + + +@pytest.mark.parametrize( + ( + "argument_type", + "argument_default", + "arguments", + ), + [ + # Invalid Arguments + (int, ..., "--test invalid"), + (float, ..., "--test invalid"), + (List[int], ..., "--test invalid"), + (Tuple[int, int, int], ..., "--test invalid"), + (Set[int], ..., "--test invalid"), + (FrozenSet[int], ..., "--test invalid"), + (Deque[int], ..., "--test invalid"), + (Dict[str, int], ..., "--test invalid"), + (dt.date, ..., "--test invalid"), + (dt.datetime, ..., "--test invalid"), + (dt.time, ..., "--test invalid"), + (dt.timedelta, ..., "--test invalid"), + (bool, ..., "--test invalid"), + (Literal["A"], ..., "--test invalid"), + (Literal["A", 1], ..., "--test invalid"), + (conf.TestEnumSingle, ..., "--test invalid"), + (conf.TestEnum, ..., "--test invalid"), + + # Missing Argument Values + (int, ..., "--test"), + (float, ..., "--test"), + (str, ..., "--test"), + (bytes, ..., "--test"), + (List[int], ..., "--test"), + (Tuple[int, int, int], ..., "--test"), + (Set[int], ..., "--test"), + (FrozenSet[int], ..., "--test"), + (Deque[int], ..., "--test"), + (Dict[str, int], ..., "--test"), + (dt.date, ..., "--test"), + (dt.datetime, ..., "--test"), + (dt.time, ..., "--test"), + (dt.timedelta, ..., "--test"), + (Literal["A"], ..., "--test"), + (Literal["A", 1], ..., "--test"), + (conf.TestEnumSingle, ..., "--test"), + (conf.TestEnum, ..., "--test"), + + # Missing Arguments + (int, ..., ""), + (float, ..., ""), + (str, ..., ""), + (bytes, ..., ""), + (List[int], ..., ""), + (Tuple[int, int, int], ..., ""), + (Set[int], ..., ""), + (FrozenSet[int], ..., ""), + (Deque[int], ..., ""), + (Dict[str, int], ..., ""), + (dt.date, ..., ""), + (dt.datetime, ..., ""), + (dt.time, ..., ""), + (dt.timedelta, ..., ""), + (bool, ..., ""), + (Literal["A"], ..., ""), + (Literal["A", 1], ..., ""), + (conf.TestEnumSingle, ..., ""), + (conf.TestEnum, ..., ""), + + # Invalid Optional Arguments + (Optional[int], None, "--test invalid"), + (Optional[float], None, "--test invalid"), + (Optional[List[int]], None, "--test invalid"), + (Optional[Tuple[int, int, int]], None, "--test invalid"), + (Optional[Set[int]], None, "--test invalid"), + (Optional[FrozenSet[int]], None, "--test invalid"), + (Optional[Deque[int]], None, "--test invalid"), + (Optional[Dict[str, int]], None, "--test invalid"), + (Optional[dt.date], None, "--test invalid"), + (Optional[dt.datetime], None, "--test invalid"), + (Optional[dt.time], None, "--test invalid"), + (Optional[dt.timedelta], None, "--test invalid"), + (Optional[bool], None, "--test invalid"), + (Optional[Literal["A"]], None, "--test invalid"), + (Optional[Literal["A", 1]], None, "--test invalid"), + (Optional[conf.TestEnumSingle], None, "--test invalid"), + (Optional[conf.TestEnum], None, "--test invalid"), + + # Missing Optional Argument Values + (Optional[int], None, "--test"), + (Optional[float], None, "--test"), + (Optional[str], None, "--test"), + (Optional[bytes], None, "--test"), + (Optional[List[int]], None, "--test"), + (Optional[Tuple[int, int, int]], None, "--test"), + (Optional[Set[int]], None, "--test"), + (Optional[FrozenSet[int]], None, "--test"), + (Optional[Deque[int]], None, "--test"), + (Optional[Dict[str, int]], None, "--test"), + (Optional[dt.date], None, "--test"), + (Optional[dt.datetime], None, "--test"), + (Optional[dt.time], None, "--test"), + (Optional[dt.timedelta], None, "--test"), + (Optional[Literal["A", 1]], None, "--test"), + (Optional[conf.TestEnum], None, "--test"), + + # Commands + (conf.TestCommand, ..., ""), + (conf.TestCommand, ..., "invalid"), + (conf.TestCommands, ..., "test"), + (conf.TestCommands, ..., "test invalid"), + (Optional[conf.TestCommand], ..., ""), + (Optional[conf.TestCommand], ..., "invalid"), + (Optional[conf.TestCommands], ..., "test"), + (Optional[conf.TestCommands], ..., "test invalid"), + ], +) +@pytest.mark.parametrize( + ( + "exit_on_error", + "error" + ), + [ + (True, SystemExit), + (False, argparse.ArgumentError), + ], +) +def test_invalid_arguments( + argument_type: Type[ArgumentT], + argument_default: ArgumentT, + arguments: str, + exit_on_error: bool, + error: Type[Exception], +) -> None: + """Tests ArgumentParser Invalid Arguments. + + Args: + argument_type (Type[ArgumentT]): Type of the argument. + argument_default (ArgumentT): Default for the argument. + arguments (str): An example string of arguments for testing. + exit_on_error (bool): Whether to raise or exit on error. + error (Type[Exception]): Exception that should be raised for testing. + """ + # Construct Pydantic Model + model = conf.create_test_model(test=(argument_type, argument_default)) + + # Create ArgumentParser + parser = pydantic_argparse.ArgumentParser(model, exit_on_error=exit_on_error) + + # Assert Parser Raises Error + with pytest.raises(error): + # Parse + parser.parse_typed_args(arguments.split()) + + +def test_help_message(capsys: pytest.CaptureFixture[str]) -> None: + """Tests ArgumentParser Help Message. + + Args: + capsys (pytest.CaptureFixture[str]): Fixture to capture STDOUT/STDERR. + """ + # Construct Pydantic Model + model = conf.create_test_model() + + # Create ArgumentParser + parser = pydantic_argparse.ArgumentParser( + model=model, + prog="AA", + description="BB", + version="CC", + epilog="DD", + ) + + # Assert Parser Exits + with pytest.raises(SystemExit): + # Ask for Help + parser.parse_typed_args(["--help"]) + + # Check STDOUT + captured = capsys.readouterr() + assert captured.out == textwrap.dedent( + """ + usage: AA [-h] [-v] + + BB + + help: + -h, --help show this help message and exit + -v, --version show program's version number and exit + + DD + """ + ).lstrip() + + +def test_version_message(capsys: pytest.CaptureFixture[str]) -> None: + """Tests ArgumentParser Version Message. + + Args: + capsys (pytest.CaptureFixture[str]): Fixture to capture STDOUT/STDERR. + """ + # Construct Pydantic Model + model = conf.create_test_model() + + # Create ArgumentParser + parser = pydantic_argparse.ArgumentParser( + model=model, + prog="AA", + description="BB", + version="CC", + epilog="DD", + ) + + # Assert Parser Exits + with pytest.raises(SystemExit): + # Ask for Version + parser.parse_typed_args(["--version"]) + + # Check STDOUT + captured = capsys.readouterr() + assert captured.out == textwrap.dedent( + """ + CC + """ + ).lstrip() + + +@pytest.mark.parametrize( + ( + "argument_name", + "argument_field", + ), + conf.TestModel.__fields__.items() +) +def test_argument_descriptions( + argument_name: str, + argument_field: pydantic.fields.ModelField, + capsys: pytest.CaptureFixture[str], +) -> None: + """Tests Argument Descriptions. + + Args: + argument_name (str): Argument name. + argument_field (pydantic.fields.ModelField): Argument pydantic field. + capsys (pytest.CaptureFixture[str]): Fixture to capture STDOUT/STDERR. + """ + # Create ArgumentParser + parser = pydantic_argparse.ArgumentParser(conf.TestModel) + + # Assert Parser Exits + with pytest.raises(SystemExit): + # Ask for Help + parser.parse_typed_args(["--help"]) + + # Capture STDOUT + captured = capsys.readouterr() + + # Process STDOUT + # Capture all arguments below 'commands:' + # Capture all arguments below 'required arguments:' + # Capture all arguments below 'optional arguments:' + _, commands, required, optional, _ = re.split(r".+:\n", captured.out) + + # Check if Command, Required or Optional + if isinstance(argument_field.outer_type_, pydantic.main.ModelMetaclass): + # Assert Argument Name in Commands Section + assert argument_name in commands + assert argument_name not in required + assert argument_name not in optional + + # Assert Argument Description in Commands Section + assert argument_field.field_info.description in commands + assert argument_field.field_info.description not in required + assert argument_field.field_info.description not in optional + + elif argument_field.required: + # Format Argument Name + argument_name = argument_name.replace("_", "-") + + # Assert Argument Name in Required Args Section + assert argument_name in required + assert argument_name not in commands + assert argument_name not in optional + + # Assert Argument Description in Required Args Section + assert argument_field.field_info.description in required + assert argument_field.field_info.description not in commands + assert argument_field.field_info.description not in optional + + else: + # Format Argument Name and Default + argument_name = argument_name.replace("_", "-") + default = f"(default: {argument_field.get_default()})" + + # Assert Argument Name in Optional Args Section + assert argument_name in optional + assert argument_name not in commands + assert argument_name not in required + + # Assert Argument Description in Optional Args Section + assert argument_field.field_info.description in optional + assert argument_field.field_info.description not in commands + assert argument_field.field_info.description not in required + + # Assert Argument Default in Optional Args Section + assert default in optional + assert default not in commands + assert default not in required diff --git a/vendor/pydantic-argparse/tests/conftest.py b/vendor/pydantic-argparse/tests/conftest.py new file mode 100644 index 000000000..c93b41b25 --- /dev/null +++ b/vendor/pydantic-argparse/tests/conftest.py @@ -0,0 +1,208 @@ +"""Configures Testing and Defines Pytest Fixtures. + +The `conftest.py` file serves as a means of providing fixtures for an entire +directory. Fixtures defined in a `conftest.py` can be used by any test in the +package without needing to import them. +""" + + +# Standard +import argparse +import collections +import datetime +import enum +import sys + +# Third-Party +import pydantic + +# Local +from pydantic_argparse.argparse import actions + +# Typing +from typing import Any, Deque, Dict, FrozenSet, List, Optional, Set, Tuple, Type + +# Version-Guarded +if sys.version_info < (3, 8): # pragma: <3.8 cover + from typing_extensions import Literal +else: # pragma: >=3.8 cover + from typing import Literal + + +def create_test_model( + name: str = "test", + base: Type[pydantic.BaseModel] = pydantic.BaseSettings, + **fields: Tuple[Type[Any], Any], +) -> Any: + """Constructs a `pydantic` model with sensible defaults for testing. + + This function returns `Any` instead of `Type[pydantic.BaseModel]` because + we cannot accurately type the dynamically constructed fields on the + resultant model. As such, it is more convenient to work with the `Any` type + in the unit tests. + + Args: + name (str): Name of the model. + base (Type[pydantic.BaseModel]): Base class for the model. + fields (Tuple[Type[Any], Any]): Model fields as `name=(type, default)`. + + Returns: + Any: Dynamically constructed `pydantic` model. + """ + # Construct Pydantic Model + return pydantic.create_model( + name, + __base__=base, + **fields, # type: ignore[call-overload] + ) + + +def create_test_field( + name: str = "test", + type: Type[Any] = str, # noqa: A002 + default: Any = ..., + description: Optional[str] = None, +) -> pydantic.fields.ModelField: + """Constructs a `pydantic` field with sensible defaults for testing. + + Args: + name (str): Name of the field. + type (Type[Any]): Type of the field. + default (Any): Default value for the field. + description (Optional[str]): Description for the field. + + Returns: + pydantic.fields.ModelField: Dynamically constructed `pydantic` model. + """ + # Construct Pydantic Field + return pydantic.fields.ModelField.infer( + name=name, + value=pydantic.Field(default, description=description), # type: ignore[arg-type] + annotation=type, + class_validators=None, + config=pydantic.BaseConfig, + ) + + +def create_test_subparser( + name: str = "test", + parser_class: Type[argparse.ArgumentParser] = argparse.ArgumentParser, +) -> actions.SubParsersAction: + """Constructs a `SubParsersAction` with sensible defaults for testing. + + Args: + name (str): Name of the action. + parser_class (Type[argparse.ArgumentParser]): Parser for the action. + + Returns: + actions.SubParsersAction: Dynamically constructed `SubParsersAction`. + """ + # Construct SubParsersAction + return actions.SubParsersAction( + option_strings=[], # Always empty for the `SubParsersAction` + prog=name, + parser_class=parser_class, + ) + + +class TestEnum(enum.Enum): + """Test Enum for Testing.""" + A = enum.auto() + B = enum.auto() + C = enum.auto() + + +class TestEnumSingle(enum.Enum): + """Test Enum with Single Member for Testing.""" + D = enum.auto() + + +class TestCommand(pydantic.BaseModel): + """Test Command Model for Testing.""" + flag: bool = pydantic.Field(False, description="flag") + + +class TestCommands(pydantic.BaseModel): + """Test Commands Model for Testing.""" + cmd_01: Optional[TestCommand] = pydantic.Field(None, description="cmd_01") + cmd_02: Optional[TestCommand] = pydantic.Field(None, description="cmd_02") + cmd_03: Optional[TestCommand] = pydantic.Field(None, description="cmd_03") + + +class TestModel(pydantic.BaseModel): + """Test Model for Testing.""" + # Required Arguments + arg_01: int = pydantic.Field(description="arg_01") + arg_02: float = pydantic.Field(description="arg_02") + arg_03: str = pydantic.Field(description="arg_03") + arg_04: bytes = pydantic.Field(description="arg_04") + arg_05: List[str] = pydantic.Field(description="arg_05") + arg_06: Tuple[str, str, str] = pydantic.Field(description="arg_06") + arg_07: Set[str] = pydantic.Field(description="arg_07") + arg_08: FrozenSet[str] = pydantic.Field(description="arg_08") + arg_09: Deque[str] = pydantic.Field(description="arg_09") + arg_10: Dict[str, int] = pydantic.Field(description="arg_10") + arg_11: datetime.date = pydantic.Field(description="arg_11") + arg_12: datetime.datetime = pydantic.Field(description="arg_12") + arg_13: datetime.time = pydantic.Field(description="arg_13") + arg_14: datetime.timedelta = pydantic.Field(description="arg_14") + arg_15: bool = pydantic.Field(description="arg_15") + arg_16: Literal["A"] = pydantic.Field(description="arg_16") + arg_17: Literal["A", 1] = pydantic.Field(description="arg_17") + arg_18: TestEnumSingle = pydantic.Field(description="arg_18") + arg_19: TestEnum = pydantic.Field(description="arg_19") + + # Optional Arguments (With Default) + arg_20: int = pydantic.Field(12345, description="arg_20") + arg_21: float = pydantic.Field(6.789, description="arg_21") + arg_22: str = pydantic.Field("ABC", description="arg_22") + arg_23: bytes = pydantic.Field(b"ABC", description="arg_23") + arg_24: List[str] = pydantic.Field(list(("A", "B", "C")), description="arg_24") + arg_25: Tuple[str, str, str] = pydantic.Field(("A", "B", "C"), description="arg_25") + arg_26: Set[str] = pydantic.Field(set(("A", "B", "C")), description="arg_26") + arg_27: FrozenSet[str] = pydantic.Field(frozenset(("A", "B", "C")), description="arg_27") + arg_28: Deque[str] = pydantic.Field(collections.deque(("A", "B", "C")), description="arg_28") + arg_29: Dict[str, int] = pydantic.Field(dict(A=123), description="arg_29") + arg_30: datetime.date = pydantic.Field(datetime.date(2021, 12, 25), description="arg_30") + arg_31: datetime.datetime = pydantic.Field(datetime.datetime(2021, 12, 25, 7), description="arg_31") + arg_32: datetime.time = pydantic.Field(datetime.time(7, 30), description="arg_32") + arg_33: datetime.timedelta = pydantic.Field(datetime.timedelta(hours=5), description="arg_33") + arg_34: bool = pydantic.Field(False, description="arg_34") + arg_35: bool = pydantic.Field(True, description="arg_35") + arg_36: Literal["A"] = pydantic.Field("A", description="arg_36") + arg_37: Literal["A", 1] = pydantic.Field("A", description="arg_37") + arg_38: TestEnumSingle = pydantic.Field(TestEnumSingle.D, description="arg_38") + arg_39: TestEnum = pydantic.Field(TestEnum.A, description="arg_39") + + # Optional Arguments (No Default) + arg_40: Optional[int] = pydantic.Field(description="arg_40") + arg_41: Optional[float] = pydantic.Field(description="arg_41") + arg_42: Optional[str] = pydantic.Field(description="arg_42") + arg_43: Optional[bytes] = pydantic.Field(description="arg_43") + arg_44: Optional[List[str]] = pydantic.Field(description="arg_44") + arg_45: Optional[Tuple[str, str, str]] = pydantic.Field(description="arg_45") + arg_46: Optional[Set[str]] = pydantic.Field(description="arg_46") + arg_47: Optional[FrozenSet[str]] = pydantic.Field(description="arg_47") + arg_48: Optional[Deque[str]] = pydantic.Field(description="arg_48") + arg_49: Optional[Dict[str, int]] = pydantic.Field(description="arg_49") + arg_50: Optional[datetime.date] = pydantic.Field(description="arg_50") + arg_51: Optional[datetime.datetime] = pydantic.Field(description="arg_51") + arg_52: Optional[datetime.time] = pydantic.Field(description="arg_52") + arg_53: Optional[datetime.timedelta] = pydantic.Field(description="arg_53") + arg_54: Optional[bool] = pydantic.Field(description="arg_54") + arg_55: Optional[Literal["A"]] = pydantic.Field(description="arg_55") + arg_56: Optional[Literal["A", 1]] = pydantic.Field(description="arg_56") + arg_57: Optional[TestEnumSingle] = pydantic.Field(description="arg_57") + arg_58: Optional[TestEnum] = pydantic.Field(description="arg_58") + + # Special Enums and Literals Optional Flag Behaviour + arg_59: Optional[Literal["A"]] = pydantic.Field(description="arg_59") + arg_60: Optional[Literal["A"]] = pydantic.Field("A", description="arg_60") + arg_61: Optional[TestEnumSingle] = pydantic.Field(description="arg_61") + arg_62: Optional[TestEnumSingle] = pydantic.Field(TestEnumSingle.D, description="arg_62") + + # Commands + arg_63: Optional[TestCommand] = pydantic.Field(description="arg_63") + arg_64: TestCommand = pydantic.Field(description="arg_64") + arg_65: Optional[TestCommands] = pydantic.Field(description="arg_65") + arg_66: TestCommands = pydantic.Field(description="arg_66") diff --git a/vendor/pydantic-argparse/tests/functional/__init__.py b/vendor/pydantic-argparse/tests/functional/__init__.py new file mode 100644 index 000000000..c7bffc45a --- /dev/null +++ b/vendor/pydantic-argparse/tests/functional/__init__.py @@ -0,0 +1,5 @@ +"""Functional Tests for the `pydantic-argparse` Package. + +This file is required to mark the unit tests as a package, so they can resolve +and import the actual top level package code +""" diff --git a/vendor/pydantic-argparse/tests/functional/test_environment_variables.py b/vendor/pydantic-argparse/tests/functional/test_environment_variables.py new file mode 100644 index 000000000..52f828469 --- /dev/null +++ b/vendor/pydantic-argparse/tests/functional/test_environment_variables.py @@ -0,0 +1,340 @@ +"""Tests the `pydantic-argparse` Environment Variables Functionality. + +This module provides functional regression tests for the `pydantic-argparse` +environment variable parsing capabilities. +""" + + +# Standard +import argparse +import collections as coll +import datetime as dt +import os +import sys + +# Third-Party +import pytest +import pytest_mock + +# Local +import pydantic_argparse +import tests.conftest as conf + +# Typing +from typing import Deque, Dict, FrozenSet, List, Optional, Set, Tuple, Type, TypeVar + +# Version-Guarded +if sys.version_info < (3, 8): # pragma: <3.8 cover + from typing_extensions import Literal +else: # pragma: >=3.8 cover + from typing import Literal + + +# Constants +ArgumentT = TypeVar("ArgumentT") + + +@pytest.mark.parametrize( + ( + "argument_type", + "argument_default", + "env", + "result", + ), + [ + # Required Arguments + (int, ..., "TEST=123", 123), + (float, ..., "TEST=4.56", 4.56), + (str, ..., "TEST=hello", "hello"), + (bytes, ..., "TEST=bytes", b"bytes"), + (List[str], ..., 'TEST=["a","b","c"]', list(("a", "b", "c"))), + (Tuple[str, str, str], ..., 'TEST=["a","b","c"]', tuple(("a", "b", "c"))), + (Set[str], ..., 'TEST=["a","b","c"]', set(("a", "b", "c"))), + (FrozenSet[str], ..., 'TEST=["a","b","c"]', frozenset(("a", "b", "c"))), + (Deque[str], ..., 'TEST=["a","b","c"]', coll.deque(("a", "b", "c"))), + (Dict[str, int], ..., 'TEST={"a":2}', dict(a=2)), + (dt.date, ..., "TEST=2021-12-25", dt.date(2021, 12, 25)), + (dt.datetime, ..., "TEST=2021-12-25T12:34", dt.datetime(2021, 12, 25, 12, 34)), + (dt.time, ..., "TEST=12:34", dt.time(12, 34)), + (dt.timedelta, ..., "TEST=PT12H", dt.timedelta(hours=12)), + (bool, ..., "TEST=true", True), + (bool, ..., "TEST=false", False), + (Literal["A"], ..., "TEST=A", "A"), + (Literal["A", 1], ..., "TEST=1", 1), + (conf.TestEnumSingle, ..., "TEST=D", conf.TestEnumSingle.D), + (conf.TestEnum, ..., "TEST=C", conf.TestEnum.C), + + # Optional Arguments (With Default) + (int, 456, "TEST=123", 123), + (float, 1.23, "TEST=4.56", 4.56), + (str, "world", "TEST=hello", "hello"), + (bytes, b"bits", "TEST=bytes", b"bytes"), + (List[str], list(("d", "e", "f")), 'TEST=["a","b","c"]', list(("a", "b", "c"))), + (Tuple[str, str, str], tuple(("d", "e", "f")), 'TEST=["a","b","c"]', tuple(("a", "b", "c"))), + (Set[str], set(("d", "e", "f")), 'TEST=["a","b","c"]', set(("a", "b", "c"))), + (FrozenSet[str], frozenset(("d", "e", "f")), 'TEST=["a","b","c"]', frozenset(("a", "b", "c"))), + (Deque[str], coll.deque(("d", "e", "f")), 'TEST=["a","b","c"]', coll.deque(("a", "b", "c"))), + (Dict[str, int], dict(b=3), 'TEST={"a":2}', dict(a=2)), + (dt.date, dt.date(2021, 7, 21), "TEST=2021-12-25", dt.date(2021, 12, 25)), + (dt.datetime, dt.datetime(2021, 7, 21, 3), "TEST=2021-04-03T02:00", dt.datetime(2021, 4, 3, 2)), + (dt.time, dt.time(3, 21), "TEST=12:34", dt.time(12, 34)), + (dt.timedelta, dt.timedelta(hours=6), "TEST=PT12H", dt.timedelta(hours=12)), + (bool, False, "TEST=true", True), + (bool, True, "TEST=false", False), + (Literal["A"], "A", "TEST=A", "A"), + (Literal["A", 1], "A", "TEST=1", 1), + (conf.TestEnumSingle, conf.TestEnumSingle.D, "TEST=D", conf.TestEnumSingle.D), + (conf.TestEnum, conf.TestEnum.B, "TEST=C", conf.TestEnum.C), + + # Optional Arguments (With Default) (No Value Given) + (int, 456, "", 456), + (float, 1.23, "", 1.23), + (str, "world", "", "world"), + (bytes, b"bits", "", b"bits"), + (List[str], list(("d", "e", "f")), "", list(("d", "e", "f"))), + (Tuple[str, str, str], tuple(("d", "e", "f")), "", tuple(("d", "e", "f"))), + (Set[str], set(("d", "e", "f")), "", set(("d", "e", "f"))), + (FrozenSet[str], frozenset(("d", "e", "f")), "", frozenset(("d", "e", "f"))), + (Deque[str], coll.deque(("d", "e", "f")), "", coll.deque(("d", "e", "f"))), + (Dict[str, int], dict(b=3), "", dict(b=3)), + (dt.date, dt.date(2021, 7, 21), "", dt.date(2021, 7, 21)), + (dt.datetime, dt.datetime(2021, 7, 21, 3, 7), "", dt.datetime(2021, 7, 21, 3, 7)), + (dt.time, dt.time(3, 21), "", dt.time(3, 21)), + (dt.timedelta, dt.timedelta(hours=6), "", dt.timedelta(hours=6)), + (bool, False, "", False), + (bool, True, "", True), + (Literal["A"], "A", "", "A"), + (Literal["A", 1], "A", "", "A"), + (conf.TestEnumSingle, conf.TestEnumSingle.D, "", conf.TestEnumSingle.D), + (conf.TestEnum, conf.TestEnum.B, "", conf.TestEnum.B), + + # Optional Arguments (No Default) + (Optional[int], None, "TEST=123", 123), + (Optional[float], None, "TEST=4.56", 4.56), + (Optional[str], None, "TEST=hello", "hello"), + (Optional[bytes], None, "TEST=bytes", b"bytes"), + (Optional[List[str]], None, 'TEST=["a","b","c"]', list(("a", "b", "c"))), + (Optional[Tuple[str, str, str]], None, 'TEST=["a","b","c"]', tuple(("a", "b", "c"))), + (Optional[Set[str]], None, 'TEST=["a","b","c"]', set(("a", "b", "c"))), + (Optional[FrozenSet[str]], None, 'TEST=["a","b","c"]', frozenset(("a", "b", "c"))), + (Optional[Deque[str]], None, 'TEST=["a","b","c"]', coll.deque(("a", "b", "c"))), + (Optional[Dict[str, int]], None, 'TEST={"a":2}', dict(a=2)), + (Optional[dt.date], None, "TEST=2021-12-25", dt.date(2021, 12, 25)), + (Optional[dt.datetime], None, "TEST=2021-12-25T12:34", dt.datetime(2021, 12, 25, 12, 34)), + (Optional[dt.time], None, "TEST=12:34", dt.time(12, 34)), + (Optional[dt.timedelta], None, "TEST=PT12H", dt.timedelta(hours=12)), + (Optional[bool], None, "TEST=true", True), + (Optional[Literal["A"]], None, "TEST=A", "A"), + (Optional[Literal["A", 1]], None, "TEST=1", 1), + (Optional[conf.TestEnumSingle], None, "TEST=D", conf.TestEnumSingle.D), + (Optional[conf.TestEnum], None, "TEST=C", conf.TestEnum.C), + + # Optional Arguments (No Default) (No Value Given) + (Optional[int], None, "", None), + (Optional[float], None, "", None), + (Optional[str], None, "", None), + (Optional[bytes], None, "", None), + (Optional[List[str]], None, "", None), + (Optional[Tuple[str, str, str]], None, "", None), + (Optional[Set[str]], None, "", None), + (Optional[FrozenSet[str]], None, "", None), + (Optional[Deque[str]], None, "", None), + (Optional[Dict[str, int]], None, "", None), + (Optional[dt.date], None, "", None), + (Optional[dt.datetime], None, "", None), + (Optional[dt.time], None, "", None), + (Optional[dt.timedelta], None, "", None), + (Optional[bool], None, "", None), + (Optional[Literal["A"]], None, "", None), + (Optional[Literal["A", 1]], None, "", None), + (Optional[conf.TestEnumSingle], None, "", None), + (Optional[conf.TestEnum], None, "", None), + + # Special Enums and Literals Optional Flag Behaviour + (Optional[Literal["A"]], "A", "TEST=", None), + (Optional[Literal["A"]], "A", "", "A"), + (Optional[conf.TestEnumSingle], conf.TestEnumSingle.D, "TEST=", None), + (Optional[conf.TestEnumSingle], conf.TestEnumSingle.D, "", conf.TestEnumSingle.D), + + # Missing Optional Argument Values + (Optional[int], None, "TEST=", None), + (Optional[float], None, "TEST=", None), + (Optional[str], None, "TEST=", None), + (Optional[bytes], None, "TEST=", None), + (Optional[List[int]], None, "TEST=", None), + (Optional[Tuple[int, int, int]], None, "TEST=", None), + (Optional[Set[int]], None, "TEST=", None), + (Optional[FrozenSet[int]], None, "TEST=", None), + (Optional[Deque[int]], None, "TEST=", None), + (Optional[Dict[str, int]], None, "TEST=", None), + (Optional[dt.date], None, "TEST=", None), + (Optional[dt.datetime], None, "TEST=", None), + (Optional[dt.time], None, "TEST=", None), + (Optional[dt.timedelta], None, "TEST=", None), + ], +) +def test_valid_environment_variables( + argument_type: Type[ArgumentT], + argument_default: ArgumentT, + env: str, + result: ArgumentT, + mocker: pytest_mock.MockerFixture, +) -> None: + """Tests ArgumentParser Valid Arguments as Environment Variables. + + Args: + argument_type (Type[ArgumentT]): Type of the argument. + argument_default (ArgumentT): Default for the argument. + env (str): An example string of environment variables for testing. + result (ArgumentT): Result from parsing the argument. + mocker (pytest_mock.MockerFixture): PyTest Mocker Fixture. + """ + # Construct Pydantic Model + model = conf.create_test_model(test=(argument_type, argument_default)) + + # Create ArgumentParser + parser = pydantic_argparse.ArgumentParser(model) + + # Construct Environment Variables + environment_variables: Dict[str, str] = dict([env.split("=")]) if env else {} + + # Mock Environment Variables + mocker.patch.dict(os.environ, environment_variables, clear=True) + + # Parse + args = parser.parse_typed_args([]) # Empty Arguments + + # Asserts + assert isinstance(args.test, type(result)) + assert args.test == result + + +@pytest.mark.parametrize( + ( + "argument_type", + "argument_default", + "env", + ), + [ + # Invalid Arguments + (int, ..., "TEST=invalid"), + (float, ..., "TEST=invalid"), + (List[int], ..., "TEST=invalid"), + (Tuple[int, int, int], ..., "TEST=invalid"), + (Set[int], ..., "TEST=invalid"), + (FrozenSet[int], ..., "TEST=invalid"), + (Deque[int], ..., "TEST=invalid"), + (Dict[str, int], ..., "TEST=invalid"), + (dt.date, ..., "TEST=invalid"), + (dt.datetime, ..., "TEST=invalid"), + (dt.time, ..., "TEST=invalid"), + (dt.timedelta, ..., "TEST=invalid"), + (bool, ..., "TEST=invalid"), + (Literal["A"], ..., "TEST=invalid"), + (Literal["A", 1], ..., "TEST=invalid"), + (conf.TestEnumSingle, ..., "TEST=invalid"), + (conf.TestEnum, ..., "TEST=invalid"), + + # Missing Argument Values + (int, ..., "TEST="), + (float, ..., "TEST="), + (List[int], ..., "TEST="), + (Tuple[int, int, int], ..., "TEST="), + (Set[int], ..., "TEST="), + (FrozenSet[int], ..., "TEST="), + (Deque[int], ..., "TEST="), + (Dict[str, int], ..., "TEST="), + (dt.date, ..., "TEST="), + (dt.datetime, ..., "TEST="), + (dt.time, ..., "TEST="), + (dt.timedelta, ..., "TEST="), + (Literal["A"], ..., "TEST="), + (Literal["A", 1], ..., "TEST="), + (conf.TestEnumSingle, ..., "TEST="), + (conf.TestEnum, ..., "TEST="), + + # Missing Arguments + (int, ..., ""), + (float, ..., ""), + (str, ..., ""), + (bytes, ..., ""), + (List[int], ..., ""), + (Tuple[int, int, int], ..., ""), + (Set[int], ..., ""), + (FrozenSet[int], ..., ""), + (Deque[int], ..., ""), + (Dict[str, int], ..., ""), + (dt.date, ..., ""), + (dt.datetime, ..., ""), + (dt.time, ..., ""), + (dt.timedelta, ..., ""), + (bool, ..., ""), + (Literal["A"], ..., ""), + (Literal["A", 1], ..., ""), + (conf.TestEnumSingle, ..., ""), + (conf.TestEnum, ..., ""), + + # Invalid Optional Arguments + (Optional[int], None, "TEST=invalid"), + (Optional[float], None, "TEST=invalid"), + (Optional[List[int]], None, "TEST=invalid"), + (Optional[Tuple[int, int, int]], None, "TEST=invalid"), + (Optional[Set[int]], None, "TEST=invalid"), + (Optional[FrozenSet[int]], None, "TEST=invalid"), + (Optional[Deque[int]], None, "TEST=invalid"), + (Optional[Dict[str, int]], None, "TEST=invalid"), + (Optional[dt.date], None, "TEST=invalid"), + (Optional[dt.datetime], None, "TEST=invalid"), + (Optional[dt.time], None, "TEST=invalid"), + (Optional[dt.timedelta], None, "TEST=invalid"), + (Optional[bool], None, "TEST=invalid"), + (Optional[Literal["A"]], None, "TEST=invalid"), + (Optional[Literal["A", 1]], None, "TEST=invalid"), + (Optional[conf.TestEnumSingle], None, "TEST=invalid"), + (Optional[conf.TestEnum], None, "TEST=invalid"), + ], +) +@pytest.mark.parametrize( + ( + "exit_on_error", + "error" + ), + [ + (True, SystemExit), + (False, argparse.ArgumentError), + ], +) +def test_invalid_environment_variables( + argument_type: Type[ArgumentT], + argument_default: ArgumentT, + env: str, + exit_on_error: bool, + error: Type[Exception], + mocker: pytest_mock.MockerFixture, +) -> None: + """Tests ArgumentParser Invalid Arguments as Environment Variables. + + Args: + argument_type (Type[ArgumentT]): Type of the argument. + argument_default (ArgumentT): Default for the argument. + env (str): An example string of environment variables for testing. + exit_on_error (bool): Whether to raise or exit on error. + error (Type[Exception]): Exception that should be raised for testing. + mocker (pytest_mock.MockerFixture): PyTest Mocker Fixture. + """ + # Construct Pydantic Model + model = conf.create_test_model(test=(argument_type, argument_default)) + + # Create ArgumentParser + parser = pydantic_argparse.ArgumentParser(model, exit_on_error=exit_on_error) + + # Construct Environment Variables + environment_variables: Dict[str, str] = dict([env.split("=")]) if env else {} + + # Mock Environment Variables + mocker.patch.dict(os.environ, environment_variables, clear=True) + + # Assert Parser Raises Error + with pytest.raises(error): + # Parse + parser.parse_typed_args([]) # Empty Arguments diff --git a/vendor/pydantic-argparse/tests/parsers/__init__.py b/vendor/pydantic-argparse/tests/parsers/__init__.py new file mode 100644 index 000000000..b7c8549c6 --- /dev/null +++ b/vendor/pydantic-argparse/tests/parsers/__init__.py @@ -0,0 +1,5 @@ +"""Unit Tests for the `parsers` Package. + +This file is required to mark the unit tests as a package, so they can resolve +and import the actual top level package code +""" diff --git a/vendor/pydantic-argparse/tests/utils/__init__.py b/vendor/pydantic-argparse/tests/utils/__init__.py new file mode 100644 index 000000000..4646b8918 --- /dev/null +++ b/vendor/pydantic-argparse/tests/utils/__init__.py @@ -0,0 +1,5 @@ +"""Unit Tests for the `utils` Package. + +This file is required to mark the unit tests as a package, so they can resolve +and import the actual top level package code +""" diff --git a/vendor/pydantic-argparse/tests/utils/test_arguments.py b/vendor/pydantic-argparse/tests/utils/test_arguments.py new file mode 100644 index 000000000..15bcbbf14 --- /dev/null +++ b/vendor/pydantic-argparse/tests/utils/test_arguments.py @@ -0,0 +1,90 @@ +"""Tests the `arguments` Module. + +This module provides full unit test coverage for the `arguments` module, +testing all branches of all functions. +""" + + +# Third-Party +import pytest + +# Local +from pydantic_argparse import utils +from tests import conftest as conf + +# Typing +from typing import Any, Optional + + +@pytest.mark.parametrize( + ( + "name", + "invert", + "expected", + ), + [ + ("test", False, "--test"), + ("test", True, "--no-test"), + ("test_two", False, "--test-two"), + ("test_two", True, "--no-test-two"), + ], +) +def test_argument_name( + name: str, + invert: bool, + expected: str, +) -> None: + """Tests `utils.arguments.name` Function. + + Args: + name (str): Argument name to test. + invert (bool): Whether to invert the name. + expected (str): Expected result of the test. + """ + # Construct Pydantic Field + field = conf.create_test_field(name) + + # Generate Argument Name + result = utils.arguments.name(field, invert) + + # Assert + assert result == expected + + +@pytest.mark.parametrize( + ( + "description", + "default", + "expected", + ), + [ + ("A", "B", "A (default: B)"), + ("A", 5, "A (default: 5)"), + ("A", None, "A (default: None)"), + ("A", ..., "A"), + (None, "B", "(default: B)"), + (None, 5, "(default: 5)"), + (None, None, "(default: None)"), + (None, ..., ""), + ], +) +def test_argument_description( + description: Optional[str], + default: Any, + expected: str, +) -> None: + """Tests `utils.arguments.description` Function. + + Args: + description (Optional[str]): Optional argument description to test. + default (Any): Optional argument default value to test. + expected (str): Expected result of the test. + """ + # Construct Pydantic Field + field = conf.create_test_field(default=default, description=description) + + # Generate Argument Description + result = utils.arguments.description(field) + + # Assert + assert result == expected diff --git a/vendor/pydantic-argparse/tests/utils/test_errors.py b/vendor/pydantic-argparse/tests/utils/test_errors.py new file mode 100644 index 000000000..f96f0a844 --- /dev/null +++ b/vendor/pydantic-argparse/tests/utils/test_errors.py @@ -0,0 +1,83 @@ +"""Tests the `errors` Module. + +This module provides full unit test coverage for the `errors` module, testing +all branches of all functions. +""" + + +# Standard +import textwrap + +# Third-Party +import pydantic +import pytest + +# Local +from pydantic_argparse import utils +from tests import conftest as conf + +# Typing +from typing import Sequence, Tuple, Union + + +# Shortcuts +# An Error Definition is just a tuple containing an Exception and Location +# This allows for more terse unit test parametrization and function typing +ErrorDefinition = Tuple[Exception, Union[str, Tuple[str, ...]]] + + +@pytest.mark.parametrize( + ( + "errors", + "expected", + ), + [ + ( + [ + (pydantic.errors.MissingError(), "argument"), + ], + """ + 1 validation error for TestModel + argument + field required (type=value_error.missing) + """, + ), + ( + [ + (pydantic.errors.IPv4AddressError(), ("a", )), + (pydantic.errors.IntegerError(), ("a", "b")), + (pydantic.errors.UUIDError(), ("a", "b", "c")), + ], + """ + 3 validation errors for TestModel + a + value is not a valid IPv4 address (type=value_error.ipv4address) + a -> b + value is not a valid integer (type=type_error.integer) + a -> b -> c + value is not a valid uuid (type=type_error.uuid) + """, + ), + ], +) +def test_error_format( + errors: Sequence[ErrorDefinition], + expected: str, +) -> None: + """Tests `utils.errors.format` Function. + + Args: + errors (Sequence[ErrorDefinition]): Errors to test. + expected (str): Expected result of the test. + """ + # Construct Validation Error + error = pydantic.ValidationError( + errors=[pydantic.error_wrappers.ErrorWrapper(exc, loc) for (exc, loc) in errors], + model=conf.TestModel, + ) + + # Format Error + result = utils.errors.format(error) + + # Assert + assert result == textwrap.dedent(expected).strip() diff --git a/vendor/pydantic-argparse/tests/utils/test_namespaces.py b/vendor/pydantic-argparse/tests/utils/test_namespaces.py new file mode 100644 index 000000000..b81063298 --- /dev/null +++ b/vendor/pydantic-argparse/tests/utils/test_namespaces.py @@ -0,0 +1,47 @@ +"""Tests the `namespaces` Module. + +This module provides full unit test coverage for the `namespaces` module, +testing all branches of all functions. +""" + + +# Standard +import argparse + +# Local +from pydantic_argparse import utils + + +def test_namespace_to_dict() -> None: + """Tests `utils.namespaces.to_dict` Function.""" + # Generate Dictionary + result = utils.namespaces.to_dict( + argparse.Namespace( + a="1", + b=2, + c=argparse.Namespace( + d="3", + e=4, + f=argparse.Namespace( + g=5, + h="6", + i=7, + ) + ) + ) + ) + + # Assert + assert result == { + "a": "1", + "b": 2, + "c": { + "d": "3", + "e": 4, + "f": { + "g": 5, + "h": "6", + "i": 7, + } + } + } diff --git a/vendor/pydantic-argparse/tests/utils/test_types.py b/vendor/pydantic-argparse/tests/utils/test_types.py new file mode 100644 index 000000000..03a7bc425 --- /dev/null +++ b/vendor/pydantic-argparse/tests/utils/test_types.py @@ -0,0 +1,86 @@ +"""Tests the `types` Module. + +This module provides full unit test coverage for the `types` module, testing +all branches of all functions. +""" + + +# Standard +import collections +import collections.abc +import enum +import sys + +# Third-Party +import pydantic +import pytest + +# Local +from pydantic_argparse import utils +from tests import conftest as conf + +# Typing +from typing import Any, Deque, Dict, FrozenSet, List, Set, Tuple + +# Version-Guarded +if sys.version_info < (3, 8): # pragma: <3.8 cover + from typing_extensions import Literal +else: # pragma: >=3.8 cover + from typing import Literal + + +@pytest.mark.parametrize( + ( + "field_type", + "expected_type", + ), + [ + (bool, bool), + (int, int), + (float, float), + (str, str), + (bytes, bytes), + (List, list), + (List, collections.abc.Container), + (List[str], list), + (List[str], collections.abc.Container), + (Tuple, tuple), + (Tuple, collections.abc.Container), + (Tuple[str, ...], tuple), + (Tuple[str, ...], collections.abc.Container), + (Set, set), + (Set, collections.abc.Container), + (Set[str], set), + (Set[str], collections.abc.Container), + (FrozenSet, frozenset), + (FrozenSet, collections.abc.Container), + (FrozenSet[str], frozenset), + (FrozenSet[str], collections.abc.Container), + (Deque, collections.deque), + (Deque, collections.abc.Container), + (Deque[str], collections.deque), + (Deque[str], collections.abc.Container), + (Dict, dict), + (Dict, collections.abc.Mapping), + (Dict[str, int], dict), + (Dict[str, int], collections.abc.Mapping), + (Literal["A"], Literal), + (Literal[1, 2, 3], Literal), + (conf.TestCommand, pydantic.BaseModel), + (conf.TestCommands, pydantic.BaseModel), + (conf.TestEnum, enum.Enum), + (conf.TestEnumSingle, enum.Enum), + ], +) +def test_is_field_a(field_type: Any, expected_type: Any) -> None: + """Tests utils.is_field_a Function. + + Args: + field_type (Any): Field type to test. + expected_type (Any): Expected type to check for the field. + """ + # Construct Pydantic Field + field = conf.create_test_field(type=field_type) + + # Check and Assert Field Type + assert utils.types.is_field_a(field, expected_type)