diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 025037ac..73cdd395 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -52,7 +52,7 @@ jobs: uses: actions/setup-python@v5 with: # Keep in sync with version in .readthedocs.yml. - python-version: "3.11" + python-version: "3.12" cache: pip cache-dependency-path: docs-requirements.txt - name: Install dependencies diff --git a/.readthedocs.yml b/.readthedocs.yml index 4719ff37..714dbce1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -6,7 +6,7 @@ build: os: ubuntu-22.04 tools: # Keep in sync with docs build CI job. - python: "3.11" + python: "3.12" python: install: - requirements: docs-requirements.txt diff --git a/README.md b/README.md index 470d2905..b0a09e40 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,9 @@ support][pydantic-support] out-of-the-box. Subclasses of `Phantom` work with bot pydantic's validation and its schema generation. ```python +import json + + class Name(str, Phantom, predicate=contained({"Jane", "Joe"})): @classmethod def __schema__(cls) -> Schema: @@ -139,30 +142,30 @@ class Person(BaseModel): created: TZAware -print(json.dumps(Person.schema(), indent=2)) +print(json.dumps(Person.model_json_schema(), indent=2)) ``` The code above outputs the following JSONSchema. ```json { - "title": "Person", - "type": "object", "properties": { "name": { - "title": "Name", "description": "Either Jane or Joe", "format": "custom-name", + "title": "Name", "type": "string" }, "created": { - "title": "TZAware", "description": "A date-time with timezone data.", - "type": "string", - "format": "date-time" + "format": "date-time", + "title": "TZAware", + "type": "string" } }, - "required": ["name", "created"] + "required": ["name", "created"], + "title": "Person", + "type": "object" } ``` @@ -213,7 +216,7 @@ $ make test-typing [typeguard]: https://github.com/agronholm/typeguard [beartype]: https://github.com/beartype/beartype [dbc]: https://en.wikipedia.org/wiki/Design_by_contract -[pydantic]: https://pydantic-docs.helpmanual.io/ +[pydantic]: https://docs.pydantic.dev [pydantic-support]: https://phantom-types.readthedocs.io/en/stable/pages/pydantic-support.html [goose]: https://github.com/antonagestam/goose diff --git a/docs-requirements.txt b/docs-requirements.txt index 1d10b557..1f393390 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -8,21 +8,25 @@ alabaster==1.0.0 \ --hash=sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e \ --hash=sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b # via sphinx +annotated-types==0.7.0 \ + --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ + --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 + # via pydantic babel==2.17.0 \ --hash=sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d \ --hash=sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2 # via sphinx -beartype==0.22.5 \ - --hash=sha256:516a9096cc77103c96153474fa35c3ebcd9d36bd2ec8d0e3a43307ced0fa6341 \ - --hash=sha256:d9743dd7cd6d193696eaa1e025f8a70fb09761c154675679ff236e61952dfba0 +beartype==0.22.9 \ + --hash=sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f \ + --hash=sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2 # via numerary -beautifulsoup4==4.14.2 \ - --hash=sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e \ - --hash=sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515 +beautifulsoup4==4.14.3 \ + --hash=sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb \ + --hash=sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86 # via furo -certifi==2025.10.5 \ - --hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de \ - --hash=sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43 +certifi==2026.1.4 \ + --hash=sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c \ + --hash=sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120 # via requests charset-normalizer==3.4.4 \ --hash=sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad \ @@ -139,17 +143,17 @@ charset-normalizer==3.4.4 \ --hash=sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e \ --hash=sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608 # via requests -docutils==0.21.2 \ - --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ - --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 +docutils==0.22.4 \ + --hash=sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968 \ + --hash=sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de # via sphinx -furo==2025.9.25 \ - --hash=sha256:2937f68e823b8e37b410c972c371bc2b1d88026709534927158e0cb3fac95afe \ - --hash=sha256:3eac05582768fdbbc2bdfa1cdbcdd5d33cfc8b4bd2051729ff4e026a1d7e0a98 +furo==2025.12.19 \ + --hash=sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7 \ + --hash=sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f # via phantom-types (pyproject.toml) -hypothesis==6.147.0 \ - --hash=sha256:72e6004ea3bd1460bdb4640b6389df23b87ba7a4851893fd84d1375635d3e507 \ - --hash=sha256:de588807b6da33550d32f47bcd42b1a86d061df85673aa73e6443680249d185e +hypothesis==6.150.2 \ + --hash=sha256:648d6a2be435889e713ba3d335b0fb5e7a250f569b56e6867887c1e7a0d1f02f \ + --hash=sha256:deb043c41c53eaf0955f4a08739c2a34c3d8040ee3d9a2da0aa5470122979f75 # via phantom-types (pyproject.toml) idna==3.11 \ --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \ @@ -261,62 +265,137 @@ packaging==25.0 \ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f # via sphinx -phonenumbers==9.0.18 \ - --hash=sha256:5537c61ba95b11b992c95e804da6e49193cc06b1224f632ade64631518a48ed1 \ - --hash=sha256:d3354454ac31c97f8a08121df97a7145b8dca641f734c6f1518a41c2f60c5764 +phonenumbers==9.0.22 \ + --hash=sha256:645e66cd9a136b3b257b5f941fa97d324124114d31ad3c9f2488682f47ad7ee1 \ + --hash=sha256:eff985c65575749d1d54e07c56c3517d5243e03e08e4a6191761df9aab2278f2 # via phantom-types (pyproject.toml) -pydantic==1.10.24 \ - --hash=sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c \ - --hash=sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d \ - --hash=sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193 \ - --hash=sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6 \ - --hash=sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624 \ - --hash=sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667 \ - --hash=sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2 \ - --hash=sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c \ - --hash=sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621 \ - --hash=sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52 \ - --hash=sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816 \ - --hash=sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f \ - --hash=sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813 \ - --hash=sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49 \ - --hash=sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666 \ - --hash=sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a \ - --hash=sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a \ - --hash=sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683 \ - --hash=sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1 \ - --hash=sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c \ - --hash=sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94 \ - --hash=sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f \ - --hash=sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47 \ - --hash=sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702 \ - --hash=sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd \ - --hash=sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70 \ - --hash=sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53 \ - --hash=sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b \ - --hash=sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36 \ - --hash=sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c \ - --hash=sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993 \ - --hash=sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b \ - --hash=sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8 \ - --hash=sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01 \ - --hash=sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69 \ - --hash=sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3 \ - --hash=sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00 \ - --hash=sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37 \ - --hash=sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2 \ - --hash=sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6 \ - --hash=sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d \ - --hash=sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def \ - --hash=sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574 \ - --hash=sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6 \ - --hash=sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c \ - --hash=sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de \ - --hash=sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0 \ - --hash=sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579 \ - --hash=sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4 \ - --hash=sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb +pydantic==2.12.5 \ + --hash=sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49 \ + --hash=sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d # via phantom-types (pyproject.toml) +pydantic-core==2.41.5 \ + --hash=sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90 \ + --hash=sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740 \ + --hash=sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504 \ + --hash=sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84 \ + --hash=sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33 \ + --hash=sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c \ + --hash=sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0 \ + --hash=sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e \ + --hash=sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0 \ + --hash=sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a \ + --hash=sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34 \ + --hash=sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2 \ + --hash=sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3 \ + --hash=sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815 \ + --hash=sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14 \ + --hash=sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba \ + --hash=sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375 \ + --hash=sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf \ + --hash=sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963 \ + --hash=sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1 \ + --hash=sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808 \ + --hash=sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553 \ + --hash=sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1 \ + --hash=sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2 \ + --hash=sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5 \ + --hash=sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470 \ + --hash=sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2 \ + --hash=sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b \ + --hash=sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660 \ + --hash=sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c \ + --hash=sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093 \ + --hash=sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5 \ + --hash=sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594 \ + --hash=sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008 \ + --hash=sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a \ + --hash=sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a \ + --hash=sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd \ + --hash=sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284 \ + --hash=sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586 \ + --hash=sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869 \ + --hash=sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294 \ + --hash=sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f \ + --hash=sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66 \ + --hash=sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51 \ + --hash=sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc \ + --hash=sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97 \ + --hash=sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a \ + --hash=sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d \ + --hash=sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9 \ + --hash=sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c \ + --hash=sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07 \ + --hash=sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36 \ + --hash=sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e \ + --hash=sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05 \ + --hash=sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e \ + --hash=sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941 \ + --hash=sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3 \ + --hash=sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612 \ + --hash=sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3 \ + --hash=sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b \ + --hash=sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe \ + --hash=sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146 \ + --hash=sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11 \ + --hash=sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60 \ + --hash=sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd \ + --hash=sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b \ + --hash=sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c \ + --hash=sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a \ + --hash=sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460 \ + --hash=sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1 \ + --hash=sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf \ + --hash=sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf \ + --hash=sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858 \ + --hash=sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2 \ + --hash=sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9 \ + --hash=sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2 \ + --hash=sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3 \ + --hash=sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6 \ + --hash=sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770 \ + --hash=sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d \ + --hash=sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc \ + --hash=sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23 \ + --hash=sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26 \ + --hash=sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa \ + --hash=sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8 \ + --hash=sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d \ + --hash=sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3 \ + --hash=sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d \ + --hash=sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034 \ + --hash=sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9 \ + --hash=sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1 \ + --hash=sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56 \ + --hash=sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b \ + --hash=sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c \ + --hash=sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a \ + --hash=sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e \ + --hash=sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9 \ + --hash=sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5 \ + --hash=sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a \ + --hash=sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556 \ + --hash=sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e \ + --hash=sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49 \ + --hash=sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2 \ + --hash=sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9 \ + --hash=sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b \ + --hash=sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc \ + --hash=sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb \ + --hash=sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0 \ + --hash=sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8 \ + --hash=sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82 \ + --hash=sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69 \ + --hash=sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b \ + --hash=sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c \ + --hash=sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75 \ + --hash=sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5 \ + --hash=sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f \ + --hash=sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad \ + --hash=sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b \ + --hash=sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7 \ + --hash=sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425 \ + --hash=sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52 + # via pydantic pygments==2.19.2 \ --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b @@ -332,9 +411,9 @@ requests==2.32.5 \ --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf # via sphinx -roman-numerals-py==3.1.0 \ - --hash=sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c \ - --hash=sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d +roman-numerals==4.1.0 \ + --hash=sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2 \ + --hash=sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7 # via sphinx six==1.17.0 \ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ @@ -348,21 +427,21 @@ sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via hypothesis -soupsieve==2.8 \ - --hash=sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c \ - --hash=sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f +soupsieve==2.8.2 \ + --hash=sha256:0f4c2f6b5a5fb97a641cf69c0bd163670a0e45e6d6c01a2107f93a6a6f93c51a \ + --hash=sha256:78a66b0fdee2ab40b7199dc3e747ee6c6e231899feeaae0b9b98a353afd48fd8 # via beautifulsoup4 -sphinx==8.2.3 \ - --hash=sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348 \ - --hash=sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3 +sphinx==9.1.0 \ + --hash=sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb \ + --hash=sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978 # via # phantom-types (pyproject.toml) # furo # sphinx-autodoc-typehints # sphinx-basic-ng -sphinx-autodoc-typehints==3.5.2 \ - --hash=sha256:0accd043619f53c86705958e323b419e41667917045ac9215d7be1b493648d8c \ - --hash=sha256:5fcd4a3eb7aa89424c1e2e32bedca66edc38367569c9169a80f4b3e934171fdb +sphinx-autodoc-typehints==3.6.2 \ + --hash=sha256:3d37709a21b7b765ad6e20a04ecefcb229b9eb0007cb24f6ebaa8a4576ea7f06 \ + --hash=sha256:9e70bee1f487b087c83ba0f4949604a4630bee396e263a324aae1dc4268d2c0f # via phantom-types (pyproject.toml) sphinx-basic-ng==1.0.0b2 \ --hash=sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9 \ @@ -403,8 +482,14 @@ typing-extensions==4.15.0 \ # phantom-types (pyproject.toml) # beautifulsoup4 # pydantic + # pydantic-core # typeguard -urllib3==2.5.0 \ - --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ - --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + # typing-inspection +typing-inspection==0.4.2 \ + --hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \ + --hash=sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464 + # via pydantic +urllib3==2.6.3 \ + --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ + --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 # via requests diff --git a/docs/pages/pydantic-support.rst b/docs/pages/pydantic-support.rst index 6f152c33..deadce75 100644 --- a/docs/pages/pydantic-support.rst +++ b/docs/pages/pydantic-support.rst @@ -1,14 +1,15 @@ Pydantic Support ================ -phantom-types supports pydantic_ out of the box by providing a -:func:`__get_validators__() ` hook -on the base :class:`Phantom ` class. Most of the shipped types also -implements full JSON Schema and OpenAPI support. +phantom-types supports pydantic_ out of the box by providing +:func:`__get_pydantic_core_schema__() ` +and :func:`__get_pydantic_json_schema__() ` +hooks on the base :class:`Phantom ` class. Most of the shipped types also +implement full JSON Schema and OpenAPI support. -.. _pydantic: https://pydantic-docs.helpmanual.io/ +.. _pydantic: https://docs.pydantic.dev/ -To make a phantom type compatible with pydantic, all you need to do is override +To customize the JSON schema representation of a phantom type, override :func:`Phantom.__schema__() `: .. code-block:: python @@ -28,3 +29,9 @@ To make a phantom type compatible with pydantic, all you need to do is override As can be seen in the example, ``__schema__()`` implementations are expected to return a dict extending its ``super().__schema__()``, however this is not a requirement and any :class:`Schema `-compatible ``dict`` can be returned. + +.. note:: + + phantom-types 3.0.2 supported Pydantic v1 via the ``__get_validators__()`` hook. + As of phantom-types 3.1.0, only Pydantic v2 is supported. If you need Pydantic v1 + support, pin to ``phantom-types<3.1``. diff --git a/pyproject.toml b/pyproject.toml index e1399e6c..1fccd8b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ dependencies = [ [project.optional-dependencies] phonenumbers = ["phonenumbers>=8.12.41"] -pydantic = ["pydantic>=1.9.0,<2"] +pydantic = ["pydantic>=2.0.0,<3"] dateutil = ["python-dateutil>=2.8.2"] hypothesis = ["hypothesis[zoneinfo]>=6.68.0"] all = [ diff --git a/src/phantom/_base.py b/src/phantom/_base.py index 7af83c76..471cf257 100644 --- a/src/phantom/_base.py +++ b/src/phantom/_base.py @@ -1,9 +1,9 @@ from __future__ import annotations import abc -from collections.abc import Callable +from abc import ABCMeta from collections.abc import Iterable -from collections.abc import Iterator +from typing import TYPE_CHECKING from typing import Any from typing import ClassVar from typing import Generic @@ -11,9 +11,18 @@ from typing import TypeVar from typing import runtime_checkable +if TYPE_CHECKING: + try: + from pydantic import GetCoreSchemaHandler + from pydantic_core import CoreSchema + from pydantic_core.core_schema import ValidatorFunctionWrapHandler + except ImportError: + pass + from typing_extensions import Self from . import _hypothesis +from ._utils.compat import require_pydantic from ._utils.misc import BoundType from ._utils.misc import UnresolvedClassAttribute from ._utils.misc import fully_qualified_name @@ -43,6 +52,16 @@ def parse(cls, instance: object) -> Self: ... V = TypeVar("V", bound=SupportsParse) +def _is_protocol(tp: type) -> bool: + """Check if type is a Protocol.""" + return isinstance(tp, type) and getattr(tp, "_is_protocol", False) + + +def _is_abc(tp: type) -> bool: + """Check if type is an ABC.""" + return isinstance(tp, ABCMeta) + + class PhantomMeta(abc.ABCMeta): """ Metaclass that defers __instancecheck__ to derived classes and prevents actual @@ -84,9 +103,51 @@ def parse(cls: type[Derived], instance: object) -> Derived: def __instancecheck__(cls, instance: object) -> bool: ... @classmethod - def __get_validators__(cls: type[Derived]) -> Iterator[Callable[[object], Derived]]: - """Hook that makes phantom types compatible with pydantic.""" - yield cls.parse + def _validate( + cls: type[Derived], + value: Any, + handler: ValidatorFunctionWrapHandler, + ) -> Derived: + """Pydantic V2 wrap validator.""" + require_pydantic() + + from pydantic_core import PydanticCustomError + + try: + validated = handler(value) + return cls.parse(validated) + except Exception as exc: + raise PydanticCustomError( + "value_error", + f"value is not a valid {cls.__name__}", + ) from exc + + @classmethod + def __get_pydantic_core_schema__( + cls: type[Derived], source: type[Any], handler: GetCoreSchemaHandler + ) -> CoreSchema: + """Pydantic V2 hook for core schema generation.""" + require_pydantic() + + from pydantic.errors import PydanticSchemaGenerationError + from pydantic_core.core_schema import any_schema + from pydantic_core.core_schema import is_instance_schema + from pydantic_core.core_schema import no_info_wrap_validator_function + + bound = getattr(cls, "__bound__", object) + + bound_schema: CoreSchema + if _is_protocol(bound) or (_is_abc(bound) and bound is not cls): + # For protocols and ABCs, use is_instance_schema + bound_schema = is_instance_schema(bound) + else: + # For concrete types, let Pydantic generate the schema + try: + bound_schema = handler.generate_schema(bound) + except PydanticSchemaGenerationError: + bound_schema = any_schema() + + return no_info_wrap_validator_function(cls._validate, bound_schema) class AbstractInstanceCheck(TypeError): ... diff --git a/src/phantom/_utils/compat.py b/src/phantom/_utils/compat.py new file mode 100644 index 00000000..92c3d139 --- /dev/null +++ b/src/phantom/_utils/compat.py @@ -0,0 +1,11 @@ +from phantom.errors import MissingDependency + + +def require_pydantic() -> None: + try: + import pydantic_core # noqa: F401 + except ImportError: + raise MissingDependency( + "pydantic>=2 is required for Pydantic schema generation. " + "Install it with: pip install phantom-types[pydantic]" + ) from None diff --git a/src/phantom/datetime.py b/src/phantom/datetime.py index bee3ec7d..3bbc61f7 100644 --- a/src/phantom/datetime.py +++ b/src/phantom/datetime.py @@ -20,38 +20,33 @@ from .predicates.datetime import is_tz_naive from .schema import Schema -try: - import dateutil.parser - - parse_datetime_str = dateutil.parser.parse - DateutilParseError = dateutil.parser.ParserError -except ImportError as e: - exception = e - - def parse_datetime_str( - *_: object, - **__: object, - ) -> datetime.datetime: - raise MissingDependency( - "python-dateutil needs to be installed to use this type for parsing. It " - "can be installed with the phantom-types[dateutil] extra." - ) from exception +__all__ = ("TZAware", "TZNaive") - class DateutilParseError(Exception): # type: ignore[no-redef] - ... +def parse_datetime_str(value: str) -> datetime.datetime: + try: + from dateutil.parser import ParserError + from dateutil.parser import parse + except ImportError as exc: + raise MissingDependency( + "python-dateutil needs to be installed to use this type for parsing. " + "It can be installed with the phantom-types[dateutil] extra." + ) from exc -__all__ = ("TZAware", "TZNaive") + try: + return parse(value) + except ParserError as exc: + raise TypeError("Could not parse datetime from given string") from exc def parse_datetime(value: object) -> datetime.datetime: if isinstance(value, datetime.datetime): return value + + # Do not allow stdlib datetime parsing + # All string parsing must go through dateutil str_value = parse_str(value) - try: - return parse_datetime_str(str_value) - except DateutilParseError as exc: - raise TypeError("Could not parse datetime from given string") from exc + return parse_datetime_str(str_value) class TZAware(datetime.datetime, Phantom, predicate=is_tz_aware): @@ -76,6 +71,8 @@ def parse(cls, instance: object) -> TZAware: def __schema__(cls) -> Schema: return { **super().__schema__(), + "type": "string", + "format": "date-time", "description": "A date-time with timezone data.", } @@ -103,6 +100,7 @@ def parse(cls, instance: object) -> TZNaive: def __schema__(cls) -> Schema: return { **super().__schema__(), + "type": "string", "description": "A date-time without timezone data.", "format": "date-time-naive", } diff --git a/src/phantom/interval.py b/src/phantom/interval.py index 9f9624a5..b2df97fe 100644 --- a/src/phantom/interval.py +++ b/src/phantom/interval.py @@ -163,6 +163,17 @@ def parse(cls: type[Derived], instance: object) -> Derived: cls.__bound__(instance) if isinstance(instance, str) else instance ) + @classmethod + def _schema_bound( + cls, value: FloatComparable[Any], infinity: float + ) -> int | float | None: + """Convert interval bound to schema-appropriate numeric type.""" + if value == infinity: + return None + if isinstance(cls.__bound__, type) and issubclass(cls.__bound__, int): + return int(value) # type: ignore[call-overload, no-any-return] + return float(value) + def _format_limit(value: SupportsEq) -> str: if value == inf: @@ -183,8 +194,8 @@ def __schema__(cls) -> Schema: f"A value in the exclusive range ({_format_limit(cls.__low__)}, " f"{_format_limit(cls.__high__)})." ), - "exclusiveMinimum": float(cls.__low__) if cls.__low__ != neg_inf else None, - "exclusiveMaximum": float(cls.__high__) if cls.__high__ != inf else None, + "exclusiveMinimum": cls._schema_bound(cls.__low__, neg_inf), + "exclusiveMaximum": cls._schema_bound(cls.__high__, inf), } @classmethod @@ -215,8 +226,8 @@ def __schema__(cls) -> Schema: f"A value in the inclusive range [{_format_limit(cls.__low__)}, " f"{_format_limit(cls.__high__)}]." ), - "minimum": float(cls.__low__) if cls.__low__ != neg_inf else None, - "maximum": float(cls.__high__) if cls.__high__ != inf else None, + "minimum": cls._schema_bound(cls.__low__, neg_inf), + "maximum": cls._schema_bound(cls.__high__, inf), } @classmethod @@ -243,8 +254,8 @@ def __schema__(cls) -> Schema: f"A value in the half-open range ({_format_limit(cls.__low__)}, " f"{_format_limit(cls.__high__)}]." ), - "exclusiveMinimum": float(cls.__low__) if cls.__low__ != neg_inf else None, - "maximum": float(cls.__high__) if cls.__high__ != inf else None, + "exclusiveMinimum": cls._schema_bound(cls.__low__, neg_inf), + "maximum": cls._schema_bound(cls.__high__, inf), } @classmethod @@ -271,8 +282,8 @@ def __schema__(cls) -> Schema: f"A value in the half-open range [{_format_limit(cls.__low__)}, " f"{_format_limit(cls.__high__)})." ), - "minimum": float(cls.__low__) if cls.__low__ != neg_inf else None, - "exclusiveMaximum": float(cls.__high__) if cls.__high__ != inf else None, + "minimum": cls._schema_bound(cls.__low__, neg_inf), + "exclusiveMaximum": cls._schema_bound(cls.__high__, inf), } @classmethod diff --git a/src/phantom/negated.py b/src/phantom/negated.py index 59177a5b..820b1497 100644 --- a/src/phantom/negated.py +++ b/src/phantom/negated.py @@ -9,10 +9,22 @@ from __future__ import annotations from collections.abc import Sequence +from typing import TYPE_CHECKING +from typing import Any from typing import Generic from typing import TypeVar from typing import get_args +from ._utils.compat import require_pydantic + +if TYPE_CHECKING: + try: + from pydantic import GetCoreSchemaHandler + from pydantic_core import CoreSchema + from pydantic_core.core_schema import ValidatorFunctionWrapHandler + except ImportError: + pass + from . import Phantom from . import _hypothesis from .predicates import boolean @@ -44,3 +56,34 @@ def create_strategy( return tuples(from_type(inner_type)) return create_strategy + + @classmethod + def _validate( + cls, + value: Any, + handler: ValidatorFunctionWrapHandler, + ) -> Any: + """Pydantic V2 wrap validator.""" + validated = handler(value) + if isinstance(validated, list): + validated = tuple(validated) + return cls.parse(validated) + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> CoreSchema: + """Pydantic V2 hook for core schema generation.""" + require_pydantic() + + from pydantic_core.core_schema import list_schema + from pydantic_core.core_schema import no_info_wrap_validator_function + + args = get_args(source) + if args: + item_schema = handler.generate_schema(args[0]) + bound_schema = list_schema(items_schema=item_schema) + else: + bound_schema = list_schema() + + return no_info_wrap_validator_function(cls._validate, bound_schema) diff --git a/src/phantom/schema.py b/src/phantom/schema.py index 622e8d7d..141043b2 100644 --- a/src/phantom/schema.py +++ b/src/phantom/schema.py @@ -1,6 +1,17 @@ +from __future__ import annotations + from collections.abc import Sequence +from typing import TYPE_CHECKING from typing import Literal +if TYPE_CHECKING: + try: + from pydantic import GetJsonSchemaHandler + from pydantic.json_schema import JsonSchemaValue + from pydantic_core import CoreSchema + except ImportError: + pass + from typing_extensions import TypedDict from typing_extensions import final @@ -24,16 +35,21 @@ class Schema(TypedDict, total=False): class SchemaField: @classmethod @final - def __modify_schema__(cls, field_schema: dict) -> None: + def __get_pydantic_json_schema__( + cls, schema: CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: """ - This final method is called by pydantic and collects overrides from + Pydantic V2 hook for JSON schema generation. Collects overrides from :func:`Phantom.__schema__() `. Override :func:`__schema__() ` to provide custom schema representations for phantom types. """ - field_schema.update( + json_schema = handler(schema) + json_schema = handler.resolve_ref_schema(json_schema) + json_schema.update( {key: value for key, value in cls.__schema__().items() if value is not None} ) + return json_schema @classmethod def __schema__(cls) -> Schema: diff --git a/src/phantom/sized.py b/src/phantom/sized.py index 74fcecab..1a85b189 100644 --- a/src/phantom/sized.py +++ b/src/phantom/sized.py @@ -27,6 +27,7 @@ class SizedStr(str, PhantomBound[str], max=255): ... from collections.abc import Iterable from collections.abc import Sized +from typing import TYPE_CHECKING # This is the closest I could find to documentation of _ProtocolMeta. # https://github.com/python/cpython/commit/74d7f76e2c953fbfdb7ce01b7319d91d471cc5ef @@ -38,10 +39,19 @@ class SizedStr(str, PhantomBound[str], max=255): ... from typing import get_args from typing import runtime_checkable +if TYPE_CHECKING: + try: + from pydantic import GetCoreSchemaHandler + from pydantic_core import CoreSchema + from pydantic_core.core_schema import ValidatorFunctionWrapHandler + except ImportError: + pass + from . import Phantom from . import PhantomMeta from . import Predicate from . import _hypothesis +from ._utils.compat import require_pydantic from ._utils.misc import is_not_known_mutable_instance from .predicates import boolean from .predicates import collection @@ -98,6 +108,38 @@ def __init_subclass__( **kwargs, ) + @classmethod + def _validate( + cls, + value: Any, + handler: ValidatorFunctionWrapHandler, + ) -> Any: + """Pydantic V2 wrap validator.""" + validated = handler(value) + if isinstance(validated, list): + validated = tuple(validated) + return cls.parse(validated) + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> CoreSchema: + """Pydantic V2 hook for core schema generation.""" + require_pydantic() + + from pydantic_core.core_schema import list_schema + from pydantic_core.core_schema import no_info_wrap_validator_function + + args = get_args(source) + bound_schema: CoreSchema + if args: + item_schema = handler.generate_schema(args[0]) + bound_schema = list_schema(items_schema=item_schema) + else: + bound_schema = list_schema() + + return no_info_wrap_validator_function(cls._validate, bound_schema) + @classmethod def __schema__(cls) -> Schema: return { @@ -183,6 +225,44 @@ def __init_subclass__( **kwargs, ) + @classmethod + def _validate( + cls, + value: Any, + handler: ValidatorFunctionWrapHandler, + ) -> Any: + """Pydantic V2 wrap validator.""" + validated = handler(value) + # Convert list to tuple for immutability check in parse() + if isinstance(validated, list): + validated = tuple(validated) + return cls.parse(validated) + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> CoreSchema: + """Pydantic V2 hook for core schema generation.""" + require_pydantic() + + from pydantic_core.core_schema import list_schema + from pydantic_core.core_schema import no_info_wrap_validator_function + from pydantic_core.core_schema import str_schema + + bound_schema: CoreSchema + if str in cls.__mro__: + bound_schema = str_schema() + else: + # Collection types: extract generic parameter if present + args = get_args(source) + if args: + item_schema = handler.generate_schema(args[0]) + bound_schema = list_schema(items_schema=item_schema) + else: + bound_schema = list_schema() + + return no_info_wrap_validator_function(cls._validate, bound_schema) + @classmethod def __schema__(cls) -> Schema: return ( diff --git a/tests/pydantic/test_datetime.py b/tests/pydantic/test_datetime.py index d353fc99..ae15dd8e 100644 --- a/tests/pydantic/test_datetime.py +++ b/tests/pydantic/test_datetime.py @@ -19,13 +19,13 @@ class HasTZAware(pydantic.BaseModel): class TestPydanticTZAware: @parametrize_aware_str def test_can_parse_tz_aware(self, value: str, expected: datetime.datetime): - obj = HasTZAware.parse_obj({"created_at": value}) + obj = HasTZAware.model_validate({"created_at": value}) assert type(obj.created_at) is datetime.datetime assert obj.created_at == expected def test_tz_aware_rejects_naive_datetime(self): with pytest.raises(ValidationError): - HasTZAware.parse_obj({"created_at": "2022-09-24T10:40:20"}) + HasTZAware.model_validate({"created_at": "2022-09-24T10:40:20"}) class HasTZNaive(pydantic.BaseModel): @@ -35,10 +35,10 @@ class HasTZNaive(pydantic.BaseModel): class TestPydanticTZNaive: @parametrize_naive_str def test_can_parse_tz_naive(self, value: str, expected: datetime.datetime): - obj = HasTZNaive.parse_obj({"time_of_day": value}) + obj = HasTZNaive.model_validate({"time_of_day": value}) assert type(obj.time_of_day) is datetime.datetime assert obj.time_of_day == expected def test_tz_naive_rejects_aware_datetime(self): with pytest.raises(ValidationError): - HasTZNaive.parse_obj({"time_of_day": "2022-09-24T10:40:20+00:00"}) + HasTZNaive.model_validate({"time_of_day": "2022-09-24T10:40:20+00:00"}) diff --git a/tests/pydantic/test_schemas.py b/tests/pydantic/test_schemas.py index f4e146fa..e71ee6be 100644 --- a/tests/pydantic/test_schemas.py +++ b/tests/pydantic/test_schemas.py @@ -70,7 +70,7 @@ class DataModel(pydantic.BaseModel): class TestShippedTypesImplementsSchema: def test_interval_open_implements_schema(self): - assert DataModel.schema()["properties"]["exclusive"] == { + assert DataModel.model_json_schema()["properties"]["exclusive"] == { "exclusiveMinimum": 0, "exclusiveMaximum": 100, "description": "A value in the exclusive range (0, 100).", @@ -79,7 +79,7 @@ def test_interval_open_implements_schema(self): } def test_interval_closed_implements_schema(self): - assert DataModel.schema()["properties"]["inclusive"] == { + assert DataModel.model_json_schema()["properties"]["inclusive"] == { "description": "A value in the inclusive range [-1, 1].", "minimum": -1, "maximum": 1, @@ -88,7 +88,7 @@ def test_interval_closed_implements_schema(self): } def test_interval_exclusive_inclusive_implements_schema(self): - assert DataModel.schema()["properties"]["exclusive_inclusive"] == { + assert DataModel.model_json_schema()["properties"]["exclusive_inclusive"] == { "description": "A value in the half-open range (0, 100].", "exclusiveMinimum": 0, "maximum": 100, @@ -97,7 +97,7 @@ def test_interval_exclusive_inclusive_implements_schema(self): } def test_interval_inclusive_exclusive_implements_schema(self): - assert DataModel.schema()["properties"]["inclusive_exclusive"] == { + assert DataModel.model_json_schema()["properties"]["inclusive_exclusive"] == { "title": "InclusiveExclusiveType", "description": "A value in the half-open range [-100, 0).", "minimum": -100, @@ -106,7 +106,7 @@ def test_interval_inclusive_exclusive_implements_schema(self): } def test_interval_negative_int_implements_schema(self): - assert DataModel.schema()["properties"]["negative_int"] == { + assert DataModel.model_json_schema()["properties"]["negative_int"] == { "title": "NegativeInt", "maximum": 0, "description": "An integer value in the inclusive range (-∞, 0].", @@ -114,7 +114,7 @@ def test_interval_negative_int_implements_schema(self): } def test_interval_natural_implements_schema(self): - assert DataModel.schema()["properties"]["natural"] == { + assert DataModel.model_json_schema()["properties"]["natural"] == { "title": "Natural", "description": "An integer value in the inclusive range [0, ∞).", "minimum": 0, @@ -122,7 +122,7 @@ def test_interval_natural_implements_schema(self): } def test_interval_portion_implements_schema(self): - assert DataModel.schema()["properties"]["portion"] == { + assert DataModel.model_json_schema()["properties"]["portion"] == { "title": "Portion", "description": "A float value in the inclusive range [0, 1].", "minimum": 0, @@ -131,7 +131,7 @@ def test_interval_portion_implements_schema(self): } def test_tz_aware_implements_schema(self): - assert DataModel.schema()["properties"]["tz_aware"] == { + assert DataModel.model_json_schema()["properties"]["tz_aware"] == { "title": "TZAware", "description": "A date-time with timezone data.", "type": "string", @@ -139,7 +139,7 @@ def test_tz_aware_implements_schema(self): } def test_tz_naive_implements_schema(self): - assert DataModel.schema()["properties"]["tz_naive"] == { + assert DataModel.model_json_schema()["properties"]["tz_naive"] == { "title": "TZNaive", "description": "A date-time without timezone data.", "type": "string", @@ -147,7 +147,7 @@ def test_tz_naive_implements_schema(self): } def test_re_match_implements_schema(self): - assert DataModel.schema()["properties"]["match"] == { + assert DataModel.model_json_schema()["properties"]["match"] == { "title": "MatchType", "description": ( "A string starting with a match of the format regular expression." @@ -157,7 +157,7 @@ def test_re_match_implements_schema(self): } def test_re_full_match_implements_schema(self): - assert DataModel.schema()["properties"]["full_match"] == { + assert DataModel.model_json_schema()["properties"]["full_match"] == { "title": "FullMatchType", "description": "A string that matches the format regular expression.", "type": "string", @@ -165,24 +165,27 @@ def test_re_full_match_implements_schema(self): } def test_sized_non_empty_implements_schema(self): - assert DataModel.schema()["properties"]["non_empty"] == { - "allOf": [{"type": "integer"}], + assert DataModel.model_json_schema()["properties"]["non_empty"] == { "title": "NonEmpty", "type": "array", + "items": { + "type": "integer", + }, "description": "A non-empty array.", "minItems": 1, } def test_sized_empty_implements_schema(self): - assert DataModel.schema()["properties"]["empty"] == { + assert DataModel.model_json_schema()["properties"]["empty"] == { "title": "Empty", "type": "array", + "items": {}, "description": "An empty array.", "maxItems": 0, } def test_sized_non_empty_str_implements_schema(self): - assert DataModel.schema()["properties"]["non_empty_str"] == { + assert DataModel.model_json_schema()["properties"]["non_empty_str"] == { "title": "NonEmptyStr", "type": "string", "description": "A non-empty string.", @@ -190,13 +193,14 @@ def test_sized_non_empty_str_implements_schema(self): } def test_phantom_sized_implements_schema(self): - assert DataModel.schema()["properties"]["odd_size"] == { + assert DataModel.model_json_schema()["properties"]["odd_size"] == { + "items": {}, "title": "OddSize", "type": "array", } def test_country_code_implements_schema(self): - assert DataModel.schema()["properties"]["country"] == { + assert DataModel.model_json_schema()["properties"]["country"] == { "title": "Alpha2", "description": "ISO3166-1 alpha-2 country code", "examples": ["NR", "KZ", "ET", "VC", "AE", "NZ", "SX", "XK", "AX"], @@ -205,7 +209,7 @@ def test_country_code_implements_schema(self): } def test_phone_number_implements_schema(self): - assert DataModel.schema()["properties"]["phone_number"] == { + assert DataModel.model_json_schema()["properties"]["phone_number"] == { "title": "PhoneNumber", "description": "A valid E.164 phone number.", "type": "string", @@ -213,7 +217,9 @@ def test_phone_number_implements_schema(self): } def test_formatted_phone_number_implements_schema(self): - assert DataModel.schema()["properties"]["formatted_phone_number"] == { + assert DataModel.model_json_schema()["properties"][ + "formatted_phone_number" + ] == { "title": "PhoneNumber", "description": "A valid E.164 phone number.", "type": "string", @@ -221,7 +227,7 @@ def test_formatted_phone_number_implements_schema(self): } def test_sequence_not_str_implements_schema(self): - assert DataModel.schema()["properties"]["sequence_not_str"] == { + assert DataModel.model_json_schema()["properties"]["sequence_not_str"] == { "title": "SequenceNotStr", "type": "array", "items": {"type": "integer"}, diff --git a/typing-requirements.txt b/typing-requirements.txt index 49843620..f204d7a1 100644 --- a/typing-requirements.txt +++ b/typing-requirements.txt @@ -1,56 +1,138 @@ # This file was autogenerated by uv via the following command: # 'make typing-requirements' -beartype==0.22.5 \ - --hash=sha256:516a9096cc77103c96153474fa35c3ebcd9d36bd2ec8d0e3a43307ced0fa6341 \ - --hash=sha256:d9743dd7cd6d193696eaa1e025f8a70fb09761c154675679ff236e61952dfba0 +annotated-types==0.7.0 \ + --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ + --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 + # via pydantic +beartype==0.22.9 \ + --hash=sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f \ + --hash=sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2 # via numerary -hypothesis==6.147.0 \ - --hash=sha256:72e6004ea3bd1460bdb4640b6389df23b87ba7a4851893fd84d1375635d3e507 \ - --hash=sha256:de588807b6da33550d32f47bcd42b1a86d061df85673aa73e6443680249d185e +hypothesis==6.150.2 \ + --hash=sha256:648d6a2be435889e713ba3d335b0fb5e7a250f569b56e6867887c1e7a0d1f02f \ + --hash=sha256:deb043c41c53eaf0955f4a08739c2a34c3d8040ee3d9a2da0aa5470122979f75 # via phantom-types (pyproject.toml) iniconfig==2.3.0 \ --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 # via pytest -mypy==1.18.2 \ - --hash=sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914 \ - --hash=sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b \ - --hash=sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b \ - --hash=sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc \ - --hash=sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544 \ - --hash=sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86 \ - --hash=sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d \ - --hash=sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075 \ - --hash=sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e \ - --hash=sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac \ - --hash=sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b \ - --hash=sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34 \ - --hash=sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37 \ - --hash=sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b \ - --hash=sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428 \ - --hash=sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893 \ - --hash=sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce \ - --hash=sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8 \ - --hash=sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c \ - --hash=sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf \ - --hash=sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341 \ - --hash=sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e \ - --hash=sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba \ - --hash=sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed \ - --hash=sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f \ - --hash=sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d \ - --hash=sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8 \ - --hash=sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764 \ - --hash=sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d \ - --hash=sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0 \ - --hash=sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c \ - --hash=sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133 \ - --hash=sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986 \ - --hash=sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6 \ - --hash=sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074 \ - --hash=sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb \ - --hash=sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e \ - --hash=sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66 +librt==0.7.8 \ + --hash=sha256:00105e7d541a8f2ee5be52caacea98a005e0478cfe78c8080fbb7b5d2b340c63 \ + --hash=sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac \ + --hash=sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62 \ + --hash=sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85 \ + --hash=sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365 \ + --hash=sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862 \ + --hash=sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232 \ + --hash=sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850 \ + --hash=sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7 \ + --hash=sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d \ + --hash=sha256:389bd25a0db916e1d6bcb014f11aa9676cedaa485e9ec3752dfe19f196fd377b \ + --hash=sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b \ + --hash=sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e \ + --hash=sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44 \ + --hash=sha256:41d7bb1e07916aeb12ae4a44e3025db3691c4149ab788d0315781b4d29b86afb \ + --hash=sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09 \ + --hash=sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32 \ + --hash=sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2 \ + --hash=sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6 \ + --hash=sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93 \ + --hash=sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c \ + --hash=sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca \ + --hash=sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc \ + --hash=sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3 \ + --hash=sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581 \ + --hash=sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce \ + --hash=sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde \ + --hash=sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0 \ + --hash=sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83 \ + --hash=sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93 \ + --hash=sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8 \ + --hash=sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a \ + --hash=sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d \ + --hash=sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d \ + --hash=sha256:6d772edc6a5f7835635c7562f6688e031f0b97e31d538412a852c49c9a6c92d5 \ + --hash=sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708 \ + --hash=sha256:73fd300f501a052f2ba52ede721232212f3b06503fa12665408ecfc9d8fd149c \ + --hash=sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758 \ + --hash=sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d \ + --hash=sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611 \ + --hash=sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac \ + --hash=sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b \ + --hash=sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06 \ + --hash=sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873 \ + --hash=sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592 \ + --hash=sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666 \ + --hash=sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c \ + --hash=sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63 \ + --hash=sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff \ + --hash=sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81 \ + --hash=sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c \ + --hash=sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b \ + --hash=sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449 \ + --hash=sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951 \ + --hash=sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d \ + --hash=sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3 \ + --hash=sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d \ + --hash=sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea \ + --hash=sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714 \ + --hash=sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418 \ + --hash=sha256:bfde8a130bd0f239e45503ab39fab239ace094d63ee1d6b67c25a63d741c0f71 \ + --hash=sha256:c6f8947d3dfd7f91066c5b4385812c18be26c9d5a99ca56667547f2c39149d94 \ + --hash=sha256:c7e8f88f79308d86d8f39c491773cbb533d6cb7fa6476f35d711076ee04fceb6 \ + --hash=sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f \ + --hash=sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca \ + --hash=sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c \ + --hash=sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75 \ + --hash=sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac \ + --hash=sha256:e90a8e237753c83b8e484d478d9a996dc5e39fd5bd4c6ce32563bc8123f132be \ + --hash=sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c \ + --hash=sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e \ + --hash=sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398 \ + --hash=sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34 \ + --hash=sha256:fdec6e2368ae4f796fc72fad7fd4bd1753715187e6d870932b0904609e7c878e \ + --hash=sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f \ + --hash=sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0 + # via mypy +mypy==1.19.1 \ + --hash=sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd \ + --hash=sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b \ + --hash=sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1 \ + --hash=sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba \ + --hash=sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b \ + --hash=sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045 \ + --hash=sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac \ + --hash=sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6 \ + --hash=sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a \ + --hash=sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24 \ + --hash=sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957 \ + --hash=sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042 \ + --hash=sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e \ + --hash=sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec \ + --hash=sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3 \ + --hash=sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718 \ + --hash=sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f \ + --hash=sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331 \ + --hash=sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1 \ + --hash=sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1 \ + --hash=sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13 \ + --hash=sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67 \ + --hash=sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2 \ + --hash=sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a \ + --hash=sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b \ + --hash=sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8 \ + --hash=sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376 \ + --hash=sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef \ + --hash=sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288 \ + --hash=sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75 \ + --hash=sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74 \ + --hash=sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250 \ + --hash=sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab \ + --hash=sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6 \ + --hash=sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247 \ + --hash=sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925 \ + --hash=sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e \ + --hash=sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e # via phantom-types (pyproject.toml) mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ @@ -63,77 +145,152 @@ packaging==25.0 \ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f # via pytest -pathspec==0.12.1 \ - --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ - --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 +pathspec==1.0.3 \ + --hash=sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d \ + --hash=sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c # via mypy -phonenumbers==9.0.18 \ - --hash=sha256:5537c61ba95b11b992c95e804da6e49193cc06b1224f632ade64631518a48ed1 \ - --hash=sha256:d3354454ac31c97f8a08121df97a7145b8dca641f734c6f1518a41c2f60c5764 +phonenumbers==9.0.22 \ + --hash=sha256:645e66cd9a136b3b257b5f941fa97d324124114d31ad3c9f2488682f47ad7ee1 \ + --hash=sha256:eff985c65575749d1d54e07c56c3517d5243e03e08e4a6191761df9aab2278f2 # via phantom-types (pyproject.toml) pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 # via pytest -pydantic==1.10.24 \ - --hash=sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c \ - --hash=sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d \ - --hash=sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193 \ - --hash=sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6 \ - --hash=sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624 \ - --hash=sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667 \ - --hash=sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2 \ - --hash=sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c \ - --hash=sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621 \ - --hash=sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52 \ - --hash=sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816 \ - --hash=sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f \ - --hash=sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813 \ - --hash=sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49 \ - --hash=sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666 \ - --hash=sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a \ - --hash=sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a \ - --hash=sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683 \ - --hash=sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1 \ - --hash=sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c \ - --hash=sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94 \ - --hash=sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f \ - --hash=sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47 \ - --hash=sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702 \ - --hash=sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd \ - --hash=sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70 \ - --hash=sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53 \ - --hash=sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b \ - --hash=sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36 \ - --hash=sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c \ - --hash=sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993 \ - --hash=sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b \ - --hash=sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8 \ - --hash=sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01 \ - --hash=sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69 \ - --hash=sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3 \ - --hash=sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00 \ - --hash=sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37 \ - --hash=sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2 \ - --hash=sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6 \ - --hash=sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d \ - --hash=sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def \ - --hash=sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574 \ - --hash=sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6 \ - --hash=sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c \ - --hash=sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de \ - --hash=sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0 \ - --hash=sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579 \ - --hash=sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4 \ - --hash=sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb +pydantic==2.12.5 \ + --hash=sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49 \ + --hash=sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d # via phantom-types (pyproject.toml) +pydantic-core==2.41.5 \ + --hash=sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90 \ + --hash=sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740 \ + --hash=sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504 \ + --hash=sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84 \ + --hash=sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33 \ + --hash=sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c \ + --hash=sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0 \ + --hash=sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e \ + --hash=sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0 \ + --hash=sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a \ + --hash=sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34 \ + --hash=sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2 \ + --hash=sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3 \ + --hash=sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815 \ + --hash=sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14 \ + --hash=sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba \ + --hash=sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375 \ + --hash=sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf \ + --hash=sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963 \ + --hash=sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1 \ + --hash=sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808 \ + --hash=sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553 \ + --hash=sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1 \ + --hash=sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2 \ + --hash=sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5 \ + --hash=sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470 \ + --hash=sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2 \ + --hash=sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b \ + --hash=sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660 \ + --hash=sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c \ + --hash=sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093 \ + --hash=sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5 \ + --hash=sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594 \ + --hash=sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008 \ + --hash=sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a \ + --hash=sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a \ + --hash=sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd \ + --hash=sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284 \ + --hash=sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586 \ + --hash=sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869 \ + --hash=sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294 \ + --hash=sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f \ + --hash=sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66 \ + --hash=sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51 \ + --hash=sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc \ + --hash=sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97 \ + --hash=sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a \ + --hash=sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d \ + --hash=sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9 \ + --hash=sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c \ + --hash=sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07 \ + --hash=sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36 \ + --hash=sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e \ + --hash=sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05 \ + --hash=sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e \ + --hash=sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941 \ + --hash=sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3 \ + --hash=sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612 \ + --hash=sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3 \ + --hash=sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b \ + --hash=sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe \ + --hash=sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146 \ + --hash=sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11 \ + --hash=sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60 \ + --hash=sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd \ + --hash=sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b \ + --hash=sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c \ + --hash=sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a \ + --hash=sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460 \ + --hash=sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1 \ + --hash=sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf \ + --hash=sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf \ + --hash=sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858 \ + --hash=sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2 \ + --hash=sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9 \ + --hash=sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2 \ + --hash=sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3 \ + --hash=sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6 \ + --hash=sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770 \ + --hash=sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d \ + --hash=sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc \ + --hash=sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23 \ + --hash=sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26 \ + --hash=sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa \ + --hash=sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8 \ + --hash=sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d \ + --hash=sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3 \ + --hash=sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d \ + --hash=sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034 \ + --hash=sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9 \ + --hash=sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1 \ + --hash=sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56 \ + --hash=sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b \ + --hash=sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c \ + --hash=sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a \ + --hash=sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e \ + --hash=sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9 \ + --hash=sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5 \ + --hash=sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a \ + --hash=sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556 \ + --hash=sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e \ + --hash=sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49 \ + --hash=sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2 \ + --hash=sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9 \ + --hash=sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b \ + --hash=sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc \ + --hash=sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb \ + --hash=sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0 \ + --hash=sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8 \ + --hash=sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82 \ + --hash=sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69 \ + --hash=sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b \ + --hash=sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c \ + --hash=sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75 \ + --hash=sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5 \ + --hash=sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f \ + --hash=sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad \ + --hash=sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b \ + --hash=sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7 \ + --hash=sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425 \ + --hash=sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52 + # via pydantic pygments==2.19.2 \ --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b # via pytest -pytest==9.0.0 \ - --hash=sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e \ - --hash=sha256:e5ccdf10b0bac554970ee88fc1a4ad0ee5d221f8ef22321f9b7e4584e19d7f96 +pytest==9.0.2 \ + --hash=sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b \ + --hash=sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11 # via phantom-types (pyproject.toml) python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ @@ -151,9 +308,9 @@ typeguard==4.4.4 \ --hash=sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74 \ --hash=sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e # via phantom-types (pyproject.toml) -types-python-dateutil==2.9.0.20251108 \ - --hash=sha256:a4a537f0ea7126f8ccc2763eec9aa31ac8609e3c8e530eb2ddc5ee234b3cd764 \ - --hash=sha256:d8a6687e197f2fa71779ce36176c666841f811368710ab8d274b876424ebfcaa +types-python-dateutil==2.9.0.20251115 \ + --hash=sha256:8a47f2c3920f52a994056b8786309b43143faa5a64d4cbb2722d6addabdf1a58 \ + --hash=sha256:9cf9c1c582019753b8639a081deefd7e044b9fa36bd8217f565c6c4e36ee0624 # via phantom-types (pyproject.toml) typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ @@ -162,4 +319,10 @@ typing-extensions==4.15.0 \ # phantom-types (pyproject.toml) # mypy # pydantic + # pydantic-core # typeguard + # typing-inspection +typing-inspection==0.4.2 \ + --hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \ + --hash=sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464 + # via pydantic