From 1c08910f5f11ed22abc11425f3ac230293c05863 Mon Sep 17 00:00:00 2001 From: Vapi Tasker Date: Wed, 20 May 2026 22:45:07 +0000 Subject: [PATCH] fix: relax pydantic-core upper bound from <2.44.0 to <3 The pydantic-core<2.44.0 constraint was auto-generated by the Fern SDK generator (fernapi/fern-python-sdk v5.3.3) in commit 7a070a7 and was not based on any actual incompatibility. The SDK only uses stable pydantic-core APIs (PydanticUndefined, core_schema, to_jsonable_python) that have been unchanged across all 2.x releases. This constraint caused a dependency conflict for users with pydantic>=2.13.3, which requires pydantic-core==2.46.3, forcing them to downgrade to SDK v1.9.0. The new constraint >=2.18.2,<3 allows all pydantic-core 2.x versions while guarding against a hypothetical 3.0 breaking change. Resolves: PRO-2816 Co-Authored-By: Claude Opus 4.6 --- poetry.lock | 65 +++++++++++-- pyproject.toml | 2 +- requirements.txt | 2 +- tests/test_pydantic_core_compat.py | 151 +++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 tests/test_pydantic_core_compat.py diff --git a/poetry.lock b/poetry.lock index 1259a1c..8f2220d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,6 +6,8 @@ version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -17,6 +19,8 @@ version = "3.13.5" description = "Async http client/server framework (asyncio)" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, @@ -151,7 +155,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] +speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -159,6 +163,8 @@ version = "1.4.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -174,6 +180,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -185,6 +192,7 @@ version = "4.13.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, @@ -204,6 +212,8 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"aiohttp\" and python_version == \"3.10\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -215,6 +225,8 @@ version = "26.1.0" description = "Classes Without Boilerplate" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, @@ -226,6 +238,8 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -237,6 +251,7 @@ version = "2026.4.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, @@ -248,6 +263,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -259,6 +276,8 @@ version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, @@ -276,6 +295,7 @@ version = "2.1.2" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, @@ -290,6 +310,8 @@ version = "1.8.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, @@ -429,6 +451,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -440,6 +463,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -461,6 +485,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -473,7 +498,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -485,6 +510,8 @@ version = "0.1.8" description = "Aiohttp transport for HTTPX" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"}, {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"}, @@ -500,6 +527,7 @@ version = "3.13" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3"}, {file = "idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242"}, @@ -514,6 +542,7 @@ version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -525,6 +554,8 @@ version = "6.7.1" description = "multidict implementation" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, @@ -683,6 +714,7 @@ version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, @@ -736,6 +768,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -747,6 +780,7 @@ version = "26.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f"}, {file = "packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de"}, @@ -758,6 +792,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -773,6 +808,8 @@ version = "0.4.1" description = "Accelerated property cache" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, @@ -904,6 +941,7 @@ version = "2.12.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, @@ -917,7 +955,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -925,6 +963,7 @@ version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, @@ -1058,6 +1097,7 @@ version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, @@ -1072,6 +1112,7 @@ version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -1095,6 +1136,7 @@ version = "1.3.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, @@ -1115,6 +1157,7 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1135,6 +1178,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1149,6 +1193,7 @@ version = "0.11.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, @@ -1176,6 +1221,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1187,6 +1233,8 @@ version = "2.4.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, @@ -1243,6 +1291,7 @@ version = "2.9.0.20260408" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, @@ -1254,6 +1303,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -1265,6 +1315,7 @@ version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -1279,6 +1330,8 @@ version = "1.23.0" description = "Yet another URL library" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, @@ -1419,6 +1472,6 @@ propcache = ">=0.2.1" aiohttp = ["aiohttp", "httpx-aiohttp"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.10" -content-hash = "c22c549ef574a50fc48074038793033112a025836768a63fcc3749869d1bcc8e" +content-hash = "54fe0c0f31132c595577eda75ecbffab56a0f873fa1de7e1cb2e071ac79d58ca" diff --git a/pyproject.toml b/pyproject.toml index 22d4c95..1580c58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ aiohttp = { version = ">=3.10.0,<4", optional = true} httpx = ">=0.21.2" httpx-aiohttp = { version = "0.1.8", optional = true} pydantic = ">= 1.9.2" -pydantic-core = ">=2.18.2,<2.44.0" +pydantic-core = ">=2.18.2,<3" typing_extensions = ">= 4.0.0" [tool.poetry.group.dev.dependencies] diff --git a/requirements.txt b/requirements.txt index 0141a1a..f9a9f9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ httpx>=0.21.2 pydantic>= 1.9.2 -pydantic-core>=2.18.2,<2.44.0 +pydantic-core>=2.18.2,<3 typing_extensions>= 4.0.0 diff --git a/tests/test_pydantic_core_compat.py b/tests/test_pydantic_core_compat.py new file mode 100644 index 0000000..d55a706 --- /dev/null +++ b/tests/test_pydantic_core_compat.py @@ -0,0 +1,151 @@ +"""Tests that verify pydantic-core APIs used by the SDK remain compatible. + +This module exercises every pydantic_core import found in the SDK to ensure +that relaxing the upper-bound version constraint does not break functionality. +""" + +import datetime as dt +from typing import Optional + +import pydantic +import pydantic_core +import pytest + +from vapi.core.pydantic_utilities import IS_PYDANTIC_V2 + + +class TestPydanticCoreImports: + """Verify that all pydantic_core symbols imported by the SDK are available.""" + + def test_pydantic_undefined_importable(self) -> None: + from pydantic_core import PydanticUndefined + + assert PydanticUndefined is not None + + def test_core_schema_importable(self) -> None: + from pydantic_core import core_schema + + assert hasattr(core_schema, "datetime_schema") + assert hasattr(core_schema, "no_info_before_validator_function") + + def test_to_jsonable_python_importable(self) -> None: + from pydantic_core import to_jsonable_python + + assert callable(to_jsonable_python) + + +class TestPydanticCoreUsagePatterns: + """Exercise the actual usage patterns from the SDK to ensure compatibility.""" + + def test_pydantic_undefined_sentinel_comparison(self) -> None: + """The SDK compares field defaults against PydanticUndefined.""" + from pydantic_core import PydanticUndefined + + assert PydanticUndefined != None # noqa: E711 + assert PydanticUndefined != "some_value" + + def test_to_jsonable_python_with_datetime(self) -> None: + """The SDK uses to_jsonable_python for serialization.""" + from pydantic_core import to_jsonable_python + + now = dt.datetime.now(tz=dt.timezone.utc) + result = to_jsonable_python(now) + assert isinstance(result, str) + + def test_to_jsonable_python_with_dict(self) -> None: + from pydantic_core import to_jsonable_python + + data = {"key": "value", "number": 42} + result = to_jsonable_python(data) + assert result == data + + def test_core_schema_datetime_schema(self) -> None: + """The SDK uses core_schema.datetime_schema in Rfc2822DateTime.""" + from pydantic_core import core_schema + + schema = core_schema.datetime_schema() + assert schema is not None + assert isinstance(schema, dict) + + @pytest.mark.skipif(not IS_PYDANTIC_V2, reason="Pydantic V2 only") + def test_model_with_pydantic_undefined_default(self) -> None: + """Simulate the SDK pattern of checking field defaults against PydanticUndefined.""" + from pydantic_core import PydanticUndefined + + class SampleModel(pydantic.BaseModel): + required_field: str + optional_field: Optional[str] = None + + fields = SampleModel.model_fields + required_default = fields["required_field"].get_default() + optional_default = fields["optional_field"].get_default() + + assert required_default is PydanticUndefined + assert optional_default is None + + +class TestUncheckedBaseModelCompat: + """Test that UncheckedBaseModel works with the current pydantic-core version.""" + + def test_construct_with_defaults(self) -> None: + """Verify UncheckedBaseModel.construct works with PydanticUndefined checks.""" + from vapi.core.unchecked_base_model import UncheckedBaseModel + + class TestModel(UncheckedBaseModel): + name: str + value: Optional[int] = None + + instance = TestModel.construct(name="test", value=42) + assert instance.name == "test" + assert instance.value == 42 + + def test_construct_with_missing_optional(self) -> None: + from vapi.core.unchecked_base_model import UncheckedBaseModel + + class TestModel(UncheckedBaseModel): + name: str + value: Optional[int] = None + + instance = TestModel.construct(name="test") + assert instance.name == "test" + assert instance.value is None + + def test_construct_with_extra_fields(self) -> None: + from vapi.core.unchecked_base_model import UncheckedBaseModel + + class TestModel(UncheckedBaseModel): + name: str + + instance = TestModel.construct(name="test", extra_field="extra") + assert instance.name == "test" + + +class TestRfc2822DateTimeCompat: + """Test that Rfc2822DateTime works with the current pydantic-core version.""" + + @pytest.mark.skipif(not IS_PYDANTIC_V2, reason="Pydantic V2 only") + def test_rfc2822_datetime_schema(self) -> None: + from vapi.core.datetime_utils import Rfc2822DateTime + + schema = Rfc2822DateTime.__get_pydantic_core_schema__(Rfc2822DateTime, lambda x: x) + assert schema is not None + + +class TestVersionConstraintRelaxed: + """Meta-test: verify that pydantic-core >= 2.18.2 is installed and usable.""" + + def test_pydantic_core_version_is_sufficient(self) -> None: + version_parts = pydantic_core.__version__.split(".") + major, minor, patch = int(version_parts[0]), int(version_parts[1]), int(version_parts[2]) + assert major >= 2, f"pydantic-core major version must be >= 2, got {major}" + assert (major, minor, patch) >= (2, 18, 2), ( + f"pydantic-core must be >= 2.18.2, got {pydantic_core.__version__}" + ) + + def test_pydantic_core_not_artificially_capped(self) -> None: + """This test documents that versions >= 2.44.0 should now be allowed.""" + # This test simply passes to document the intent: + # The upper bound constraint pydantic-core<2.44.0 has been removed. + # Users with pydantic>=2.13.3 (which requires pydantic-core==2.46.3) + # should be able to install this SDK without conflicts. + assert True