├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── LICENSE ├── README.md ├── poetry.lock ├── pydantic_numpy ├── __init__.py ├── dtype.py └── ndarray.py ├── pyproject.toml ├── tests ├── __init__.py ├── test_dtype.py └── test_ndarray.py └── tox.ini /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 14 | poetry-version: [1.1.13] 15 | os: [ubuntu-18.04, macos-latest, windows-latest] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Run image 23 | uses: abatilo/actions-poetry@v2.0.0 24 | with: 25 | poetry-version: ${{ matrix.poetry-version }} 26 | - name: Install dependencies 27 | run: | 28 | poetry install 29 | - name: Lint with flake8 30 | run: | 31 | # stop the build if there are Python syntax errors or undefined names 32 | poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 33 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 34 | poetry run flake8 flake8 . --count --exit-zero --statistics 35 | - name: Test with pytest 36 | run: | 37 | poetry run pytest 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # JetBrains 132 | /.idea/ 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Christoph Heindl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/cheind/pydantic-numpy/actions/workflows/python-package.yml/badge.svg)](https://github.com/cheind/pydantic-numpy/actions/workflows/python-package.yml) 2 | 3 | # pydantic-numpy 4 | 5 | This library provides support for integrating numpy `np.ndarray`'s into pydantic models. 6 | 7 | ## Usage 8 | 9 | For more examples see [test_ndarray.py](./tests/test_ndarray.py) 10 | 11 | ```python 12 | from pydantic import BaseModel 13 | 14 | import pydantic_numpy.dtype as pnd 15 | from pydantic_numpy import NDArray, NDArrayFp32 16 | 17 | 18 | class MyPydanticNumpyModel(BaseModel): 19 | K: NDArray[pnd.float32] 20 | C: NDArrayFp32 # <- Shorthand for same type as K 21 | 22 | 23 | # Instantiate from array 24 | cfg = MyPydanticNumpyModel(K=[1, 2]) 25 | # Instantiate from numpy file 26 | cfg = MyPydanticNumpyModel(K={"path": "path_to/array.npy"}) 27 | # Instantiate from npz file with key 28 | cfg = MyPydanticNumpyModel(K={"path": "path_to/array.npz", "key": "K"}) 29 | 30 | cfg.K 31 | # np.ndarray[np.float32] 32 | ``` 33 | 34 | ### Subfields 35 | 36 | This package also comes with `pydantic_numpy.dtype`, which adds subtyping support such as `NDArray[pnd.float32]`. All subfields must be from this package as numpy dtypes have no [Pydantic support](https://pydantic-docs.helpmanual.io/usage/types/#generic-classes-as-types). 37 | 38 | ## Install 39 | 40 | Via github 41 | 42 | ```shell 43 | pip install git+https://github.com/cheind/pydantic-numpy.git 44 | ``` 45 | 46 | Via PyPi (note that the package might be outdated) 47 | 48 | ```shell 49 | pip install pydantic-numpy 50 | ``` 51 | 52 | ## History 53 | 54 | The original idea originates from [this discussion](https://gist.github.com/danielhfrank/00e6b8556eed73fb4053450e602d2434), but stopped working for `numpy>=1.22`. This repository picks up where the previous discussion ended 55 | 56 | - added designated repository for better handling of PRs 57 | - added support for `numpy>1.22` 58 | - Dtypes are no longer strings but `np.generics`. I.e. `NDArray['np.float32']` becomes `NDArray[np.float32]` 59 | - added automated tests and continuous integration for different numpy/python versions 60 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "attrs" 3 | version = "22.1.0" 4 | description = "Classes Without Boilerplate" 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=3.5" 8 | 9 | [package.extras] 10 | dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] 11 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] 12 | tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] 13 | tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] 14 | 15 | [[package]] 16 | name = "colorama" 17 | version = "0.4.6" 18 | description = "Cross-platform colored terminal text." 19 | category = "dev" 20 | optional = false 21 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 22 | 23 | [[package]] 24 | name = "exceptiongroup" 25 | version = "1.0.4" 26 | description = "Backport of PEP 654 (exception groups)" 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=3.7" 30 | 31 | [package.extras] 32 | test = ["pytest (>=6)"] 33 | 34 | [[package]] 35 | name = "flake8" 36 | version = "4.0.1" 37 | description = "the modular source code checker: pep8 pyflakes and co" 38 | category = "dev" 39 | optional = false 40 | python-versions = ">=3.6" 41 | 42 | [package.dependencies] 43 | importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} 44 | mccabe = ">=0.6.0,<0.7.0" 45 | pycodestyle = ">=2.8.0,<2.9.0" 46 | pyflakes = ">=2.4.0,<2.5.0" 47 | 48 | [[package]] 49 | name = "importlib-metadata" 50 | version = "4.2.0" 51 | description = "Read metadata from Python packages" 52 | category = "dev" 53 | optional = false 54 | python-versions = ">=3.6" 55 | 56 | [package.dependencies] 57 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 58 | zipp = ">=0.5" 59 | 60 | [package.extras] 61 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 62 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] 63 | 64 | [[package]] 65 | name = "iniconfig" 66 | version = "1.1.1" 67 | description = "iniconfig: brain-dead simple config-ini parsing" 68 | category = "dev" 69 | optional = false 70 | python-versions = "*" 71 | 72 | [[package]] 73 | name = "mccabe" 74 | version = "0.6.1" 75 | description = "McCabe checker, plugin for flake8" 76 | category = "dev" 77 | optional = false 78 | python-versions = "*" 79 | 80 | [[package]] 81 | name = "numpy" 82 | version = "1.21.6" 83 | description = "NumPy is the fundamental package for array computing with Python." 84 | category = "main" 85 | optional = false 86 | python-versions = ">=3.7,<3.11" 87 | 88 | [[package]] 89 | name = "numpy" 90 | version = "1.23.5" 91 | description = "NumPy is the fundamental package for array computing with Python." 92 | category = "main" 93 | optional = false 94 | python-versions = ">=3.8" 95 | 96 | [[package]] 97 | name = "packaging" 98 | version = "21.3" 99 | description = "Core utilities for Python packages" 100 | category = "dev" 101 | optional = false 102 | python-versions = ">=3.6" 103 | 104 | [package.dependencies] 105 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 106 | 107 | [[package]] 108 | name = "pluggy" 109 | version = "1.0.0" 110 | description = "plugin and hook calling mechanisms for python" 111 | category = "dev" 112 | optional = false 113 | python-versions = ">=3.6" 114 | 115 | [package.dependencies] 116 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 117 | 118 | [package.extras] 119 | dev = ["pre-commit", "tox"] 120 | testing = ["pytest", "pytest-benchmark"] 121 | 122 | [[package]] 123 | name = "pycodestyle" 124 | version = "2.8.0" 125 | description = "Python style guide checker" 126 | category = "dev" 127 | optional = false 128 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 129 | 130 | [[package]] 131 | name = "pydantic" 132 | version = "1.10.2" 133 | description = "Data validation and settings management using python type hints" 134 | category = "main" 135 | optional = false 136 | python-versions = ">=3.7" 137 | 138 | [package.dependencies] 139 | typing-extensions = ">=4.1.0" 140 | 141 | [package.extras] 142 | dotenv = ["python-dotenv (>=0.10.4)"] 143 | email = ["email-validator (>=1.0.3)"] 144 | 145 | [[package]] 146 | name = "pyflakes" 147 | version = "2.4.0" 148 | description = "passive checker of Python programs" 149 | category = "dev" 150 | optional = false 151 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 152 | 153 | [[package]] 154 | name = "pyparsing" 155 | version = "3.0.9" 156 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 157 | category = "dev" 158 | optional = false 159 | python-versions = ">=3.6.8" 160 | 161 | [package.extras] 162 | diagrams = ["jinja2", "railroad-diagrams"] 163 | 164 | [[package]] 165 | name = "pytest" 166 | version = "7.2.0" 167 | description = "pytest: simple powerful testing with Python" 168 | category = "dev" 169 | optional = false 170 | python-versions = ">=3.7" 171 | 172 | [package.dependencies] 173 | attrs = ">=19.2.0" 174 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 175 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 176 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 177 | iniconfig = "*" 178 | packaging = "*" 179 | pluggy = ">=0.12,<2.0" 180 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 181 | 182 | [package.extras] 183 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 184 | 185 | [[package]] 186 | name = "tomli" 187 | version = "2.0.1" 188 | description = "A lil' TOML parser" 189 | category = "dev" 190 | optional = false 191 | python-versions = ">=3.7" 192 | 193 | [[package]] 194 | name = "typing-extensions" 195 | version = "4.4.0" 196 | description = "Backported and Experimental Type Hints for Python 3.7+" 197 | category = "main" 198 | optional = false 199 | python-versions = ">=3.7" 200 | 201 | [[package]] 202 | name = "zipp" 203 | version = "3.10.0" 204 | description = "Backport of pathlib-compatible object wrapper for zip files" 205 | category = "dev" 206 | optional = false 207 | python-versions = ">=3.7" 208 | 209 | [package.extras] 210 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] 211 | testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 212 | 213 | [metadata] 214 | lock-version = "1.1" 215 | python-versions = ">=3.7, <3.12" 216 | content-hash = "afe3f1e873e85d9562210c94aad40432b33aa9d4f0b091328e42d3f6ba728a3a" 217 | 218 | [metadata.files] 219 | attrs = [ 220 | {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, 221 | {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, 222 | ] 223 | colorama = [ 224 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 225 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 226 | ] 227 | exceptiongroup = [ 228 | {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, 229 | {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, 230 | ] 231 | flake8 = [ 232 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 233 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 234 | ] 235 | importlib-metadata = [ 236 | {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, 237 | {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, 238 | ] 239 | iniconfig = [ 240 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 241 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 242 | ] 243 | mccabe = [ 244 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 245 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 246 | ] 247 | numpy = [ 248 | {file = "numpy-1.21.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8737609c3bbdd48e380d463134a35ffad3b22dc56295eff6f79fd85bd0eeeb25"}, 249 | {file = "numpy-1.21.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fdffbfb6832cd0b300995a2b08b8f6fa9f6e856d562800fea9182316d99c4e8e"}, 250 | {file = "numpy-1.21.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3820724272f9913b597ccd13a467cc492a0da6b05df26ea09e78b171a0bb9da6"}, 251 | {file = "numpy-1.21.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f17e562de9edf691a42ddb1eb4a5541c20dd3f9e65b09ded2beb0799c0cf29bb"}, 252 | {file = "numpy-1.21.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f30427731561ce75d7048ac254dbe47a2ba576229250fb60f0fb74db96501a1"}, 253 | {file = "numpy-1.21.6-cp310-cp310-win32.whl", hash = "sha256:d4bf4d43077db55589ffc9009c0ba0a94fa4908b9586d6ccce2e0b164c86303c"}, 254 | {file = "numpy-1.21.6-cp310-cp310-win_amd64.whl", hash = "sha256:d136337ae3cc69aa5e447e78d8e1514be8c3ec9b54264e680cf0b4bd9011574f"}, 255 | {file = "numpy-1.21.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6aaf96c7f8cebc220cdfc03f1d5a31952f027dda050e5a703a0d1c396075e3e7"}, 256 | {file = "numpy-1.21.6-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:67c261d6c0a9981820c3a149d255a76918278a6b03b6a036800359aba1256d46"}, 257 | {file = "numpy-1.21.6-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a6be4cb0ef3b8c9250c19cc122267263093eee7edd4e3fa75395dfda8c17a8e2"}, 258 | {file = "numpy-1.21.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4068a8c44014b2d55f3c3f574c376b2494ca9cc73d2f1bd692382b6dffe3db"}, 259 | {file = "numpy-1.21.6-cp37-cp37m-win32.whl", hash = "sha256:7c7e5fa88d9ff656e067876e4736379cc962d185d5cd808014a8a928d529ef4e"}, 260 | {file = "numpy-1.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bcb238c9c96c00d3085b264e5c1a1207672577b93fa666c3b14a45240b14123a"}, 261 | {file = "numpy-1.21.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:82691fda7c3f77c90e62da69ae60b5ac08e87e775b09813559f8901a88266552"}, 262 | {file = "numpy-1.21.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:643843bcc1c50526b3a71cd2ee561cf0d8773f062c8cbaf9ffac9fdf573f83ab"}, 263 | {file = "numpy-1.21.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:357768c2e4451ac241465157a3e929b265dfac85d9214074985b1786244f2ef3"}, 264 | {file = "numpy-1.21.6-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f411b2c3f3d76bba0865b35a425157c5dcf54937f82bbeb3d3c180789dd66a6"}, 265 | {file = "numpy-1.21.6-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4aa48afdce4660b0076a00d80afa54e8a97cd49f457d68a4342d188a09451c1a"}, 266 | {file = "numpy-1.21.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a96eef20f639e6a97d23e57dd0c1b1069a7b4fd7027482a4c5c451cd7732f4"}, 267 | {file = "numpy-1.21.6-cp38-cp38-win32.whl", hash = "sha256:5c3c8def4230e1b959671eb959083661b4a0d2e9af93ee339c7dada6759a9470"}, 268 | {file = "numpy-1.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:bf2ec4b75d0e9356edea834d1de42b31fe11f726a81dfb2c2112bc1eaa508fcf"}, 269 | {file = "numpy-1.21.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4391bd07606be175aafd267ef9bea87cf1b8210c787666ce82073b05f202add1"}, 270 | {file = "numpy-1.21.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67f21981ba2f9d7ba9ade60c9e8cbaa8cf8e9ae51673934480e45cf55e953673"}, 271 | {file = "numpy-1.21.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee5ec40fdd06d62fe5d4084bef4fd50fd4bb6bfd2bf519365f569dc470163ab0"}, 272 | {file = "numpy-1.21.6-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1dbe1c91269f880e364526649a52eff93ac30035507ae980d2fed33aaee633ac"}, 273 | {file = "numpy-1.21.6-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d9caa9d5e682102453d96a0ee10c7241b72859b01a941a397fd965f23b3e016b"}, 274 | {file = "numpy-1.21.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58459d3bad03343ac4b1b42ed14d571b8743dc80ccbf27444f266729df1d6f5b"}, 275 | {file = "numpy-1.21.6-cp39-cp39-win32.whl", hash = "sha256:7f5ae4f304257569ef3b948810816bc87c9146e8c446053539947eedeaa32786"}, 276 | {file = "numpy-1.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:e31f0bb5928b793169b87e3d1e070f2342b22d5245c755e2b81caa29756246c3"}, 277 | {file = "numpy-1.21.6-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd1c8f6bd65d07d3810b90d02eba7997e32abbdf1277a481d698969e921a3be0"}, 278 | {file = "numpy-1.21.6.zip", hash = "sha256:ecb55251139706669fdec2ff073c98ef8e9a84473e51e716211b41aa0f18e656"}, 279 | {file = "numpy-1.23.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c88793f78fca17da0145455f0d7826bcb9f37da4764af27ac945488116efe63"}, 280 | {file = "numpy-1.23.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e9f4c4e51567b616be64e05d517c79a8a22f3606499941d97bb76f2ca59f982d"}, 281 | {file = "numpy-1.23.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7903ba8ab592b82014713c491f6c5d3a1cde5b4a3bf116404e08f5b52f6daf43"}, 282 | {file = "numpy-1.23.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e05b1c973a9f858c74367553e236f287e749465f773328c8ef31abe18f691e1"}, 283 | {file = "numpy-1.23.5-cp310-cp310-win32.whl", hash = "sha256:522e26bbf6377e4d76403826ed689c295b0b238f46c28a7251ab94716da0b280"}, 284 | {file = "numpy-1.23.5-cp310-cp310-win_amd64.whl", hash = "sha256:dbee87b469018961d1ad79b1a5d50c0ae850000b639bcb1b694e9981083243b6"}, 285 | {file = "numpy-1.23.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ce571367b6dfe60af04e04a1834ca2dc5f46004ac1cc756fb95319f64c095a96"}, 286 | {file = "numpy-1.23.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56e454c7833e94ec9769fa0f86e6ff8e42ee38ce0ce1fa4cbb747ea7e06d56aa"}, 287 | {file = "numpy-1.23.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5039f55555e1eab31124a5768898c9e22c25a65c1e0037f4d7c495a45778c9f2"}, 288 | {file = "numpy-1.23.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58f545efd1108e647604a1b5aa809591ccd2540f468a880bedb97247e72db387"}, 289 | {file = "numpy-1.23.5-cp311-cp311-win32.whl", hash = "sha256:b2a9ab7c279c91974f756c84c365a669a887efa287365a8e2c418f8b3ba73fb0"}, 290 | {file = "numpy-1.23.5-cp311-cp311-win_amd64.whl", hash = "sha256:0cbe9848fad08baf71de1a39e12d1b6310f1d5b2d0ea4de051058e6e1076852d"}, 291 | {file = "numpy-1.23.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f063b69b090c9d918f9df0a12116029e274daf0181df392839661c4c7ec9018a"}, 292 | {file = "numpy-1.23.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0aaee12d8883552fadfc41e96b4c82ee7d794949e2a7c3b3a7201e968c7ecab9"}, 293 | {file = "numpy-1.23.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92c8c1e89a1f5028a4c6d9e3ccbe311b6ba53694811269b992c0b224269e2398"}, 294 | {file = "numpy-1.23.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d208a0f8729f3fb790ed18a003f3a57895b989b40ea4dce4717e9cf4af62c6bb"}, 295 | {file = "numpy-1.23.5-cp38-cp38-win32.whl", hash = "sha256:06005a2ef6014e9956c09ba07654f9837d9e26696a0470e42beedadb78c11b07"}, 296 | {file = "numpy-1.23.5-cp38-cp38-win_amd64.whl", hash = "sha256:ca51fcfcc5f9354c45f400059e88bc09215fb71a48d3768fb80e357f3b457e1e"}, 297 | {file = "numpy-1.23.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8969bfd28e85c81f3f94eb4a66bc2cf1dbdc5c18efc320af34bffc54d6b1e38f"}, 298 | {file = "numpy-1.23.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7ac231a08bb37f852849bbb387a20a57574a97cfc7b6cabb488a4fc8be176de"}, 299 | {file = "numpy-1.23.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf837dc63ba5c06dc8797c398db1e223a466c7ece27a1f7b5232ba3466aafe3d"}, 300 | {file = "numpy-1.23.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33161613d2269025873025b33e879825ec7b1d831317e68f4f2f0f84ed14c719"}, 301 | {file = "numpy-1.23.5-cp39-cp39-win32.whl", hash = "sha256:af1da88f6bc3d2338ebbf0e22fe487821ea4d8e89053e25fa59d1d79786e7481"}, 302 | {file = "numpy-1.23.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b7847f7e83ca37c6e627682f145856de331049013853f344f37b0c9690e3df"}, 303 | {file = "numpy-1.23.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:abdde9f795cf292fb9651ed48185503a2ff29be87770c3b8e2a14b0cd7aa16f8"}, 304 | {file = "numpy-1.23.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9a909a8bae284d46bbfdefbdd4a262ba19d3bc9921b1e76126b1d21c3c34135"}, 305 | {file = "numpy-1.23.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:01dd17cbb340bf0fc23981e52e1d18a9d4050792e8fb8363cecbf066a84b827d"}, 306 | {file = "numpy-1.23.5.tar.gz", hash = "sha256:1b1766d6f397c18153d40015ddfc79ddb715cabadc04d2d228d4e5a8bc4ded1a"}, 307 | ] 308 | packaging = [ 309 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 310 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 311 | ] 312 | pluggy = [ 313 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 314 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 315 | ] 316 | pycodestyle = [ 317 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 318 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 319 | ] 320 | pydantic = [ 321 | {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, 322 | {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, 323 | {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, 324 | {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, 325 | {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, 326 | {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, 327 | {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, 328 | {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, 329 | {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, 330 | {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, 331 | {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, 332 | {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, 333 | {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, 334 | {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, 335 | {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, 336 | {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, 337 | {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, 338 | {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, 339 | {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, 340 | {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, 341 | {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, 342 | {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, 343 | {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, 344 | {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, 345 | {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, 346 | {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, 347 | {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, 348 | {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, 349 | {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, 350 | {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, 351 | {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, 352 | {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, 353 | {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, 354 | {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, 355 | {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, 356 | {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, 357 | ] 358 | pyflakes = [ 359 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 360 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 361 | ] 362 | pyparsing = [ 363 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 364 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 365 | ] 366 | pytest = [ 367 | {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, 368 | {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, 369 | ] 370 | tomli = [ 371 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 372 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 373 | ] 374 | typing-extensions = [ 375 | {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, 376 | {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, 377 | ] 378 | zipp = [ 379 | {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, 380 | {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, 381 | ] 382 | -------------------------------------------------------------------------------- /pydantic_numpy/__init__.py: -------------------------------------------------------------------------------- 1 | from pydantic_numpy.dtype import * 2 | from pydantic_numpy.ndarray import NDArray, NPFileDesc, PotentialNDArray 3 | -------------------------------------------------------------------------------- /pydantic_numpy/dtype.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | from typing import Dict 5 | from typing import TYPE_CHECKING 6 | 7 | import numpy as np 8 | from pydantic import ValidationError 9 | from pydantic.fields import ModelField 10 | 11 | from pydantic_numpy.ndarray import NDArray 12 | 13 | if TYPE_CHECKING: 14 | from pydantic.typing import CallableGenerator 15 | 16 | 17 | class _BaseDType: 18 | @classmethod 19 | def __modify_schema__(cls, field_schema: dict[str, Any]) -> None: 20 | field_schema.update({"type": cls.__name__}) 21 | 22 | @classmethod 23 | def __get_validators__(cls) -> CallableGenerator: 24 | yield cls.validate 25 | 26 | @classmethod 27 | def validate(cls, val: Any, field: ModelField) -> "_BaseDType": 28 | if field.sub_fields: 29 | msg = f"{cls.__name__} has no subfields" 30 | raise ValidationError(msg) 31 | if not isinstance(val, cls): 32 | return cls(val) 33 | return val 34 | 35 | 36 | class longdouble(np.longdouble, _BaseDType): 37 | pass 38 | 39 | 40 | float128 = longdouble 41 | 42 | 43 | class double(np.double, _BaseDType): 44 | pass 45 | 46 | 47 | float64 = double 48 | 49 | 50 | class single(np.single, _BaseDType): 51 | pass 52 | 53 | 54 | float32 = single 55 | 56 | 57 | class half(np.half, _BaseDType): 58 | pass 59 | 60 | 61 | float16 = half 62 | 63 | 64 | class int_(np.int_, _BaseDType): 65 | pass 66 | 67 | 68 | int64 = int_ 69 | 70 | 71 | class intc(np.intc, _BaseDType): 72 | pass 73 | 74 | 75 | int32 = intc 76 | 77 | 78 | class short(np.short, _BaseDType): 79 | pass 80 | 81 | 82 | int16 = short 83 | 84 | 85 | class byte(np.byte, _BaseDType): 86 | pass 87 | 88 | 89 | int8 = byte 90 | 91 | 92 | class uint(np.uint, _BaseDType): 93 | pass 94 | 95 | 96 | uint64 = uint 97 | 98 | 99 | class uintc(np.uintc, _BaseDType): 100 | pass 101 | 102 | 103 | uint32 = uintc 104 | 105 | 106 | class ushort(np.ushort, _BaseDType): 107 | pass 108 | 109 | 110 | uint16 = ushort 111 | 112 | 113 | class ubyte(np.ubyte, _BaseDType): 114 | pass 115 | 116 | 117 | uint8 = ubyte 118 | 119 | 120 | class clongdouble(np.clongdouble, _BaseDType): 121 | pass 122 | 123 | 124 | complex256 = clongdouble 125 | 126 | 127 | class cdouble(np.cdouble, _BaseDType): 128 | pass 129 | 130 | 131 | complex128 = cdouble 132 | 133 | 134 | class csingle(np.csingle, _BaseDType): 135 | pass 136 | 137 | 138 | complex64 = csingle 139 | 140 | # NDArray typings 141 | 142 | NDArrayFp128 = NDArray[float128] 143 | NDArrayFp64 = NDArray[float64] 144 | NDArrayFp32 = NDArray[float32] 145 | NDArrayFp16 = NDArray[float16] 146 | 147 | NDArrayInt64 = NDArray[int64] 148 | NDArrayInt32 = NDArray[int32] 149 | NDArrayInt16 = NDArray[int16] 150 | NDArrayInt8 = NDArray[int8] 151 | 152 | NDArrayUint64 = NDArray[uint64] 153 | NDArrayUint32 = NDArray[uint32] 154 | NDArrayUint16 = NDArray[uint16] 155 | NDArrayUint8 = NDArray[uint8] 156 | 157 | NDArrayComplex256 = NDArray[complex256] 158 | NDArrayComplex128 = NDArray[complex128] 159 | NDArrayComplex64 = NDArray[complex64] 160 | 161 | NDArrayBool = NDArray[bool] 162 | -------------------------------------------------------------------------------- /pydantic_numpy/ndarray.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | from abc import ABC 5 | from abc import abstractmethod 6 | from pathlib import Path 7 | from typing import Any 8 | from typing import Generic 9 | from typing import Mapping 10 | from typing import Optional 11 | from typing import TypeVar 12 | from typing import TYPE_CHECKING 13 | 14 | import numpy as np 15 | from numpy.lib import NumpyVersion 16 | from pydantic import BaseModel 17 | from pydantic import FilePath 18 | from pydantic import validator 19 | from pydantic.fields import ModelField 20 | 21 | if TYPE_CHECKING: 22 | from pydantic.typing import CallableGenerator 23 | 24 | T = TypeVar("T", bound=np.generic) 25 | 26 | if sys.version_info < (3, 9) or NumpyVersion(np.__version__) < "1.22.0": 27 | nd_array_type = np.ndarray 28 | else: 29 | nd_array_type = np.ndarray[Any, T] 30 | 31 | 32 | class NPFileDesc(BaseModel): 33 | path: FilePath = ... 34 | key: Optional[str] = None 35 | 36 | @validator("path") 37 | def absolute(cls, value: Path) -> Path: 38 | return value.resolve().absolute() 39 | 40 | 41 | class _CommonNDArray(ABC): 42 | 43 | @classmethod 44 | @abstractmethod 45 | def validate(cls, val: Any, field: ModelField) -> nd_array_type: 46 | ... 47 | 48 | @classmethod 49 | def __modify_schema__( 50 | cls, field_schema: dict[str, Any], field: ModelField | None 51 | ) -> None: 52 | if field and field.sub_fields: 53 | type_with_potential_subtype = f"np.ndarray[{field.sub_fields[0]}]" 54 | else: 55 | type_with_potential_subtype = "np.ndarray" 56 | field_schema.update({"type": type_with_potential_subtype}) 57 | 58 | @classmethod 59 | def __get_validators__(cls) -> CallableGenerator: 60 | yield cls.validate 61 | 62 | @staticmethod 63 | def _validate(val: Any, field: ModelField) -> nd_array_type: 64 | if isinstance(val, Mapping): 65 | val = NPFileDesc(**val) 66 | 67 | if isinstance(val, NPFileDesc): 68 | val: NPFileDesc 69 | 70 | if val.path.suffix.lower() not in [".npz", ".npy"]: 71 | raise ValueError("Expected npz or npy file.") 72 | 73 | if not val.path.is_file(): 74 | raise ValueError(f"Path does not exist {val.path}") 75 | 76 | try: 77 | content = np.load(str(val.path)) 78 | except FileNotFoundError: 79 | raise ValueError(f"Failed to load numpy data from file {val.path}") 80 | 81 | if val.path.suffix.lower() == ".npz": 82 | key = val.key or content.files[0] 83 | try: 84 | data = content[key] 85 | except KeyError: 86 | raise ValueError(f"Key {key} not found in npz.") 87 | else: 88 | data = content 89 | 90 | else: 91 | data = val 92 | 93 | if field.sub_fields is not None: 94 | dtype_field = field.sub_fields[0] 95 | return np.asarray(data, dtype=dtype_field.type_) 96 | return np.asarray(data) 97 | 98 | 99 | class NDArray(Generic[T], nd_array_type, _CommonNDArray): 100 | @classmethod 101 | def validate(cls, val: Any, field: ModelField) -> nd_array_type: 102 | return cls._validate(val, field) 103 | 104 | 105 | class PotentialNDArray(Generic[T], nd_array_type, _CommonNDArray): 106 | """Like NDArray, but validation errors result in None.""" 107 | 108 | @classmethod 109 | def validate(cls, val: Any, field: ModelField) -> Optional[nd_array_type]: 110 | try: 111 | return cls._validate(val, field) 112 | except ValueError: 113 | return None 114 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pydantic_numpy" 3 | version = "1.4.0" 4 | description = "Seamlessly integrate numpy arrays into pydantic models" 5 | authors = ["Christoph Heindl, Can H. Tartanoglu "] 6 | license = "MIT" 7 | 8 | readme = "README.md" 9 | packages = [{include = "pydantic_numpy"}] 10 | 11 | [tool.poetry.dependencies] 12 | python = ">=3.7, <3.12" 13 | numpy = [ 14 | {version = "<1.22", python = "<3.8"}, 15 | {version = "^1.22", python = "^3.8"} 16 | ] 17 | pydantic = "*" 18 | 19 | [tool.poetry.dev-dependencies] 20 | pytest = "^7.1.2" 21 | flake8 = "^4.0.1" 22 | 23 | [tool.isort] 24 | profile = "black" 25 | 26 | [tool.black] 27 | line-length = 120 28 | target-version = ["py310"] 29 | 30 | [build-system] 31 | requires = ["poetry-core>=1.0.0"] 32 | build-backend = "poetry.core.masonry.api" 33 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheind/pydantic-numpy/ff55505956238cedca7864d1bca18b7dc6433f44/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_dtype.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import numpy as np 4 | import pytest 5 | from pydantic import BaseModel 6 | 7 | import pydantic_numpy.dtype as pnd 8 | 9 | try: 10 | np_float128 = np.float128 11 | except AttributeError: 12 | # Not available on windows. 13 | np_float128 = None 14 | 15 | try: 16 | np_complex256 = np.complex256 17 | except AttributeError: 18 | # Not available on windows. 19 | np_complex256 = None 20 | 21 | 22 | @pytest.mark.parametrize("data", (1, 1.0)) 23 | @pytest.mark.parametrize( 24 | "pnp_dtype,np_dtype", ( 25 | (pnd.float16, np.float16), 26 | (pnd.float32, np.float32), 27 | (pnd.float64, np.float64), 28 | pytest.param( 29 | pnd.float128, np_float128, 30 | marks=pytest.mark.skipif( 31 | sys.platform == "win32", reason="dtype is not available on windows" 32 | ) 33 | ), 34 | (pnd.int8, np.int8), 35 | (pnd.int16, np.int16), 36 | (pnd.int32, np.int32), 37 | (pnd.int64, np.int64), 38 | (pnd.uint8, np.uint8), 39 | (pnd.uint16, np.uint16), 40 | (pnd.uint32, np.uint32), 41 | (pnd.uint64, np.uint64), 42 | ) 43 | ) 44 | def test_float32(data, pnp_dtype, np_dtype): 45 | class MyModel(BaseModel): 46 | V: pnp_dtype 47 | 48 | assert MyModel(V=data).V == np_dtype(data) 49 | 50 | 51 | @pytest.mark.parametrize("data", (1 + 1j, 1.0 + 1.0j)) 52 | @pytest.mark.parametrize( 53 | "pnp_dtype,np_dtype", ( 54 | (pnd.complex64, np.complex64), 55 | (pnd.complex128, np.complex128), 56 | pytest.param( 57 | pnd.complex256, np_complex256, 58 | marks=pytest.mark.skipif( 59 | sys.platform == "win32", reason="dtype is not available on windows" 60 | ) 61 | ), 62 | ) 63 | ) 64 | def test_complex256(data, pnp_dtype, np_dtype): 65 | class MyModel(BaseModel): 66 | V: pnp_dtype 67 | 68 | assert MyModel(V=data).V == np_dtype(data) 69 | -------------------------------------------------------------------------------- /tests/test_ndarray.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Optional 3 | from typing import Dict 4 | 5 | import numpy as np 6 | import pytest 7 | from numpy.testing import assert_allclose 8 | from pydantic import BaseModel, ValidationError 9 | 10 | import pydantic_numpy.dtype as pnd 11 | from pydantic_numpy import NDArray, NPFileDesc, PotentialNDArray 12 | 13 | JSON_ENCODERS = {np.ndarray: lambda arr: arr.tolist()} 14 | 15 | 16 | class NDArrayTestingModel(BaseModel): 17 | K: pnd.NDArrayFp32 18 | 19 | class Config: 20 | json_encoders = JSON_ENCODERS 21 | 22 | 23 | def test_init_from_values(): 24 | # Directly specify values 25 | cfg = NDArrayTestingModel(K=[1, 2]) 26 | assert_allclose(cfg.K, [1.0, 2.0]) 27 | assert cfg.K.dtype == np.float32 28 | assert cfg.json() 29 | 30 | cfg = NDArrayTestingModel(K=np.eye(2)) 31 | assert_allclose(cfg.K, [[1.0, 0], [0.0, 1.0]]) 32 | assert cfg.K.dtype == np.float32 33 | 34 | 35 | def test_load_from_npy_path(tmpdir): 36 | # Load from npy 37 | np.save(Path(tmpdir) / "data.npy", np.arange(5)) 38 | cfg = NDArrayTestingModel(K={"path": Path(tmpdir) / "data.npy"}) 39 | assert_allclose(cfg.K, [0.0, 1.0, 2.0, 3.0, 4.0]) 40 | assert cfg.K.dtype == np.float32 41 | 42 | 43 | def test_load_from_NPFileDesc(tmpdir): 44 | np.save(Path(tmpdir) / "data.npy", np.arange(5)) 45 | cfg = NDArrayTestingModel(K=NPFileDesc(path=Path(tmpdir) / "data.npy")) 46 | assert_allclose(cfg.K, [0.0, 1.0, 2.0, 3.0, 4.0]) 47 | assert cfg.K.dtype == np.float32 48 | 49 | 50 | def test_load_field_from_npz(tmpdir): 51 | np.savez(Path(tmpdir) / "data.npz", values=np.arange(5)) 52 | cfg = NDArrayTestingModel(K={"path": Path(tmpdir) / "data.npz", "key": "values"}) 53 | assert_allclose(cfg.K, [0.0, 1.0, 2.0, 3.0, 4.0]) 54 | assert cfg.K.dtype == np.float32 55 | 56 | 57 | def test_exceptional(tmpdir): 58 | with pytest.raises(ValidationError): 59 | NDArrayTestingModel(K={"path": Path(tmpdir) / "nosuchfile.npz", "key": "values"}) 60 | 61 | with pytest.raises(ValidationError): 62 | NDArrayTestingModel(K={"path": Path(tmpdir) / "nosuchfile.npy", "key": "nosuchkey"}) 63 | 64 | with pytest.raises(ValidationError): 65 | NDArrayTestingModel(K={"path": Path(tmpdir) / "nosuchfile.npy"}) 66 | 67 | with pytest.raises(ValidationError): 68 | NDArrayTestingModel(K="absc") 69 | 70 | 71 | def test_unspecified_npdtype(): 72 | # Not specifying a dtype will use numpy default dtype resolver 73 | 74 | class NDArrayNoGeneric(BaseModel): 75 | K: NDArray 76 | 77 | cfg = NDArrayNoGeneric(K=[1, 2]) 78 | assert_allclose(cfg.K, [1, 2]) 79 | assert cfg.K.dtype == int 80 | 81 | 82 | def test_json_encoders(): 83 | import json 84 | 85 | class NDArrayNoGeneric(BaseModel): 86 | K: NDArray 87 | 88 | class Config: 89 | json_encoders = JSON_ENCODERS 90 | 91 | cfg = NDArrayNoGeneric(K=[1, 2]) 92 | jdata = json.loads(cfg.json()) 93 | 94 | assert "K" in jdata 95 | assert type(jdata["K"]) == list 96 | assert jdata["K"] == list([1, 2]) 97 | 98 | 99 | def test_optional_construction(): 100 | class NDArrayOptional(BaseModel): 101 | K: Optional[pnd.NDArrayFp32] 102 | 103 | cfg = NDArrayOptional() 104 | assert cfg.K is None 105 | 106 | cfg = NDArrayOptional(K=[1, 2]) 107 | assert type(cfg.K) == np.ndarray 108 | assert cfg.K.dtype == np.float32 109 | 110 | 111 | def test_potential_array(tmpdir): 112 | class NDArrayPotential(BaseModel): 113 | K: PotentialNDArray[pnd.float32] 114 | 115 | np.savez(Path(tmpdir) / "data.npz", values=np.arange(5)) 116 | 117 | cfg = NDArrayPotential(K={"path": Path(tmpdir) / "data.npz", "key": "values"}) 118 | assert cfg.K is not None 119 | assert_allclose(cfg.K, [0.0, 1.0, 2.0, 3.0, 4.0]) 120 | 121 | # Path not found 122 | cfg = NDArrayPotential(K={"path": Path(tmpdir) / "nothere.npz", "key": "values"}) 123 | assert cfg.K is None 124 | 125 | # Key not there 126 | cfg = NDArrayPotential(K={"path": Path(tmpdir) / "data.npz", "key": "nothere"}) 127 | assert cfg.K is None 128 | 129 | 130 | def test_subclass_basemodel(): 131 | model_field = NDArrayTestingModel(K=[1.0, 2.0]) 132 | assert model_field.json() 133 | 134 | class MappingTestingModel(BaseModel): 135 | L: Dict[str, NDArrayTestingModel] 136 | 137 | class Config: 138 | json_encoders = JSON_ENCODERS 139 | 140 | model = MappingTestingModel(L={"a": NDArrayTestingModel(K=[1.0, 2.0])}) 141 | assert model.L["a"].K.dtype == np.dtype("float32") 142 | assert model.json() 143 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = D203,F401 3 | max-line-length = 120 4 | exclude = .git,__pycache__,docs/source/conf.py,old,build,dist 5 | max-complexity = 11 --------------------------------------------------------------------------------