Merge pull request #3 from p2p-ld/vendor-nptyping

Vendor nptyping
This commit is contained in:
Jonny Saunders 2024-07-31 16:57:38 -07:00 committed by GitHub
commit 880dafb151
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 2504 additions and 327 deletions

28
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: Lint
on:
push:
branches:
- main
pull_request:
branches:
- main
permissions:
contents: read
jobs:
ruff:
name: Ruff Linting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1
black:
name: Black Formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: psf/black@stable

View file

@ -2,14 +2,34 @@ name: Tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
strategy:
matrix:
python-version: ["3.9", "3.11", "3.12"]
platform: ["ubuntu-latest", "macos-latest", "windows-latest"]
numpy-version: ["<2.0.0", ">=2.0.0"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
exclude:
- numpy-version: "<2.0.0"
python-version: "3.10"
- numpy-version: "<2.0.0"
python-version: "3.11"
- platform: "macos-latest"
python-version: "3.10"
- platform: "macos-latest"
python-version: "3.11"
- platform: "windows-latest"
python-version: "3.10"
- platform: "windows-latest"
python-version: "3.11"
runs-on: ubuntu-latest
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
@ -21,21 +41,28 @@ jobs:
cache: 'pip'
- name: Install dependencies
run: pip install -e ".[tests,linkml]"
run: pip install -e ".[tests]"
- name: Install numpy version
run: pip install "numpy${{ matrix.numpy-version }}"
- name: Run Tests
run: pytest
- name: Report coverage
if: ${{ matrix.python-version }} == "3.11"
run: "coveralls --service=github"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Coveralls Parallel
uses: coverallsapp/github-action@v2.3.0
if: runner.os != 'macOS'
with:
flag-name: run-${{ join(matrix.*, '-') }}
parallel: true
debug: true
lint:
finish-coverage:
needs: test
if: ${{ always() }}
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1
- uses: psf/black@stable
- name: Coveralls Finished
uses: coverallsapp/github-action@v2.3.0
with:
parallel-finished: true

View file

@ -417,3 +417,10 @@ dumped = instance.model_dump_json(context={'zarr_dump_array': True})
}
}
```
## Vendored Dependencies
We have vendored dependencies in the `src/numpydantic/vendor` package,
and reproduced their licenses in the `licenses` directory.
- [nptyping](https://github.com/ramonhagenaars/nptyping) - `numpydantic.vendor.nptyping` - `/licenses/nptyping.txt`

View file

@ -2,6 +2,33 @@
## 1.*
### 1.2.3 - 24-07-31 - Vendor `nptyping`
`nptyping` vendored into `numpydantic.vendor.nptyping` -
`nptyping` is no longer maintained, and pins `numpy<2`.
It also has many obnoxious warnings and we have to monkeypatch it
so it performs halfway decently. Since we are en-route to deprecating
usage of `nptyping` anyway, in the meantime we have just vendored it in
(it is MIT licensed, included) so that we can make those changes ourselves
and have to patch less of it. Currently the whole package is vendored with
modifications, but will be whittled away until we have replaced it with
updated type specification system :)
Bugfix:
- [#2](https://github.com/p2p-ld/numpydantic/issues/2) - Support `numpy>=2`
- Remove deprecated numpy dtypes
CI:
- Add windows and mac tests
- Add testing with numpy>=2 and <2
DevOps:
- Make a tox file for local testing, not used in CI.
Tidying:
- Remove `monkeypatch` module! we don't need it anymore!
everything has either been upstreamed or vendored.
### 1.2.2 - 24-07-31
Add `datetime` map to numpy's :class:`numpy.datetime64` type

7
licenses/nptyping.txt Normal file
View file

@ -0,0 +1,7 @@
Copyright 2022, Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

407
pdm.lock
View file

@ -5,7 +5,7 @@
groups = ["default", "arrays", "dask", "dev", "docs", "hdf5", "tests", "video"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.2"
content_hash = "sha256:221d8b83742574a9be8f41986d0def3392a7b248d1bb4fa5d3676e5fd2ebbefb"
content_hash = "sha256:62c153ae2eb738b173ac882538810a727eee2ac364ea674c0d8a1ef310bbefd6"
[[package]]
name = "alabaster"
@ -133,13 +133,13 @@ files = [
[[package]]
name = "certifi"
version = "2024.6.2"
version = "2024.7.4"
requires_python = ">=3.6"
summary = "Python package for providing Mozilla's CA Bundle."
groups = ["dev", "docs", "tests"]
files = [
{file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"},
{file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"},
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
]
[[package]]
@ -352,7 +352,7 @@ files = [
[[package]]
name = "dask"
version = "2024.6.2"
version = "2024.7.1"
requires_python = ">=3.9"
summary = "Parallel PyData with Task Scheduling"
groups = ["arrays", "dask", "dev", "tests"]
@ -362,13 +362,13 @@ dependencies = [
"fsspec>=2021.09.0",
"importlib-metadata>=4.13.0; python_version < \"3.12\"",
"packaging>=20.0",
"partd>=1.2.0",
"partd>=1.4.0",
"pyyaml>=5.3.1",
"toolz>=0.10.0",
]
files = [
{file = "dask-2024.6.2-py3-none-any.whl", hash = "sha256:81b80ee015b2e057b93bb2d1bf13a866136e762e2b24bf54b6b621e8b86b7708"},
{file = "dask-2024.6.2.tar.gz", hash = "sha256:d429d6b19e85fd1306ac37c188aaf99d03bbe69a6fe59d2b42882b2ac188686f"},
{file = "dask-2024.7.1-py3-none-any.whl", hash = "sha256:dd046840050376c317de90629db5c6197adda820176cf3e2df10c3219d11951f"},
{file = "dask-2024.7.1.tar.gz", hash = "sha256:dbaef2d50efee841a9d981a218cfeb50392fc9a95e0403b6d680450e4f50d531"},
]
[[package]]
@ -393,14 +393,14 @@ files = [
[[package]]
name = "exceptiongroup"
version = "1.2.1"
version = "1.2.2"
requires_python = ">=3.7"
summary = "Backport of PEP 654 (exception groups)"
groups = ["dev", "tests"]
marker = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[[package]]
@ -428,7 +428,7 @@ files = [
[[package]]
name = "furo"
version = "2024.5.6"
version = "2024.7.18"
requires_python = ">=3.8"
summary = "A clean customisable Sphinx documentation theme."
groups = ["dev", "docs"]
@ -439,8 +439,8 @@ dependencies = [
"sphinx<8.0,>=6.0",
]
files = [
{file = "furo-2024.5.6-py3-none-any.whl", hash = "sha256:490a00d08c0a37ecc90de03ae9227e8eb5d6f7f750edf9807f398a2bdf2358de"},
{file = "furo-2024.5.6.tar.gz", hash = "sha256:81f205a6605ebccbb883350432b4831c0196dd3d1bc92f61e1f459045b3d2b0b"},
{file = "furo-2024.7.18-py3-none-any.whl", hash = "sha256:b192c7c1f59805494c8ed606d9375fdac6e6ba8178e747e72bc116745fb7e13f"},
{file = "furo-2024.7.18.tar.gz", hash = "sha256:37b08c5fccc95d46d8712c8be97acd46043963895edde05b0f4f135d58325c83"},
]
[[package]]
@ -518,7 +518,7 @@ files = [
[[package]]
name = "importlib-metadata"
version = "8.0.0"
version = "8.2.0"
requires_python = ">=3.8"
summary = "Read metadata from Python packages"
groups = ["arrays", "dask", "dev", "docs", "tests"]
@ -527,8 +527,8 @@ dependencies = [
"zipp>=0.5",
]
files = [
{file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"},
{file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"},
{file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"},
{file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"},
]
[[package]]
@ -697,21 +697,6 @@ files = [
{file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"},
]
[[package]]
name = "nptyping"
version = "2.5.0"
requires_python = ">=3.7"
summary = "Type hints for NumPy."
groups = ["arrays", "default", "dev", "tests"]
dependencies = [
"numpy<2.0.0,>=1.20.0; python_version >= \"3.8\"",
"typing-extensions<5.0.0,>=4.0.0; python_version < \"3.10\"",
]
files = [
{file = "nptyping-2.5.0-py3-none-any.whl", hash = "sha256:764e51836faae33a7ae2e928af574cfb701355647accadcc89f2ad793630b7c8"},
{file = "nptyping-2.5.0.tar.gz", hash = "sha256:e3d35b53af967e6fb407c3016ff9abae954d3a0568f7cc13a461084224e8e20a"},
]
[[package]]
name = "numcodecs"
version = "0.12.1"
@ -743,47 +728,56 @@ files = [
[[package]]
name = "numpy"
version = "1.26.4"
version = "2.0.1"
requires_python = ">=3.9"
summary = "Fundamental package for array computing in Python"
groups = ["arrays", "default", "dev", "hdf5", "tests", "video"]
files = [
{file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"},
{file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"},
{file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"},
{file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"},
{file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"},
{file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"},
{file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"},
{file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"},
{file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"},
{file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"},
{file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"},
{file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"},
{file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"},
{file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"},
{file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"},
{file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"},
{file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
{file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
{file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
{file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"},
{file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"},
{file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"},
{file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"},
{file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"},
{file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"},
{file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"},
{file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"},
{file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"},
{file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"},
{file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"},
{file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"},
{file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"},
{file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"},
{file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"},
{file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"},
{file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
{file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"},
{file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"},
{file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"},
{file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"},
{file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"},
{file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"},
{file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"},
{file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"},
{file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"},
{file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"},
{file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"},
{file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"},
{file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"},
{file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"},
{file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"},
{file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"},
{file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"},
{file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"},
{file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"},
{file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"},
{file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"},
{file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"},
{file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"},
{file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"},
{file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"},
{file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"},
{file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"},
{file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"},
{file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"},
{file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"},
{file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"},
{file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"},
{file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"},
{file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"},
{file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"},
{file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"},
{file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"},
{file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"},
{file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"},
{file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"},
{file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"},
{file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"},
{file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"},
{file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"},
{file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"},
]
[[package]]
@ -874,23 +868,24 @@ files = [
[[package]]
name = "pydantic"
version = "2.7.4"
version = "2.8.2"
requires_python = ">=3.8"
summary = "Data validation using Python type hints"
groups = ["arrays", "default", "dev", "docs", "tests"]
dependencies = [
"annotated-types>=0.4.0",
"pydantic-core==2.18.4",
"typing-extensions>=4.6.1",
"pydantic-core==2.20.1",
"typing-extensions>=4.12.2; python_version >= \"3.13\"",
"typing-extensions>=4.6.1; python_version < \"3.13\"",
]
files = [
{file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"},
{file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"},
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
]
[[package]]
name = "pydantic-core"
version = "2.18.4"
version = "2.20.1"
requires_python = ">=3.8"
summary = "Core functionality for Pydantic validation and serialization"
groups = ["arrays", "default", "dev", "docs", "tests"]
@ -898,78 +893,88 @@ dependencies = [
"typing-extensions!=4.7.0,>=4.6.0",
]
files = [
{file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"},
{file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"},
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"},
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"},
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"},
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"},
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"},
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"},
{file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"},
{file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"},
{file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"},
{file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"},
{file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"},
{file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"},
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"},
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"},
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"},
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"},
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"},
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"},
{file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"},
{file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"},
{file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"},
{file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"},
{file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"},
{file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"},
{file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"},
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"},
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"},
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"},
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"},
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"},
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"},
{file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"},
{file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"},
{file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"},
{file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"},
{file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"},
{file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"},
{file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"},
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"},
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"},
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"},
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"},
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"},
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"},
{file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"},
{file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"},
{file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"},
{file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"},
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"},
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"},
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"},
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"},
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"},
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"},
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"},
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"},
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"},
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"},
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"},
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"},
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"},
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"},
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"},
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"},
{file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"},
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
{file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
{file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
{file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
{file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
{file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
{file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
{file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
{file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"},
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"},
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"},
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"},
{file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"},
{file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"},
{file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
]
[[package]]
name = "pydantic-settings"
version = "2.3.4"
version = "2.4.0"
requires_python = ">=3.8"
summary = "Settings management using Pydantic"
groups = ["dev", "docs"]
@ -978,8 +983,8 @@ dependencies = [
"python-dotenv>=0.21.0",
]
files = [
{file = "pydantic_settings-2.3.4-py3-none-any.whl", hash = "sha256:11ad8bacb68a045f00e4f862c7a718c8a9ec766aa8fd4c32e39a0594b207b53a"},
{file = "pydantic_settings-2.3.4.tar.gz", hash = "sha256:c5802e3d62b78e82522319bbc9b8f8ffb28ad1c988a99311d04f2a6051fca0a7"},
{file = "pydantic_settings-2.4.0-py3-none-any.whl", hash = "sha256:bb6849dc067f1687574c12a639e231f3a6feeed0a12d710c1382045c5db1c315"},
{file = "pydantic_settings-2.4.0.tar.gz", hash = "sha256:ed81c3a0f46392b4d7c0a565c05884e6e54b3456e6f0fe4d8814981172dc9a88"},
]
[[package]]
@ -995,7 +1000,7 @@ files = [
[[package]]
name = "pytest"
version = "8.2.2"
version = "8.3.2"
requires_python = ">=3.8"
summary = "pytest: simple powerful testing with Python"
groups = ["dev", "tests"]
@ -1004,12 +1009,12 @@ dependencies = [
"exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
"iniconfig",
"packaging",
"pluggy<2.0,>=1.5",
"pluggy<2,>=1.5",
"tomli>=1; python_version < \"3.11\"",
]
files = [
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
{file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
{file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
]
[[package]]
@ -1114,29 +1119,29 @@ files = [
[[package]]
name = "ruff"
version = "0.5.0"
version = "0.5.5"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["dev"]
files = [
{file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"},
{file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"},
{file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"},
{file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"},
{file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"},
{file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"},
{file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"},
{file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"},
{file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"},
{file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"},
{file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"},
{file = "ruff-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf"},
{file = "ruff-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8"},
{file = "ruff-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"},
{file = "ruff-0.5.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523"},
{file = "ruff-0.5.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f"},
{file = "ruff-0.5.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8"},
{file = "ruff-0.5.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab"},
{file = "ruff-0.5.5-py3-none-win32.whl", hash = "sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d"},
{file = "ruff-0.5.5-py3-none-win_amd64.whl", hash = "sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445"},
{file = "ruff-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6"},
{file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"},
]
[[package]]
@ -1173,22 +1178,22 @@ files = [
[[package]]
name = "sphinx"
version = "7.3.7"
version = "7.4.7"
requires_python = ">=3.9"
summary = "Python documentation generator"
groups = ["dev", "docs"]
dependencies = [
"Jinja2>=3.0",
"Pygments>=2.14",
"Jinja2>=3.1",
"Pygments>=2.17",
"alabaster~=0.7.14",
"babel>=2.9",
"colorama>=0.4.5; sys_platform == \"win32\"",
"docutils<0.22,>=0.18.1",
"babel>=2.13",
"colorama>=0.4.6; sys_platform == \"win32\"",
"docutils<0.22,>=0.20",
"imagesize>=1.3",
"importlib-metadata>=4.8; python_version < \"3.10\"",
"packaging>=21.0",
"requests>=2.25.0",
"snowballstemmer>=2.0",
"importlib-metadata>=6.0; python_version < \"3.10\"",
"packaging>=23.0",
"requests>=2.30.0",
"snowballstemmer>=2.2",
"sphinxcontrib-applehelp",
"sphinxcontrib-devhelp",
"sphinxcontrib-htmlhelp>=2.0.0",
@ -1198,8 +1203,8 @@ dependencies = [
"tomli>=2; python_version < \"3.11\"",
]
files = [
{file = "sphinx-7.3.7-py3-none-any.whl", hash = "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3"},
{file = "sphinx-7.3.7.tar.gz", hash = "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc"},
{file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"},
{file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"},
]
[[package]]
@ -1251,35 +1256,35 @@ files = [
[[package]]
name = "sphinxcontrib-applehelp"
version = "1.0.8"
version = "2.0.0"
requires_python = ">=3.9"
summary = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
groups = ["dev", "docs"]
files = [
{file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"},
{file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"},
{file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
{file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
]
[[package]]
name = "sphinxcontrib-devhelp"
version = "1.0.6"
version = "2.0.0"
requires_python = ">=3.9"
summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
groups = ["dev", "docs"]
files = [
{file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"},
{file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"},
{file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
{file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
]
[[package]]
name = "sphinxcontrib-htmlhelp"
version = "2.0.5"
version = "2.1.0"
requires_python = ">=3.9"
summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
groups = ["dev", "docs"]
files = [
{file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"},
{file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"},
{file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
{file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
]
[[package]]
@ -1306,29 +1311,29 @@ files = [
[[package]]
name = "sphinxcontrib-qthelp"
version = "1.0.7"
version = "2.0.0"
requires_python = ">=3.9"
summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
groups = ["dev", "docs"]
files = [
{file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"},
{file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"},
{file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
{file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
]
[[package]]
name = "sphinxcontrib-serializinghtml"
version = "1.1.10"
version = "2.0.0"
requires_python = ">=3.9"
summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
groups = ["dev", "docs"]
files = [
{file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"},
{file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"},
{file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
{file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
]
[[package]]
name = "starlette"
version = "0.37.2"
version = "0.38.2"
requires_python = ">=3.8"
summary = "The little ASGI library that shines."
groups = ["dev"]
@ -1337,8 +1342,8 @@ dependencies = [
"typing-extensions>=3.10.0; python_version < \"3.10\"",
]
files = [
{file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
{file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
{file = "starlette-0.38.2-py3-none-any.whl", hash = "sha256:4ec6a59df6bbafdab5f567754481657f7ed90dc9d69b0c9ff017907dd54faeff"},
{file = "starlette-0.38.2.tar.gz", hash = "sha256:c7c0441065252160993a1a37cf2a73bb64d271b17303e0b0c1eb7191cfb12d75"},
]
[[package]]
@ -1388,7 +1393,7 @@ files = [
[[package]]
name = "uvicorn"
version = "0.30.1"
version = "0.30.4"
requires_python = ">=3.8"
summary = "The lightning-fast ASGI server."
groups = ["dev"]
@ -1398,8 +1403,8 @@ dependencies = [
"typing-extensions>=4.0; python_version < \"3.11\"",
]
files = [
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"},
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"},
{file = "uvicorn-0.30.4-py3-none-any.whl", hash = "sha256:06b00e3087e58c6865c284143c0c42f810b32ff4f265ab19d08c566f74a08728"},
{file = "uvicorn-0.30.4.tar.gz", hash = "sha256:00db9a9e3711a5fa59866e2b02fac69d8dc70ce0814aaec9a66d1d9e5c832a30"},
]
[[package]]

View file

@ -1,14 +1,14 @@
[project]
name = "numpydantic"
version = "1.2.2"
version = "1.2.3"
description = "Type and shape validation and serialization for numpy arrays in pydantic models"
authors = [
{name = "sneakers-the-rat", email = "sneakers-the-rat@protonmail.com"},
]
dependencies = [
"pydantic>=2.3.0",
"nptyping>=2.5.0",
"numpy>=1.24.0",
"typing-extensions>=4.11.0;python_version<'3.11'"
]
homepage = "https://numpydantic.readthedocs.io"
requires-python = "<4.0,>=3.9"
@ -40,7 +40,6 @@ tests = [
"coverage>=6.1.1",
"pytest-cov<5.0.0,>=4.1.0",
"coveralls<4.0.0,>=3.3.1",
"typing-extensions>=4.11.0;python_version<'3.11'",
]
docs = [
"sphinx<8.0.0,>=7.2.6",
@ -101,6 +100,10 @@ select = [
"I",
# annotations
"ANN",
# perf
"PERF",
# numpy
"NPY",
## ----------
# pydocstyle
# undocumented public objects
@ -132,3 +135,8 @@ fixable = ["ALL"]
plugins = [
"pydantic.mypy"
]
[tool.coverage.run]
omit = [
"src/numpydantic/vendor/*"
]

View file

@ -1,10 +1,6 @@
# ruff: noqa: E402
# ruff: noqa: F401
# ruff: noqa: I001
# ruff: noqa: D104
from numpydantic.monkeypatch import apply_patches
apply_patches()
from numpydantic.ndarray import NDArray
from numpydantic.meta import update_ndarray_stub

View file

@ -69,9 +69,7 @@ Half = np.half
Single = np.single
Double = np.double
LongDouble = np.longdouble
LongFloat = np.longfloat
Float = (
np.float_,
np.float16,
np.float32,
np.float64,
@ -80,36 +78,26 @@ Float = (
)
Floating = Float
ComplexFloating = np.complexfloating
Complex64 = np.complex64
Complex128 = np.complex128
CSingle = np.csingle
SingleComplex = np.singlecomplex
CDouble = np.cdouble
CFloat = np.cfloat
CLongDouble = np.clongdouble
CLongFloat = np.clongfloat
Complex = (
np.complex_,
np.complexfloating,
np.complex64,
np.complex128,
np.csingle,
np.singlecomplex,
np.cdouble,
np.cfloat,
np.clongdouble,
np.clongfloat,
)
LongComplex = np.longcomplex
Flexible = np.flexible
Void = np.void
Character = np.character
Bytes = np.bytes_
Str = np.str_
String = np.string_
Unicode = np.unicode_
String = np.str_
Unicode = np.str_
Number = tuple(
[

View file

@ -144,9 +144,9 @@ class VideoProxy:
elif isinstance(item, slice):
# slice of frames
item = self._complete_slice(item)
frames = []
for i in range(item.start, item.stop, item.step):
frames.append(self._get_frame(i))
frames = [
self._get_frame(i) for i in range(item.start, item.stop, item.step)
]
return np.stack(frames)
else:
# slices are passed as tuples

View file

@ -21,7 +21,7 @@ np_to_python = {
**{n: int for n in dt.Integer},
**{n: float for n in dt.Float},
**{n: complex for n in dt.Complex},
**{n: str for n in (np.character, np.str_, np.string_, np.unicode_)},
**{n: str for n in (np.character, np.str_, np.bytes_, np.str_)},
}
"""Map from python types to numpy"""

View file

@ -1,66 +0,0 @@
"""
Functions to monkeypatch dependent packages - most notably nptyping
"""
# ruff: noqa: ANN001
def patch_npytyping_perf() -> None:
"""
npytyping makes an expensive call to inspect.stack()
that makes imports of pydantic models take ~200x longer than
they should:
References:
- https://github.com/ramonhagenaars/nptyping/issues/110
"""
import inspect
from types import FrameType
from nptyping import base_meta_classes, ndarray, recarray
from nptyping.pandas_ import dataframe
# make a new __module__ methods for the affected classes
def new_module_ndarray(cls) -> str: # pragma: no cover
return cls._get_module(inspect.currentframe(), "nptyping.ndarray")
def new_module_recarray(cls) -> str: # pragma: no cover
return cls._get_module(inspect.currentframe(), "nptyping.recarray")
def new_module_dataframe(cls) -> str: # pragma: no cover
return cls._get_module(inspect.currentframe(), "nptyping.pandas_.dataframe")
# and a new _get_module method for the parent class
def new_get_module(cls, stack: FrameType, module: str) -> str: # pragma: no cover
return (
"typing"
if inspect.getframeinfo(stack.f_back).function == "formatannotation"
else module
)
# now apply the patches
ndarray.NDArrayMeta.__module__ = property(new_module_ndarray)
recarray.RecArrayMeta.__module__ = property(new_module_recarray)
dataframe.DataFrameMeta.__module__ = property(new_module_dataframe)
base_meta_classes.SubscriptableMeta._get_module = new_get_module
def patch_nptyping_warnings() -> None:
"""
nptyping shits out a bunch of numpy deprecation warnings from using
olde aliases
References:
- https://github.com/ramonhagenaars/nptyping/issues/113
- https://github.com/ramonhagenaars/nptyping/issues/102
"""
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning, module="nptyping.*")
def apply_patches() -> None:
"""Apply all monkeypatches!"""
patch_npytyping_perf()
patch_nptyping_warnings()

View file

@ -16,14 +16,6 @@ Extension of nptyping NDArray for pydantic that allows for JSON-Schema serializa
from typing import TYPE_CHECKING, Any, Tuple
import numpy as np
from nptyping.error import InvalidArgumentsError
from nptyping.ndarray import NDArrayMeta as _NDArrayMeta
from nptyping.nptyping_type import NPTypingType
from nptyping.structure import Structure
from nptyping.structure_expression import check_type_names
from nptyping.typing_ import (
dtype_per_name,
)
from pydantic import GetJsonSchemaHandler
from pydantic_core import core_schema
@ -38,6 +30,14 @@ from numpydantic.schema import (
make_json_schema,
)
from numpydantic.types import DtypeType, ShapeType
from numpydantic.vendor.nptyping.error import InvalidArgumentsError
from numpydantic.vendor.nptyping.ndarray import NDArrayMeta as _NDArrayMeta
from numpydantic.vendor.nptyping.nptyping_type import NPTypingType
from numpydantic.vendor.nptyping.structure import Structure
from numpydantic.vendor.nptyping.structure_expression import check_type_names
from numpydantic.vendor.nptyping.typing_ import (
dtype_per_name,
)
if TYPE_CHECKING: # pragma: no cover
from nptyping.base_meta_classes import SubscriptableMeta

View file

@ -7,7 +7,6 @@ import hashlib
import json
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
import nptyping.structure
import numpy as np
from pydantic import SerializationInfo
from pydantic_core import CoreSchema, core_schema
@ -17,8 +16,9 @@ from numpydantic import dtype as dt
from numpydantic.interface import Interface
from numpydantic.maps import np_to_python
from numpydantic.types import DtypeType, NDArrayType, ShapeType
from numpydantic.vendor.nptyping.structure import StructureMeta
if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from numpydantic import Shape
_handler_type = Callable[[Any], core_schema.CoreSchema]
@ -45,12 +45,12 @@ def _numeric_dtype(dtype: DtypeType, _handler: _handler_type) -> CoreSchema:
def _lol_dtype(dtype: DtypeType, _handler: _handler_type) -> CoreSchema:
"""Get the innermost dtype schema to use in the generated pydantic schema"""
if isinstance(dtype, nptyping.structure.StructureMeta): # pragma: no cover
if isinstance(dtype, StructureMeta): # pragma: no cover
raise NotImplementedError("Structured dtypes are currently unsupported")
if isinstance(dtype, tuple):
# if it's a meta-type that refers to a generic float/int, just make that
if dtype == dt.Float:
if dtype in (dt.Float, dt.Number):
array_type = core_schema.float_schema()
elif dtype == dt.Integer:
array_type = core_schema.int_schema()
@ -143,7 +143,7 @@ def list_of_lists_schema(shape: "Shape", array_type: CoreSchema) -> ListSchema:
arg = int(arg)
arg_min = arg
arg_max = arg
except ValueError as e:
except ValueError as e: # pragma: no cover
raise ValueError(
"Array shapes must be integers, wildcards, ellipses, or "

View file

@ -29,15 +29,15 @@ from abc import ABC
from functools import lru_cache
from typing import Any, Dict, List, Union
from nptyping.base_meta_classes import ContainerMeta
from nptyping.error import InvalidShapeError, NPTypingError
from nptyping.nptyping_type import NPTypingType
from nptyping.shape_expression import (
from numpydantic.vendor.nptyping.base_meta_classes import ContainerMeta
from numpydantic.vendor.nptyping.error import InvalidShapeError, NPTypingError
from numpydantic.vendor.nptyping.nptyping_type import NPTypingType
from numpydantic.vendor.nptyping.shape_expression import (
get_dimensions,
normalize_shape_expression,
remove_labels,
)
from nptyping.typing_ import ShapeExpression, ShapeTuple
from numpydantic.vendor.nptyping.typing_ import ShapeExpression, ShapeTuple
class ShapeMeta(ContainerMeta, implementation="Shape"):

View file

@ -8,7 +8,7 @@ Note that these are types as in python typing types, not classes.
from typing import Any, Protocol, Tuple, Union, runtime_checkable
from nptyping import DType
from numpydantic.vendor.nptyping import DType
ShapeType = Union[Tuple[int, ...], Any]
DtypeType = Union[str, type, Any, DType]

10
src/numpydantic/vendor/__init__.py vendored Normal file
View file

@ -0,0 +1,10 @@
"""
Vendored modules - see licenses in /licenses.
Currently consists just of nptyping, as it is no longer maintained
and pins a version of numpy<2, and we have to do an increasing
number of awkward monkeypatches for perf and customization reasons.
This vendored module will slowly be worked out of the code and
all its functionality replaced.
"""

View file

@ -0,0 +1,182 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from numpydantic.vendor.nptyping.assert_isinstance import assert_isinstance
from numpydantic.vendor.nptyping.error import (
InvalidArgumentsError,
InvalidDTypeError,
InvalidShapeError,
InvalidStructureError,
NPTypingError,
)
from numpydantic.vendor.nptyping.ndarray import NDArray
from numpydantic.vendor.nptyping.package_info import __version__
from numpydantic.vendor.nptyping.pandas_.dataframe import DataFrame
from numpydantic.vendor.nptyping.recarray import RecArray
from numpydantic.vendor.nptyping.shape import Shape
from numpydantic.vendor.nptyping.shape_expression import (
normalize_shape_expression,
validate_shape_expression,
)
from numpydantic.vendor.nptyping.structure import Structure
from numpydantic.vendor.nptyping.typing_ import (
Bool,
Byte,
Bytes,
CDouble,
CFloat,
Character,
CLongDouble,
CLongFloat,
Complex,
Complex64,
Complex128,
ComplexFloating,
CSingle,
Datetime64,
Double,
DType,
Flexible,
Float,
Float16,
Float32,
Float64,
Floating,
Half,
Inexact,
Int,
Int8,
Int16,
Int32,
Int64,
IntC,
Integer,
IntP,
LongComplex,
LongDouble,
LongFloat,
LongLong,
Number,
Object,
Short,
SignedInteger,
Single,
SingleComplex,
String,
Timedelta64,
UByte,
UInt,
UInt8,
UInt16,
UInt32,
UInt64,
UIntC,
UIntP,
ULongLong,
Unicode,
UnsignedInteger,
UShort,
Void,
)
__all__ = [
"NDArray",
"RecArray",
"assert_isinstance",
"validate_shape_expression",
"normalize_shape_expression",
"NPTypingError",
"InvalidArgumentsError",
"InvalidShapeError",
"InvalidStructureError",
"InvalidDTypeError",
"Shape",
"Structure",
"__version__",
"DType",
"Number",
"Bool",
"Bool8",
"Object",
"Object0",
"Datetime64",
"Integer",
"SignedInteger",
"Int8",
"Int16",
"Int32",
"Int64",
"Byte",
"Short",
"IntC",
"IntP",
"Int0",
"Int",
"LongLong",
"Timedelta64",
"UnsignedInteger",
"UInt8",
"UInt16",
"UInt32",
"UInt64",
"UByte",
"UShort",
"UIntC",
"UIntP",
"UInt0",
"UInt",
"ULongLong",
"Inexact",
"Floating",
"Float16",
"Float32",
"Float64",
"Half",
"Single",
"Double",
"Float",
"LongDouble",
"LongFloat",
"ComplexFloating",
"Complex64",
"Complex128",
"CSingle",
"SingleComplex",
"CDouble",
"Complex",
"CFloat",
"CLongDouble",
"CLongFloat",
"LongComplex",
"Flexible",
"Void",
"Void0",
"Character",
"Bytes",
"String",
"Bytes0",
"Unicode",
"Str0",
"DataFrame",
]

View file

@ -0,0 +1,53 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from typing import (
Any,
Optional,
Type,
TypeVar,
)
try:
from typing import TypeGuard # type: ignore[attr-defined]
except ImportError: # pragma: no cover
from typing_extensions import TypeGuard # type: ignore[attr-defined]
TYPE = TypeVar("TYPE")
def assert_isinstance(
instance: Any, cls: Type[TYPE], message: Optional[str] = None
) -> TypeGuard[TYPE]:
"""
A TypeGuard function that is equivalent to `assert instance, cls, message`
that hides nasty MyPy or IDE warnings.
:param instance: the instance that is checked against cls.
:param cls: the class
:param message: any message that is displayed when the assert check fails.
:return: the type of cls.
"""
message = message or f"instance={instance!r}, cls={cls!r}"
assert isinstance(instance, cls), message
return True

View file

@ -0,0 +1,249 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from abc import ABCMeta, abstractmethod
from inspect import getframeinfo
from types import FrameType
from typing import (
Any,
Dict,
Optional,
Set,
Tuple,
TypeVar,
)
from numpydantic.vendor.nptyping.error import InvalidArgumentsError, NPTypingError
_T = TypeVar("_T")
class InconstructableMeta(ABCMeta):
"""
Makes it impossible for a class to get instantiated.
"""
def __call__(cls, *_: Any, **__: Any) -> None:
"""Raise an error if constructed"""
raise NPTypingError(
f"Cannot instantiate nptyping.{cls.__name__}. Did you mean to use [ ] ?"
)
class ImmutableMeta(ABCMeta):
"""
Makes it impossible to changes values on a class.
"""
def __setattr__(cls, key: str, value: Any) -> None:
if key not in ("_abc_impl", "__abstractmethods__"):
raise NPTypingError(f"Cannot set values to nptyping.{cls.__name__}.")
class FinalMeta(ABCMeta):
"""
Makes it impossible for classes to inherit from some class.
An concrete inheriting meta class requires to define a name for its
implementation. The class with this name will be the only class that is
allowed to use that concrete meta class.
"""
_name_per_meta_cls: Dict[type, Optional[str]] = {}
def __init_subclass__(cls, implementation: Optional[str] = None) -> None:
# implementation is made Optional here, to allow other meta classes to
# inherit.
cls._name_per_meta_cls[cls] = implementation
def __new__(cls, name: str, *args: Any, **kwargs: Any) -> type:
"""Prevent subclasses, return from internal dict instead"""
if name == cls._name_per_meta_cls[cls]:
assert name, "cls_name not set"
return type.__new__(cls, name, *args, **kwargs)
raise NPTypingError(f"Cannot subclass nptyping.{cls._name_per_meta_cls[cls]}.")
class MaybeCheckableMeta(ABCMeta):
"""
Makes instance and subclass checks raise by default.
"""
def __instancecheck__(cls, instance: Any) -> bool:
raise NPTypingError(
f"Instance checking is not supported for nptyping.{cls.__name__}."
)
def __subclasscheck__(cls, subclass: Any) -> bool:
raise NPTypingError(
f"Subclass checking is not supported for nptyping.{cls.__name__}."
)
class PrintableMeta(ABCMeta):
"""
Ensures that a class can be printed nicely.
"""
@abstractmethod
def __str__(cls) -> str: ... # pragma: no cover
def __repr__(cls) -> str:
return str(cls)
class SubscriptableMeta(ABCMeta):
"""
Makes a class subscriptable: it accepts arguments between brackets and a
new type is returned for every unique set of arguments.
"""
_all_types: Dict[Tuple[type, Tuple[Any, ...]], type] = {}
_parameterized: bool = False
@abstractmethod
def _get_item(cls, item: Any) -> Tuple[Any, ...]: ... # pragma: no cover
def _get_module(cls, stack: FrameType, module: str) -> str:
# The magic below makes Python's help function display a meaningful
# text with nptyping types.
return (
"typing"
if getframeinfo(stack.f_back).function == "formatannotation"
else module
)
def _get_additional_values(
cls, item: Any # pylint: disable=unused-argument
) -> Dict[str, Any]:
# This method is invoked after _get_item and right before returning
# the result of __getitem__. It can be overridden to provide extra
# values that are to be set as attributes on the new type.
return {}
def __getitem__(cls, item: Any) -> type:
if getattr(cls, "_parameterized", False):
raise NPTypingError(f"Type nptyping.{cls} is already parameterized.")
args = cls._get_item(item)
additional_values = cls._get_additional_values(item)
assert hasattr(cls, "__args__"), "A SubscriptableMeta must have __args__."
if args != cls.__args__: # type: ignore[attr-defined]
result = cls._create_type(args, additional_values)
else:
result = cls
return result
def _create_type(
cls, args: Tuple[Any, ...], additional_values: Dict[str, Any]
) -> type:
key = (cls, args)
if key not in cls._all_types:
cls._all_types[key] = type(
cls.__name__,
(cls,),
{"__args__": args, "_parameterized": True, **additional_values},
)
return cls._all_types[key]
class ComparableByArgsMeta(ABCMeta):
"""
Makes a class comparable by means of its __args__.
"""
__args__: Tuple[Any, ...]
def __eq__(cls, other: Any) -> bool:
return (
hasattr(cls, "__args__")
and hasattr(other, "__args__")
and cls.__args__ == other.__args__
)
def __hash__(cls) -> int:
return hash(cls.__args__)
class ContainerMeta(
InconstructableMeta,
ImmutableMeta,
FinalMeta,
MaybeCheckableMeta,
PrintableMeta,
SubscriptableMeta,
ComparableByArgsMeta,
ABCMeta,
):
"""
Base meta class for "containers" such as Shape and Structure.
"""
_known_expressions: Set[str] = set()
__args__: Tuple[str, ...]
@abstractmethod
def _validate_expression(cls, item: str) -> None: ... # pragma: no cover
@abstractmethod
def _normalize_expression(cls, item: str) -> str: ... # pragma: no cover
def _get_item(cls, item: Any) -> Tuple[Any, ...]:
if not isinstance(item, str):
raise InvalidArgumentsError(
f"Unexpected argument of type {type(item)}, expecting a string."
)
if item in cls._known_expressions:
# No need to do costly validations and normalizations if it has been done
# before.
return (item,)
cls._validate_expression(item)
norm_shape_expression = cls._normalize_expression(item)
cls._known_expressions.add(norm_shape_expression)
return (norm_shape_expression,)
def __subclasscheck__(cls, subclass: Any) -> bool:
type_match = type(subclass) is type(cls)
return type_match and (
subclass.__args__ == cls.__args__ or not cls._parameterized
)
def __str__(cls) -> str:
return f"{cls.__name__}['{cls.__args__[0]}']"
def __eq__(cls, other: Any) -> bool:
result = cls is other
if not result and hasattr(cls, "__args__") and hasattr(other, "__args__"):
normalized_args = tuple(
cls._normalize_expression(str(arg)) for arg in other.__args__
)
result = cls.__args__ == normalized_args
return result
def __hash__(cls) -> int:
return hash(cls.__args__)

View file

@ -0,0 +1,47 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
class NPTypingError(Exception):
"""Base error for all NPTyping errors."""
class InvalidArgumentsError(NPTypingError):
"""Raised when a invalid arguments are provided to an nptyping type."""
class InvalidShapeError(NPTypingError):
"""Raised when a shape is considered not valid."""
class InvalidStructureError(NPTypingError):
"""Raised when a structure is considered not valid."""
class InvalidDTypeError(NPTypingError):
"""Raised when an argument is not a DType."""
class DependencyError(NPTypingError):
"""Raised when a dependency has not been installed."""

View file

@ -0,0 +1,200 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import inspect
from abc import ABC
from typing import Any, Tuple
import numpy as np
from numpydantic.vendor.nptyping.base_meta_classes import (
FinalMeta,
ImmutableMeta,
InconstructableMeta,
MaybeCheckableMeta,
PrintableMeta,
SubscriptableMeta,
)
from numpydantic.vendor.nptyping.error import InvalidArgumentsError
from numpydantic.vendor.nptyping.nptyping_type import NPTypingType
from numpydantic.vendor.nptyping.shape import Shape
from numpydantic.vendor.nptyping.shape_expression import check_shape
from numpydantic.vendor.nptyping.structure import Structure
from numpydantic.vendor.nptyping.structure_expression import (
check_structure,
check_type_names,
)
from numpydantic.vendor.nptyping.typing_ import (
DType,
dtype_per_name,
name_per_dtype,
)
class NDArrayMeta(
SubscriptableMeta,
InconstructableMeta,
ImmutableMeta,
FinalMeta,
MaybeCheckableMeta,
PrintableMeta,
implementation="NDArray",
):
"""
Metaclass that is coupled to nptyping.NDArray. It contains all actual logic
such as instance checking.
"""
__args__: Tuple[Shape, DType]
_parameterized: bool
@property
def __module__(cls) -> str:
return cls._get_module(inspect.currentframe(), "nptyping.ndarray")
def _get_item(cls, item: Any) -> Tuple[Any, ...]:
cls._check_item(item)
shape, dtype = cls._get_from_tuple(item)
return shape, dtype
def __instancecheck__( # pylint: disable=bad-mcs-method-argument
self, instance: Any
) -> bool:
shape, dtype = self.__args__
dtype_is_structure = issubclass(dtype, Structure)
structure_is_ok = dtype_is_structure and check_structure(
instance.dtype, dtype, dtype_per_name
)
return (
isinstance(instance, np.ndarray)
and (shape is Any or check_shape(instance.shape, shape))
and (
dtype is Any
or structure_is_ok
or issubclass(instance.dtype.type, dtype)
)
)
def __str__(cls) -> str:
shape, dtype = cls.__args__
return (
f"{cls.__name__}[{cls._shape_expression_to_str(shape)}, "
f"{cls._dtype_to_str(dtype)}]"
)
def _is_literal_like(cls, item: Any) -> bool:
# item is a Literal or "Literal enough" (ducktyping).
return hasattr(item, "__args__")
def _check_item(cls, item: Any) -> None:
# Check if the item is what we expect and raise if it is not.
if not isinstance(item, tuple):
raise InvalidArgumentsError(f"Unexpected argument of type {type(item)}.")
if len(item) > 2:
raise InvalidArgumentsError(f"Unexpected argument {item[2]}.")
def _get_from_tuple(cls, item: Tuple[Any, ...]) -> Tuple[Shape, DType]:
# Return the Shape Expression and DType from a tuple.
shape = cls._get_shape(item[0])
dtype = cls._get_dtype(item[1])
return shape, dtype
def _get_shape(cls, dtype_candidate: Any) -> Shape:
if dtype_candidate is Any or dtype_candidate is Shape:
shape = Any
elif issubclass(dtype_candidate, Shape):
shape = dtype_candidate
elif cls._is_literal_like(dtype_candidate):
shape_expression = dtype_candidate.__args__[0]
shape = Shape[shape_expression]
else:
raise InvalidArgumentsError(
f"Unexpected argument '{dtype_candidate}', expecting"
" Shape[<ShapeExpression>]"
" or Literal[<ShapeExpression>]"
" or typing.Any."
)
return shape
def _get_dtype(cls, dtype_candidate: Any) -> DType:
is_dtype = isinstance(dtype_candidate, type) and issubclass(
dtype_candidate, np.generic
)
if dtype_candidate is Any:
dtype = Any
elif is_dtype:
dtype = dtype_candidate
elif issubclass(dtype_candidate, Structure):
dtype = dtype_candidate
check_type_names(dtype, dtype_per_name)
elif cls._is_literal_like(dtype_candidate):
structure_expression = dtype_candidate.__args__[0]
dtype = Structure[structure_expression]
check_type_names(dtype, dtype_per_name)
else:
raise InvalidArgumentsError(
f"Unexpected argument '{dtype_candidate}', expecting"
" Structure[<StructureExpression>]"
" or Literal[<StructureExpression>]"
" or a dtype"
" or typing.Any."
)
return dtype
def _dtype_to_str(cls, dtype: Any) -> str:
if dtype is Any:
result = "Any"
elif issubclass(dtype, Structure):
result = str(dtype)
else:
result = name_per_dtype[dtype]
return result
def _shape_expression_to_str(cls, shape_expression: Any) -> str:
return "Any" if shape_expression is Any else str(shape_expression)
class NDArray(NPTypingType, ABC, metaclass=NDArrayMeta):
"""
An nptyping equivalent of numpy ndarray.
## No arguments means an NDArray with any DType and any shape.
>>> NDArray
NDArray[Any, Any]
## You can provide a DType and a Shape Expression.
>>> from nptyping import Int32, Shape
>>> NDArray[Shape["2, 2"], Int32]
NDArray[Shape['2, 2'], Int32]
## Instance checking can be done and the shape is also checked.
>>> import numpy as np
>>> isinstance(np.array([[1, 2], [3, 4]]), NDArray[Shape['2, 2'], Int32])
True
>>> isinstance(np.array([[1, 2], [3, 4], [5, 6]]), NDArray[Shape['2, 2'], Int32])
False
"""
__args__ = (Any, Any)

View file

@ -0,0 +1,31 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from abc import ABC
class NPTypingType(ABC): # noqa: B024
"""
Baseclass for all nptyping types.
"""

View file

@ -0,0 +1,38 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
__title__ = "nptyping"
__version__ = "2.5.0"
__author__ = "Ramon Hagenaars"
__author_email__ = "ramon.hagenaars@gmail.com"
__description__ = "Type hints for NumPy."
__url__ = "https://github.com/ramonhagenaars/nptyping"
__license__ = "MIT"
__python_versions__ = [
"3.7",
"3.8",
"3.9",
"3.10",
"3.11",
]

View file

@ -0,0 +1,3 @@
"""
Pandas dataframe types
"""

View file

@ -0,0 +1,140 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import inspect
from abc import ABC
from typing import Any, Tuple
import numpy as np
from numpydantic.vendor.nptyping import InvalidArgumentsError
from numpydantic.vendor.nptyping.base_meta_classes import (
FinalMeta,
ImmutableMeta,
InconstructableMeta,
MaybeCheckableMeta,
PrintableMeta,
SubscriptableMeta,
)
from numpydantic.vendor.nptyping.error import DependencyError
from numpydantic.vendor.nptyping.nptyping_type import NPTypingType
from numpydantic.vendor.nptyping.pandas_.typing_ import dtype_per_name
from numpydantic.vendor.nptyping.structure import Structure
from numpydantic.vendor.nptyping.structure_expression import check_structure
try:
import pandas as pd
except ImportError: # pragma: no cover
pd = None # type: ignore[misc, assignment]
class DataFrameMeta(
SubscriptableMeta,
InconstructableMeta,
ImmutableMeta,
FinalMeta,
MaybeCheckableMeta,
PrintableMeta,
implementation="DataFrame",
):
"""
Metaclass that is coupled to nptyping.DataFrame. It contains all actual logic
such as instance checking.
"""
__args__: Tuple[Structure]
_parameterized: bool
def __instancecheck__( # pylint: disable=bad-mcs-method-argument
self, instance: Any
) -> bool:
structure = self.__args__[0]
if pd is None:
raise DependencyError( # pragma: no cover
"Pandas needs to be installed for instance checking. Use `pip "
"install nptyping[pandas]` or `pip install nptyping[complete]`"
)
if not isinstance(instance, pd.DataFrame):
return False
if structure is Any:
return True
structured_dtype = np.dtype(
[(column, dtype.str) for column, dtype in instance.dtypes.items()]
)
return check_structure(structured_dtype, structure, dtype_per_name)
def _get_item(cls, item: Any) -> Tuple[Structure]:
if item is Any:
return (Any,)
cls._check_item(item)
return (Structure[item.__args__[0]],)
def __str__(cls) -> str:
structure = cls.__args__[0]
structure_str = "Any" if structure is Any else structure.__args__[0]
return f"{cls.__name__}[{structure_str}]"
def __repr__(cls) -> str:
structure = cls.__args__[0]
structure_str = "Any" if structure is Any else structure
return f"{cls.__name__}[{structure_str}]"
@property
def __module__(cls) -> str:
return cls._get_module(inspect.currentframe(), "nptyping.ndarray")
def _check_item(cls, item: Any) -> None:
# Check if the item is what we expect and raise if it is not.
if not hasattr(item, "__args__"):
raise InvalidArgumentsError(f"Unexpected argument of type {type(item)}.")
class DataFrame(NPTypingType, ABC, metaclass=DataFrameMeta):
"""
An nptyping equivalent of pandas DataFrame.
## No arguments means a DataFrame of any structure.
>>> DataFrame
DataFrame[Any]
## You can use Structure Expression.
>>> from nptyping import DataFrame, Structure
>>> DataFrame[Structure["x: Int, y: Int"]]
DataFrame[Structure['[x, y]: Int']]
## Instance checking can be done and the structure is also checked.
>>> import pandas as pd
>>> df = pd.DataFrame({'x': [1, 2, 3], 'y': [4., 5., 6.]})
>>> isinstance(df, DataFrame[Structure['x: Int, y: Float']])
True
>>> isinstance(df, DataFrame[Structure['x: Float, y: Int']])
False
"""
__args__ = (Any,)

View file

@ -0,0 +1,27 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import pandas as pd
DataFrame = pd.DataFrame

View file

@ -0,0 +1,34 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from numpydantic.vendor.nptyping.typing_ import Object
from numpydantic.vendor.nptyping.typing_ import dtype_per_name as dtype_per_name_default
dtype_per_name = {
**dtype_per_name_default, # type: ignore[arg-type]
# Override the `String` and `Str` to point to `Object`. Pandas uses Object
# for string types in Dataframes and Series.
"String": Object,
"Str": Object,
}

View file

View file

@ -0,0 +1,79 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import inspect
from typing import Any, Tuple
import numpy as np
from numpydantic.vendor.nptyping.error import InvalidArgumentsError
from numpydantic.vendor.nptyping.ndarray import NDArray, NDArrayMeta
from numpydantic.vendor.nptyping.structure import Structure
from numpydantic.vendor.nptyping.typing_ import DType
class RecArrayMeta(NDArrayMeta, implementation="RecArray"):
"""
Metaclass that is coupled to nptyping.RecArray. It takes most of its logic
from NDArrayMeta.
"""
def _get_item(cls, item: Any) -> Tuple[Any, ...]:
cls._check_item(item)
shape, dtype = cls._get_from_tuple(item)
return shape, dtype
def _get_dtype(cls, dtype_candidate: Any) -> DType:
if not issubclass(dtype_candidate, Structure) and dtype_candidate is not Any:
raise InvalidArgumentsError(
f"Unexpected argument {dtype_candidate}. Expecting a Structure."
)
return dtype_candidate
@property
def __module__(cls) -> str:
return cls._get_module(inspect.currentframe(), "nptyping.ndarray")
def __instancecheck__( # pylint: disable=bad-mcs-method-argument
self, instance: Any
) -> bool:
return isinstance(instance, np.recarray) and NDArrayMeta.__instancecheck__(
self, instance
)
class RecArray(NDArray, metaclass=RecArrayMeta):
"""
An nptyping equivalent of numpy recarray.
## RecArrays can take a Shape and must take a Structure
>>> from nptyping import Shape, Structure
>>> RecArray[Shape["2, 2"], Structure["x: Float, y: Float"]]
RecArray[Shape['2, 2'], Structure['[x, y]: Float']]
## Or Any
>>> from typing import Any
>>> RecArray[Shape["2, 2"], Any]
RecArray[Shape['2, 2'], Any]
"""

View file

@ -0,0 +1,27 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import numpy as np
RecArray = np.recarray

View file

@ -0,0 +1,76 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from abc import ABC
from typing import Any, Dict
from numpydantic.vendor.nptyping.base_meta_classes import ContainerMeta
from numpydantic.vendor.nptyping.nptyping_type import NPTypingType
from numpydantic.vendor.nptyping.shape_expression import (
get_dimensions,
normalize_shape_expression,
remove_labels,
validate_shape_expression,
)
class ShapeMeta(ContainerMeta, implementation="Shape"):
"""
Metaclass that is coupled to nptyping.Shape.
"""
def _validate_expression(cls, item: str) -> None:
validate_shape_expression(item)
def _normalize_expression(cls, item: str) -> str:
return normalize_shape_expression(item)
def _get_additional_values(cls, item: Any) -> Dict[str, Any]:
dim_strings = get_dimensions(item)
dim_string_without_labels = remove_labels(dim_strings)
return {"prepared_args": dim_string_without_labels}
class Shape(NPTypingType, ABC, metaclass=ShapeMeta):
"""
A container for shape expressions that describe the shape of an multi
dimensional array.
Simple example:
>>> Shape['2, 2']
Shape['2, 2']
A Shape can be compared to a typing.Literal. You can use Literals in
NDArray as well.
>>> from typing import Literal
>>> Shape['2, 2'] == Literal['2, 2']
True
"""
__args__ = ("*, ...",)
prepared_args = "*, ..."

View file

@ -0,0 +1,37 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
try:
from typing import Literal # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Literal # type: ignore[attr-defined,misc,assignment]
from typing import Any, cast
# For MyPy:
Shape = cast(Literal, Shape) # type: ignore[has-type,misc,valid-type]
# For PyRight:
class Shape: # type: ignore[no-redef]
def __class_getitem__(cls, item: Any) -> Any: ...

View file

@ -0,0 +1,191 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import re
import string
from functools import lru_cache
from typing import (
TYPE_CHECKING,
Any,
Dict,
List,
Union,
)
from numpydantic.vendor.nptyping.error import InvalidShapeError
from numpydantic.vendor.nptyping.typing_ import ShapeExpression, ShapeTuple
if TYPE_CHECKING:
from nptyping.shape import Shape # pragma: no cover
@lru_cache
def check_shape(shape: ShapeTuple, target: "Shape") -> bool:
"""
Check whether the given shape corresponds to the given shape_expression.
:param shape: the shape in question.
:param target: the shape expression to which shape is tested.
:return: True if the given shape corresponds to shape_expression.
"""
target_shape = _handle_ellipsis(shape, target.prepared_args)
return _check_dimensions_against_shape(shape, target_shape)
def validate_shape_expression(shape_expression: Union[ShapeExpression, Any]) -> None:
"""
Validate shape_expression and raise an InvalidShapeError if it is not
considered valid.
:param shape_expression: the shape expression to validate.
:return: None.
"""
shape_expression_no_quotes = shape_expression.replace("'", "").replace('"', "")
if shape_expression is not Any and not re.match(
_REGEX_SHAPE_EXPRESSION, shape_expression_no_quotes
):
raise InvalidShapeError(
f"'{shape_expression}' is not a valid shape expression."
)
def normalize_shape_expression(shape_expression: ShapeExpression) -> ShapeExpression:
"""
Normalize the given shape expression, e.g. by removing whitespaces, making
similar expressions look the same.
:param shape_expression: the shape expression that is to be normalized.
:return: a normalized shape expression.
"""
shape_expression = shape_expression.replace("'", "").replace('"', "")
# Replace whitespaces right before labels with $.
shape_expression = re.sub(rf"\s*{_REGEX_LABEL}", r"$\1", shape_expression)
# Let all commas be followed by a $.
shape_expression = shape_expression.replace(",", ",$")
# Remove all whitespaces left.
shape_expression = re.sub(r"\s*", "", shape_expression)
# Remove $ right after a bracket.
shape_expression = re.sub(r"\[\$+", "[", shape_expression)
# Replace $ with a single space.
shape_expression = re.sub(r"\$+", " ", shape_expression)
return shape_expression
def get_dimensions(shape_expression: str) -> List[str]:
"""
Find all "break downs" (the parts between brackets) in a shape expressions
and replace them with mere dimension sizes.
:param shape_expression: the shape expression that gets the break downs replaced.
:return: a list of dimensions without break downs.
"""
shape_expression_without_breakdowns = shape_expression
for dim_breakdown in re.findall(
r"(\[[^\]]+\])", shape_expression_without_breakdowns
):
dim_size = len(dim_breakdown.split(","))
shape_expression_without_breakdowns = (
shape_expression_without_breakdowns.replace(dim_breakdown, str(dim_size))
)
return shape_expression_without_breakdowns.split(",")
def remove_labels(dimensions: List[str]) -> List[str]:
"""
Remove all labels (words that start with a lowercase).
:param dimensions: a list of dimensions.
:return: a copy of the given list without labels.
"""
return [re.sub(r"\b[a-z]\w*", "", dim).strip() for dim in dimensions]
def _check_dimensions_against_shape(shape: ShapeTuple, target: List[str]) -> bool:
# Walk through the shape and test them against the given target,
# taking into consideration variables, wildcards, etc.
if len(shape) != len(target):
return False
shape_as_strings = (str(dim) for dim in shape)
variables: Dict[str, str] = {}
for dim, target_dim in zip(shape_as_strings, target):
if _is_wildcard(target_dim) or _is_assignable_var(dim, target_dim, variables):
continue
if dim != target_dim:
return False
return True
def _handle_ellipsis(shape: ShapeTuple, target: List[str]) -> List[str]:
# Let the ellipsis allows for any number of dimensions by replacing the
# ellipsis with the dimension size repeated the number of times that
# corresponds to the shape of the instance.
if target[-1] == "...":
dim_to_repeat = target[-2]
target = target[0:-1]
if len(shape) > len(target):
difference = len(shape) - len(target)
target += difference * [dim_to_repeat]
return target
def _is_assignable_var(dim: str, target_dim: str, variables: Dict[str, str]) -> bool:
# Return whether target_dim is a variable and can be assigned with dim.
return _is_variable(target_dim) and _can_assign_variable(dim, target_dim, variables)
def _is_variable(dim: str) -> bool:
# Return whether dim is a variable.
return dim[0] in string.ascii_uppercase
def _can_assign_variable(dim: str, target_dim: str, variables: Dict[str, str]) -> bool:
# Check and assign a variable.
assignable = variables.get(target_dim) in (None, dim)
variables[target_dim] = dim
return assignable
def _is_wildcard(dim: str) -> bool:
# Return whether dim is a wildcard (i.e. the character that takes any
# dimension size).
return dim == "*"
_REGEX_SEPARATOR = r"(\s*,\s*)"
_REGEX_DIMENSION_SIZE = r"(\s*[0-9]+\s*)"
_REGEX_VARIABLE = r"(\s*\b[A-Z]\w*\s*)"
_REGEX_LABEL = r"(\s*\b[a-z]\w*\s*)"
_REGEX_LABELS = rf"({_REGEX_LABEL}({_REGEX_SEPARATOR}{_REGEX_LABEL})*)"
_REGEX_WILDCARD = r"(\s*\*\s*)"
_REGEX_DIMENSION_BREAKDOWN = rf"(\s*\[{_REGEX_LABELS}\]\s*)"
_REGEX_DIMENSION = (
rf"({_REGEX_DIMENSION_SIZE}"
rf"|{_REGEX_VARIABLE}"
rf"|{_REGEX_WILDCARD}"
rf"|{_REGEX_DIMENSION_BREAKDOWN})"
)
_REGEX_DIMENSION_WITH_LABEL = rf"({_REGEX_DIMENSION}(\s+{_REGEX_LABEL})*)"
_REGEX_DIMENSIONS = (
rf"{_REGEX_DIMENSION_WITH_LABEL}({_REGEX_SEPARATOR}{_REGEX_DIMENSION_WITH_LABEL})*"
)
_REGEX_DIMENSIONS_ELLIPSIS = rf"({_REGEX_DIMENSIONS}{_REGEX_SEPARATOR}\.\.\.\s*)"
_REGEX_SHAPE_EXPRESSION = rf"^({_REGEX_DIMENSIONS}|{_REGEX_DIMENSIONS_ELLIPSIS})$"

View file

@ -0,0 +1,108 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from abc import ABC
from typing import (
Any,
Dict,
List,
)
from numpydantic.vendor.nptyping.base_meta_classes import ContainerMeta
from numpydantic.vendor.nptyping.nptyping_type import NPTypingType
from numpydantic.vendor.nptyping.structure_expression import (
create_name_to_type_dict,
normalize_structure_expression,
validate_structure_expression,
)
class StructureMeta(ContainerMeta, implementation="Structure"):
"""
Metaclass that is coupled to nptyping.Structure.
"""
__args__ = tuple()
def _validate_expression(cls, item: str) -> None:
validate_structure_expression(item)
def _normalize_expression(cls, item: str) -> str:
return normalize_structure_expression(item)
def _get_additional_values(cls, item: Any) -> Dict[str, Any]:
return {
"_type_per_name": create_name_to_type_dict(item),
"_has_wildcard": item.replace(" ", "").endswith(",*"),
}
class Structure(NPTypingType, ABC, metaclass=StructureMeta):
"""
A container for structure expressions that describe the structured dtype of
an array.
Simple example:
>>> Structure["x: Float, y: Float"]
Structure['[x, y]: Float']
"""
_type_per_name = {}
_has_wildcard = False
@classmethod
def has_wildcard(cls) -> bool:
"""
Returns whether this Structure has a wildcard for any other columns.
:return: True if this Structure expresses "any other columns".
"""
return cls._has_wildcard
@classmethod
def get_types(cls) -> List[str]:
"""
Return a list of all types (strings) in this Structure.
:return: a list of all types in this Structure.
"""
return list(set(cls._type_per_name.values()))
@classmethod
def get_names(cls) -> List[str]:
"""
Return a list of all names in this Structure.
:return: a list of all names in this Structure.
"""
return list(cls._type_per_name.keys())
@classmethod
def get_type(cls, name: str) -> str:
"""
Get the type (str) that corresponds to the given name. For example for
Structure["x: Float"], get_type("x") would give "Float".
:param name: the name of which the type is to be returned.
:return: the type as a string that corresponds to that name.
"""
return cls._type_per_name[name]

View file

@ -0,0 +1,48 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
try:
from typing import Literal # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Literal # type: ignore[attr-defined,misc,assignment]
from typing import Any, Dict, cast
import numpy as np
from numpydantic.vendor.nptyping.base_meta_classes import ContainerMeta
class StructureMeta(ContainerMeta, implementation="Structure"):
__args__ = tuple()
def _validate_expression(cls, item: str) -> None: ...
def _normalize_expression(cls, item: str) -> str: ...
def _get_additional_values(cls, item: Any) -> Dict[str, Any]: ...
# For MyPy:
Structure = cast(Literal, Structure) # type: ignore[has-type,misc,valid-type]
# For PyRight:
class Structure(np.dtype[Any]): # type: ignore[no-redef,misc]
def __class_getitem__(cls, item: Any) -> Any: ...

View file

@ -0,0 +1,339 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import re
from collections import Counter, defaultdict
from difflib import get_close_matches
from typing import (
TYPE_CHECKING,
Any,
Dict,
Generator,
List,
Mapping,
Tuple,
Type,
Union,
)
import numpy as np
from numpydantic.vendor.nptyping.error import InvalidShapeError, InvalidStructureError
from numpydantic.vendor.nptyping.shape import Shape
from numpydantic.vendor.nptyping.shape_expression import (
check_shape,
normalize_shape_expression,
validate_shape_expression,
)
from numpydantic.vendor.nptyping.typing_ import StructureExpression
if TYPE_CHECKING:
from nptyping.structure import Structure # pragma: no cover
def validate_structure_expression(
structure_expression: Union[StructureExpression, Any]
) -> None:
"""
Validate the given structure_expression and raise an InvalidStructureError
if it is deemed invalid.
:param structure_expression: the structure expression in question.
:return: None.
"""
if structure_expression is not Any:
if not re.match(_REGEX_STRUCTURE_EXPRESSION, structure_expression):
raise InvalidStructureError(
f"'{structure_expression}' is not a valid structure expression."
)
_validate_structure_expression_contains_no_multiple_field_names(
structure_expression
)
_validate_sub_array_expressions(structure_expression)
def check_structure(
structured_dtype: np.dtype, # type: ignore[type-arg]
target: "Structure",
type_per_name: Dict[str, type],
) -> bool:
"""
Check the given structured_dtype against the given target Structure and
return whether it corresponds (True) or not (False). The given dictionary
contains the vocabulary context for the check.
:param structured_dtype: the dtype in question.
:param target: the target Structure that is checked against.
:param type_per_name: a dict that holds the types by their names as they
occur in a structure expression.
:return: True if the given dtype is valid with the given target.
"""
fields: Mapping[str, Any] = structured_dtype.fields or {} # type: ignore[assignment]
# Add the wildcard to the lexicon. We want to do this here to keep
# knowledge on wildcards in one place (this module).
type_per_name_with_wildcard: Dict[str, type] = {
**type_per_name,
"*": object,
} # type: ignore[arg-type]
if target.has_wildcard():
# Check from the Target's perspective. All fields in the Target should be
# in the subject.
def iterator() -> Generator[Tuple[str, Tuple[np.dtype, int]], None, None]: # type: ignore[type-arg] # pylint: disable=line-too-long
for name_ in target.get_names():
yield name_, fields.get(name_) # type: ignore[misc]
else:
# Check from the subject's perspective. All fields in the subject
# should be in the target.
if set(target.get_names()) != set(fields.keys()):
return False
def iterator() -> Generator[Tuple[str, Tuple[np.dtype, int]], None, None]: # type: ignore[type-arg] # pylint: disable=line-too-long
yield from fields.items()
for name, dtype_tuple in iterator():
field_in_target_not_in_subject = dtype_tuple is None
if field_in_target_not_in_subject or not _check_structure_field(
name, dtype_tuple, target, type_per_name_with_wildcard
):
return False
return True
def _check_structure_field(
name: str,
dtype_tuple: Tuple[np.dtype, int], # type: ignore[type-arg]
target: "Structure",
type_per_name_with_wildcard: Dict[str, type],
) -> bool:
dtype = dtype_tuple[0]
target_type_name = target.get_type(name)
target_type_shape_match = re.search(_REGEX_FIELD_SHAPE, target_type_name)
actual_type = dtype.type
if target_type_shape_match:
if not dtype.subdtype:
# the dtype does not contain a shape.
return False
actual_type = dtype.subdtype[0].type
target_type_shape = target_type_shape_match.group(1)
shape_corresponds = check_shape(dtype.shape, Shape[target_type_shape])
if not shape_corresponds:
return False
target_type_name = target_type_name.replace(
target_type_shape_match.group(0), ""
)
check_type_name(target_type_name, type_per_name_with_wildcard)
target_type = type_per_name_with_wildcard[target_type_name]
return issubclass(actual_type, target_type)
def check_type_names(
structure: "Structure", type_per_name: Dict[str, Type[object]]
) -> None:
"""
Check the given structure for any invalid type names in the given context
of type_per_name. Raises an InvalidStructureError if a type name is
invalid.
:param structure: the Structure that is checked.
:param type_per_name: the context that determines which type names are valid.
:return: None.
"""
for type_ in structure.get_types():
check_type_name(type_, type_per_name)
def check_type_name(type_name: str, type_per_name: Dict[str, Type[object]]) -> None:
"""
Check if the given type_name is in type_per_name and raise a meaningful
error if not.
:param type_name: the key that is checked to be in type_per_name.
:param type_per_name: a dict that is looked in for type_name.
:return: None.
"""
# Remove any subarray stuff here.
type_name = type_name.split("[")[0]
if type_name not in type_per_name:
close_matches = get_close_matches(
type_name, type_per_name.keys(), 3, cutoff=0.4
)
close_matches_str = ", ".join(f"'{match}'" for match in close_matches)
extra_help = ""
if len(close_matches) > 1:
extra_help = f" Did you mean one of {close_matches_str}?"
elif close_matches:
extra_help = f" Did you mean {close_matches_str}?"
raise InvalidStructureError( # pylint: disable=raise-missing-from
f"Type '{type_name}' is not valid in this context.{extra_help}"
)
def normalize_structure_expression(
structure_expression: StructureExpression,
) -> StructureExpression:
"""
Normalize the given structure expression, e.g. by removing whitespaces,
making similar expressions look the same.
:param structure_expression: the structure expression that is to be normalized.
:return: a normalized structure expression.
"""
structure_expression = re.sub(r"\s*", "", structure_expression)
type_to_names_dict = _create_type_to_names_dict(structure_expression)
normalized_structure_expression = _type_to_names_dict_to_str(type_to_names_dict)
result = normalized_structure_expression.replace(",", ", ").replace(" ", " ")
has_wildcard_end = structure_expression.replace(" ", "").endswith(",*")
if has_wildcard_end:
result += ", *"
return result
def create_name_to_type_dict(
structure_expression: StructureExpression,
) -> Dict[str, str]:
"""
Create a dict with a name as key and a type (str) as value from the given
structure expression. Structure["x: Int, y: Float"] would yield
{"x: "Int", "y": "Float"}.
:param structure_expression: the structure expression from which the dict
is extracted.
:return: a dict with names and their types, both as strings.
"""
type_to_names_dict = _create_type_to_names_dict(structure_expression)
return {
name.strip(): type_.strip()
for type_, names in type_to_names_dict.items()
for name in names
}
def _validate_structure_expression_contains_no_multiple_field_names(
structure_expression: StructureExpression,
) -> None:
# Validate that there are not multiple occurrences of the same field names.
matches = re.findall(_REGEX_FIELD, re.sub(r"\s*", "", structure_expression))
field_name_combinations = [match[0].split(":")[0] for match in matches]
field_names: List[str] = []
for field_name_combination in field_name_combinations:
field_name_combination_match = re.match(
_REGEX_FIELD_NAMES_COMBINATION, field_name_combination
)
if field_name_combination_match:
field_names += field_name_combination_match.group(2).split(_SEPARATOR)
else:
field_names.append(field_name_combination)
field_name_counter = Counter(field_names)
field_names_occurring_multiple_times = [
field_name for field_name, amount in field_name_counter.items() if amount > 1
]
if field_names_occurring_multiple_times:
# If there are multiple, just raise about the first. Otherwise the
# error message gets bloated.
field_name_under_fire = field_names_occurring_multiple_times[0]
raise InvalidStructureError(
f"Field names may occur only once in a structure expression."
f" Field name '{field_name_under_fire}' occurs"
f" {field_name_counter[field_name_under_fire]} times in"
f" '{structure_expression}'."
)
def _validate_sub_array_expressions(structure_expression: str) -> None:
# Validate that the given structure expression does not contain any shape
# expressions for sub arrays that are invalid.
for field_match in re.findall(_REGEX_FIELD, structure_expression):
field_type = field_match[0].split(_FIELD_TYPE_POINTER)[1]
type_shape_match = re.search(_REGEX_FIELD_SHAPE, field_type)
if type_shape_match:
type_shape = type_shape_match[1]
try:
validate_shape_expression(type_shape)
except InvalidShapeError as err:
raise InvalidStructureError(
f"'{structure_expression}' is not a valid structure"
f" expression; {str(err)}"
) from err
def _create_type_to_names_dict(
structure_expression: StructureExpression,
) -> Dict[str, List[str]]:
# Create a dictionary with field names per type, sorted by type and then by
# name.
names_per_type: Dict[str, List[str]] = defaultdict(list)
for field_match in re.findall(_REGEX_FIELD, structure_expression):
field_name_combination, field_type = field_match[0].split(_FIELD_TYPE_POINTER)
field_name_combination_match = re.match(
_REGEX_FIELD_NAMES_COMBINATION, field_name_combination
)
field_type_shape_match = re.search(_REGEX_FIELD_SHAPE, field_type)
if field_name_combination_match:
field_names = field_name_combination_match.group(2).split(_SEPARATOR)
else:
field_names = [field_name_combination]
if field_type_shape_match:
type_shape = field_type_shape_match.group(1)
normalized_type_shape = normalize_shape_expression(type_shape)
field_type = field_type.replace(
field_type_shape_match.group(0), f"[{normalized_type_shape}]"
)
names_per_type[field_type] += field_names
return {
field_type: sorted(names_per_type[field_type])
for field_type in sorted(names_per_type.keys())
}
def _type_to_names_dict_to_str(type_to_names_dict: Dict[str, List[str]]) -> str:
# Turn the given dict into a structure expression.
field_strings = []
for field_type, field_names in type_to_names_dict.items():
field_names_joined = f"{_SEPARATOR}".join(field_names)
if len(field_names) > 1:
field_names_joined = f"[{field_names_joined}]"
field_strings.append(f"{field_names_joined}{_FIELD_TYPE_POINTER} {field_type}")
return f"{_SEPARATOR}".join(field_strings)
_SEPARATOR = ","
_FIELD_TYPE_POINTER = ":"
_REGEX_SEPARATOR = rf"(\s*{_SEPARATOR}\s*)"
_REGEX_FIELD_NAME = r"(\s*[a-zA-Z]\w*\s*)"
_REGEX_FIELD_NAMES = rf"({_REGEX_FIELD_NAME}({_REGEX_SEPARATOR}{_REGEX_FIELD_NAME})+)"
_REGEX_FIELD_NAMES_COMBINATION = rf"(\s*\[{_REGEX_FIELD_NAMES}\]\s*)"
_REGEX_FIELD_LEFT = rf"({_REGEX_FIELD_NAME}|{_REGEX_FIELD_NAMES_COMBINATION})"
_REGEX_FIELD_TYPE = r"(\s*[a-zA-Z]\w*\s*)"
_REGEX_FIELD_TYPE_WILDCARD = r"(\s*\*\s*)"
_REGEX_FIELD_SHAPE = r"\[([^\]]+)\]"
_REGEX_FIELD_SHAPE_MAYBE = rf"\s*({_REGEX_FIELD_SHAPE})?\s*"
_REGEX_FIELD_RIGHT = (
rf"({_REGEX_FIELD_TYPE}|{_REGEX_FIELD_TYPE_WILDCARD}){_REGEX_FIELD_SHAPE_MAYBE}"
)
_REGEX_FIELD_TYPE_POINTER = rf"(\s*{_FIELD_TYPE_POINTER}\s*)"
_REGEX_FIELD = (
rf"(\s*{_REGEX_FIELD_LEFT}{_REGEX_FIELD_TYPE_POINTER}{_REGEX_FIELD_RIGHT}\s*)"
)
_REGEX_STRUCTURE_EXPRESSION = (
rf"^({_REGEX_FIELD}"
rf"({_REGEX_SEPARATOR}{_REGEX_FIELD})*"
rf"({_REGEX_SEPARATOR}{_REGEX_FIELD_TYPE_WILDCARD})?)$"
)

View file

@ -0,0 +1,162 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import sys
from typing import Tuple, Union
if sys.version_info.minor >= 10:
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
import numpy as np
ShapeExpression: TypeAlias = str
StructureExpression: TypeAlias = str
DType: TypeAlias = Union[np.generic, StructureExpression]
ShapeTuple: TypeAlias = Tuple[int, ...]
Number = np.number
Bool = np.bool_
Obj = np.object_ # Obj is a common abbreviation and should be usable.
Object = np.object_
Datetime64 = np.datetime64
Integer = np.integer
SignedInteger = np.signedinteger
Int8 = np.int8
Int16 = np.int16
Int32 = np.int32
Int64 = np.int64
Byte = np.byte
Short = np.short
IntC = np.intc
IntP = np.intp
Int = np.integer # Int should translate to the "generic" int type.
Int_ = np.int_
LongLong = np.longlong
Timedelta64 = np.timedelta64
UnsignedInteger = np.unsignedinteger
UInt8 = np.uint8
UInt16 = np.uint16
UInt32 = np.uint32
UInt64 = np.uint64
UByte = np.ubyte
UShort = np.ushort
UIntC = np.uintc
UIntP = np.uintp
UInt = np.uint
ULongLong = np.ulonglong
Inexact = np.inexact
Floating = np.floating
Float16 = np.float16
Float32 = np.float32
Float64 = np.float64
Half = np.half
Single = np.single
Double = np.double
Float = np.float64
LongDouble = np.longdouble
LongFloat = np.longdouble
ComplexFloating = np.complexfloating
Complex64 = np.complex64
Complex128 = np.complex128
CSingle = np.csingle
SingleComplex = np.complex64
CDouble = np.cdouble
Complex = np.complex128
CFloat = np.complex128
CLongDouble = np.clongdouble
CLongFloat = np.clongdouble
LongComplex = np.clongdouble
Flexible = np.flexible
Void = np.void
Character = np.character
Bytes = np.bytes_
Str = np.str_
String = np.str_
Unicode = np.str_
dtypes = [
(Number, "Number"),
(Bool, "Bool"),
(Obj, "Obj"),
(Object, "Object"),
(Datetime64, "Datetime64"),
(Integer, "Integer"),
(SignedInteger, "SignedInteger"),
(Int8, "Int8"),
(Int16, "Int16"),
(Int32, "Int32"),
(Int64, "Int64"),
(Byte, "Byte"),
(Short, "Short"),
(IntC, "IntC"),
(IntP, "IntP"),
(Int, "Int"),
(LongLong, "LongLong"),
(Timedelta64, "Timedelta64"),
(UnsignedInteger, "UnsignedInteger"),
(UInt8, "UInt8"),
(UInt16, "UInt16"),
(UInt32, "UInt32"),
(UInt64, "UInt64"),
(UByte, "UByte"),
(UShort, "UShort"),
(UIntC, "UIntC"),
(UIntP, "UIntP"),
(UInt, "UInt"),
(ULongLong, "ULongLong"),
(Inexact, "Inexact"),
(Floating, "Floating"),
(Float16, "Float16"),
(Float32, "Float32"),
(Float64, "Float64"),
(Half, "Half"),
(Single, "Single"),
(Double, "Double"),
(Float, "Float"),
(LongDouble, "LongDouble"),
(LongFloat, "LongFloat"),
(ComplexFloating, "ComplexFloating"),
(Complex64, "Complex64"),
(Complex128, "Complex128"),
(CSingle, "CSingle"),
(SingleComplex, "SingleComplex"),
(CDouble, "CDouble"),
(Complex, "Complex"),
(CFloat, "CFloat"),
(CLongDouble, "CLongDouble"),
(CLongFloat, "CLongFloat"),
(LongComplex, "LongComplex"),
(Flexible, "Flexible"),
(Void, "Void"),
(Character, "Character"),
(Bytes, "Bytes"),
(String, "String"),
(Str, "Str"),
(Unicode, "Unicode"),
]
name_per_dtype = dict(dtypes)
dtype_per_name = {name: dtype for dtype, name in dtypes}

View file

@ -1,6 +1,7 @@
import shutil
from pathlib import Path
from typing import Any, Callable, Optional, Tuple, Type, Union
from warnings import warn
import h5py
import numpy as np
@ -25,7 +26,13 @@ def tmp_output_dir(request: pytest.FixtureRequest) -> Path:
yield path
if not request.config.getvalue("--with-output"):
try:
shutil.rmtree(str(path))
except PermissionError as e:
# sporadic error on windows machines...
warn(
f"Temporary directory could not be removed due to a permissions error: \n{str(e)}"
)
@pytest.fixture(scope="function")

View file

@ -7,11 +7,12 @@ import json
import numpy as np
from pydantic import BaseModel, ValidationError, Field
from nptyping import Number
from numpydantic import NDArray, Shape
from numpydantic.exceptions import ShapeError, DtypeError
from numpydantic import dtype
from numpydantic.dtype import Number
def test_ndarray_type():
@ -39,6 +40,53 @@ def test_ndarray_type():
instance = Model(array=np.zeros((2, 3)), array_any=np.ones((3, 4, 5)))
def test_schema_unsupported_type():
"""
Complex numbers should just be made with an `any` schema
"""
class Model(BaseModel):
array: NDArray[Shape["2 x, * y"], complex]
schema = Model.model_json_schema()
assert schema["properties"]["array"]["items"] == {
"items": {},
"type": "array",
}
def test_schema_tuple():
"""
Types specified as tupled should have their schemas as a union
"""
class Model(BaseModel):
array: NDArray[Shape["2 x, * y"], (np.uint8, np.uint16)]
schema = Model.model_json_schema()
assert "anyOf" in schema["properties"]["array"]["items"]["items"]
conditions = schema["properties"]["array"]["items"]["items"]["anyOf"]
assert all([i["type"] == "integer" for i in conditions])
assert sorted([i["maximum"] for i in conditions]) == [255, 65535]
assert all([i["minimum"] == 0 for i in conditions])
def test_schema_number():
"""
np.numeric should just be the float schema
"""
class Model(BaseModel):
array: NDArray[Shape["2 x, * y"], np.number]
schema = Model.model_json_schema()
assert schema["properties"]["array"]["items"] == {
"items": {"type": "number"},
"type": "array",
}
def test_ndarray_union():
class Model(BaseModel):
array: Optional[

14
tox.ini Normal file
View file

@ -0,0 +1,14 @@
[tox]
requires =
tox>=4
env_list = py{39,310,311,312}-numpy{1,2}
[testenv]
package = editable
extras =
tests
deps =
py{39,310,311,312}-numpy1: numpy<2.0.0
py{39,310,311,312}-numpy2: numpy>=2.0.0
commands =
python -m pytest