├── .github └── workflows │ ├── test.yaml │ └── upload.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Justfile ├── LICENSE ├── README.md ├── poetry.lock ├── pydantic_numpy ├── __init__.py ├── helper │ ├── __init__.py │ ├── annotation.py │ ├── typing.py │ └── validation.py ├── model.py ├── typing │ ├── __init__.py │ ├── type_safe │ │ ├── __init__.py │ │ ├── i_dimensional.py │ │ ├── ii_dimensional.py │ │ ├── iii_dimensional.py │ │ └── n_dimensional.py │ └── with_loader │ │ ├── __init__.py │ │ ├── i_dimensional.py │ │ ├── ii_dimensional.py │ │ ├── iii_dimensional.py │ │ └── n_dimensional.py └── util.py ├── pyproject.toml ├── tests ├── __init__.py ├── helper │ ├── __init__.py │ ├── cache.py │ └── testing_groups.py ├── model.py ├── test_annotation.py ├── test_np_model.py ├── test_pydantic.py └── test_typing.py └── typegen └── generate_typing.py /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ trunk ] 6 | pull_request: 7 | branches: [ trunk ] 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | jobs: 14 | unit_tests: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | python-version: [ "3.10", "3.11", "3.12", "3.13" ] 19 | os: [ ubuntu-latest, windows-latest, macos-latest ] 20 | 21 | runs-on: ${{ matrix.os }} 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Install poetry 27 | run: pipx install poetry 28 | 29 | - name: Setup the Python Environment ${{ matrix.python-version }} 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | cache: "poetry" 34 | 35 | - name: Install dependencies 36 | run: | 37 | poetry install --all-extras --with dev 38 | 39 | - name: Run tests 40 | run: | 41 | poetry run pytest . 42 | 43 | - name: Upload coverage reports to Codecov 44 | uses: codecov/codecov-action@v4 45 | env: 46 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 47 | mypy: 48 | runs-on: ubuntu-latest 49 | 50 | strategy: 51 | matrix: 52 | python-version: [ "3.9", "3.10", "3.11", "3.12" ] 53 | 54 | steps: 55 | - uses: actions/checkout@v4 56 | 57 | - name: Install poetry 58 | run: pipx install poetry 59 | 60 | - name: Setup the Python Environment ${{ matrix.python-version }} 61 | uses: actions/setup-python@v5 62 | with: 63 | python-version: ${{ matrix.python-version }} 64 | cache: "poetry" 65 | 66 | - name: Install dependencies 67 | run: | 68 | poetry install --all-extras --with dev,typecheck 69 | 70 | - name: Validate type-hints with MyPy 71 | run: poetry run mypy 72 | 73 | pyright: 74 | runs-on: ubuntu-latest 75 | 76 | strategy: 77 | matrix: 78 | python-version: [ "3.9", "3.10", "3.11", "3.12" ] 79 | 80 | steps: 81 | - uses: actions/checkout@v4 82 | 83 | - name: Install poetry 84 | run: pipx install poetry 85 | 86 | - name: Setup the Python Environment ${{ matrix.python-version }} 87 | uses: actions/setup-python@v5 88 | with: 89 | python-version: ${{ matrix.python-version }} 90 | cache: "poetry" 91 | 92 | - name: Install dependencies 93 | run: | 94 | poetry install --all-extras --with dev,typecheck 95 | 96 | - name: Validate type-hints with pyright 97 | run: | 98 | poetry run pyright pydantic_numpy 99 | 100 | format: 101 | runs-on: ubuntu-latest 102 | 103 | strategy: 104 | matrix: 105 | python-version: [ "3.12" ] 106 | 107 | steps: 108 | - uses: actions/checkout@v4 109 | 110 | - name: Install poetry 111 | run: pipx install poetry 112 | 113 | - name: Setup the Python Environment ${{ matrix.python-version }} 114 | uses: actions/setup-python@v5 115 | with: 116 | python-version: ${{ matrix.python-version }} 117 | cache: "poetry" 118 | 119 | - name: Install dependencies 120 | run: | 121 | poetry install --all-extras --with format 122 | 123 | - name: Check code formatting 124 | run: | 125 | poetry run black . --check --diff 126 | poetry run isort . --check --diff 127 | poetry run ruff check . -------------------------------------------------------------------------------- /.github/workflows/upload.yaml: -------------------------------------------------------------------------------- 1 | name: Upload 2 | 3 | on: 4 | workflow_run: 5 | workflows: [ "Tests" ] 6 | types: 7 | - completed 8 | push: 9 | branches: [ trunk ] 10 | 11 | defaults: 12 | run: 13 | shell: bash 14 | 15 | jobs: 16 | upload: 17 | runs-on: ubuntu-latest 18 | continue-on-error: true 19 | 20 | strategy: 21 | matrix: 22 | python-version: [ "3.12" ] 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Install poetry 28 | run: pipx install poetry 29 | 30 | - name: Setup the Python Environment ${{ matrix.python-version }} 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | cache: "poetry" 35 | 36 | - name: Install dependencies 37 | run: poetry install 38 | 39 | - name: Build package 40 | run: poetry build 41 | 42 | - name: Upload to PyPi 43 | run: poetry publish -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project 2 | 3 | delete_me_test_dump*/ 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # JetBrains 136 | /.idea/ 137 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors Guide 2 | 3 | Welcome to our project! We value your contributions and want to make it easy for you to get involved. This guide outlines our project's workflow, versioning strategy, and commit message conventions. 4 | 5 | ## Workflow: GitHub Flow 6 | 7 | To get started with contributing, please follow the GitHub Flow: 8 | 9 | 1. **Fork** this repository to your own GitHub account. 10 | 2. **Clone** the forked repository to your local machine: 11 | ```bash 12 | git clone 13 | git checkout -b your-new-branch-name 14 | ``` 15 | 3. **Create a branch** for your modifications: 16 | ```bash 17 | git checkout -b feature-branch-name 18 | ``` 19 | 4. **Make your changes** and commit them using the [Conventional Commits](#commit-messages) format. 20 | 5. **Push** your branch to your fork: 21 | ```bash 22 | git push origin feature-branch-name 23 | ``` 24 | 6. **Create a pull request** against the original repository’s main branch. 25 | 7. **Review** the changes with maintainers and **address any feedback**. 26 | 27 | The project maintainers will merge the changes once they are approved. If you have any questions or need help, please feel free to reach out to us. 28 | 29 | ## Semantic Versioning 30 | 31 | Our project adheres to Semantic Versioning (SemVer) principles, which helps us manage releases and dependencies systematically. The version number format is: 32 | 33 | MAJOR.MINOR.PATCH 34 | 35 | - **MAJOR**: Incompatible API changes, 36 | - **MINOR**: Additions that are backward compatible, 37 | - **PATCH**: Backward-compatible bug fixes. 38 | 39 | ## Commit Messages 40 | 41 | We use the Conventional Commits format for our commit messages to facilitate consistency and changelog generation. Please structure your commits as follows: 42 | 43 | (): 44 | 45 | [optional body] 46 | 47 | [optional footer] 48 | 49 | markdown 50 | 51 | 52 | ### Commit Types 53 | 54 | - `feat`: Introduces a new feature. 55 | - `fix`: Patches a bug. 56 | - `docs`: Documentation only changes. 57 | - `style`: Code style update (formatting, semi-colons, etc.). 58 | - `refactor`: Neither fixes a bug nor introduces a feature. 59 | - `perf`: Performance improvements. 60 | - `test`: Adding missing tests or correcting existing ones. 61 | - `chore`: Other changes that don't modify src or test files. 62 | 63 | **Example Commit Message**: 64 | 65 | ``` 66 | feat(authentication): add biometric login support 67 | 68 | - Support fingerprint and face recognition login. 69 | - Ensure compatibility with mobile devices. 70 | ``` 71 | 72 | Thank you for contributing to our project! We appreciate your effort to follow these guidelines. 73 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | 2 | 3 | test: 4 | poetry run pytest tests 5 | 6 | 7 | format: 8 | poetry run black . 9 | poetry run isort . 10 | poetry run ruff check --fix --exit-zero . 11 | @echo "Formatting complete 🎉" 12 | 13 | mypy: 14 | poetry run mypy 15 | 16 | mypy_test: 17 | poetry run mypy tests/ 18 | 19 | pyright: 20 | poetry run pyright pydantic_numpy 21 | 22 | pyright_test: 23 | poetry run pyright tests/ 24 | 25 | typegen: 26 | poetry run python typegen/generate_typing.py 27 | 28 | check: format pyright mypy test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 4-Clause License 2 | 3 | Copyright 2025 Can Hicabi Tartanoglu 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pydantic-numpy 2 | 3 | ![Python 3.10-3.13](https://img.shields.io/badge/python-3.9--3.13-blue.svg) 4 | [![Packaged with Poetry](https://img.shields.io/badge/packaging-poetry-cyan.svg)](https://python-poetry.org/) 5 | ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg) 6 | ![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336) 7 | ![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json) 8 | 9 | 10 | ## Usage 11 | 12 | Package that integrates NumPy Arrays into Pydantic! 13 | 14 | - `pydantic_numpy.typing` provides many typings such as `NpNDArrayFp64`, `Np3DArrayFp64` (float64 that must be 3D)! Works with both `pydantic.BaseModel` and `pydantic.dataclass` 15 | - `NumpyModel` (derived from `pydantic.BaseModel`) make it possible to dump and load `np.ndarray` within model fields alongside other fields that are not instances of `np.ndarray`! 16 | 17 | See the [`test.helper.testing_groups`](https://github.com/caniko/pydantic-numpy/blob/trunk/tests/helper/testing_groups.py) to see types that are defined explicitly. 18 | 19 | ### Examples 20 | 21 | For more examples see [test_ndarray.py](./tests/test_typing.py) 22 | 23 | ```python 24 | import numpy as np 25 | from pydantic import BaseModel 26 | 27 | import pydantic_numpy.typing as pnd 28 | from pydantic_numpy import np_array_pydantic_annotated_typing 29 | from pydantic_numpy.model import NumpyModel, MultiArrayNumpyFile 30 | 31 | 32 | class MyBaseModelDerivedModel(BaseModel): 33 | any_array_dtype_and_dimension: pnd.NpNDArray 34 | 35 | # Must be numpy float32 as dtype 36 | k: np_array_pydantic_annotated_typing(data_type=np.float32) 37 | shorthand_for_k: pnd.NpNDArrayFp32 38 | 39 | must_be_1d_np_array: np_array_pydantic_annotated_typing(dimensions=1) 40 | 41 | 42 | class MyDemoNumpyModel(NumpyModel): 43 | k: np_array_pydantic_annotated_typing(data_type=np.float32) 44 | 45 | 46 | # Instantiate from array 47 | cfg = MyDemoModel(k=[1, 2]) 48 | # Instantiate from numpy file 49 | cfg = MyDemoModel(k="path_to/array.npy") 50 | # Instantiate from npz file with key 51 | cfg = MyDemoModel(k=MultiArrayNumpyFile(path="path_to/array.npz", key="k")) 52 | 53 | cfg.k # np.ndarray[np.float32] 54 | 55 | cfg.dump("path_to_dump_dir", "object_id") 56 | cfg.load("path_to_dump_dir", "object_id") 57 | ``` 58 | 59 | `NumpyModel.load` requires the original model: 60 | ```python 61 | MyNumpyModel.load() 62 | ``` 63 | Use `model_agnostic_load` when you have several models that may be the correct model: 64 | 65 | ```python 66 | from pydantic_numpy.model import model_agnostic_load 67 | 68 | cfg.dump("path_to_dump_dir", "object_id") 69 | equals_cfg = model_agnostic_load("path_to_dump_dir", "object_id", models=[MyNumpyModel, MyDemoModel]) 70 | ``` 71 | 72 | ### Custom type 73 | There are two ways to define. Function derived types with `pydantic_numpy.helper.annotation.np_array_pydantic_annotated_typing`. 74 | 75 | Function derived types don't work with static type checkers like Pyright and MyPy. In case you need the support, 76 | just create the types yourself: 77 | 78 | ```python 79 | NpStrict1DArrayInt64 = Annotated[ 80 | np.ndarray[tuple[int], np.dtype[np.int64]], 81 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=1, strict_data_typing=True), 82 | ] 83 | ``` 84 | 85 | #### Custom serialization 86 | 87 | If the default serialization of NumpyDataDict, as outlined in [typing.py](https://github.com/caniko/pydantic-numpy/blob/trunk/pydantic_numpy/helper/typing.py), doesn't meet your requirements, you have the option to define a custom type with its own serializer. This can be achieved using the NpArrayPydanticAnnotation.factory method, which accepts a custom serialization function through its serialize_numpy_array_to_json parameter. This parameter expects a function of the form `Callable[[npt.ArrayLike], Iterable]`, allowing you to tailor the serialization process to your specific needs. 88 | 89 | Example below illustrates definition of 1d-array of `float32` type that serializes to flat Python list (without nested dict as in default `NumpyDataDict` case): 90 | 91 | ```python 92 | def _serialize_numpy_array_to_float_list(array_like: npt.ArrayLike) -> Iterable: 93 | return np.array(array_like).astype(float).tolist() 94 | 95 | 96 | Np1DArrayFp32 = Annotated[ 97 | np.ndarray[tuple[int], np.dtype[np.float32]], 98 | NpArrayPydanticAnnotation.factory( 99 | data_type=np.float32, 100 | dimensions=1, 101 | strict_data_typing=False, 102 | serialize_numpy_array_to_json=_serialize_numpy_array_to_float_list, 103 | ), 104 | ] 105 | ``` 106 | 107 | ### Install 108 | ```shell 109 | pip install pydantic-numpy 110 | ``` 111 | 112 | ### History 113 | The original idea originates from [this discussion](https://gist.github.com/danielhfrank/00e6b8556eed73fb4053450e602d2434), and forked from [cheind's](https://github.com/cheind/pydantic-numpy) repository. 114 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "annotated-types" 5 | version = "0.7.0" 6 | description = "Reusable constraint types to use with typing.Annotated" 7 | optional = false 8 | python-versions = ">=3.8" 9 | groups = ["main"] 10 | files = [ 11 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 12 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 13 | ] 14 | 15 | [[package]] 16 | name = "black" 17 | version = "23.12.1" 18 | description = "The uncompromising code formatter." 19 | optional = false 20 | python-versions = ">=3.8" 21 | groups = ["format"] 22 | files = [ 23 | {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, 24 | {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, 25 | {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, 26 | {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, 27 | {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, 28 | {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, 29 | {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, 30 | {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, 31 | {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, 32 | {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, 33 | {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, 34 | {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, 35 | {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, 36 | {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, 37 | {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, 38 | {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, 39 | {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, 40 | {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, 41 | {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, 42 | {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, 43 | {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, 44 | {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, 45 | ] 46 | 47 | [package.dependencies] 48 | click = ">=8.0.0" 49 | mypy-extensions = ">=0.4.3" 50 | packaging = ">=22.0" 51 | pathspec = ">=0.9.0" 52 | platformdirs = ">=2" 53 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 54 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 55 | 56 | [package.extras] 57 | colorama = ["colorama (>=0.4.3)"] 58 | d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] 59 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 60 | uvloop = ["uvloop (>=0.15.2)"] 61 | 62 | [[package]] 63 | name = "click" 64 | version = "8.1.8" 65 | description = "Composable command line interface toolkit" 66 | optional = false 67 | python-versions = ">=3.7" 68 | groups = ["format"] 69 | files = [ 70 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 71 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 72 | ] 73 | 74 | [package.dependencies] 75 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 76 | 77 | [[package]] 78 | name = "colorama" 79 | version = "0.4.6" 80 | description = "Cross-platform colored terminal text." 81 | optional = false 82 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 83 | groups = ["dev", "format"] 84 | files = [ 85 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 86 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 87 | ] 88 | markers = {dev = "sys_platform == \"win32\"", format = "platform_system == \"Windows\""} 89 | 90 | [[package]] 91 | name = "compress-pickle" 92 | version = "2.1.0" 93 | description = "Standard pickle, wrapped with standard compression libraries" 94 | optional = false 95 | python-versions = ">=3.6" 96 | groups = ["main"] 97 | files = [ 98 | {file = "compress_pickle-2.1.0-py3-none-any.whl", hash = "sha256:598650da4686d9bd97bee185b61e74d7fe1872bb0c23909d5ed2d8793b4a8818"}, 99 | {file = "compress_pickle-2.1.0.tar.gz", hash = "sha256:3e944ce0eeab5b6331324d62351c957d41c9327c8417d439843e88fe69b77991"}, 100 | ] 101 | 102 | [package.dependencies] 103 | lz4 = {version = "*", optional = true, markers = "extra == \"lz4\""} 104 | 105 | [package.extras] 106 | cloudpickle = ["cloudpickle"] 107 | dill = ["dill"] 108 | full = ["cloudpickle", "dill", "lz4"] 109 | lz4 = ["lz4"] 110 | 111 | [[package]] 112 | name = "coverage" 113 | version = "7.6.9" 114 | description = "Code coverage measurement for Python" 115 | optional = false 116 | python-versions = ">=3.9" 117 | groups = ["dev"] 118 | files = [ 119 | {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, 120 | {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, 121 | {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, 122 | {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, 123 | {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, 124 | {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, 125 | {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, 126 | {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, 127 | {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, 128 | {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, 129 | {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, 130 | {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, 131 | {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, 132 | {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, 133 | {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, 134 | {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, 135 | {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, 136 | {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, 137 | {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, 138 | {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, 139 | {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, 140 | {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, 141 | {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, 142 | {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, 143 | {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, 144 | {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, 145 | {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, 146 | {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, 147 | {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, 148 | {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, 149 | {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, 150 | {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, 151 | {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, 152 | {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, 153 | {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, 154 | {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, 155 | {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, 156 | {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, 157 | {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, 158 | {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, 159 | {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, 160 | {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, 161 | {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, 162 | {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, 163 | {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, 164 | {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, 165 | {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, 166 | {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, 167 | {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, 168 | {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, 169 | {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, 170 | {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, 171 | {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, 172 | {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, 173 | {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, 174 | {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, 175 | {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, 176 | {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, 177 | {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, 178 | {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, 179 | {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, 180 | {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, 181 | ] 182 | 183 | [package.extras] 184 | toml = ["tomli ; python_full_version <= \"3.11.0a6\""] 185 | 186 | [[package]] 187 | name = "exceptiongroup" 188 | version = "1.2.2" 189 | description = "Backport of PEP 654 (exception groups)" 190 | optional = false 191 | python-versions = ">=3.7" 192 | groups = ["dev"] 193 | markers = "python_version < \"3.11\"" 194 | files = [ 195 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 196 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 197 | ] 198 | 199 | [package.extras] 200 | test = ["pytest (>=6)"] 201 | 202 | [[package]] 203 | name = "iniconfig" 204 | version = "2.0.0" 205 | description = "brain-dead simple config-ini parsing" 206 | optional = false 207 | python-versions = ">=3.7" 208 | groups = ["dev"] 209 | files = [ 210 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 211 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 212 | ] 213 | 214 | [[package]] 215 | name = "isort" 216 | version = "5.13.2" 217 | description = "A Python utility / library to sort Python imports." 218 | optional = false 219 | python-versions = ">=3.8.0" 220 | groups = ["format"] 221 | files = [ 222 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, 223 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, 224 | ] 225 | 226 | [package.extras] 227 | colors = ["colorama (>=0.4.6)"] 228 | 229 | [[package]] 230 | name = "lz4" 231 | version = "4.3.3" 232 | description = "LZ4 Bindings for Python" 233 | optional = false 234 | python-versions = ">=3.8" 235 | groups = ["main"] 236 | files = [ 237 | {file = "lz4-4.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b891880c187e96339474af2a3b2bfb11a8e4732ff5034be919aa9029484cd201"}, 238 | {file = "lz4-4.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:222a7e35137d7539c9c33bb53fcbb26510c5748779364014235afc62b0ec797f"}, 239 | {file = "lz4-4.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f76176492ff082657ada0d0f10c794b6da5800249ef1692b35cf49b1e93e8ef7"}, 240 | {file = "lz4-4.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1d18718f9d78182c6b60f568c9a9cec8a7204d7cb6fad4e511a2ef279e4cb05"}, 241 | {file = "lz4-4.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cdc60e21ec70266947a48839b437d46025076eb4b12c76bd47f8e5eb8a75dcc"}, 242 | {file = "lz4-4.3.3-cp310-cp310-win32.whl", hash = "sha256:c81703b12475da73a5d66618856d04b1307e43428a7e59d98cfe5a5d608a74c6"}, 243 | {file = "lz4-4.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:43cf03059c0f941b772c8aeb42a0813d68d7081c009542301637e5782f8a33e2"}, 244 | {file = "lz4-4.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30e8c20b8857adef7be045c65f47ab1e2c4fabba86a9fa9a997d7674a31ea6b6"}, 245 | {file = "lz4-4.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7b1839f795315e480fb87d9bc60b186a98e3e5d17203c6e757611ef7dcef61"}, 246 | {file = "lz4-4.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edfd858985c23523f4e5a7526ca6ee65ff930207a7ec8a8f57a01eae506aaee7"}, 247 | {file = "lz4-4.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e9c410b11a31dbdc94c05ac3c480cb4b222460faf9231f12538d0074e56c563"}, 248 | {file = "lz4-4.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2507ee9c99dbddd191c86f0e0c8b724c76d26b0602db9ea23232304382e1f21"}, 249 | {file = "lz4-4.3.3-cp311-cp311-win32.whl", hash = "sha256:f180904f33bdd1e92967923a43c22899e303906d19b2cf8bb547db6653ea6e7d"}, 250 | {file = "lz4-4.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:b14d948e6dce389f9a7afc666d60dd1e35fa2138a8ec5306d30cd2e30d36b40c"}, 251 | {file = "lz4-4.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e36cd7b9d4d920d3bfc2369840da506fa68258f7bb176b8743189793c055e43d"}, 252 | {file = "lz4-4.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31ea4be9d0059c00b2572d700bf2c1bc82f241f2c3282034a759c9a4d6ca4dc2"}, 253 | {file = "lz4-4.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33c9a6fd20767ccaf70649982f8f3eeb0884035c150c0b818ea660152cf3c809"}, 254 | {file = "lz4-4.3.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca8fccc15e3add173da91be8f34121578dc777711ffd98d399be35487c934bf"}, 255 | {file = "lz4-4.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d84b479ddf39fe3ea05387f10b779155fc0990125f4fb35d636114e1c63a2e"}, 256 | {file = "lz4-4.3.3-cp312-cp312-win32.whl", hash = "sha256:337cb94488a1b060ef1685187d6ad4ba8bc61d26d631d7ba909ee984ea736be1"}, 257 | {file = "lz4-4.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:5d35533bf2cee56f38ced91f766cd0038b6abf46f438a80d50c52750088be93f"}, 258 | {file = "lz4-4.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:363ab65bf31338eb364062a15f302fc0fab0a49426051429866d71c793c23394"}, 259 | {file = "lz4-4.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a136e44a16fc98b1abc404fbabf7f1fada2bdab6a7e970974fb81cf55b636d0"}, 260 | {file = "lz4-4.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abc197e4aca8b63f5ae200af03eb95fb4b5055a8f990079b5bdf042f568469dd"}, 261 | {file = "lz4-4.3.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56f4fe9c6327adb97406f27a66420b22ce02d71a5c365c48d6b656b4aaeb7775"}, 262 | {file = "lz4-4.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0e822cd7644995d9ba248cb4b67859701748a93e2ab7fc9bc18c599a52e4604"}, 263 | {file = "lz4-4.3.3-cp38-cp38-win32.whl", hash = "sha256:24b3206de56b7a537eda3a8123c644a2b7bf111f0af53bc14bed90ce5562d1aa"}, 264 | {file = "lz4-4.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:b47839b53956e2737229d70714f1d75f33e8ac26e52c267f0197b3189ca6de24"}, 265 | {file = "lz4-4.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6756212507405f270b66b3ff7f564618de0606395c0fe10a7ae2ffcbbe0b1fba"}, 266 | {file = "lz4-4.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee9ff50557a942d187ec85462bb0960207e7ec5b19b3b48949263993771c6205"}, 267 | {file = "lz4-4.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b901c7784caac9a1ded4555258207d9e9697e746cc8532129f150ffe1f6ba0d"}, 268 | {file = "lz4-4.3.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d9ec061b9eca86e4dcc003d93334b95d53909afd5a32c6e4f222157b50c071"}, 269 | {file = "lz4-4.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4c7bf687303ca47d69f9f0133274958fd672efaa33fb5bcde467862d6c621f0"}, 270 | {file = "lz4-4.3.3-cp39-cp39-win32.whl", hash = "sha256:054b4631a355606e99a42396f5db4d22046a3397ffc3269a348ec41eaebd69d2"}, 271 | {file = "lz4-4.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:eac9af361e0d98335a02ff12fb56caeb7ea1196cf1a49dbf6f17828a131da807"}, 272 | {file = "lz4-4.3.3.tar.gz", hash = "sha256:01fe674ef2889dbb9899d8a67361e0c4a2c833af5aeb37dd505727cf5d2a131e"}, 273 | ] 274 | 275 | [package.extras] 276 | docs = ["sphinx (>=1.6.0)", "sphinx-bootstrap-theme"] 277 | flake8 = ["flake8"] 278 | tests = ["psutil", "pytest (!=3.3.0)", "pytest-cov"] 279 | 280 | [[package]] 281 | name = "mypy" 282 | version = "1.14.0" 283 | description = "Optional static typing for Python" 284 | optional = false 285 | python-versions = ">=3.8" 286 | groups = ["typecheck"] 287 | files = [ 288 | {file = "mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87"}, 289 | {file = "mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179"}, 290 | {file = "mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e"}, 291 | {file = "mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3"}, 292 | {file = "mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44"}, 293 | {file = "mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a"}, 294 | {file = "mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc"}, 295 | {file = "mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015"}, 296 | {file = "mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb"}, 297 | {file = "mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc"}, 298 | {file = "mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd"}, 299 | {file = "mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1"}, 300 | {file = "mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63"}, 301 | {file = "mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d"}, 302 | {file = "mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba"}, 303 | {file = "mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741"}, 304 | {file = "mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7"}, 305 | {file = "mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8"}, 306 | {file = "mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc"}, 307 | {file = "mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f"}, 308 | {file = "mypy-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b16738b1d80ec4334654e89e798eb705ac0c36c8a5c4798496cd3623aa02286"}, 309 | {file = "mypy-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10065fcebb7c66df04b05fc799a854b1ae24d9963c8bb27e9064a9bdb43aa8ad"}, 310 | {file = "mypy-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbb7d683fa6bdecaa106e8368aa973ecc0ddb79a9eaeb4b821591ecd07e9e03c"}, 311 | {file = "mypy-1.14.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3498cb55448dc5533e438cd13d6ddd28654559c8c4d1fd4b5ca57a31b81bac01"}, 312 | {file = "mypy-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:c7b243408ea43755f3a21a0a08e5c5ae30eddb4c58a80f415ca6b118816e60aa"}, 313 | {file = "mypy-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14117b9da3305b39860d0aa34b8f1ff74d209a368829a584eb77524389a9c13e"}, 314 | {file = "mypy-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af98c5a958f9c37404bd4eef2f920b94874507e146ed6ee559f185b8809c44cc"}, 315 | {file = "mypy-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b343a1d3989547024377c2ba0dca9c74a2428ad6ed24283c213af8dbb0710b"}, 316 | {file = "mypy-1.14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cdb5563c1726c85fb201be383168f8c866032db95e1095600806625b3a648cb7"}, 317 | {file = "mypy-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:74e925649c1ee0a79aa7448baf2668d81cc287dc5782cff6a04ee93f40fb8d3f"}, 318 | {file = "mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab"}, 319 | {file = "mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6"}, 320 | ] 321 | 322 | [package.dependencies] 323 | mypy_extensions = ">=1.0.0" 324 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 325 | typing_extensions = ">=4.6.0" 326 | 327 | [package.extras] 328 | dmypy = ["psutil (>=4.0)"] 329 | faster-cache = ["orjson"] 330 | install-types = ["pip"] 331 | mypyc = ["setuptools (>=50)"] 332 | reports = ["lxml"] 333 | 334 | [[package]] 335 | name = "mypy-extensions" 336 | version = "1.0.0" 337 | description = "Type system extensions for programs checked with the mypy type checker." 338 | optional = false 339 | python-versions = ">=3.5" 340 | groups = ["format", "typecheck"] 341 | files = [ 342 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 343 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 344 | ] 345 | 346 | [[package]] 347 | name = "nodeenv" 348 | version = "1.9.1" 349 | description = "Node.js virtual environment builder" 350 | optional = false 351 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 352 | groups = ["typecheck"] 353 | files = [ 354 | {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, 355 | {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, 356 | ] 357 | 358 | [[package]] 359 | name = "numpy" 360 | version = "2.2.1" 361 | description = "Fundamental package for array computing in Python" 362 | optional = false 363 | python-versions = ">=3.10" 364 | groups = ["main"] 365 | files = [ 366 | {file = "numpy-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440"}, 367 | {file = "numpy-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab"}, 368 | {file = "numpy-2.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:61048b4a49b1c93fe13426e04e04fdf5a03f456616f6e98c7576144677598675"}, 369 | {file = "numpy-2.2.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7671dc19c7019103ca44e8d94917eba8534c76133523ca8406822efdd19c9308"}, 370 | {file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4250888bcb96617e00bfa28ac24850a83c9f3a16db471eca2ee1f1714df0f957"}, 371 | {file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7746f235c47abc72b102d3bce9977714c2444bdfaea7888d241b4c4bb6a78bf"}, 372 | {file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:059e6a747ae84fce488c3ee397cee7e5f905fd1bda5fb18c66bc41807ff119b2"}, 373 | {file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f62aa6ee4eb43b024b0e5a01cf65a0bb078ef8c395e8713c6e8a12a697144528"}, 374 | {file = "numpy-2.2.1-cp310-cp310-win32.whl", hash = "sha256:48fd472630715e1c1c89bf1feab55c29098cb403cc184b4859f9c86d4fcb6a95"}, 375 | {file = "numpy-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:b541032178a718c165a49638d28272b771053f628382d5e9d1c93df23ff58dbf"}, 376 | {file = "numpy-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484"}, 377 | {file = "numpy-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7"}, 378 | {file = "numpy-2.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb"}, 379 | {file = "numpy-2.2.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5"}, 380 | {file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73"}, 381 | {file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591"}, 382 | {file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8"}, 383 | {file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0"}, 384 | {file = "numpy-2.2.1-cp311-cp311-win32.whl", hash = "sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd"}, 385 | {file = "numpy-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16"}, 386 | {file = "numpy-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab"}, 387 | {file = "numpy-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa"}, 388 | {file = "numpy-2.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315"}, 389 | {file = "numpy-2.2.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355"}, 390 | {file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7"}, 391 | {file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d"}, 392 | {file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51"}, 393 | {file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046"}, 394 | {file = "numpy-2.2.1-cp312-cp312-win32.whl", hash = "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2"}, 395 | {file = "numpy-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8"}, 396 | {file = "numpy-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780"}, 397 | {file = "numpy-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821"}, 398 | {file = "numpy-2.2.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e"}, 399 | {file = "numpy-2.2.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348"}, 400 | {file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59"}, 401 | {file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af"}, 402 | {file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51"}, 403 | {file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716"}, 404 | {file = "numpy-2.2.1-cp313-cp313-win32.whl", hash = "sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e"}, 405 | {file = "numpy-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60"}, 406 | {file = "numpy-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e"}, 407 | {file = "numpy-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712"}, 408 | {file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008"}, 409 | {file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84"}, 410 | {file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631"}, 411 | {file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d"}, 412 | {file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5"}, 413 | {file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71"}, 414 | {file = "numpy-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2"}, 415 | {file = "numpy-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268"}, 416 | {file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ba9cc93a91d86365a5d270dee221fdc04fb68d7478e6bf6af650de78a8339e3"}, 417 | {file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3d03883435a19794e41f147612a77a8f56d4e52822337844fff3d4040a142964"}, 418 | {file = "numpy-2.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4511d9e6071452b944207c8ce46ad2f897307910b402ea5fa975da32e0102800"}, 419 | {file = "numpy-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e"}, 420 | {file = "numpy-2.2.1.tar.gz", hash = "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918"}, 421 | ] 422 | 423 | [[package]] 424 | name = "orjson" 425 | version = "3.10.12" 426 | description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" 427 | optional = false 428 | python-versions = ">=3.8" 429 | groups = ["dev"] 430 | files = [ 431 | {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, 432 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, 433 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, 434 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, 435 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, 436 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, 437 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, 438 | {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, 439 | {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, 440 | {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, 441 | {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, 442 | {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, 443 | {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, 444 | {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, 445 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, 446 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, 447 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, 448 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, 449 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, 450 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, 451 | {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, 452 | {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, 453 | {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, 454 | {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, 455 | {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, 456 | {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, 457 | {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, 458 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, 459 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, 460 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, 461 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, 462 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, 463 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, 464 | {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, 465 | {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, 466 | {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, 467 | {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, 468 | {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, 469 | {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, 470 | {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, 471 | {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, 472 | {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, 473 | {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, 474 | {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, 475 | {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, 476 | {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, 477 | {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, 478 | {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, 479 | {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, 480 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, 481 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, 482 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, 483 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, 484 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, 485 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, 486 | {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, 487 | {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, 488 | {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, 489 | {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, 490 | {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, 491 | {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, 492 | {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, 493 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, 494 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, 495 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, 496 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, 497 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, 498 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, 499 | {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, 500 | {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, 501 | {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, 502 | {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, 503 | {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, 504 | {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, 505 | {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, 506 | ] 507 | 508 | [[package]] 509 | name = "packaging" 510 | version = "24.2" 511 | description = "Core utilities for Python packages" 512 | optional = false 513 | python-versions = ">=3.8" 514 | groups = ["dev", "format"] 515 | files = [ 516 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 517 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 518 | ] 519 | 520 | [[package]] 521 | name = "parameterized" 522 | version = "0.9.0" 523 | description = "Parameterized testing with any Python test framework" 524 | optional = false 525 | python-versions = ">=3.7" 526 | groups = ["dev"] 527 | files = [ 528 | {file = "parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b"}, 529 | {file = "parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1"}, 530 | ] 531 | 532 | [package.extras] 533 | dev = ["jinja2"] 534 | 535 | [[package]] 536 | name = "pathspec" 537 | version = "0.12.1" 538 | description = "Utility library for gitignore style pattern matching of file paths." 539 | optional = false 540 | python-versions = ">=3.8" 541 | groups = ["format"] 542 | files = [ 543 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 544 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 545 | ] 546 | 547 | [[package]] 548 | name = "platformdirs" 549 | version = "4.3.6" 550 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 551 | optional = false 552 | python-versions = ">=3.8" 553 | groups = ["format"] 554 | files = [ 555 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 556 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 557 | ] 558 | 559 | [package.extras] 560 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 561 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 562 | type = ["mypy (>=1.11.2)"] 563 | 564 | [[package]] 565 | name = "pluggy" 566 | version = "1.5.0" 567 | description = "plugin and hook calling mechanisms for python" 568 | optional = false 569 | python-versions = ">=3.8" 570 | groups = ["dev"] 571 | files = [ 572 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 573 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 574 | ] 575 | 576 | [package.extras] 577 | dev = ["pre-commit", "tox"] 578 | testing = ["pytest", "pytest-benchmark"] 579 | 580 | [[package]] 581 | name = "pydantic" 582 | version = "2.10.4" 583 | description = "Data validation using Python type hints" 584 | optional = false 585 | python-versions = ">=3.8" 586 | groups = ["main"] 587 | files = [ 588 | {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, 589 | {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, 590 | ] 591 | 592 | [package.dependencies] 593 | annotated-types = ">=0.6.0" 594 | pydantic-core = "2.27.2" 595 | typing-extensions = ">=4.12.2" 596 | 597 | [package.extras] 598 | email = ["email-validator (>=2.0.0)"] 599 | timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] 600 | 601 | [[package]] 602 | name = "pydantic-core" 603 | version = "2.27.2" 604 | description = "Core functionality for Pydantic validation and serialization" 605 | optional = false 606 | python-versions = ">=3.8" 607 | groups = ["main"] 608 | files = [ 609 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, 610 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, 611 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, 612 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, 613 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, 614 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, 615 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, 616 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, 617 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, 618 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, 619 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, 620 | {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, 621 | {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, 622 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, 623 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, 624 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, 625 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, 626 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, 627 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, 628 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, 629 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, 630 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, 631 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, 632 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, 633 | {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, 634 | {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, 635 | {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, 636 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, 637 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, 638 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, 639 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, 640 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, 641 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, 642 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, 643 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, 644 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, 645 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, 646 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, 647 | {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, 648 | {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, 649 | {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, 650 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, 651 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, 652 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, 653 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, 654 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, 655 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, 656 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, 657 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, 658 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, 659 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, 660 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, 661 | {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, 662 | {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, 663 | {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, 664 | {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, 665 | {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, 666 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, 667 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, 668 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, 669 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, 670 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, 671 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, 672 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, 673 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, 674 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, 675 | {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, 676 | {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, 677 | {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, 678 | {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, 679 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, 680 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, 681 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, 682 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, 683 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, 684 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, 685 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, 686 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, 687 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, 688 | {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, 689 | {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, 690 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, 691 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, 692 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, 693 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, 694 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, 695 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, 696 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, 697 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, 698 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, 699 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, 700 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, 701 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, 702 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, 703 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, 704 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, 705 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, 706 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, 707 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, 708 | {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, 709 | ] 710 | 711 | [package.dependencies] 712 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 713 | 714 | [[package]] 715 | name = "pyright" 716 | version = "1.1.391" 717 | description = "Command line wrapper for pyright" 718 | optional = false 719 | python-versions = ">=3.7" 720 | groups = ["typecheck"] 721 | files = [ 722 | {file = "pyright-1.1.391-py3-none-any.whl", hash = "sha256:54fa186f8b3e8a55a44ebfa842636635688670c6896dcf6cf4a7fc75062f4d15"}, 723 | {file = "pyright-1.1.391.tar.gz", hash = "sha256:66b2d42cdf5c3cbab05f2f4b76e8bec8aa78e679bfa0b6ad7b923d9e027cadb2"}, 724 | ] 725 | 726 | [package.dependencies] 727 | nodeenv = ">=1.6.0" 728 | typing-extensions = ">=4.1" 729 | 730 | [package.extras] 731 | all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"] 732 | dev = ["twine (>=3.4.1)"] 733 | nodejs = ["nodejs-wheel-binaries"] 734 | 735 | [[package]] 736 | name = "pytest" 737 | version = "7.4.4" 738 | description = "pytest: simple powerful testing with Python" 739 | optional = false 740 | python-versions = ">=3.7" 741 | groups = ["dev"] 742 | files = [ 743 | {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, 744 | {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, 745 | ] 746 | 747 | [package.dependencies] 748 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 749 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 750 | iniconfig = "*" 751 | packaging = "*" 752 | pluggy = ">=0.12,<2.0" 753 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 754 | 755 | [package.extras] 756 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 757 | 758 | [[package]] 759 | name = "ruamel-yaml" 760 | version = "0.18.6" 761 | description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" 762 | optional = false 763 | python-versions = ">=3.7" 764 | groups = ["main"] 765 | files = [ 766 | {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, 767 | {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, 768 | ] 769 | 770 | [package.dependencies] 771 | "ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} 772 | 773 | [package.extras] 774 | docs = ["mercurial (>5.7)", "ryd"] 775 | jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] 776 | 777 | [[package]] 778 | name = "ruamel-yaml-clib" 779 | version = "0.2.12" 780 | description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" 781 | optional = false 782 | python-versions = ">=3.9" 783 | groups = ["main"] 784 | markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" 785 | files = [ 786 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, 787 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, 788 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, 789 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, 790 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, 791 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, 792 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, 793 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, 794 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, 795 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, 796 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, 797 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, 798 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, 799 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, 800 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, 801 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, 802 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, 803 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, 804 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, 805 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, 806 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, 807 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, 808 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, 809 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, 810 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, 811 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, 812 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, 813 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, 814 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, 815 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, 816 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, 817 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, 818 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, 819 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, 820 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, 821 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, 822 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, 823 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, 824 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, 825 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, 826 | {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, 827 | ] 828 | 829 | [[package]] 830 | name = "ruff" 831 | version = "0.8.4" 832 | description = "An extremely fast Python linter and code formatter, written in Rust." 833 | optional = false 834 | python-versions = ">=3.7" 835 | groups = ["format"] 836 | files = [ 837 | {file = "ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60"}, 838 | {file = "ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac"}, 839 | {file = "ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296"}, 840 | {file = "ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643"}, 841 | {file = "ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e"}, 842 | {file = "ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3"}, 843 | {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f"}, 844 | {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604"}, 845 | {file = "ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf"}, 846 | {file = "ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720"}, 847 | {file = "ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae"}, 848 | {file = "ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7"}, 849 | {file = "ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111"}, 850 | {file = "ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8"}, 851 | {file = "ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835"}, 852 | {file = "ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d"}, 853 | {file = "ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08"}, 854 | {file = "ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8"}, 855 | ] 856 | 857 | [[package]] 858 | name = "semver" 859 | version = "3.0.2" 860 | description = "Python helper for Semantic Versioning (https://semver.org)" 861 | optional = false 862 | python-versions = ">=3.7" 863 | groups = ["main"] 864 | files = [ 865 | {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, 866 | {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, 867 | ] 868 | 869 | [[package]] 870 | name = "tomli" 871 | version = "2.2.1" 872 | description = "A lil' TOML parser" 873 | optional = false 874 | python-versions = ">=3.8" 875 | groups = ["dev", "format", "typecheck"] 876 | markers = "python_version < \"3.11\"" 877 | files = [ 878 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 879 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 880 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 881 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 882 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 883 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 884 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 885 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 886 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 887 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 888 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 889 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 890 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 891 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 892 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 893 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 894 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 895 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 896 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 897 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 898 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 899 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 900 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 901 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 902 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 903 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 904 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 905 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 906 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 907 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 908 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 909 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 910 | ] 911 | 912 | [[package]] 913 | name = "typing-extensions" 914 | version = "4.12.2" 915 | description = "Backported and Experimental Type Hints for Python 3.8+" 916 | optional = false 917 | python-versions = ">=3.8" 918 | groups = ["main", "format", "typecheck"] 919 | files = [ 920 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 921 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 922 | ] 923 | markers = {format = "python_version < \"3.11\""} 924 | 925 | [metadata] 926 | lock-version = "2.1" 927 | python-versions = ">=3.10,<3.14" 928 | content-hash = "1d9c49d01d3718031018269d63c26ff2034905573e363c24a135bacb06b5f390" 929 | -------------------------------------------------------------------------------- /pydantic_numpy/__init__.py: -------------------------------------------------------------------------------- 1 | from pydantic_numpy.helper.annotation import np_array_pydantic_annotated_typing 2 | 3 | __all__ = ["np_array_pydantic_annotated_typing", "model", "typing"] 4 | -------------------------------------------------------------------------------- /pydantic_numpy/helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caniko/pydantic-numpy/d0c7f4c71f330c58348d4799689283245d9c8f2e/pydantic_numpy/helper/__init__.py -------------------------------------------------------------------------------- /pydantic_numpy/helper/annotation.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Sequence 2 | from pathlib import Path 3 | from typing import Any, Callable, ClassVar, Iterable, Optional, Union, cast 4 | 5 | import numpy as np 6 | import numpy.typing as npt 7 | from pydantic import FilePath, GetJsonSchemaHandler, PositiveInt, validate_call 8 | from pydantic.json_schema import JsonSchemaValue 9 | from pydantic_core import core_schema 10 | from typing_extensions import Annotated, Final 11 | 12 | from pydantic_numpy.helper.typing import NumpyArrayTypeData, SupportedDTypes 13 | from pydantic_numpy.helper.validation import ( 14 | create_array_validator, 15 | validate_multi_array_numpy_file, 16 | validate_numpy_array_file, 17 | ) 18 | from pydantic_numpy.model import MultiArrayNumpyFile 19 | 20 | 21 | def pd_np_native_numpy_array_to_data_dict_serializer(array_like: npt.ArrayLike) -> NumpyArrayTypeData: 22 | """ 23 | Serialize a NumPy array into a data dictionary format suitable for frontend display or processing. 24 | 25 | This function converts a given NumPy array into a dictionary format, which includes the data type 26 | and the data itself. If the array contains datetime or timedelta objects, it converts them into integer 27 | representations. Otherwise, the array is converted to a floating-point representation. This is particularly 28 | useful for preparing NumPy array data for JSON serialization or similar use cases where NumPy's native 29 | data types are not directly compatible. 30 | 31 | Note 32 | ---- 33 | This function is intended for internal use within a package for handling specific serialization needs 34 | of NumPy arrays for frontend applications or similar use cases. It should not be used as a general-purpose 35 | serialization tool. 36 | 37 | Parameters 38 | ---------- 39 | array_like: np.ndarray 40 | The NumPy array to be serialized. This can be a standard numerical array or an array 41 | of datetime/timedelta objects. 42 | 43 | Returns 44 | ------- 45 | NumpyArrayTypeData 46 | A dictionary with two keys: 'data_type', a string representing the data type of the array, 47 | and 'data', a list of values converted from the array. The conversion is to integer if the 48 | original data type is datetime or timedelta, and to float for other data types. 49 | 50 | Example 51 | ------- 52 | >>> my_array = np.array([1, 2, 3]) 53 | >>> pd_np_native_numpy_array_to_data_dict_serializer(my_array) 54 | {'data_type': 'int64', 'data': [1.0, 2.0, 3.0]} 55 | """ 56 | array = np.array(array_like) 57 | 58 | data = array.astype( 59 | int if issubclass(array.dtype.type, np.timedelta64) or issubclass(array.dtype.type, np.datetime64) else float 60 | ).tolist() 61 | cast_data = cast(list, data) 62 | 63 | return NumpyArrayTypeData(data_type=str(array.dtype), data=cast_data) 64 | 65 | 66 | def pd_np_native_numpy_array_json_schema_from_type_data( 67 | _field_core_schema: core_schema.CoreSchema, 68 | _handler: GetJsonSchemaHandler, 69 | dimensions: Optional[PositiveInt] = None, 70 | data_type: Optional[SupportedDTypes] = None, 71 | ) -> JsonSchemaValue: 72 | """ 73 | Generates a JSON schema for a NumPy array field within a Pydantic model. 74 | 75 | This function constructs a JSON schema definition compatible with Pydantic models 76 | that are intended to validate NumPy array inputs. It supports specifying the data type 77 | and dimensions of the NumPy array, which are used to construct a schema that ensures 78 | input data matches the expected structure and type. 79 | 80 | Parameters 81 | ---------- 82 | _field_core_schema : core_schema.CoreSchema 83 | The core schema component of the Pydantic model, used for building basic schema structures. 84 | _handler : GetJsonSchemaHandler 85 | A handler function or object responsible for converting Python types to JSON schema components. 86 | dimensions : Optional[PositiveInt], optional 87 | The dimensions (shape) of the NumPy array. If specified, the schema will enforce that the 88 | input array matches this dimensionality. If `None`, no dimensionality constraint is applied, 89 | by default None. 90 | data_type : Optional[SupportedDTypes], optional 91 | The expected data type of the NumPy array elements. If specified, the schema will enforce 92 | that the input array's data type is compatible with this. If `None`, any data type is allowed, 93 | by default None. 94 | 95 | Returns 96 | ------- 97 | JsonSchemaValue 98 | A dictionary representing the JSON schema for a NumPy array field within a Pydantic model. 99 | This schema includes details about the expected array dimensions and data type. 100 | """ 101 | array_shape = _dimensions_to_shape_type[dimensions] if dimensions else "Any" 102 | 103 | if data_type and _data_type_resolver(data_type): 104 | array_data_type = data_type.__name__ 105 | item_schema = core_schema.list_schema( 106 | items_schema=core_schema.any_schema( 107 | metadata=dict(typing=f"Must be compatible with numpy.dtype: {array_data_type}") 108 | ) 109 | ) 110 | else: 111 | array_data_type = "Any" 112 | item_schema = core_schema.list_schema(items_schema=core_schema.any_schema()) 113 | 114 | if dimensions: 115 | data_schema = core_schema.list_schema(items_schema=item_schema, min_length=dimensions, max_length=dimensions) 116 | else: 117 | data_schema = item_schema 118 | 119 | return dict( 120 | title="Numpy Array", 121 | type=f"np.ndarray[{array_shape}, np.dtype[{array_data_type}]]", 122 | required=["data_type", "data"], 123 | properties=dict( 124 | data_type={"title": "dtype", "default": array_data_type, "type": "string"}, 125 | data=data_schema, 126 | ), 127 | ) 128 | 129 | 130 | class NpArrayPydanticAnnotation: 131 | dimensions: ClassVar[Optional[PositiveInt]] 132 | data_type: ClassVar[SupportedDTypes] 133 | 134 | strict_data_typing: ClassVar[bool] 135 | 136 | serialize_numpy_array_to_json: ClassVar[Callable[[npt.ArrayLike], Iterable]] 137 | json_schema_from_type_data: ClassVar[ 138 | Callable[ 139 | [core_schema.CoreSchema, GetJsonSchemaHandler, Optional[PositiveInt], Optional[SupportedDTypes]], 140 | JsonSchemaValue, 141 | ] 142 | ] 143 | 144 | @classmethod 145 | def factory( 146 | cls, 147 | *, 148 | data_type: Optional[SupportedDTypes] = None, 149 | dimensions: Optional[PositiveInt] = None, 150 | strict_data_typing: bool = False, 151 | serialize_numpy_array_to_json: Callable[ 152 | [npt.ArrayLike], Iterable 153 | ] = pd_np_native_numpy_array_to_data_dict_serializer, 154 | json_schema_from_type_data: Callable[ 155 | [core_schema.CoreSchema, GetJsonSchemaHandler, Optional[PositiveInt], Optional[SupportedDTypes]], 156 | JsonSchemaValue, 157 | ] = pd_np_native_numpy_array_json_schema_from_type_data, 158 | ) -> type: 159 | """ 160 | Create an instance NpArrayPydanticAnnotation that is configured for a specific dimension and dtype. 161 | 162 | The signature of the function is data_type, dimension and not dimension, data_type to reduce amount of 163 | code for all the types. 164 | 165 | Parameters 166 | ---------- 167 | data_type: SupportedDTypes 168 | dimensions: Optional[PositiveInt] 169 | If defined, the number of dimensions determine the depth of the numpy array. Defaults to None, 170 | e.g. any number of dimensions 171 | strict_data_typing: bool 172 | If True, the dtype of the numpy array must be identical to the data_type. No conversion attempts. 173 | serialize_numpy_array_to_json: Callable[[npt.ArrayLike], Iterable] 174 | Json serialization function to use. Defaults to NumpyArrayTypeData serializer. 175 | json_schema_from_type_data: Callable 176 | Json schema generation function to use. Defaults to NumpyArrayTypeData schema generator. 177 | 178 | Returns 179 | ------- 180 | NpArrayPydanticAnnotation 181 | """ 182 | if strict_data_typing and not data_type: 183 | msg = "Strict data typing requires data_type (SupportedDTypes) definition" 184 | raise ValueError(msg) 185 | 186 | return type( 187 | ( 188 | f"Np{'Strict' if strict_data_typing else ''}{dimensions or 'N'}DArray" 189 | f"{data_type.__name__.capitalize() if data_type else ''}PydanticAnnotation" 190 | ), 191 | (cls,), 192 | { 193 | "dimensions": dimensions, 194 | "data_type": data_type, 195 | "strict_data_typing": strict_data_typing, 196 | "serialize_numpy_array_to_json": serialize_numpy_array_to_json, 197 | "json_schema_from_type_data": json_schema_from_type_data, 198 | }, 199 | ) 200 | 201 | @classmethod 202 | def __get_pydantic_core_schema__( 203 | cls, 204 | _source_type: Any, 205 | _handler: Callable[[Any], core_schema.CoreSchema], 206 | ) -> core_schema.CoreSchema: 207 | np_array_validator = create_array_validator(cls.dimensions, cls.data_type, cls.strict_data_typing) 208 | np_array_schema = core_schema.no_info_plain_validator_function(np_array_validator) 209 | 210 | return core_schema.json_or_python_schema( 211 | python_schema=core_schema.chain_schema([_common_numpy_array_validator, np_array_schema]), 212 | json_schema=np_array_schema, 213 | serialization=core_schema.plain_serializer_function_ser_schema( 214 | cls.serialize_numpy_array_to_json, 215 | is_field_serializer=False, 216 | when_used="json-unless-none", 217 | ), 218 | ) 219 | 220 | @classmethod 221 | def __get_pydantic_json_schema__( 222 | cls, field_core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler 223 | ) -> JsonSchemaValue: 224 | return cls.json_schema_from_type_data(field_core_schema, handler, cls.dimensions, cls.data_type) 225 | 226 | 227 | def np_array_pydantic_annotated_typing( 228 | data_type: Optional[SupportedDTypes] = None, 229 | dimensions: Optional[int] = None, 230 | strict_data_typing: bool = False, 231 | serialize_numpy_array_to_json: Callable[ 232 | [npt.ArrayLike], Iterable 233 | ] = pd_np_native_numpy_array_to_data_dict_serializer, 234 | ): 235 | """ 236 | Generates typing and pydantic annotation of a np.ndarray parametrized with given constraints 237 | 238 | Parameters 239 | ---------- 240 | data_type: SupportedDTypes 241 | dimensions: Optional[int] 242 | Number of dimensions determine the depth of the numpy array. 243 | strict_data_typing: bool 244 | If True, the dtype of the numpy array must be identical to the data_type. No conversion attempts. 245 | serialize_numpy_array_to_json: Callable[[npt.ArrayLike], Iterable] 246 | Json serialization function to use. Defaults to NumpyArrayTypeData serializer. 247 | 248 | Returns 249 | ------- 250 | type-hint for np.ndarray with Pydantic support 251 | 252 | Note 253 | ---- 254 | The function generates the type hints dynamically, and will not work with static type checkers such as mypy 255 | or pyright. For that you need to create your types manually. 256 | """ 257 | return Annotated[ 258 | Union[ 259 | FilePath, 260 | MultiArrayNumpyFile, 261 | np.ndarray[ # type: ignore[misc] 262 | _dimensions_to_shape_type[dimensions] # pyright: ignore[reportGeneralTypeIssues] 263 | if dimensions 264 | else Any, 265 | np.dtype[data_type] if _data_type_resolver(data_type) else data_type, # type: ignore[valid-type] 266 | ], 267 | ], 268 | NpArrayPydanticAnnotation.factory( 269 | data_type=data_type, 270 | dimensions=dimensions, 271 | strict_data_typing=strict_data_typing, 272 | serialize_numpy_array_to_json=serialize_numpy_array_to_json, 273 | ), 274 | ] 275 | 276 | 277 | def _data_type_resolver(data_type: Optional[SupportedDTypes]) -> bool: 278 | return data_type is not None and issubclass(data_type, np.generic) 279 | 280 | 281 | @validate_call 282 | def _deserialize_numpy_array_from_data_dict(data_dict: NumpyArrayTypeData) -> np.ndarray: 283 | return np.array(data_dict["data"]).astype(data_dict["data_type"]) 284 | 285 | 286 | # IN_THE_FUTURE: Only works with 3.11 and above 287 | # @validate_call 288 | # def _dimension_type_from_depth(depth: PositiveInt) -> type[tuple[int, ...]]: 289 | # return tuple[*[int] * depth] # type: ignore 290 | 291 | 292 | _dimensions_to_shape_type: Final[dict[PositiveInt, type[tuple[int, ...]]]] = { 293 | 1: tuple[int], # type: ignore[dict-item] 294 | 2: tuple[int, int], # type: ignore[dict-item] 295 | 3: tuple[int, int, int], # type: ignore[dict-item] 296 | 4: tuple[int, int, int, int], # type: ignore[dict-item] 297 | 5: tuple[int, int, int, int, int], # type: ignore[dict-item] 298 | 6: tuple[int, int, int, int, int, int], # type: ignore[dict-item] 299 | 7: tuple[int, int, int, int, int, int, int], # type: ignore[dict-item] 300 | } 301 | 302 | 303 | _common_numpy_array_validator = core_schema.union_schema( 304 | [ 305 | core_schema.chain_schema( 306 | [ 307 | core_schema.is_instance_schema(Path), 308 | core_schema.no_info_plain_validator_function(validate_numpy_array_file), 309 | ] 310 | ), 311 | core_schema.chain_schema( 312 | [ 313 | core_schema.is_instance_schema(MultiArrayNumpyFile), 314 | core_schema.no_info_plain_validator_function(validate_multi_array_numpy_file), 315 | ] 316 | ), 317 | core_schema.is_instance_schema(np.ndarray), 318 | core_schema.chain_schema( 319 | [ 320 | core_schema.is_instance_schema(Sequence), 321 | core_schema.no_info_plain_validator_function(lambda v: np.asarray(v)), 322 | ] 323 | ), 324 | core_schema.chain_schema( 325 | [ 326 | core_schema.is_instance_schema(dict), 327 | core_schema.no_info_plain_validator_function(_deserialize_numpy_array_from_data_dict), 328 | ] 329 | ), 330 | ] 331 | ) 332 | -------------------------------------------------------------------------------- /pydantic_numpy/helper/typing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing_extensions import TypedDict 3 | 4 | SupportedDTypes = type[np.generic] 5 | 6 | 7 | class NumpyArrayTypeData(TypedDict): 8 | data_type: str 9 | data: list 10 | -------------------------------------------------------------------------------- /pydantic_numpy/helper/validation.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, Union 2 | 3 | import numpy as np 4 | import numpy.typing as npt 5 | from numpy import floating, integer 6 | from numpy.lib.npyio import NpzFile 7 | from pydantic import FilePath 8 | 9 | from pydantic_numpy.helper.typing import NumpyArrayTypeData, SupportedDTypes 10 | from pydantic_numpy.model import MultiArrayNumpyFile 11 | 12 | 13 | class PydanticNumpyMultiArrayNumpyFileOnFilePath(Exception): 14 | pass 15 | 16 | 17 | def create_array_validator( 18 | dimensions: Optional[int], target_data_type: SupportedDTypes, strict_data_typing: bool 19 | ) -> Callable[[npt.NDArray], npt.NDArray]: 20 | """ 21 | Creates a validator that ensures the numpy array has the defined dimensions and dtype (data_type). 22 | 23 | Parameters 24 | ---------- 25 | dimensions: int | None 26 | Default to None; if set to an integer, enforce the dimension of the numpy array to that integer 27 | target_data_type: DTypeLike 28 | The data type the array must have after validation, arrays with different data types will be converted 29 | during validation. Float to integer is rounded (np.round) followed by an astype with target data type. 30 | strict_data_typing: bool 31 | Default False; if True, the incoming array must its dtype match the target_data_type. Strict mode. 32 | 33 | Returns 34 | ------- 35 | Callable[[npt.NDArray], npt.NDArray] 36 | Validator for numpy array 37 | """ 38 | 39 | def array_validator(array_data: Union[npt.NDArray, NumpyArrayTypeData]) -> npt.NDArray: 40 | array: npt.NDArray 41 | if isinstance(array_data, dict): 42 | array = np.array(array_data["data"], dtype=array_data["data_type"]) 43 | else: 44 | array = array_data 45 | 46 | if dimensions and (array_dimensions := len(array.shape)) != dimensions: 47 | msg = f"Array {array_dimensions}-dimensional; the target dimensions is {dimensions}" 48 | raise ValueError(msg) 49 | 50 | if target_data_type and array.dtype.type != target_data_type: 51 | if strict_data_typing: 52 | msg = f"The data_type {array.dtype.type} does not coincide with type hint; {target_data_type}" 53 | raise ValueError(msg) 54 | 55 | if issubclass(target_data_type, integer) and issubclass(array.dtype.type, floating): 56 | array = np.round(array).astype(target_data_type, copy=False) 57 | else: 58 | array = array.astype(target_data_type, copy=True) 59 | 60 | return array 61 | 62 | return array_validator 63 | 64 | 65 | def validate_numpy_array_file(v: FilePath) -> npt.NDArray: 66 | """ 67 | Validate file path to numpy file by loading and return the respective numpy array 68 | 69 | Parameters 70 | ---------- 71 | v: FilePath 72 | Path to the numpy file 73 | 74 | Returns 75 | ------- 76 | NDArray 77 | """ 78 | result = np.load(v) 79 | 80 | if isinstance(result, NpzFile): 81 | files = result.files 82 | if len(files) > 1: 83 | msg = ( 84 | f"The provided file path is a multi array NpzFile, which is not supported; " 85 | f"convert to single array NpzFiles.\n" 86 | f"Path to multi array file: {result}\n" 87 | f"Array keys: {', '.join(result.files)}\n" 88 | f"Use pydantic_numpy.{MultiArrayNumpyFile.__name__} instead of a PathLike alone" 89 | ) 90 | raise PydanticNumpyMultiArrayNumpyFileOnFilePath(msg) 91 | result = result[files[0]] 92 | 93 | return result 94 | 95 | 96 | def validate_multi_array_numpy_file(v: MultiArrayNumpyFile) -> npt.NDArray: 97 | """ 98 | Validation function for loading numpy array from a name mapping numpy file 99 | 100 | Parameters 101 | ---------- 102 | v: MultiArrayNumpyFile 103 | MultiArrayNumpyFile to load 104 | 105 | Returns 106 | ------- 107 | NDArray from MultiArrayNumpyFile 108 | """ 109 | return v.load() 110 | -------------------------------------------------------------------------------- /pydantic_numpy/model.py: -------------------------------------------------------------------------------- 1 | import pickle as pickle_pkg 2 | from dataclasses import dataclass 3 | from functools import lru_cache 4 | from pathlib import Path 5 | from typing import Any, Callable, ClassVar, Iterable, Optional 6 | 7 | import compress_pickle 8 | import numpy as np 9 | import numpy.typing as npt 10 | from pydantic import BaseModel, DirectoryPath, FilePath, validate_call 11 | from ruamel.yaml import YAML 12 | 13 | from pydantic_numpy.util import np_general_all_close 14 | 15 | yaml = YAML() 16 | 17 | 18 | @dataclass(frozen=True) 19 | class MultiArrayNumpyFile: 20 | path: FilePath 21 | key: str 22 | cached_load: bool = False 23 | 24 | def load(self) -> npt.NDArray: 25 | """ 26 | Load the NDArray stored in the given path within the given key 27 | 28 | Returns 29 | ------- 30 | NDArray 31 | """ 32 | loaded = _cached_np_array_load(self.path) if self.cached_load else np.load(self.path) 33 | try: 34 | return loaded[self.key] 35 | except IndexError: 36 | msg = f"The given path points to an uncompressed numpy file, which only has one array in it: {self.path}" 37 | raise AttributeError(msg) 38 | 39 | 40 | class NumpyModel(BaseModel): 41 | _dump_compression: ClassVar[str] = "lz4" 42 | _dump_numpy_savez_file_name: ClassVar[str] = "arrays.npz" 43 | _dump_non_array_file_stem: ClassVar[str] = "object_info" 44 | 45 | _directory_suffix: ClassVar[str] = ".pdnp" 46 | 47 | def __eq__(self, other: Any) -> bool: 48 | if not isinstance(other, BaseModel): 49 | return NotImplemented # delegate to the other item in the comparison 50 | 51 | self_type = self.__pydantic_generic_metadata__["origin"] or self.__class__ 52 | other_type = other.__pydantic_generic_metadata__["origin"] or other.__class__ 53 | 54 | if not ( 55 | self_type == other_type 56 | and getattr(self, "__pydantic_private__", None) == getattr(other, "__pydantic_private__", None) 57 | and self.__pydantic_extra__ == other.__pydantic_extra__ 58 | ): 59 | return False 60 | 61 | if isinstance(other, NumpyModel): 62 | self_ndarray_field_to_array, self_other_field_to_value = self.nm_dump_numpy_split_dict() 63 | other_ndarray_field_to_array, other_other_field_to_value = other.nm_dump_numpy_split_dict() 64 | 65 | return self_other_field_to_value == other_other_field_to_value and _compare_np_array_dicts( 66 | self_ndarray_field_to_array, other_ndarray_field_to_array 67 | ) 68 | 69 | # Self is NumpyModel, other is not; likely unequal; checking anyway. 70 | return super().__eq__(other) 71 | 72 | @classmethod 73 | @validate_call 74 | def load( 75 | cls, 76 | output_directory: DirectoryPath, 77 | object_id: str, 78 | *, 79 | pre_load_modifier: Optional[Callable[[dict[str, Any]], dict[str, Any]]] = None, 80 | ): 81 | """ 82 | Load NumpyModel instance 83 | 84 | Parameters 85 | ---------- 86 | output_directory: DirectoryPath 87 | The root directory where all model instances of interest are stored 88 | object_id: String 89 | The ID of the model instance 90 | pre_load_modifier: Callable[[dict[str, Any]], dict[str, Any]] | None 91 | Optional function that modifies the loaded arrays 92 | 93 | Returns 94 | ------- 95 | NumpyModel instance 96 | """ 97 | object_directory_path = cls.nm_model_directory_path(output_directory, object_id) 98 | 99 | npz_file = np.load(object_directory_path / cls._dump_numpy_savez_file_name) 100 | 101 | other_path: FilePath 102 | if (other_path := object_directory_path / cls.nm_dump_compressed_pickle_file_name()).exists(): # type: ignore[operator] 103 | other_field_to_value = compress_pickle.load(other_path) 104 | elif (other_path := object_directory_path / cls.nm_dump_pickle_file_name()).exists(): # type: ignore[operator] 105 | with open(other_path, "rb") as in_pickle: 106 | other_field_to_value = pickle_pkg.load(in_pickle) 107 | elif (other_path := object_directory_path / cls.nm_dump_non_array_yaml_name()).exists(): # type: ignore[operator] 108 | with open(other_path, "r") as in_yaml: 109 | other_field_to_value = yaml.load(in_yaml) 110 | else: 111 | other_field_to_value = {} 112 | 113 | field_to_value = {**npz_file, **other_field_to_value} 114 | if pre_load_modifier: 115 | field_to_value = pre_load_modifier(field_to_value) 116 | 117 | return cls(**field_to_value) 118 | 119 | @validate_call 120 | def dump( 121 | self, output_directory: Path, object_id: str, *, compress: bool = True, pickle: bool = False 122 | ) -> DirectoryPath: 123 | assert "arbitrary_types_allowed" not in self.model_config or ( 124 | self.model_config["arbitrary_types_allowed"] and pickle 125 | ), "Arbitrary types are only supported in pickle mode" 126 | 127 | dump_directory_path = self.nm_model_directory_path(output_directory, object_id) 128 | dump_directory_path.mkdir(parents=True, exist_ok=True) 129 | 130 | ndarray_field_to_array, other_field_to_value = self.nm_dump_numpy_split_dict() 131 | 132 | if ndarray_field_to_array: 133 | (np.savez_compressed if compress else np.savez)( 134 | dump_directory_path / self._dump_numpy_savez_file_name, **ndarray_field_to_array 135 | ) 136 | 137 | if other_field_to_value: 138 | if pickle: 139 | if compress: 140 | compress_pickle.dump( 141 | other_field_to_value, 142 | dump_directory_path / self.nm_dump_compressed_pickle_file_name(), # pyright: ignore 143 | compression=self._dump_compression, 144 | ) 145 | else: 146 | with open( 147 | dump_directory_path / self.nm_dump_pickle_file_name(), "wb" 148 | ) as out_pickle: # pyright: ignore 149 | pickle_pkg.dump(other_field_to_value, out_pickle) 150 | 151 | else: 152 | with open(dump_directory_path / self.nm_dump_non_array_yaml_name(), "w") as out_yaml: # pyright: ignore 153 | yaml.dump(other_field_to_value, out_yaml) 154 | 155 | return dump_directory_path 156 | 157 | def nm_dump_numpy_split_dict(self) -> tuple[dict, dict]: 158 | ndarray_field_to_array = {} 159 | other_field_to_value = {} 160 | 161 | for k, v in self.model_dump().items(): 162 | if isinstance(v, np.ndarray): 163 | ndarray_field_to_array[k] = v 164 | elif v: 165 | other_field_to_value[k] = v 166 | 167 | return ndarray_field_to_array, other_field_to_value 168 | 169 | @classmethod 170 | @validate_call 171 | def nm_model_directory_path(cls, output_directory: DirectoryPath, object_id: str) -> DirectoryPath: 172 | return output_directory / f"{object_id}.{cls.__name__}{cls._directory_suffix}" 173 | 174 | @classmethod 175 | def nm_dump_compressed_pickle_file_name(cls) -> str: 176 | return f"{cls._dump_non_array_file_stem}.pickle.{cls._dump_compression}" 177 | 178 | @classmethod 179 | def nm_dump_pickle_file_name(cls) -> str: 180 | return f"{cls._dump_non_array_file_stem}.pickle" 181 | 182 | @classmethod 183 | def nm_dump_non_array_yaml_name(cls) -> str: 184 | return f"{cls._dump_non_array_file_stem}.yaml" 185 | 186 | 187 | def model_agnostic_load( 188 | output_directory: DirectoryPath, 189 | object_id: str, 190 | models: Iterable[type[NumpyModel]], 191 | not_found_error: bool = False, 192 | **load_kwargs, 193 | ) -> Optional[NumpyModel]: 194 | """ 195 | Provided an Iterable containing possible models, and the directory where they have been dumped. Load the first 196 | instance of model that matches the provided object ID. 197 | 198 | Parameters 199 | ---------- 200 | output_directory: DirectoryPath 201 | The root directory where all model instances of interest are stored 202 | object_id: String 203 | The ID of the model instance 204 | models: Iterable[type[NumpyModel]] 205 | All NumpyModel instances of interest, note that they should have differing names 206 | not_found_error: bool 207 | If True, throw error when the respective model instance was not found 208 | load_kwargs 209 | Key-word arguments to pass to the load function 210 | 211 | Returns 212 | ------- 213 | NumpyModel instance if found 214 | """ 215 | for model in models: 216 | if model.nm_model_directory_path(output_directory, object_id).exists(): 217 | return model.load(output_directory, object_id, **load_kwargs) 218 | 219 | if not_found_error: 220 | raise FileNotFoundError( 221 | f"Could not find NumpyModel with {object_id} in {output_directory}." 222 | f"Tried from following classes:\n{', '.join(model.__name__ for model in models)}" 223 | ) 224 | 225 | return None 226 | 227 | 228 | @lru_cache 229 | def _cached_np_array_load(path: FilePath): 230 | """ 231 | Store the loaded numpy object within LRU cache in case we need it several times 232 | 233 | Parameters 234 | ---------- 235 | path: FilePath 236 | Path to the numpy file 237 | 238 | Returns 239 | ------- 240 | Same as np.load 241 | """ 242 | return np.load(path) 243 | 244 | 245 | def _compare_np_array_dicts( 246 | dict_a: dict[str, npt.NDArray], dict_b: dict[str, npt.NDArray], rtol: float = 1e-05, atol: float = 1e-08 247 | ) -> bool: 248 | """ 249 | Compare two dictionaries containing numpy arrays as values. 250 | 251 | Parameters: 252 | dict_a, dict_b: dictionaries to compare. They should have same keys. 253 | rtol, atol: relative and absolute tolerances for np.isclose() 254 | 255 | Returns: 256 | Boolean value for each key, True if corresponding arrays are close, else False. 257 | """ 258 | 259 | keys1 = frozenset(dict_a.keys()) 260 | keys2 = frozenset(dict_b.keys()) 261 | 262 | if keys1 != keys2: 263 | return False 264 | 265 | for key in keys1: 266 | arr_a = dict_a[key] 267 | arr_b = dict_b[key] 268 | 269 | if arr_a.shape != arr_b.shape or not np_general_all_close(arr_a, arr_b, rtol, atol): 270 | return False 271 | 272 | return True 273 | 274 | 275 | __all__ = ["NumpyModel", "MultiArrayNumpyFile", "model_agnostic_load"] 276 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/__init__.py: -------------------------------------------------------------------------------- 1 | from pydantic_numpy.typing.type_safe.i_dimensional import * 2 | from pydantic_numpy.typing.type_safe.ii_dimensional import * 3 | from pydantic_numpy.typing.type_safe.iii_dimensional import * 4 | from pydantic_numpy.typing.type_safe.n_dimensional import * 5 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/type_safe/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caniko/pydantic-numpy/d0c7f4c71f330c58348d4799689283245d9c8f2e/pydantic_numpy/typing/type_safe/__init__.py -------------------------------------------------------------------------------- /pydantic_numpy/typing/type_safe/i_dimensional.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any, TypeAlias 2 | 3 | import numpy as np 4 | 5 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 6 | 7 | Np1DArray: TypeAlias = Annotated[ 8 | np.ndarray[tuple[int, ...], np.dtype[Any]], 9 | NpArrayPydanticAnnotation.factory(data_type=None, dimensions=1, strict_data_typing=False), 10 | ] 11 | 12 | Np1DArrayInt64: TypeAlias = Annotated[ 13 | np.ndarray[tuple[int, ...], np.dtype[np.int64]], 14 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=1, strict_data_typing=True), 15 | ] 16 | 17 | Np1DArrayInt32: TypeAlias = Annotated[ 18 | np.ndarray[tuple[int, ...], np.dtype[np.int32]], 19 | NpArrayPydanticAnnotation.factory(data_type=np.int32, dimensions=1, strict_data_typing=True), 20 | ] 21 | 22 | Np1DArrayInt16: TypeAlias = Annotated[ 23 | np.ndarray[tuple[int, ...], np.dtype[np.int16]], 24 | NpArrayPydanticAnnotation.factory(data_type=np.int16, dimensions=1, strict_data_typing=True), 25 | ] 26 | 27 | Np1DArrayInt8: TypeAlias = Annotated[ 28 | np.ndarray[tuple[int, ...], np.dtype[np.int8]], 29 | NpArrayPydanticAnnotation.factory(data_type=np.int8, dimensions=1, strict_data_typing=True), 30 | ] 31 | 32 | Np1DArrayUint64: TypeAlias = Annotated[ 33 | np.ndarray[tuple[int, ...], np.dtype[np.uint64]], 34 | NpArrayPydanticAnnotation.factory(data_type=np.uint64, dimensions=1, strict_data_typing=True), 35 | ] 36 | 37 | Np1DArrayUint32: TypeAlias = Annotated[ 38 | np.ndarray[tuple[int, ...], np.dtype[np.uint32]], 39 | NpArrayPydanticAnnotation.factory(data_type=np.uint32, dimensions=1, strict_data_typing=True), 40 | ] 41 | 42 | Np1DArrayUint16: TypeAlias = Annotated[ 43 | np.ndarray[tuple[int, ...], np.dtype[np.uint16]], 44 | NpArrayPydanticAnnotation.factory(data_type=np.uint16, dimensions=1, strict_data_typing=True), 45 | ] 46 | 47 | Np1DArrayUint8: TypeAlias = Annotated[ 48 | np.ndarray[tuple[int, ...], np.dtype[np.uint8]], 49 | NpArrayPydanticAnnotation.factory(data_type=np.uint8, dimensions=1, strict_data_typing=True), 50 | ] 51 | 52 | Np1DArrayFpLongDouble: TypeAlias = Annotated[ 53 | np.ndarray[tuple[int, ...], np.dtype[np.longdouble]], 54 | NpArrayPydanticAnnotation.factory(data_type=np.longdouble, dimensions=1, strict_data_typing=True), 55 | ] 56 | 57 | Np1DArrayFp64: TypeAlias = Annotated[ 58 | np.ndarray[tuple[int, ...], np.dtype[np.float64]], 59 | NpArrayPydanticAnnotation.factory(data_type=np.float64, dimensions=1, strict_data_typing=True), 60 | ] 61 | 62 | Np1DArrayFp32: TypeAlias = Annotated[ 63 | np.ndarray[tuple[int, ...], np.dtype[np.float32]], 64 | NpArrayPydanticAnnotation.factory(data_type=np.float32, dimensions=1, strict_data_typing=True), 65 | ] 66 | 67 | Np1DArrayFp16: TypeAlias = Annotated[ 68 | np.ndarray[tuple[int, ...], np.dtype[np.float16]], 69 | NpArrayPydanticAnnotation.factory(data_type=np.float16, dimensions=1, strict_data_typing=True), 70 | ] 71 | 72 | Np1DArrayComplexLongDouble: TypeAlias = Annotated[ 73 | np.ndarray[tuple[int, ...], np.dtype[np.clongdouble]], 74 | NpArrayPydanticAnnotation.factory(data_type=np.clongdouble, dimensions=1, strict_data_typing=True), 75 | ] 76 | 77 | Np1DArrayComplex128: TypeAlias = Annotated[ 78 | np.ndarray[tuple[int, ...], np.dtype[np.complex128]], 79 | NpArrayPydanticAnnotation.factory(data_type=np.complex128, dimensions=1, strict_data_typing=True), 80 | ] 81 | 82 | Np1DArrayComplex64: TypeAlias = Annotated[ 83 | np.ndarray[tuple[int, ...], np.dtype[np.complex64]], 84 | NpArrayPydanticAnnotation.factory(data_type=np.complex64, dimensions=1, strict_data_typing=True), 85 | ] 86 | 87 | Np1DArrayBool: TypeAlias = Annotated[ 88 | np.ndarray[tuple[int, ...], np.dtype[np.bool_]], 89 | NpArrayPydanticAnnotation.factory(data_type=np.bool_, dimensions=1, strict_data_typing=True), 90 | ] 91 | 92 | Np1DArrayDatetime64: TypeAlias = Annotated[ 93 | np.ndarray[tuple[int, ...], np.dtype[np.datetime64]], 94 | NpArrayPydanticAnnotation.factory(data_type=np.datetime64, dimensions=1, strict_data_typing=True), 95 | ] 96 | 97 | Np1DArrayTimedelta64: TypeAlias = Annotated[ 98 | np.ndarray[tuple[int, ...], np.dtype[np.timedelta64]], 99 | NpArrayPydanticAnnotation.factory(data_type=np.timedelta64, dimensions=1, strict_data_typing=True), 100 | ] 101 | 102 | __all__ = [ 103 | "Np1DArray", 104 | "Np1DArrayInt64", 105 | "Np1DArrayInt32", 106 | "Np1DArrayInt16", 107 | "Np1DArrayInt8", 108 | "Np1DArrayUint64", 109 | "Np1DArrayUint32", 110 | "Np1DArrayUint16", 111 | "Np1DArrayUint8", 112 | "Np1DArrayFpLongDouble", 113 | "Np1DArrayFp64", 114 | "Np1DArrayFp32", 115 | "Np1DArrayFp16", 116 | "Np1DArrayComplexLongDouble", 117 | "Np1DArrayComplex128", 118 | "Np1DArrayComplex64", 119 | "Np1DArrayBool", 120 | "Np1DArrayDatetime64", 121 | "Np1DArrayTimedelta64", 122 | ] 123 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/type_safe/ii_dimensional.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any, TypeAlias 2 | 3 | import numpy as np 4 | 5 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 6 | 7 | Np2DArray: TypeAlias = Annotated[ 8 | np.ndarray[tuple[int, ...], np.dtype[Any]], 9 | NpArrayPydanticAnnotation.factory(data_type=None, dimensions=2, strict_data_typing=False), 10 | ] 11 | 12 | Np2DArrayInt64: TypeAlias = Annotated[ 13 | np.ndarray[tuple[int, ...], np.dtype[np.int64]], 14 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=2, strict_data_typing=True), 15 | ] 16 | 17 | Np2DArrayInt32: TypeAlias = Annotated[ 18 | np.ndarray[tuple[int, ...], np.dtype[np.int32]], 19 | NpArrayPydanticAnnotation.factory(data_type=np.int32, dimensions=2, strict_data_typing=True), 20 | ] 21 | 22 | Np2DArrayInt16: TypeAlias = Annotated[ 23 | np.ndarray[tuple[int, ...], np.dtype[np.int16]], 24 | NpArrayPydanticAnnotation.factory(data_type=np.int16, dimensions=2, strict_data_typing=True), 25 | ] 26 | 27 | Np2DArrayInt8: TypeAlias = Annotated[ 28 | np.ndarray[tuple[int, ...], np.dtype[np.int8]], 29 | NpArrayPydanticAnnotation.factory(data_type=np.int8, dimensions=2, strict_data_typing=True), 30 | ] 31 | 32 | Np2DArrayUint64: TypeAlias = Annotated[ 33 | np.ndarray[tuple[int, ...], np.dtype[np.uint64]], 34 | NpArrayPydanticAnnotation.factory(data_type=np.uint64, dimensions=2, strict_data_typing=True), 35 | ] 36 | 37 | Np2DArrayUint32: TypeAlias = Annotated[ 38 | np.ndarray[tuple[int, ...], np.dtype[np.uint32]], 39 | NpArrayPydanticAnnotation.factory(data_type=np.uint32, dimensions=2, strict_data_typing=True), 40 | ] 41 | 42 | Np2DArrayUint16: TypeAlias = Annotated[ 43 | np.ndarray[tuple[int, ...], np.dtype[np.uint16]], 44 | NpArrayPydanticAnnotation.factory(data_type=np.uint16, dimensions=2, strict_data_typing=True), 45 | ] 46 | 47 | Np2DArrayUint8: TypeAlias = Annotated[ 48 | np.ndarray[tuple[int, ...], np.dtype[np.uint8]], 49 | NpArrayPydanticAnnotation.factory(data_type=np.uint8, dimensions=2, strict_data_typing=True), 50 | ] 51 | 52 | Np2DArrayFpLongDouble: TypeAlias = Annotated[ 53 | np.ndarray[tuple[int, ...], np.dtype[np.longdouble]], 54 | NpArrayPydanticAnnotation.factory(data_type=np.longdouble, dimensions=2, strict_data_typing=True), 55 | ] 56 | 57 | Np2DArrayFp64: TypeAlias = Annotated[ 58 | np.ndarray[tuple[int, ...], np.dtype[np.float64]], 59 | NpArrayPydanticAnnotation.factory(data_type=np.float64, dimensions=2, strict_data_typing=True), 60 | ] 61 | 62 | Np2DArrayFp32: TypeAlias = Annotated[ 63 | np.ndarray[tuple[int, ...], np.dtype[np.float32]], 64 | NpArrayPydanticAnnotation.factory(data_type=np.float32, dimensions=2, strict_data_typing=True), 65 | ] 66 | 67 | Np2DArrayFp16: TypeAlias = Annotated[ 68 | np.ndarray[tuple[int, ...], np.dtype[np.float16]], 69 | NpArrayPydanticAnnotation.factory(data_type=np.float16, dimensions=2, strict_data_typing=True), 70 | ] 71 | 72 | Np2DArrayComplexLongDouble: TypeAlias = Annotated[ 73 | np.ndarray[tuple[int, ...], np.dtype[np.clongdouble]], 74 | NpArrayPydanticAnnotation.factory(data_type=np.clongdouble, dimensions=2, strict_data_typing=True), 75 | ] 76 | 77 | Np2DArrayComplex128: TypeAlias = Annotated[ 78 | np.ndarray[tuple[int, ...], np.dtype[np.complex128]], 79 | NpArrayPydanticAnnotation.factory(data_type=np.complex128, dimensions=2, strict_data_typing=True), 80 | ] 81 | 82 | Np2DArrayComplex64: TypeAlias = Annotated[ 83 | np.ndarray[tuple[int, ...], np.dtype[np.complex64]], 84 | NpArrayPydanticAnnotation.factory(data_type=np.complex64, dimensions=2, strict_data_typing=True), 85 | ] 86 | 87 | Np2DArrayBool: TypeAlias = Annotated[ 88 | np.ndarray[tuple[int, ...], np.dtype[np.bool_]], 89 | NpArrayPydanticAnnotation.factory(data_type=np.bool_, dimensions=2, strict_data_typing=True), 90 | ] 91 | 92 | Np2DArrayDatetime64: TypeAlias = Annotated[ 93 | np.ndarray[tuple[int, ...], np.dtype[np.datetime64]], 94 | NpArrayPydanticAnnotation.factory(data_type=np.datetime64, dimensions=2, strict_data_typing=True), 95 | ] 96 | 97 | Np2DArrayTimedelta64: TypeAlias = Annotated[ 98 | np.ndarray[tuple[int, ...], np.dtype[np.timedelta64]], 99 | NpArrayPydanticAnnotation.factory(data_type=np.timedelta64, dimensions=2, strict_data_typing=True), 100 | ] 101 | 102 | __all__ = [ 103 | "Np2DArray", 104 | "Np2DArrayInt64", 105 | "Np2DArrayInt32", 106 | "Np2DArrayInt16", 107 | "Np2DArrayInt8", 108 | "Np2DArrayUint64", 109 | "Np2DArrayUint32", 110 | "Np2DArrayUint16", 111 | "Np2DArrayUint8", 112 | "Np2DArrayFpLongDouble", 113 | "Np2DArrayFp64", 114 | "Np2DArrayFp32", 115 | "Np2DArrayFp16", 116 | "Np2DArrayComplexLongDouble", 117 | "Np2DArrayComplex128", 118 | "Np2DArrayComplex64", 119 | "Np2DArrayBool", 120 | "Np2DArrayDatetime64", 121 | "Np2DArrayTimedelta64", 122 | ] 123 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/type_safe/iii_dimensional.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any, TypeAlias 2 | 3 | import numpy as np 4 | 5 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 6 | 7 | Np3DArray: TypeAlias = Annotated[ 8 | np.ndarray[tuple[int, ...], np.dtype[Any]], 9 | NpArrayPydanticAnnotation.factory(data_type=None, dimensions=3, strict_data_typing=False), 10 | ] 11 | 12 | Np3DArrayInt64: TypeAlias = Annotated[ 13 | np.ndarray[tuple[int, ...], np.dtype[np.int64]], 14 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=3, strict_data_typing=True), 15 | ] 16 | 17 | Np3DArrayInt32: TypeAlias = Annotated[ 18 | np.ndarray[tuple[int, ...], np.dtype[np.int32]], 19 | NpArrayPydanticAnnotation.factory(data_type=np.int32, dimensions=3, strict_data_typing=True), 20 | ] 21 | 22 | Np3DArrayInt16: TypeAlias = Annotated[ 23 | np.ndarray[tuple[int, ...], np.dtype[np.int16]], 24 | NpArrayPydanticAnnotation.factory(data_type=np.int16, dimensions=3, strict_data_typing=True), 25 | ] 26 | 27 | Np3DArrayInt8: TypeAlias = Annotated[ 28 | np.ndarray[tuple[int, ...], np.dtype[np.int8]], 29 | NpArrayPydanticAnnotation.factory(data_type=np.int8, dimensions=3, strict_data_typing=True), 30 | ] 31 | 32 | Np3DArrayUint64: TypeAlias = Annotated[ 33 | np.ndarray[tuple[int, ...], np.dtype[np.uint64]], 34 | NpArrayPydanticAnnotation.factory(data_type=np.uint64, dimensions=3, strict_data_typing=True), 35 | ] 36 | 37 | Np3DArrayUint32: TypeAlias = Annotated[ 38 | np.ndarray[tuple[int, ...], np.dtype[np.uint32]], 39 | NpArrayPydanticAnnotation.factory(data_type=np.uint32, dimensions=3, strict_data_typing=True), 40 | ] 41 | 42 | Np3DArrayUint16: TypeAlias = Annotated[ 43 | np.ndarray[tuple[int, ...], np.dtype[np.uint16]], 44 | NpArrayPydanticAnnotation.factory(data_type=np.uint16, dimensions=3, strict_data_typing=True), 45 | ] 46 | 47 | Np3DArrayUint8: TypeAlias = Annotated[ 48 | np.ndarray[tuple[int, ...], np.dtype[np.uint8]], 49 | NpArrayPydanticAnnotation.factory(data_type=np.uint8, dimensions=3, strict_data_typing=True), 50 | ] 51 | 52 | Np3DArrayFpLongDouble: TypeAlias = Annotated[ 53 | np.ndarray[tuple[int, ...], np.dtype[np.longdouble]], 54 | NpArrayPydanticAnnotation.factory(data_type=np.longdouble, dimensions=3, strict_data_typing=True), 55 | ] 56 | 57 | Np3DArrayFp64: TypeAlias = Annotated[ 58 | np.ndarray[tuple[int, ...], np.dtype[np.float64]], 59 | NpArrayPydanticAnnotation.factory(data_type=np.float64, dimensions=3, strict_data_typing=True), 60 | ] 61 | 62 | Np3DArrayFp32: TypeAlias = Annotated[ 63 | np.ndarray[tuple[int, ...], np.dtype[np.float32]], 64 | NpArrayPydanticAnnotation.factory(data_type=np.float32, dimensions=3, strict_data_typing=True), 65 | ] 66 | 67 | Np3DArrayFp16: TypeAlias = Annotated[ 68 | np.ndarray[tuple[int, ...], np.dtype[np.float16]], 69 | NpArrayPydanticAnnotation.factory(data_type=np.float16, dimensions=3, strict_data_typing=True), 70 | ] 71 | 72 | Np3DArrayComplexLongDouble: TypeAlias = Annotated[ 73 | np.ndarray[tuple[int, ...], np.dtype[np.clongdouble]], 74 | NpArrayPydanticAnnotation.factory(data_type=np.clongdouble, dimensions=3, strict_data_typing=True), 75 | ] 76 | 77 | Np3DArrayComplex128: TypeAlias = Annotated[ 78 | np.ndarray[tuple[int, ...], np.dtype[np.complex128]], 79 | NpArrayPydanticAnnotation.factory(data_type=np.complex128, dimensions=3, strict_data_typing=True), 80 | ] 81 | 82 | Np3DArrayComplex64: TypeAlias = Annotated[ 83 | np.ndarray[tuple[int, ...], np.dtype[np.complex64]], 84 | NpArrayPydanticAnnotation.factory(data_type=np.complex64, dimensions=3, strict_data_typing=True), 85 | ] 86 | 87 | Np3DArrayBool: TypeAlias = Annotated[ 88 | np.ndarray[tuple[int, ...], np.dtype[np.bool_]], 89 | NpArrayPydanticAnnotation.factory(data_type=np.bool_, dimensions=3, strict_data_typing=True), 90 | ] 91 | 92 | Np3DArrayDatetime64: TypeAlias = Annotated[ 93 | np.ndarray[tuple[int, ...], np.dtype[np.datetime64]], 94 | NpArrayPydanticAnnotation.factory(data_type=np.datetime64, dimensions=3, strict_data_typing=True), 95 | ] 96 | 97 | Np3DArrayTimedelta64: TypeAlias = Annotated[ 98 | np.ndarray[tuple[int, ...], np.dtype[np.timedelta64]], 99 | NpArrayPydanticAnnotation.factory(data_type=np.timedelta64, dimensions=3, strict_data_typing=True), 100 | ] 101 | 102 | __all__ = [ 103 | "Np3DArray", 104 | "Np3DArrayInt64", 105 | "Np3DArrayInt32", 106 | "Np3DArrayInt16", 107 | "Np3DArrayInt8", 108 | "Np3DArrayUint64", 109 | "Np3DArrayUint32", 110 | "Np3DArrayUint16", 111 | "Np3DArrayUint8", 112 | "Np3DArrayFpLongDouble", 113 | "Np3DArrayFp64", 114 | "Np3DArrayFp32", 115 | "Np3DArrayFp16", 116 | "Np3DArrayComplexLongDouble", 117 | "Np3DArrayComplex128", 118 | "Np3DArrayComplex64", 119 | "Np3DArrayBool", 120 | "Np3DArrayDatetime64", 121 | "Np3DArrayTimedelta64", 122 | ] 123 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/type_safe/n_dimensional.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any, TypeAlias 2 | 3 | import numpy as np 4 | 5 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 6 | 7 | NpNDArray: TypeAlias = Annotated[ 8 | np.ndarray[tuple[int, ...], np.dtype[Any]], 9 | NpArrayPydanticAnnotation.factory(data_type=None, dimensions=None, strict_data_typing=False), 10 | ] 11 | 12 | NpNDArrayInt64: TypeAlias = Annotated[ 13 | np.ndarray[tuple[int, ...], np.dtype[np.int64]], 14 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=None, strict_data_typing=True), 15 | ] 16 | 17 | NpNDArrayInt32: TypeAlias = Annotated[ 18 | np.ndarray[tuple[int, ...], np.dtype[np.int32]], 19 | NpArrayPydanticAnnotation.factory(data_type=np.int32, dimensions=None, strict_data_typing=True), 20 | ] 21 | 22 | NpNDArrayInt16: TypeAlias = Annotated[ 23 | np.ndarray[tuple[int, ...], np.dtype[np.int16]], 24 | NpArrayPydanticAnnotation.factory(data_type=np.int16, dimensions=None, strict_data_typing=True), 25 | ] 26 | 27 | NpNDArrayInt8: TypeAlias = Annotated[ 28 | np.ndarray[tuple[int, ...], np.dtype[np.int8]], 29 | NpArrayPydanticAnnotation.factory(data_type=np.int8, dimensions=None, strict_data_typing=True), 30 | ] 31 | 32 | NpNDArrayUint64: TypeAlias = Annotated[ 33 | np.ndarray[tuple[int, ...], np.dtype[np.uint64]], 34 | NpArrayPydanticAnnotation.factory(data_type=np.uint64, dimensions=None, strict_data_typing=True), 35 | ] 36 | 37 | NpNDArrayUint32: TypeAlias = Annotated[ 38 | np.ndarray[tuple[int, ...], np.dtype[np.uint32]], 39 | NpArrayPydanticAnnotation.factory(data_type=np.uint32, dimensions=None, strict_data_typing=True), 40 | ] 41 | 42 | NpNDArrayUint16: TypeAlias = Annotated[ 43 | np.ndarray[tuple[int, ...], np.dtype[np.uint16]], 44 | NpArrayPydanticAnnotation.factory(data_type=np.uint16, dimensions=None, strict_data_typing=True), 45 | ] 46 | 47 | NpNDArrayUint8: TypeAlias = Annotated[ 48 | np.ndarray[tuple[int, ...], np.dtype[np.uint8]], 49 | NpArrayPydanticAnnotation.factory(data_type=np.uint8, dimensions=None, strict_data_typing=True), 50 | ] 51 | 52 | NpNDArrayFpLongDouble: TypeAlias = Annotated[ 53 | np.ndarray[tuple[int, ...], np.dtype[np.longdouble]], 54 | NpArrayPydanticAnnotation.factory(data_type=np.longdouble, dimensions=None, strict_data_typing=True), 55 | ] 56 | 57 | NpNDArrayFp64: TypeAlias = Annotated[ 58 | np.ndarray[tuple[int, ...], np.dtype[np.float64]], 59 | NpArrayPydanticAnnotation.factory(data_type=np.float64, dimensions=None, strict_data_typing=True), 60 | ] 61 | 62 | NpNDArrayFp32: TypeAlias = Annotated[ 63 | np.ndarray[tuple[int, ...], np.dtype[np.float32]], 64 | NpArrayPydanticAnnotation.factory(data_type=np.float32, dimensions=None, strict_data_typing=True), 65 | ] 66 | 67 | NpNDArrayFp16: TypeAlias = Annotated[ 68 | np.ndarray[tuple[int, ...], np.dtype[np.float16]], 69 | NpArrayPydanticAnnotation.factory(data_type=np.float16, dimensions=None, strict_data_typing=True), 70 | ] 71 | 72 | NpNDArrayComplexLongDouble: TypeAlias = Annotated[ 73 | np.ndarray[tuple[int, ...], np.dtype[np.clongdouble]], 74 | NpArrayPydanticAnnotation.factory(data_type=np.clongdouble, dimensions=None, strict_data_typing=True), 75 | ] 76 | 77 | NpNDArrayComplex128: TypeAlias = Annotated[ 78 | np.ndarray[tuple[int, ...], np.dtype[np.complex128]], 79 | NpArrayPydanticAnnotation.factory(data_type=np.complex128, dimensions=None, strict_data_typing=True), 80 | ] 81 | 82 | NpNDArrayComplex64: TypeAlias = Annotated[ 83 | np.ndarray[tuple[int, ...], np.dtype[np.complex64]], 84 | NpArrayPydanticAnnotation.factory(data_type=np.complex64, dimensions=None, strict_data_typing=True), 85 | ] 86 | 87 | NpNDArrayBool: TypeAlias = Annotated[ 88 | np.ndarray[tuple[int, ...], np.dtype[np.bool_]], 89 | NpArrayPydanticAnnotation.factory(data_type=np.bool_, dimensions=None, strict_data_typing=True), 90 | ] 91 | 92 | NpNDArrayDatetime64: TypeAlias = Annotated[ 93 | np.ndarray[tuple[int, ...], np.dtype[np.datetime64]], 94 | NpArrayPydanticAnnotation.factory(data_type=np.datetime64, dimensions=None, strict_data_typing=True), 95 | ] 96 | 97 | NpNDArrayTimedelta64: TypeAlias = Annotated[ 98 | np.ndarray[tuple[int, ...], np.dtype[np.timedelta64]], 99 | NpArrayPydanticAnnotation.factory(data_type=np.timedelta64, dimensions=None, strict_data_typing=True), 100 | ] 101 | 102 | __all__ = [ 103 | "NpNDArray", 104 | "NpNDArrayInt64", 105 | "NpNDArrayInt32", 106 | "NpNDArrayInt16", 107 | "NpNDArrayInt8", 108 | "NpNDArrayUint64", 109 | "NpNDArrayUint32", 110 | "NpNDArrayUint16", 111 | "NpNDArrayUint8", 112 | "NpNDArrayFpLongDouble", 113 | "NpNDArrayFp64", 114 | "NpNDArrayFp32", 115 | "NpNDArrayFp16", 116 | "NpNDArrayComplexLongDouble", 117 | "NpNDArrayComplex128", 118 | "NpNDArrayComplex64", 119 | "NpNDArrayBool", 120 | "NpNDArrayDatetime64", 121 | "NpNDArrayTimedelta64", 122 | ] 123 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/with_loader/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caniko/pydantic-numpy/d0c7f4c71f330c58348d4799689283245d9c8f2e/pydantic_numpy/typing/with_loader/__init__.py -------------------------------------------------------------------------------- /pydantic_numpy/typing/with_loader/i_dimensional.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any, TypeAlias, Union 2 | 3 | import numpy as np 4 | from pydantic import FilePath 5 | 6 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 7 | from pydantic_numpy.model import MultiArrayNumpyFile 8 | 9 | NpLoading1DArray: TypeAlias = Annotated[ 10 | Union[np.ndarray[tuple[int, ...], np.dtype[Any]], FilePath, MultiArrayNumpyFile], 11 | NpArrayPydanticAnnotation.factory(data_type=None, dimensions=1, strict_data_typing=False), 12 | ] 13 | 14 | NpLoading1DArrayInt64: TypeAlias = Annotated[ 15 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int64]], FilePath, MultiArrayNumpyFile], 16 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=1, strict_data_typing=False), 17 | ] 18 | 19 | NpLoading1DArrayInt32: TypeAlias = Annotated[ 20 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int32]], FilePath, MultiArrayNumpyFile], 21 | NpArrayPydanticAnnotation.factory(data_type=np.int32, dimensions=1, strict_data_typing=False), 22 | ] 23 | 24 | NpLoading1DArrayInt16: TypeAlias = Annotated[ 25 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int16]], FilePath, MultiArrayNumpyFile], 26 | NpArrayPydanticAnnotation.factory(data_type=np.int16, dimensions=1, strict_data_typing=False), 27 | ] 28 | 29 | NpLoading1DArrayInt8: TypeAlias = Annotated[ 30 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int8]], FilePath, MultiArrayNumpyFile], 31 | NpArrayPydanticAnnotation.factory(data_type=np.int8, dimensions=1, strict_data_typing=False), 32 | ] 33 | 34 | NpLoading1DArrayUint64: TypeAlias = Annotated[ 35 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint64]], FilePath, MultiArrayNumpyFile], 36 | NpArrayPydanticAnnotation.factory(data_type=np.uint64, dimensions=1, strict_data_typing=False), 37 | ] 38 | 39 | NpLoading1DArrayUint32: TypeAlias = Annotated[ 40 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint32]], FilePath, MultiArrayNumpyFile], 41 | NpArrayPydanticAnnotation.factory(data_type=np.uint32, dimensions=1, strict_data_typing=False), 42 | ] 43 | 44 | NpLoading1DArrayUint16: TypeAlias = Annotated[ 45 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint16]], FilePath, MultiArrayNumpyFile], 46 | NpArrayPydanticAnnotation.factory(data_type=np.uint16, dimensions=1, strict_data_typing=False), 47 | ] 48 | 49 | NpLoading1DArrayUint8: TypeAlias = Annotated[ 50 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint8]], FilePath, MultiArrayNumpyFile], 51 | NpArrayPydanticAnnotation.factory(data_type=np.uint8, dimensions=1, strict_data_typing=False), 52 | ] 53 | 54 | NpLoading1DArrayFpLongDouble: TypeAlias = Annotated[ 55 | Union[np.ndarray[tuple[int, ...], np.dtype[np.longdouble]], FilePath, MultiArrayNumpyFile], 56 | NpArrayPydanticAnnotation.factory(data_type=np.longdouble, dimensions=1, strict_data_typing=False), 57 | ] 58 | 59 | NpLoading1DArrayFp64: TypeAlias = Annotated[ 60 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float64]], FilePath, MultiArrayNumpyFile], 61 | NpArrayPydanticAnnotation.factory(data_type=np.float64, dimensions=1, strict_data_typing=False), 62 | ] 63 | 64 | NpLoading1DArrayFp32: TypeAlias = Annotated[ 65 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float32]], FilePath, MultiArrayNumpyFile], 66 | NpArrayPydanticAnnotation.factory(data_type=np.float32, dimensions=1, strict_data_typing=False), 67 | ] 68 | 69 | NpLoading1DArrayFp16: TypeAlias = Annotated[ 70 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float16]], FilePath, MultiArrayNumpyFile], 71 | NpArrayPydanticAnnotation.factory(data_type=np.float16, dimensions=1, strict_data_typing=False), 72 | ] 73 | 74 | NpLoading1DArrayComplexLongDouble: TypeAlias = Annotated[ 75 | Union[np.ndarray[tuple[int, ...], np.dtype[np.clongdouble]], FilePath, MultiArrayNumpyFile], 76 | NpArrayPydanticAnnotation.factory(data_type=np.clongdouble, dimensions=1, strict_data_typing=False), 77 | ] 78 | 79 | NpLoading1DArrayComplex128: TypeAlias = Annotated[ 80 | Union[np.ndarray[tuple[int, ...], np.dtype[np.complex128]], FilePath, MultiArrayNumpyFile], 81 | NpArrayPydanticAnnotation.factory(data_type=np.complex128, dimensions=1, strict_data_typing=False), 82 | ] 83 | 84 | NpLoading1DArrayComplex64: TypeAlias = Annotated[ 85 | Union[np.ndarray[tuple[int, ...], np.dtype[np.complex64]], FilePath, MultiArrayNumpyFile], 86 | NpArrayPydanticAnnotation.factory(data_type=np.complex64, dimensions=1, strict_data_typing=False), 87 | ] 88 | 89 | NpLoading1DArrayBool: TypeAlias = Annotated[ 90 | Union[np.ndarray[tuple[int, ...], np.dtype[np.bool_]], FilePath, MultiArrayNumpyFile], 91 | NpArrayPydanticAnnotation.factory(data_type=np.bool_, dimensions=1, strict_data_typing=False), 92 | ] 93 | 94 | NpLoading1DArrayDatetime64: TypeAlias = Annotated[ 95 | Union[np.ndarray[tuple[int, ...], np.dtype[np.datetime64]], FilePath, MultiArrayNumpyFile], 96 | NpArrayPydanticAnnotation.factory(data_type=np.datetime64, dimensions=1, strict_data_typing=False), 97 | ] 98 | 99 | NpLoading1DArrayTimedelta64: TypeAlias = Annotated[ 100 | Union[np.ndarray[tuple[int, ...], np.dtype[np.timedelta64]], FilePath, MultiArrayNumpyFile], 101 | NpArrayPydanticAnnotation.factory(data_type=np.timedelta64, dimensions=1, strict_data_typing=False), 102 | ] 103 | 104 | __all__ = [ 105 | "NpLoading1DArray", 106 | "NpLoading1DArrayInt64", 107 | "NpLoading1DArrayInt32", 108 | "NpLoading1DArrayInt16", 109 | "NpLoading1DArrayInt8", 110 | "NpLoading1DArrayUint64", 111 | "NpLoading1DArrayUint32", 112 | "NpLoading1DArrayUint16", 113 | "NpLoading1DArrayUint8", 114 | "NpLoading1DArrayFpLongDouble", 115 | "NpLoading1DArrayFp64", 116 | "NpLoading1DArrayFp32", 117 | "NpLoading1DArrayFp16", 118 | "NpLoading1DArrayComplexLongDouble", 119 | "NpLoading1DArrayComplex128", 120 | "NpLoading1DArrayComplex64", 121 | "NpLoading1DArrayBool", 122 | "NpLoading1DArrayDatetime64", 123 | "NpLoading1DArrayTimedelta64", 124 | ] 125 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/with_loader/ii_dimensional.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any, TypeAlias, Union 2 | 3 | import numpy as np 4 | from pydantic import FilePath 5 | 6 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 7 | from pydantic_numpy.model import MultiArrayNumpyFile 8 | 9 | NpLoading2DArray: TypeAlias = Annotated[ 10 | Union[np.ndarray[tuple[int, ...], np.dtype[Any]], FilePath, MultiArrayNumpyFile], 11 | NpArrayPydanticAnnotation.factory(data_type=None, dimensions=2, strict_data_typing=False), 12 | ] 13 | 14 | NpLoading2DArrayInt64: TypeAlias = Annotated[ 15 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int64]], FilePath, MultiArrayNumpyFile], 16 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=2, strict_data_typing=False), 17 | ] 18 | 19 | NpLoading2DArrayInt32: TypeAlias = Annotated[ 20 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int32]], FilePath, MultiArrayNumpyFile], 21 | NpArrayPydanticAnnotation.factory(data_type=np.int32, dimensions=2, strict_data_typing=False), 22 | ] 23 | 24 | NpLoading2DArrayInt16: TypeAlias = Annotated[ 25 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int16]], FilePath, MultiArrayNumpyFile], 26 | NpArrayPydanticAnnotation.factory(data_type=np.int16, dimensions=2, strict_data_typing=False), 27 | ] 28 | 29 | NpLoading2DArrayInt8: TypeAlias = Annotated[ 30 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int8]], FilePath, MultiArrayNumpyFile], 31 | NpArrayPydanticAnnotation.factory(data_type=np.int8, dimensions=2, strict_data_typing=False), 32 | ] 33 | 34 | NpLoading2DArrayUint64: TypeAlias = Annotated[ 35 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint64]], FilePath, MultiArrayNumpyFile], 36 | NpArrayPydanticAnnotation.factory(data_type=np.uint64, dimensions=2, strict_data_typing=False), 37 | ] 38 | 39 | NpLoading2DArrayUint32: TypeAlias = Annotated[ 40 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint32]], FilePath, MultiArrayNumpyFile], 41 | NpArrayPydanticAnnotation.factory(data_type=np.uint32, dimensions=2, strict_data_typing=False), 42 | ] 43 | 44 | NpLoading2DArrayUint16: TypeAlias = Annotated[ 45 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint16]], FilePath, MultiArrayNumpyFile], 46 | NpArrayPydanticAnnotation.factory(data_type=np.uint16, dimensions=2, strict_data_typing=False), 47 | ] 48 | 49 | NpLoading2DArrayUint8: TypeAlias = Annotated[ 50 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint8]], FilePath, MultiArrayNumpyFile], 51 | NpArrayPydanticAnnotation.factory(data_type=np.uint8, dimensions=2, strict_data_typing=False), 52 | ] 53 | 54 | NpLoading2DArrayFpLongDouble: TypeAlias = Annotated[ 55 | Union[np.ndarray[tuple[int, ...], np.dtype[np.longdouble]], FilePath, MultiArrayNumpyFile], 56 | NpArrayPydanticAnnotation.factory(data_type=np.longdouble, dimensions=2, strict_data_typing=False), 57 | ] 58 | 59 | NpLoading2DArrayFp64: TypeAlias = Annotated[ 60 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float64]], FilePath, MultiArrayNumpyFile], 61 | NpArrayPydanticAnnotation.factory(data_type=np.float64, dimensions=2, strict_data_typing=False), 62 | ] 63 | 64 | NpLoading2DArrayFp32: TypeAlias = Annotated[ 65 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float32]], FilePath, MultiArrayNumpyFile], 66 | NpArrayPydanticAnnotation.factory(data_type=np.float32, dimensions=2, strict_data_typing=False), 67 | ] 68 | 69 | NpLoading2DArrayFp16: TypeAlias = Annotated[ 70 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float16]], FilePath, MultiArrayNumpyFile], 71 | NpArrayPydanticAnnotation.factory(data_type=np.float16, dimensions=2, strict_data_typing=False), 72 | ] 73 | 74 | NpLoading2DArrayComplexLongDouble: TypeAlias = Annotated[ 75 | Union[np.ndarray[tuple[int, ...], np.dtype[np.clongdouble]], FilePath, MultiArrayNumpyFile], 76 | NpArrayPydanticAnnotation.factory(data_type=np.clongdouble, dimensions=2, strict_data_typing=False), 77 | ] 78 | 79 | NpLoading2DArrayComplex128: TypeAlias = Annotated[ 80 | Union[np.ndarray[tuple[int, ...], np.dtype[np.complex128]], FilePath, MultiArrayNumpyFile], 81 | NpArrayPydanticAnnotation.factory(data_type=np.complex128, dimensions=2, strict_data_typing=False), 82 | ] 83 | 84 | NpLoading2DArrayComplex64: TypeAlias = Annotated[ 85 | Union[np.ndarray[tuple[int, ...], np.dtype[np.complex64]], FilePath, MultiArrayNumpyFile], 86 | NpArrayPydanticAnnotation.factory(data_type=np.complex64, dimensions=2, strict_data_typing=False), 87 | ] 88 | 89 | NpLoading2DArrayBool: TypeAlias = Annotated[ 90 | Union[np.ndarray[tuple[int, ...], np.dtype[np.bool_]], FilePath, MultiArrayNumpyFile], 91 | NpArrayPydanticAnnotation.factory(data_type=np.bool_, dimensions=2, strict_data_typing=False), 92 | ] 93 | 94 | NpLoading2DArrayDatetime64: TypeAlias = Annotated[ 95 | Union[np.ndarray[tuple[int, ...], np.dtype[np.datetime64]], FilePath, MultiArrayNumpyFile], 96 | NpArrayPydanticAnnotation.factory(data_type=np.datetime64, dimensions=2, strict_data_typing=False), 97 | ] 98 | 99 | NpLoading2DArrayTimedelta64: TypeAlias = Annotated[ 100 | Union[np.ndarray[tuple[int, ...], np.dtype[np.timedelta64]], FilePath, MultiArrayNumpyFile], 101 | NpArrayPydanticAnnotation.factory(data_type=np.timedelta64, dimensions=2, strict_data_typing=False), 102 | ] 103 | 104 | __all__ = [ 105 | "NpLoading2DArray", 106 | "NpLoading2DArrayInt64", 107 | "NpLoading2DArrayInt32", 108 | "NpLoading2DArrayInt16", 109 | "NpLoading2DArrayInt8", 110 | "NpLoading2DArrayUint64", 111 | "NpLoading2DArrayUint32", 112 | "NpLoading2DArrayUint16", 113 | "NpLoading2DArrayUint8", 114 | "NpLoading2DArrayFpLongDouble", 115 | "NpLoading2DArrayFp64", 116 | "NpLoading2DArrayFp32", 117 | "NpLoading2DArrayFp16", 118 | "NpLoading2DArrayComplexLongDouble", 119 | "NpLoading2DArrayComplex128", 120 | "NpLoading2DArrayComplex64", 121 | "NpLoading2DArrayBool", 122 | "NpLoading2DArrayDatetime64", 123 | "NpLoading2DArrayTimedelta64", 124 | ] 125 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/with_loader/iii_dimensional.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any, TypeAlias, Union 2 | 3 | import numpy as np 4 | from pydantic import FilePath 5 | 6 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 7 | from pydantic_numpy.model import MultiArrayNumpyFile 8 | 9 | NpLoading3DArray: TypeAlias = Annotated[ 10 | Union[np.ndarray[tuple[int, ...], np.dtype[Any]], FilePath, MultiArrayNumpyFile], 11 | NpArrayPydanticAnnotation.factory(data_type=None, dimensions=3, strict_data_typing=False), 12 | ] 13 | 14 | NpLoading3DArrayInt64: TypeAlias = Annotated[ 15 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int64]], FilePath, MultiArrayNumpyFile], 16 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=3, strict_data_typing=False), 17 | ] 18 | 19 | NpLoading3DArrayInt32: TypeAlias = Annotated[ 20 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int32]], FilePath, MultiArrayNumpyFile], 21 | NpArrayPydanticAnnotation.factory(data_type=np.int32, dimensions=3, strict_data_typing=False), 22 | ] 23 | 24 | NpLoading3DArrayInt16: TypeAlias = Annotated[ 25 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int16]], FilePath, MultiArrayNumpyFile], 26 | NpArrayPydanticAnnotation.factory(data_type=np.int16, dimensions=3, strict_data_typing=False), 27 | ] 28 | 29 | NpLoading3DArrayInt8: TypeAlias = Annotated[ 30 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int8]], FilePath, MultiArrayNumpyFile], 31 | NpArrayPydanticAnnotation.factory(data_type=np.int8, dimensions=3, strict_data_typing=False), 32 | ] 33 | 34 | NpLoading3DArrayUint64: TypeAlias = Annotated[ 35 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint64]], FilePath, MultiArrayNumpyFile], 36 | NpArrayPydanticAnnotation.factory(data_type=np.uint64, dimensions=3, strict_data_typing=False), 37 | ] 38 | 39 | NpLoading3DArrayUint32: TypeAlias = Annotated[ 40 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint32]], FilePath, MultiArrayNumpyFile], 41 | NpArrayPydanticAnnotation.factory(data_type=np.uint32, dimensions=3, strict_data_typing=False), 42 | ] 43 | 44 | NpLoading3DArrayUint16: TypeAlias = Annotated[ 45 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint16]], FilePath, MultiArrayNumpyFile], 46 | NpArrayPydanticAnnotation.factory(data_type=np.uint16, dimensions=3, strict_data_typing=False), 47 | ] 48 | 49 | NpLoading3DArrayUint8: TypeAlias = Annotated[ 50 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint8]], FilePath, MultiArrayNumpyFile], 51 | NpArrayPydanticAnnotation.factory(data_type=np.uint8, dimensions=3, strict_data_typing=False), 52 | ] 53 | 54 | NpLoading3DArrayFpLongDouble: TypeAlias = Annotated[ 55 | Union[np.ndarray[tuple[int, ...], np.dtype[np.longdouble]], FilePath, MultiArrayNumpyFile], 56 | NpArrayPydanticAnnotation.factory(data_type=np.longdouble, dimensions=3, strict_data_typing=False), 57 | ] 58 | 59 | NpLoading3DArrayFp64: TypeAlias = Annotated[ 60 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float64]], FilePath, MultiArrayNumpyFile], 61 | NpArrayPydanticAnnotation.factory(data_type=np.float64, dimensions=3, strict_data_typing=False), 62 | ] 63 | 64 | NpLoading3DArrayFp32: TypeAlias = Annotated[ 65 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float32]], FilePath, MultiArrayNumpyFile], 66 | NpArrayPydanticAnnotation.factory(data_type=np.float32, dimensions=3, strict_data_typing=False), 67 | ] 68 | 69 | NpLoading3DArrayFp16: TypeAlias = Annotated[ 70 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float16]], FilePath, MultiArrayNumpyFile], 71 | NpArrayPydanticAnnotation.factory(data_type=np.float16, dimensions=3, strict_data_typing=False), 72 | ] 73 | 74 | NpLoading3DArrayComplexLongDouble: TypeAlias = Annotated[ 75 | Union[np.ndarray[tuple[int, ...], np.dtype[np.clongdouble]], FilePath, MultiArrayNumpyFile], 76 | NpArrayPydanticAnnotation.factory(data_type=np.clongdouble, dimensions=3, strict_data_typing=False), 77 | ] 78 | 79 | NpLoading3DArrayComplex128: TypeAlias = Annotated[ 80 | Union[np.ndarray[tuple[int, ...], np.dtype[np.complex128]], FilePath, MultiArrayNumpyFile], 81 | NpArrayPydanticAnnotation.factory(data_type=np.complex128, dimensions=3, strict_data_typing=False), 82 | ] 83 | 84 | NpLoading3DArrayComplex64: TypeAlias = Annotated[ 85 | Union[np.ndarray[tuple[int, ...], np.dtype[np.complex64]], FilePath, MultiArrayNumpyFile], 86 | NpArrayPydanticAnnotation.factory(data_type=np.complex64, dimensions=3, strict_data_typing=False), 87 | ] 88 | 89 | NpLoading3DArrayBool: TypeAlias = Annotated[ 90 | Union[np.ndarray[tuple[int, ...], np.dtype[np.bool_]], FilePath, MultiArrayNumpyFile], 91 | NpArrayPydanticAnnotation.factory(data_type=np.bool_, dimensions=3, strict_data_typing=False), 92 | ] 93 | 94 | NpLoading3DArrayDatetime64: TypeAlias = Annotated[ 95 | Union[np.ndarray[tuple[int, ...], np.dtype[np.datetime64]], FilePath, MultiArrayNumpyFile], 96 | NpArrayPydanticAnnotation.factory(data_type=np.datetime64, dimensions=3, strict_data_typing=False), 97 | ] 98 | 99 | NpLoading3DArrayTimedelta64: TypeAlias = Annotated[ 100 | Union[np.ndarray[tuple[int, ...], np.dtype[np.timedelta64]], FilePath, MultiArrayNumpyFile], 101 | NpArrayPydanticAnnotation.factory(data_type=np.timedelta64, dimensions=3, strict_data_typing=False), 102 | ] 103 | 104 | __all__ = [ 105 | "NpLoading3DArray", 106 | "NpLoading3DArrayInt64", 107 | "NpLoading3DArrayInt32", 108 | "NpLoading3DArrayInt16", 109 | "NpLoading3DArrayInt8", 110 | "NpLoading3DArrayUint64", 111 | "NpLoading3DArrayUint32", 112 | "NpLoading3DArrayUint16", 113 | "NpLoading3DArrayUint8", 114 | "NpLoading3DArrayFpLongDouble", 115 | "NpLoading3DArrayFp64", 116 | "NpLoading3DArrayFp32", 117 | "NpLoading3DArrayFp16", 118 | "NpLoading3DArrayComplexLongDouble", 119 | "NpLoading3DArrayComplex128", 120 | "NpLoading3DArrayComplex64", 121 | "NpLoading3DArrayBool", 122 | "NpLoading3DArrayDatetime64", 123 | "NpLoading3DArrayTimedelta64", 124 | ] 125 | -------------------------------------------------------------------------------- /pydantic_numpy/typing/with_loader/n_dimensional.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any, TypeAlias, Union 2 | 3 | import numpy as np 4 | from pydantic import FilePath 5 | 6 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 7 | from pydantic_numpy.model import MultiArrayNumpyFile 8 | 9 | NpLoadingNDArray: TypeAlias = Annotated[ 10 | Union[np.ndarray[tuple[int, ...], np.dtype[Any]], FilePath, MultiArrayNumpyFile], 11 | NpArrayPydanticAnnotation.factory(data_type=None, dimensions=None, strict_data_typing=False), 12 | ] 13 | 14 | NpLoadingNDArrayInt64: TypeAlias = Annotated[ 15 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int64]], FilePath, MultiArrayNumpyFile], 16 | NpArrayPydanticAnnotation.factory(data_type=np.int64, dimensions=None, strict_data_typing=False), 17 | ] 18 | 19 | NpLoadingNDArrayInt32: TypeAlias = Annotated[ 20 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int32]], FilePath, MultiArrayNumpyFile], 21 | NpArrayPydanticAnnotation.factory(data_type=np.int32, dimensions=None, strict_data_typing=False), 22 | ] 23 | 24 | NpLoadingNDArrayInt16: TypeAlias = Annotated[ 25 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int16]], FilePath, MultiArrayNumpyFile], 26 | NpArrayPydanticAnnotation.factory(data_type=np.int16, dimensions=None, strict_data_typing=False), 27 | ] 28 | 29 | NpLoadingNDArrayInt8: TypeAlias = Annotated[ 30 | Union[np.ndarray[tuple[int, ...], np.dtype[np.int8]], FilePath, MultiArrayNumpyFile], 31 | NpArrayPydanticAnnotation.factory(data_type=np.int8, dimensions=None, strict_data_typing=False), 32 | ] 33 | 34 | NpLoadingNDArrayUint64: TypeAlias = Annotated[ 35 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint64]], FilePath, MultiArrayNumpyFile], 36 | NpArrayPydanticAnnotation.factory(data_type=np.uint64, dimensions=None, strict_data_typing=False), 37 | ] 38 | 39 | NpLoadingNDArrayUint32: TypeAlias = Annotated[ 40 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint32]], FilePath, MultiArrayNumpyFile], 41 | NpArrayPydanticAnnotation.factory(data_type=np.uint32, dimensions=None, strict_data_typing=False), 42 | ] 43 | 44 | NpLoadingNDArrayUint16: TypeAlias = Annotated[ 45 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint16]], FilePath, MultiArrayNumpyFile], 46 | NpArrayPydanticAnnotation.factory(data_type=np.uint16, dimensions=None, strict_data_typing=False), 47 | ] 48 | 49 | NpLoadingNDArrayUint8: TypeAlias = Annotated[ 50 | Union[np.ndarray[tuple[int, ...], np.dtype[np.uint8]], FilePath, MultiArrayNumpyFile], 51 | NpArrayPydanticAnnotation.factory(data_type=np.uint8, dimensions=None, strict_data_typing=False), 52 | ] 53 | 54 | NpLoadingNDArrayFpLongDouble: TypeAlias = Annotated[ 55 | Union[np.ndarray[tuple[int, ...], np.dtype[np.longdouble]], FilePath, MultiArrayNumpyFile], 56 | NpArrayPydanticAnnotation.factory(data_type=np.longdouble, dimensions=None, strict_data_typing=False), 57 | ] 58 | 59 | NpLoadingNDArrayFp64: TypeAlias = Annotated[ 60 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float64]], FilePath, MultiArrayNumpyFile], 61 | NpArrayPydanticAnnotation.factory(data_type=np.float64, dimensions=None, strict_data_typing=False), 62 | ] 63 | 64 | NpLoadingNDArrayFp32: TypeAlias = Annotated[ 65 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float32]], FilePath, MultiArrayNumpyFile], 66 | NpArrayPydanticAnnotation.factory(data_type=np.float32, dimensions=None, strict_data_typing=False), 67 | ] 68 | 69 | NpLoadingNDArrayFp16: TypeAlias = Annotated[ 70 | Union[np.ndarray[tuple[int, ...], np.dtype[np.float16]], FilePath, MultiArrayNumpyFile], 71 | NpArrayPydanticAnnotation.factory(data_type=np.float16, dimensions=None, strict_data_typing=False), 72 | ] 73 | 74 | NpLoadingNDArrayComplexLongDouble: TypeAlias = Annotated[ 75 | Union[np.ndarray[tuple[int, ...], np.dtype[np.clongdouble]], FilePath, MultiArrayNumpyFile], 76 | NpArrayPydanticAnnotation.factory(data_type=np.clongdouble, dimensions=None, strict_data_typing=False), 77 | ] 78 | 79 | NpLoadingNDArrayComplex128: TypeAlias = Annotated[ 80 | Union[np.ndarray[tuple[int, ...], np.dtype[np.complex128]], FilePath, MultiArrayNumpyFile], 81 | NpArrayPydanticAnnotation.factory(data_type=np.complex128, dimensions=None, strict_data_typing=False), 82 | ] 83 | 84 | NpLoadingNDArrayComplex64: TypeAlias = Annotated[ 85 | Union[np.ndarray[tuple[int, ...], np.dtype[np.complex64]], FilePath, MultiArrayNumpyFile], 86 | NpArrayPydanticAnnotation.factory(data_type=np.complex64, dimensions=None, strict_data_typing=False), 87 | ] 88 | 89 | NpLoadingNDArrayBool: TypeAlias = Annotated[ 90 | Union[np.ndarray[tuple[int, ...], np.dtype[np.bool_]], FilePath, MultiArrayNumpyFile], 91 | NpArrayPydanticAnnotation.factory(data_type=np.bool_, dimensions=None, strict_data_typing=False), 92 | ] 93 | 94 | NpLoadingNDArrayDatetime64: TypeAlias = Annotated[ 95 | Union[np.ndarray[tuple[int, ...], np.dtype[np.datetime64]], FilePath, MultiArrayNumpyFile], 96 | NpArrayPydanticAnnotation.factory(data_type=np.datetime64, dimensions=None, strict_data_typing=False), 97 | ] 98 | 99 | NpLoadingNDArrayTimedelta64: TypeAlias = Annotated[ 100 | Union[np.ndarray[tuple[int, ...], np.dtype[np.timedelta64]], FilePath, MultiArrayNumpyFile], 101 | NpArrayPydanticAnnotation.factory(data_type=np.timedelta64, dimensions=None, strict_data_typing=False), 102 | ] 103 | 104 | __all__ = [ 105 | "NpLoadingNDArray", 106 | "NpLoadingNDArrayInt64", 107 | "NpLoadingNDArrayInt32", 108 | "NpLoadingNDArrayInt16", 109 | "NpLoadingNDArrayInt8", 110 | "NpLoadingNDArrayUint64", 111 | "NpLoadingNDArrayUint32", 112 | "NpLoadingNDArrayUint16", 113 | "NpLoadingNDArrayUint8", 114 | "NpLoadingNDArrayFpLongDouble", 115 | "NpLoadingNDArrayFp64", 116 | "NpLoadingNDArrayFp32", 117 | "NpLoadingNDArrayFp16", 118 | "NpLoadingNDArrayComplexLongDouble", 119 | "NpLoadingNDArrayComplex128", 120 | "NpLoadingNDArrayComplex64", 121 | "NpLoadingNDArrayBool", 122 | "NpLoadingNDArrayDatetime64", 123 | "NpLoadingNDArrayTimedelta64", 124 | ] 125 | -------------------------------------------------------------------------------- /pydantic_numpy/util.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.typing as npt 3 | from numpy._core._exceptions import UFuncTypeError 4 | from semver import Version 5 | 6 | 7 | def np_general_all_close(arr_a: npt.NDArray, arr_b: npt.NDArray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: 8 | """ 9 | Data type agnostic function to define if two numpy array have elements that are close 10 | 11 | Parameters 12 | ---------- 13 | arr_a: npt.NDArray 14 | arr_b: npt.NDArray 15 | rtol: float 16 | See np.allclose 17 | atol: float 18 | See np.allclose 19 | 20 | Returns 21 | ------- 22 | Bool 23 | """ 24 | return _np_general_all_close(arr_a, arr_b, rtol, atol) 25 | 26 | 27 | if Version.parse(np.version.version) < Version.parse("1.25.0"): 28 | 29 | def _np_general_all_close(arr_a: npt.NDArray, arr_b: npt.NDArray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: 30 | try: 31 | return np.allclose(arr_a, arr_b, rtol=rtol, atol=atol, equal_nan=True) 32 | except UFuncTypeError: 33 | return np.allclose(arr_a.astype(np.float64), arr_b.astype(np.float64), rtol=rtol, atol=atol, equal_nan=True) 34 | except TypeError: 35 | return bool(np.all(arr_a == arr_b)) 36 | 37 | else: 38 | from numpy.exceptions import DTypePromotionError 39 | 40 | def _np_general_all_close(arr_a: npt.NDArray, arr_b: npt.NDArray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: 41 | try: 42 | return np.allclose(arr_a, arr_b, rtol=rtol, atol=atol, equal_nan=True) 43 | except UFuncTypeError: 44 | return np.allclose(arr_a.astype(np.float64), arr_b.astype(np.float64), rtol=rtol, atol=atol, equal_nan=True) 45 | except DTypePromotionError: 46 | return bool(np.all(arr_a == arr_b)) 47 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pydantic_numpy" 3 | version = "8.0.1" 4 | description = "Pydantic Model integration of the NumPy array" 5 | authors = ["Can H. Tartanoglu", "Christoph Heindl"] 6 | maintainers = ["Can H. Tartanoglu "] 7 | readme = "README.md" 8 | homepage = "https://github.com/caniko/pydantic-numpy" 9 | license = "BSD-3-Clause" 10 | 11 | keywords = ["pydantic", "numpy", "typing", "validation"] 12 | classifiers = [ 13 | "Development Status :: 5 - Production/Stable", 14 | "Topic :: Software Development :: Libraries :: Python Modules", 15 | "Operating System :: OS Independent" 16 | ] 17 | 18 | packages = [{include = "pydantic_numpy"}] 19 | 20 | [tool.poetry.dependencies] 21 | python = ">=3.10,<3.14" 22 | compress-pickle = { version = "*", extras = ["lz4"] } 23 | ruamel-yaml = "^0.18.5" 24 | 25 | numpy = "^2" 26 | pydantic = "^2.0" 27 | semver = "^3.0.1" 28 | 29 | [tool.poetry.group.dev.dependencies] 30 | pytest = "^7.4.0" 31 | parameterized = "^0.9.0" 32 | orjson = "*" 33 | coverage = "^7.5.1" 34 | 35 | [tool.poetry.group.format.dependencies] 36 | black = "^23.7.0" 37 | isort = "^5.12.0" 38 | ruff = "*" 39 | 40 | [tool.poetry.group.typecheck.dependencies] 41 | mypy = "*" 42 | pyright = "^1.1.338" 43 | 44 | [tool.pytest.ini_options] 45 | filterwarnings = [ 46 | "ignore:invalid value encountered in multiply:RuntimeWarning", 47 | ] 48 | 49 | [tool.black] 50 | line-length = 120 51 | target-version = ["py312"] 52 | 53 | [tool.isort] 54 | profile = "black" 55 | 56 | [tool.ruff] 57 | line-length = 120 58 | lint.ignore = ["F403", "F405"] 59 | 60 | [tool.pyright] 61 | include = ["pydantic_numpy/**", "tests/**"] 62 | reportUnsupportedDunderAll = false 63 | 64 | [tool.mypy] 65 | ignore_missing_imports = true 66 | packages = ["pydantic_numpy"] 67 | 68 | [build-system] 69 | requires = ["poetry-core>=1.0.0"] 70 | build-backend = "poetry.core.masonry.api" 71 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caniko/pydantic-numpy/d0c7f4c71f330c58348d4799689283245d9c8f2e/tests/__init__.py -------------------------------------------------------------------------------- /tests/helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caniko/pydantic-numpy/d0c7f4c71f330c58348d4799689283245d9c8f2e/tests/helper/__init__.py -------------------------------------------------------------------------------- /tests/helper/cache.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | @lru_cache 7 | def get_numpy_type_model(array_type_hint) -> type[BaseModel]: 8 | class ModelForTesting(BaseModel): 9 | array_field: array_type_hint 10 | 11 | return ModelForTesting 12 | -------------------------------------------------------------------------------- /tests/helper/testing_groups.py: -------------------------------------------------------------------------------- 1 | import platform 2 | 3 | import numpy as np 4 | 5 | from pydantic_numpy.typing import * 6 | 7 | supported_data_types = ( 8 | # (np.array([0]), np.int64), # Windows confuses int64 with int32 9 | (np.array([0], dtype=np.int32), np.int32), 10 | (np.array([0], dtype=np.int16), np.int16), 11 | (np.array([0], dtype=np.int8), np.int8), 12 | (np.array([0], dtype=np.uint64), np.uint64), 13 | (np.array([0], dtype=np.uint32), np.uint32), 14 | (np.array([0], dtype=np.uint16), np.uint16), 15 | (np.array([0], dtype=np.uint8), np.uint8), 16 | (np.array([0.0]), np.float64), 17 | (np.array([0.0], dtype=np.float32), np.float32), 18 | (np.array([0.0], dtype=np.float16), np.float16), 19 | (np.array([0.0 + 0.0j]), np.complex128), 20 | (np.array([0.0 + 0.0j], dtype=np.complex64), np.complex64), 21 | (np.array([False]), np.bool_), 22 | (np.array([0], dtype=np.timedelta64), np.timedelta64), 23 | ) 24 | 25 | data_type_1d_array_typing_dimensions = [ 26 | (np.array([0]), np.int64, Np1DArrayInt64, 1), 27 | (np.array([0], dtype=np.int32), np.int32, Np1DArrayInt32, 1), 28 | (np.array([0], dtype=np.int16), np.int16, Np1DArrayInt16, 1), 29 | (np.array([0], dtype=np.int8), np.int8, Np1DArrayInt8, 1), 30 | (np.array([0], dtype=np.uint64), np.uint64, Np1DArrayUint64, 1), 31 | (np.array([0], dtype=np.uint32), np.uint32, Np1DArrayUint32, 1), 32 | (np.array([0], dtype=np.uint16), np.uint16, Np1DArrayUint16, 1), 33 | (np.array([0], dtype=np.uint8), np.uint8, Np1DArrayUint8, 1), 34 | (np.array([0.0]), np.float64, Np1DArrayFp64, 1), 35 | (np.array([0.0], dtype=np.float32), np.float32, Np1DArrayFp32, 1), 36 | (np.array([0.0], dtype=np.float16), np.float16, Np1DArrayFp16, 1), 37 | (np.array([0.0 + 0.0j]), np.complex128, Np1DArrayComplex128, 1), 38 | (np.array([0.0 + 0.0j], dtype=np.complex64), np.complex64, Np1DArrayComplex64, 1), 39 | (np.array([False]), np.bool_, Np1DArrayBool, 1), 40 | (np.array([0], dtype=np.timedelta64), np.timedelta64, Np1DArrayTimedelta64, 1), 41 | ] 42 | data_type_2d_array_typing_dimensions = [ 43 | (np.array([[0]]), np.int64, Np2DArrayInt64, 2), 44 | (np.array([[0]], dtype=np.int32), np.int32, Np2DArrayInt32, 2), 45 | (np.array([[0]], dtype=np.int16), np.int16, Np2DArrayInt16, 2), 46 | (np.array([[0]], dtype=np.int8), np.int8, Np2DArrayInt8, 2), 47 | (np.array([[0]], dtype=np.uint64), np.uint64, Np2DArrayUint64, 2), 48 | (np.array([[0]], dtype=np.uint32), np.uint32, Np2DArrayUint32, 2), 49 | (np.array([[0]], dtype=np.uint16), np.uint16, Np2DArrayUint16, 2), 50 | (np.array([[0]], dtype=np.uint8), np.uint8, Np2DArrayUint8, 2), 51 | (np.array([[0.0]]), np.float64, Np2DArrayFp64, 2), 52 | (np.array([[0.0]], dtype=np.float32), np.float32, Np2DArrayFp32, 2), 53 | (np.array([[0.0]], dtype=np.float16), np.float16, Np2DArrayFp16, 2), 54 | (np.array([[0.0 + 0.0j]]), np.complex128, Np2DArrayComplex128, 2), 55 | (np.array([[0.0 + 0.0j]], dtype=np.complex64), np.complex64, Np2DArrayComplex64, 2), 56 | (np.array([[False]]), np.bool_, Np2DArrayBool, 2), 57 | (np.array([[0]], dtype=np.timedelta64), np.timedelta64, Np2DArrayTimedelta64, 2), 58 | ] 59 | data_type_3d_array_typing_dimensions = [ 60 | (np.array([[[0]]]), np.int64, Np3DArrayInt64, 3), 61 | (np.array([[[0]]], dtype=np.int32), np.int32, Np3DArrayInt32, 3), 62 | (np.array([[[0]]], dtype=np.int16), np.int16, Np3DArrayInt16, 3), 63 | (np.array([[[0]]], dtype=np.int8), np.int8, Np3DArrayInt8, 3), 64 | (np.array([[[0]]], dtype=np.uint64), np.uint64, Np3DArrayUint64, 3), 65 | (np.array([[[0]]], dtype=np.uint32), np.uint32, Np3DArrayUint32, 3), 66 | (np.array([[[0]]], dtype=np.uint16), np.uint16, Np3DArrayUint16, 3), 67 | (np.array([[[0]]], dtype=np.uint8), np.uint8, Np3DArrayUint8, 3), 68 | (np.array([[[0.0]]]), np.float64, Np3DArrayFp64, 3), 69 | (np.array([[[0.0]]], dtype=np.float32), np.float32, Np3DArrayFp32, 3), 70 | (np.array([[[0.0]]], dtype=np.float16), np.float16, Np3DArrayFp16, 3), 71 | (np.array([[[0.0 + 0.0j]]]), np.complex128, Np3DArrayComplex128, 3), 72 | (np.array([[[0.0 + 0.0j]]], dtype=np.complex64), np.complex64, Np3DArrayComplex64, 3), 73 | (np.array([[[False]]]), np.bool_, Np3DArrayBool, 3), 74 | (np.array([[[0]]], dtype=np.timedelta64), np.timedelta64, Np3DArrayTimedelta64, 3), 75 | ] 76 | data_type_nd_array_typing_dimensions_without_complex = [ 77 | (np.array([0]), np.int64, NpNDArrayInt64, None), 78 | (np.array([0], dtype=np.int32), np.int32, NpNDArrayInt32, None), 79 | (np.array([0], dtype=np.int16), np.int16, NpNDArrayInt16, None), 80 | (np.array([0], dtype=np.int8), np.int8, NpNDArrayInt8, None), 81 | (np.array([0], dtype=np.uint64), np.uint64, NpNDArrayUint64, None), 82 | (np.array([0], dtype=np.uint32), np.uint32, NpNDArrayUint32, None), 83 | (np.array([0], dtype=np.uint16), np.uint16, NpNDArrayUint16, None), 84 | (np.array([0], dtype=np.uint8), np.uint8, NpNDArrayUint8, None), 85 | (np.array([0.0]), np.float64, NpNDArrayFp64, None), 86 | (np.array([0.0], dtype=np.float32), np.float32, NpNDArrayFp32, None), 87 | (np.array([0.0], dtype=np.float16), np.float16, NpNDArrayFp16, None), 88 | (np.array([False]), np.bool_, NpNDArrayBool, None), 89 | (np.array([0], dtype=np.timedelta64), np.timedelta64, NpNDArrayTimedelta64, None), 90 | ] 91 | 92 | data_type_nd_array_typing_dimensions = [ 93 | *data_type_nd_array_typing_dimensions_without_complex, 94 | (np.array([0.0 + 0.0j]), np.complex128, NpNDArrayComplex128, None), 95 | (np.array([0.0 + 0.0j], dtype=np.complex64), np.complex64, NpNDArrayComplex64, None), 96 | ] 97 | 98 | data_type_array_typing_dimensions = [ 99 | *data_type_1d_array_typing_dimensions, 100 | *data_type_2d_array_typing_dimensions, 101 | *data_type_3d_array_typing_dimensions, 102 | *data_type_nd_array_typing_dimensions, 103 | ] 104 | 105 | # Data type strict 106 | type_safe_data_type_1d_array_typing_dimensions = [ 107 | (np.array([0]), np.int64, Np1DArrayInt64, 1), 108 | (np.array([0], dtype=np.int32), np.int32, Np1DArrayInt32, 1), 109 | (np.array([0], dtype=np.int16), np.int16, Np1DArrayInt16, 1), 110 | (np.array([0], dtype=np.int8), np.int8, Np1DArrayInt8, 1), 111 | (np.array([0], dtype=np.uint64), np.uint64, Np1DArrayUint64, 1), 112 | (np.array([0], dtype=np.uint32), np.uint32, Np1DArrayUint32, 1), 113 | (np.array([0], dtype=np.uint16), np.uint16, Np1DArrayUint16, 1), 114 | (np.array([0], dtype=np.uint8), np.uint8, Np1DArrayUint8, 1), 115 | (np.array([0.0]), np.float64, Np1DArrayFp64, 1), 116 | (np.array([0.0], dtype=np.float32), np.float32, Np1DArrayFp32, 1), 117 | (np.array([0.0], dtype=np.float16), np.float16, Np1DArrayFp16, 1), 118 | (np.array([0.0 + 0.0j]), np.complex128, Np1DArrayComplex128, 1), 119 | (np.array([0.0 + 0.0j], dtype=np.complex64), np.complex64, Np1DArrayComplex64, 1), 120 | (np.array([False]), np.bool_, Np1DArrayBool, 1), 121 | (np.array([0], dtype=np.timedelta64), np.timedelta64, Np1DArrayTimedelta64, 1), 122 | ] 123 | type_safe_data_type_2d_array_typing_dimensions = [ 124 | (np.array([[0]]), np.int64, Np2DArrayInt64, 2), 125 | (np.array([[0]], dtype=np.int32), np.int32, Np2DArrayInt32, 2), 126 | (np.array([[0]], dtype=np.int16), np.int16, Np2DArrayInt16, 2), 127 | (np.array([[0]], dtype=np.int8), np.int8, Np2DArrayInt8, 2), 128 | (np.array([[0]], dtype=np.uint64), np.uint64, Np2DArrayUint64, 2), 129 | (np.array([[0]], dtype=np.uint32), np.uint32, Np2DArrayUint32, 2), 130 | (np.array([[0]], dtype=np.uint16), np.uint16, Np2DArrayUint16, 2), 131 | (np.array([[0]], dtype=np.uint8), np.uint8, Np2DArrayUint8, 2), 132 | (np.array([[0.0]]), np.float64, Np2DArrayFp64, 2), 133 | (np.array([[0.0]], dtype=np.float32), np.float32, Np2DArrayFp32, 2), 134 | (np.array([[0.0]], dtype=np.float16), np.float16, Np2DArrayFp16, 2), 135 | (np.array([[0.0 + 0.0j]]), np.complex128, Np2DArrayComplex128, 2), 136 | (np.array([[0.0 + 0.0j]], dtype=np.complex64), np.complex64, Np2DArrayComplex64, 2), 137 | (np.array([[False]]), np.bool_, Np2DArrayBool, 2), 138 | (np.array([[0]], dtype=np.timedelta64), np.timedelta64, Np2DArrayTimedelta64, 2), 139 | ] 140 | type_safe_data_type_3d_array_typing_dimensions = [ 141 | (np.array([[[0]]]), np.int64, Np3DArrayInt64, 3), 142 | (np.array([[[0]]], dtype=np.int32), np.int32, Np3DArrayInt32, 3), 143 | (np.array([[[0]]], dtype=np.int16), np.int16, Np3DArrayInt16, 3), 144 | (np.array([[[0]]], dtype=np.int8), np.int8, Np3DArrayInt8, 3), 145 | (np.array([[[0]]], dtype=np.uint64), np.uint64, Np3DArrayUint64, 3), 146 | (np.array([[[0]]], dtype=np.uint32), np.uint32, Np3DArrayUint32, 3), 147 | (np.array([[[0]]], dtype=np.uint16), np.uint16, Np3DArrayUint16, 3), 148 | (np.array([[[0]]], dtype=np.uint8), np.uint8, Np3DArrayUint8, 3), 149 | (np.array([[[0.0]]]), np.float64, Np3DArrayFp64, 3), 150 | (np.array([[[0.0]]], dtype=np.float32), np.float32, Np3DArrayFp32, 3), 151 | (np.array([[[0.0]]], dtype=np.float16), np.float16, Np3DArrayFp16, 3), 152 | (np.array([[[0.0 + 0.0j]]]), np.complex128, Np3DArrayComplex128, 3), 153 | (np.array([[[0.0 + 0.0j]]], dtype=np.complex64), np.complex64, Np3DArrayComplex64, 3), 154 | (np.array([[[False]]]), np.bool_, Np3DArrayBool, 3), 155 | (np.array([[[0]]], dtype=np.timedelta64), np.timedelta64, Np3DArrayTimedelta64, 3), 156 | ] 157 | type_safe_data_type_nd_array_typing_dimensions = [ 158 | (np.array([0]), np.int64, NpNDArrayInt64, None), 159 | (np.array([0], dtype=np.int32), np.int32, NpNDArrayInt32, None), 160 | (np.array([0], dtype=np.int16), np.int16, NpNDArrayInt16, None), 161 | (np.array([0], dtype=np.int8), np.int8, NpNDArrayInt8, None), 162 | (np.array([0], dtype=np.uint64), np.uint64, NpNDArrayUint64, None), 163 | (np.array([0], dtype=np.uint32), np.uint32, NpNDArrayUint32, None), 164 | (np.array([0], dtype=np.uint16), np.uint16, NpNDArrayUint16, None), 165 | (np.array([0], dtype=np.uint8), np.uint8, NpNDArrayUint8, None), 166 | (np.array([0.0]), np.float64, NpNDArrayFp64, None), 167 | (np.array([0.0], dtype=np.float32), np.float32, NpNDArrayFp32, None), 168 | (np.array([0.0], dtype=np.float16), np.float16, NpNDArrayFp16, None), 169 | (np.array([0.0 + 0.0j]), np.complex128, NpNDArrayComplex128, None), 170 | (np.array([0.0 + 0.0j], dtype=np.complex64), np.complex64, NpNDArrayComplex64, None), 171 | (np.array([False]), np.bool_, NpNDArrayBool, None), 172 | (np.array([0], dtype=np.timedelta64), np.timedelta64, NpNDArrayTimedelta64, None), 173 | ] 174 | 175 | type_safe_data_type_array_typing_dimensions = [ 176 | *type_safe_data_type_1d_array_typing_dimensions, 177 | *type_safe_data_type_2d_array_typing_dimensions, 178 | *type_safe_data_type_3d_array_typing_dimensions, 179 | *type_safe_data_type_nd_array_typing_dimensions, 180 | ] 181 | 182 | dimension_testing_group = [ 183 | (np.array([0]), np.int64, Np1DArrayInt64, 1), 184 | (np.array([[0]]), np.int64, Np2DArrayInt64, 2), 185 | (np.array([[[0]]]), np.int64, Np3DArrayInt64, 3), 186 | ] 187 | 188 | if platform.system() != "Windows": 189 | 190 | def get_type_safe_data_type_nd_array_typing_dimensions_128_bit(): 191 | return [ 192 | (np.array([0.0], dtype=np.float128), np.float128, NpNDArrayFpLongDouble, None), 193 | (np.array([0.0 + 0.0j], dtype=np.complex256), np.complex256, NpNDArrayComplexLongDouble, None), 194 | ] 195 | -------------------------------------------------------------------------------- /tests/model.py: -------------------------------------------------------------------------------- 1 | from pydantic_numpy.model import NumpyModel 2 | from pydantic_numpy.typing import Np1DArray, NpNDArray 3 | 4 | 5 | class NpNDArrayModel(NumpyModel): 6 | array: NpNDArray 7 | 8 | 9 | class N1DArrayModel(NumpyModel): 10 | array: Np1DArray 11 | 12 | 13 | class NpNDArrayModelWithNonArray(NpNDArrayModel): 14 | non_array: int 15 | 16 | 17 | class NpNDArrayModelWithNonArrayWithArbitrary(NpNDArrayModelWithNonArray, arbitrary_types_allowed=True): 18 | my_arbitrary_slice: slice 19 | -------------------------------------------------------------------------------- /tests/test_annotation.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any 2 | 3 | import numpy as np 4 | import orjson 5 | from pydantic import BaseModel 6 | from typing_extensions import TypeAlias 7 | 8 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 9 | 10 | 11 | def _custom_serializer(array: np.ndarray) -> list[float]: 12 | return array.astype(float).tolist() 13 | 14 | 15 | _Np1DArray: TypeAlias = Annotated[ 16 | np.ndarray[tuple[int], np.dtype[Any]], 17 | NpArrayPydanticAnnotation.factory( 18 | data_type=None, dimensions=1, strict_data_typing=False, serialize_numpy_array_to_json=_custom_serializer 19 | ), 20 | ] 21 | 22 | 23 | def test_custom_serializer(): 24 | class FooModel(BaseModel): 25 | arr: _Np1DArray 26 | 27 | foo_model = FooModel(arr=np.zeros(42)) 28 | 29 | model_dict = orjson.loads(foo_model.model_dump_json()) 30 | 31 | assert "arr" in model_dict 32 | assert isinstance(model_dict["arr"], list) 33 | assert len(model_dict["arr"]) == 42 34 | -------------------------------------------------------------------------------- /tests/test_np_model.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import tempfile 3 | from pathlib import Path 4 | 5 | import numpy as np 6 | import pytest 7 | 8 | from pydantic_numpy.model import NumpyModel, model_agnostic_load 9 | from pydantic_numpy.typing import NpNDArray 10 | from tests.model import ( 11 | NpNDArrayModelWithNonArray, 12 | NpNDArrayModelWithNonArrayWithArbitrary, 13 | ) 14 | 15 | TEST_MODEL_OBJECT_ID = "test" 16 | OTHER_TEST_MODEL_OBJECT_ID = "other_test" 17 | NON_ARRAY_VALUE = 5 18 | 19 | 20 | @pytest.fixture 21 | def numpy_model() -> NpNDArrayModelWithNonArray: 22 | return NpNDArrayModelWithNonArray(array=np.array([0.0]), non_array=NON_ARRAY_VALUE) 23 | 24 | 25 | @pytest.fixture 26 | def numpy_model_with_arbitrary() -> NpNDArrayModelWithNonArrayWithArbitrary: 27 | return NpNDArrayModelWithNonArrayWithArbitrary( 28 | array=np.array([0.0]), non_array=NON_ARRAY_VALUE, my_arbitrary_slice=slice(0, 1) 29 | ) 30 | 31 | 32 | if platform.system() != "Windows": 33 | 34 | def test_io_yaml(numpy_model: NpNDArrayModelWithNonArray) -> None: 35 | with tempfile.TemporaryDirectory() as tmp_dirname: 36 | numpy_model.dump(Path(tmp_dirname), TEST_MODEL_OBJECT_ID) 37 | assert numpy_model.load(Path(tmp_dirname), TEST_MODEL_OBJECT_ID) == numpy_model 38 | 39 | def test_io_compressed_pickle(numpy_model_with_arbitrary: NpNDArrayModelWithNonArray) -> None: 40 | with tempfile.TemporaryDirectory() as tmp_dirname: 41 | numpy_model_with_arbitrary.dump(Path(tmp_dirname), TEST_MODEL_OBJECT_ID, pickle=True) 42 | assert ( 43 | numpy_model_with_arbitrary.load(Path(tmp_dirname), TEST_MODEL_OBJECT_ID) == numpy_model_with_arbitrary 44 | ) 45 | 46 | def test_io_pickle(numpy_model_with_arbitrary: NpNDArrayModelWithNonArray) -> None: 47 | with tempfile.TemporaryDirectory() as tmp_dirname: 48 | numpy_model_with_arbitrary.dump(Path(tmp_dirname), TEST_MODEL_OBJECT_ID, pickle=True, compress=False) 49 | assert ( 50 | numpy_model_with_arbitrary.load(Path(tmp_dirname), TEST_MODEL_OBJECT_ID) == numpy_model_with_arbitrary 51 | ) 52 | 53 | def test_model_agnostic_load(): 54 | class NumpyModelAForTest(NpNDArrayModelWithNonArray): 55 | array: NpNDArray 56 | non_array: int 57 | 58 | class NumpyModelBForTest(NpNDArrayModelWithNonArray): 59 | array: NpNDArray 60 | non_array: int 61 | 62 | model_a = NumpyModelAForTest(array=np.array([0.0]), non_array=NON_ARRAY_VALUE) 63 | model_b = NumpyModelBForTest(array=np.array([0.0]), non_array=NON_ARRAY_VALUE) 64 | 65 | with tempfile.TemporaryDirectory() as tmp_dirname: 66 | tmp_dir_path = Path(tmp_dirname) 67 | 68 | model_a.dump(tmp_dir_path, TEST_MODEL_OBJECT_ID) 69 | model_b.dump(tmp_dir_path, OTHER_TEST_MODEL_OBJECT_ID) 70 | 71 | models = [NumpyModelAForTest, NumpyModelBForTest] 72 | assert model_a == model_agnostic_load(tmp_dir_path, TEST_MODEL_OBJECT_ID, models=models) 73 | assert model_b == model_agnostic_load(tmp_dir_path, OTHER_TEST_MODEL_OBJECT_ID, models=models) 74 | 75 | def test_simple_eq(numpy_model: NpNDArrayModelWithNonArray) -> None: 76 | assert numpy_model == numpy_model 77 | 78 | def test_not_eq_different_fields(numpy_model, numpy_model_with_arbitrary) -> None: 79 | assert numpy_model != numpy_model_with_arbitrary 80 | 81 | class AnotherModel(NumpyModel): 82 | yarra: NpNDArray 83 | 84 | assert numpy_model != AnotherModel(yarra=np.array([0.0])) 85 | 86 | def test_not_eq_different_inner(numpy_model: NpNDArrayModelWithNonArray) -> None: 87 | assert numpy_model != NpNDArrayModelWithNonArray(array=np.array([1.0]), non_array=NON_ARRAY_VALUE) 88 | 89 | def test_not_eq_different_shape(numpy_model: NpNDArrayModelWithNonArray) -> None: 90 | assert numpy_model != NpNDArrayModelWithNonArray(array=np.array([0.0, 1.0]), non_array=NON_ARRAY_VALUE) 91 | 92 | def test_random_not_eq(numpy_model: NpNDArrayModelWithNonArray) -> None: 93 | for r in (0, 5, 1.0, "1"): 94 | assert numpy_model != r 95 | 96 | def test_serde_eq(numpy_model: NpNDArrayModelWithNonArray) -> None: 97 | ser = numpy_model.model_dump_json() 98 | reread_data = numpy_model.model_validate_json(ser) 99 | 100 | assert numpy_model == reread_data 101 | -------------------------------------------------------------------------------- /tests/test_pydantic.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | from tests.model import N1DArrayModel, NpNDArrayModel, NpNDArrayModelWithNonArray 6 | 7 | test_model_instance = NpNDArrayModelWithNonArray(array=np.zeros(10), non_array=2) 8 | 9 | 10 | class TestModelValidation(unittest.TestCase): 11 | def test_model_json_schema_np_nd_array_model(self): 12 | schema = NpNDArrayModel.model_json_schema() 13 | expected = { 14 | "properties": { 15 | "array": { 16 | "properties": { 17 | "data_type": {"default": "Any", "title": "dtype", "type": "string"}, 18 | "data": {"items_schema": {"type": "any"}, "type": "list"}, 19 | }, 20 | "required": ["data_type", "data"], 21 | "title": "Numpy Array", 22 | "type": "np.ndarray[Any, np.dtype[Any]]", 23 | } 24 | }, 25 | "required": ["array"], 26 | "title": "NpNDArrayModel", 27 | "type": "object", 28 | } 29 | self.assertEqual(schema, expected) 30 | 31 | def test_model_json_schema_np_1d_array_model(self): 32 | schema = N1DArrayModel.model_json_schema() 33 | expected = { 34 | "properties": { 35 | "array": { 36 | "properties": { 37 | "data_type": {"default": "Any", "title": "dtype", "type": "string"}, 38 | "data": { 39 | "items_schema": {"items_schema": {"type": "any"}, "type": "list"}, 40 | "max_length": 1, 41 | "min_length": 1, 42 | "type": "list", 43 | }, 44 | }, 45 | "required": ["data_type", "data"], 46 | "title": "Numpy Array", 47 | "type": "np.ndarray[tuple[int], np.dtype[Any]]", 48 | } 49 | }, 50 | "required": ["array"], 51 | "title": "N1DArrayModel", 52 | "type": "object", 53 | } 54 | self.assertEqual(schema, expected) 55 | 56 | def test_validate_json(self): 57 | json_str = test_model_instance.model_dump_json() 58 | self.assertTrue(test_model_instance.model_validate_json(json_str)) 59 | -------------------------------------------------------------------------------- /tests/test_typing.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import tempfile 3 | from pathlib import Path 4 | from typing import Optional 5 | 6 | import numpy as np 7 | import numpy.typing as npt 8 | import orjson 9 | import pytest 10 | from numpy.testing import assert_almost_equal 11 | from pydantic import ValidationError 12 | 13 | from pydantic_numpy.helper.validation import PydanticNumpyMultiArrayNumpyFileOnFilePath 14 | from pydantic_numpy.model import MultiArrayNumpyFile 15 | from pydantic_numpy.typing import Np1DArrayInt64 16 | from pydantic_numpy.util import np_general_all_close 17 | from tests.helper.cache import get_numpy_type_model 18 | from tests.helper.testing_groups import ( 19 | data_type_array_typing_dimensions, 20 | data_type_nd_array_typing_dimensions_without_complex, 21 | supported_data_types, 22 | type_safe_data_type_nd_array_typing_dimensions, 23 | ) 24 | 25 | 26 | @pytest.mark.parametrize("numpy_array, numpy_dtype, pydantic_typing, dimensions", data_type_array_typing_dimensions) 27 | def test_correct_type( 28 | numpy_array: npt.ArrayLike, numpy_dtype: npt.DTypeLike, pydantic_typing, dimensions: Optional[int] 29 | ): 30 | assert get_numpy_type_model(pydantic_typing)(array_field=numpy_array) 31 | 32 | 33 | @pytest.mark.parametrize( 34 | "numpy_array, numpy_dtype, pydantic_typing, dimensions", type_safe_data_type_nd_array_typing_dimensions 35 | ) 36 | @pytest.mark.parametrize("bad_numpy_array, wrong_numpy_type", supported_data_types) 37 | def test_wrong_dtype_type( 38 | numpy_array: npt.ArrayLike, 39 | numpy_dtype: npt.DTypeLike, 40 | pydantic_typing, 41 | dimensions: Optional[int], 42 | bad_numpy_array: npt.ArrayLike, 43 | wrong_numpy_type: npt.DTypeLike, 44 | ): 45 | if wrong_numpy_type == numpy_dtype: 46 | return 47 | 48 | with pytest.raises(ValidationError): 49 | get_numpy_type_model(pydantic_typing)(array_field=bad_numpy_array) 50 | 51 | 52 | def test_wrong_dimension(): 53 | with pytest.raises(ValueError): 54 | get_numpy_type_model(Np1DArrayInt64)(array_field=np.array([[0]])) 55 | 56 | 57 | if platform.system() == "Linux": 58 | from tests.helper.testing_groups import ( 59 | get_type_safe_data_type_nd_array_typing_dimensions_128_bit, 60 | ) 61 | 62 | @pytest.mark.parametrize( 63 | "numpy_array, numpy_dtype, pydantic_typing, dimensions", data_type_nd_array_typing_dimensions_without_complex 64 | ) 65 | def test_json_serialize_deserialize( 66 | numpy_array: npt.ArrayLike, numpy_dtype: npt.DTypeLike, pydantic_typing, dimensions: Optional[int] 67 | ): 68 | numpy_model = get_numpy_type_model(pydantic_typing) 69 | dumped_model_json_loaded = orjson.loads(numpy_model(array_field=numpy_array).model_dump_json()) 70 | 71 | round_trip_result = numpy_model(array_field=dumped_model_json_loaded["array_field"]).array_field 72 | 73 | if issubclass(numpy_dtype, np.timedelta64) or issubclass(numpy_dtype, np.datetime64): 74 | assert np.all(numpy_array == round_trip_result) 75 | else: 76 | assert_almost_equal(numpy_array, round_trip_result) 77 | 78 | @pytest.mark.parametrize("numpy_array, numpy_dtype, pydantic_typing, dimensions", data_type_array_typing_dimensions) 79 | def test_file_path_passing_validation( 80 | numpy_array: npt.ArrayLike, numpy_dtype: npt.DTypeLike, pydantic_typing, dimensions: Optional[int] 81 | ): 82 | with tempfile.NamedTemporaryFile(mode="w+", delete=True, suffix=".npz") as tf: 83 | np.savez_compressed(tf.name, my_array=numpy_array) 84 | numpy_model = get_numpy_type_model(pydantic_typing)(array_field=Path(tf.name)) 85 | 86 | assert np_general_all_close(numpy_model.array_field, numpy_array) 87 | 88 | @pytest.mark.parametrize("numpy_array, numpy_dtype, pydantic_typing, dimensions", data_type_array_typing_dimensions) 89 | def test_file_path_error_on_reading_single_array_file( 90 | numpy_array: npt.ArrayLike, numpy_dtype: npt.DTypeLike, pydantic_typing, dimensions: Optional[int] 91 | ): 92 | with tempfile.NamedTemporaryFile(mode="w+", delete=True, suffix=".npz") as tf: 93 | np.savez_compressed(tf.name, my_array=numpy_array, my_identical_array=numpy_array) 94 | model = get_numpy_type_model(pydantic_typing) 95 | 96 | with pytest.raises(PydanticNumpyMultiArrayNumpyFileOnFilePath): 97 | model(array_field=Path(tf.name)) 98 | 99 | @pytest.mark.parametrize("numpy_array, numpy_dtype, pydantic_typing, dimensions", data_type_array_typing_dimensions) 100 | def test_multi_array_numpy_passing_validation( 101 | numpy_array: npt.ArrayLike, numpy_dtype: npt.DTypeLike, pydantic_typing, dimensions: Optional[int] 102 | ): 103 | with tempfile.NamedTemporaryFile(mode="w+", delete=True, suffix=".npz") as tf: 104 | np.savez_compressed(tf.name, my_array=numpy_array) 105 | numpy_model = get_numpy_type_model(pydantic_typing)( 106 | array_field=MultiArrayNumpyFile(path=Path(tf.name), key="my_array") 107 | ) 108 | assert np_general_all_close(numpy_model.array_field, numpy_array) 109 | 110 | @pytest.mark.parametrize("numpy_array, numpy_dtype, pydantic_typing, dimensions", data_type_array_typing_dimensions) 111 | def test_multi_array_numpy_error_on_reading_single_array_file( 112 | numpy_array: npt.ArrayLike, numpy_dtype: npt.DTypeLike, pydantic_typing, dimensions: Optional[int] 113 | ): 114 | with tempfile.NamedTemporaryFile(mode="w+", delete=True, suffix=".npy") as tf: 115 | np.save(tf.name, numpy_array) 116 | model = get_numpy_type_model(pydantic_typing) 117 | 118 | with pytest.raises(AttributeError): 119 | model(array_field=MultiArrayNumpyFile(path=Path(tf.name), key="my_array")) 120 | 121 | @pytest.mark.parametrize( 122 | "numpy_array, numpy_dtype, pydantic_typing, dimensions", 123 | get_type_safe_data_type_nd_array_typing_dimensions_128_bit(), 124 | ) 125 | def test_correct_128_bit_type( 126 | numpy_array: npt.ArrayLike, numpy_dtype: npt.DTypeLike, pydantic_typing, dimensions: Optional[int] 127 | ): 128 | assert get_numpy_type_model(pydantic_typing)(array_field=numpy_array) 129 | 130 | @pytest.mark.parametrize( 131 | "numpy_array, numpy_dtype, pydantic_typing, dimensions", 132 | get_type_safe_data_type_nd_array_typing_dimensions_128_bit(), 133 | ) 134 | @pytest.mark.parametrize("bad_numpy_array, wrong_numpy_type", supported_data_types) 135 | def test_wrong_dtype_128_bit_type( 136 | numpy_array: npt.ArrayLike, 137 | numpy_dtype: npt.DTypeLike, 138 | pydantic_typing, 139 | dimensions: Optional[int], 140 | bad_numpy_array: npt.ArrayLike, 141 | wrong_numpy_type: npt.DTypeLike, 142 | ): 143 | if wrong_numpy_type == numpy_dtype: 144 | return 145 | 146 | with pytest.raises(ValidationError): 147 | get_numpy_type_model(pydantic_typing)(array_field=bad_numpy_array) 148 | -------------------------------------------------------------------------------- /typegen/generate_typing.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Final 3 | 4 | 5 | def write_annotations(output_folder: Path, strict: bool) -> None: 6 | """ 7 | Generate and write typing annotations for the library 8 | 9 | Parameters 10 | ---------- 11 | output_folder : Path 12 | path within a package that gets updated 13 | 14 | strict : bool 15 | whether to generate strict types or allow conversions 16 | 17 | Returns 18 | ------- 19 | None 20 | """ 21 | generate_template = _generate_type_safe_template if strict else _generate_union_template 22 | for dimensions, filename in _DIMENSIONS_TO_FILENAME.items(): 23 | contents = "\n".join(_annotate_type(dimensions, type_name, strict) for type_name in _DATA_TYPES) 24 | all_types = "\n".join( 25 | _indent(f"{_quote(full_type_name)},") for full_type_name in _list_all_types(dimensions, strict) 26 | ) 27 | filename = output_folder / _DIMENSIONS_TO_FILENAME[dimensions] 28 | print(f"Writing {filename}..") 29 | with open(filename, "w") as f: 30 | f.write(generate_template(contents, all_types)) 31 | 32 | 33 | _DATA_TYPES: Final[dict[str, str]] = { 34 | "": "None", 35 | "Int64": "np.int64", 36 | "Int32": "np.int32", 37 | "Int16": "np.int16", 38 | "Int8": "np.int8", 39 | "Uint64": "np.uint64", 40 | "Uint32": "np.uint32", 41 | "Uint16": "np.uint16", 42 | "Uint8": "np.uint8", 43 | "FpLongDouble": "np.longdouble", 44 | "Fp64": "np.float64", 45 | "Fp32": "np.float32", 46 | "Fp16": "np.float16", 47 | "ComplexLongDouble": "np.clongdouble", 48 | "Complex128": "np.complex128", 49 | "Complex64": "np.complex64", 50 | "Bool": "np.bool_", 51 | "Datetime64": "np.datetime64", 52 | "Timedelta64": "np.timedelta64", 53 | } 54 | 55 | _DIMENSIONS_TO_PREFIX: Final[dict[int, str]] = { 56 | 0: "NDArray", 57 | 1: "1DArray", 58 | 2: "2DArray", 59 | 3: "3DArray", 60 | } 61 | 62 | _DIMENSIONS_TO_FILENAME: Final[dict[int, str]] = { 63 | 0: "n_dimensional.py", 64 | 1: "i_dimensional.py", 65 | 2: "ii_dimensional.py", 66 | 3: "iii_dimensional.py", 67 | } 68 | 69 | _SPACES: Final[str] = " " 70 | 71 | 72 | def _unindent(text: str) -> str: 73 | return "\n".join(line.removeprefix(_SPACES) for line in text.split("\n")) 74 | 75 | 76 | def _indent(text: str) -> str: 77 | return "\n".join(f"{_SPACES}{line}" for line in text.split("\n")) 78 | 79 | 80 | def _quote(text: str) -> str: 81 | return f'"{text}"' 82 | 83 | 84 | def _type_name_with_prefix(dimensions: int, type_name: str, strict: bool) -> str: 85 | strict_prefix = "" if strict else "Loading" 86 | dimension_prefix = _DIMENSIONS_TO_PREFIX[dimensions] 87 | return f"Np{strict_prefix}{dimension_prefix}{type_name}" 88 | 89 | 90 | def _strict_type(dimension_type: str, dtype: str) -> str: 91 | return f"np.ndarray[{dimension_type}, np.dtype[{dtype}]]" 92 | 93 | 94 | def _union_type(dimension_type: str, dtype: str) -> str: 95 | return f"Union[np.ndarray[{dimension_type}, np.dtype[{dtype}]], FilePath, MultiArrayNumpyFile]" 96 | 97 | 98 | def _annotate_type(dimensions: int, type_name: str, strict: bool) -> str: 99 | type_with_prefix = _type_name_with_prefix(dimensions, type_name, strict) 100 | data_type = _DATA_TYPES[type_name] 101 | 102 | dtype = "Any" if data_type == "None" else data_type 103 | dimension_type = "tuple[int, ...]" 104 | T = _strict_type(dimension_type, dtype) if strict else _union_type(dimension_type, dtype) 105 | dim = dimensions if dimensions > 0 else None 106 | annotation = f"""{type_with_prefix}: TypeAlias = Annotated[ 107 | {T}, 108 | NpArrayPydanticAnnotation.factory(data_type={data_type}, dimensions={dim}, strict_data_typing={data_type != "None" and strict}), 109 | ] 110 | """ 111 | return _unindent(annotation) 112 | 113 | 114 | def _generate_type_safe_template(contents: str, all_types: str) -> str: 115 | template = f"""from typing import Annotated, Any, TypeAlias 116 | 117 | import numpy as np 118 | 119 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 120 | 121 | {contents.strip()} 122 | 123 | __all__ = [ 124 | {all_types} 125 | ] 126 | """ 127 | return template 128 | 129 | 130 | def _generate_union_template(contents: str, all_types: str) -> str: 131 | template = f"""from typing import Annotated, Any, TypeAlias, Union 132 | 133 | import numpy as np 134 | from pydantic import FilePath 135 | 136 | from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation 137 | from pydantic_numpy.model import MultiArrayNumpyFile 138 | 139 | {contents.strip()} 140 | 141 | __all__ = [ 142 | {all_types} 143 | ] 144 | """ 145 | return template 146 | 147 | 148 | def _list_all_types(dimensions: int, strict: bool) -> list[str]: 149 | return [_type_name_with_prefix(dimensions, type_name, strict) for type_name in _DATA_TYPES] 150 | 151 | 152 | if __name__ == "__main__": 153 | write_annotations(Path("pydantic_numpy/typing/with_loader"), strict=False) 154 | write_annotations(Path("pydantic_numpy/typing/type_safe"), strict=True) 155 | --------------------------------------------------------------------------------