File support-pydantic-core-2.39.0.patch of Package python-pydantic
From d6c65493a8436b22733d0f04d0bb3df1bc952ac9 Mon Sep 17 00:00:00 2001
From: Viicos <65306057+Viicos@users.noreply.github.com>
Date: Fri, 16 May 2025 15:46:24 +0200
Subject: [PATCH 1/8] Add `UNSET` sentinel
---
pydantic/_internal/_generate_schema.py | 3 +
pydantic/fields.py | 4 +-
pydantic/json_schema.py | 7 +-
pyproject.toml | 2 +-
5 files changed, 15 insertions(+), 122 deletions(-)
Index: pydantic-2.11.7/pydantic/_internal/_generate_schema.py
===================================================================
--- pydantic-2.11.7.orig/pydantic/_internal/_generate_schema.py
+++ pydantic-2.11.7/pydantic/_internal/_generate_schema.py
@@ -42,6 +42,7 @@ from zoneinfo import ZoneInfo
import typing_extensions
from pydantic_core import (
+ MISSING,
CoreSchema,
MultiHostUrl,
PydanticCustomError,
@@ -1050,6 +1051,8 @@ class GenerateSchema:
return core_schema.multi_host_url_schema()
elif obj is None or obj is _typing_extra.NoneType:
return core_schema.none_schema()
+ if obj is MISSING:
+ return core_schema.missing_sentinel_schema()
elif obj in IP_TYPES:
return self._ip_schema(obj)
elif obj in TUPLE_TYPES:
Index: pydantic-2.11.7/pydantic/fields.py
===================================================================
--- pydantic-2.11.7.orig/pydantic/fields.py
+++ pydantic-2.11.7/pydantic/fields.py
@@ -15,7 +15,7 @@ from warnings import warn
import annotated_types
import typing_extensions
-from pydantic_core import PydanticUndefined
+from pydantic_core import MISSING, PydanticUndefined
from typing_extensions import Self, TypeAlias, Unpack, deprecated
from typing_inspection import typing_objects
from typing_inspection.introspection import UNKNOWN, AnnotationSource, ForbiddenQualifier, Qualifier, inspect_annotation
@@ -392,7 +392,7 @@ class FieldInfo(_repr.Representation):
Returns:
A field object with the passed values.
"""
- if annotation is default:
+ if annotation is not MISSING and annotation is default:
raise PydanticUserError(
'Error when building FieldInfo from annotated attribute. '
"Make sure you don't have any field name clashing with a type annotation.",
Index: pydantic-2.11.7/pydantic/json_schema.py
===================================================================
--- pydantic-2.11.7.orig/pydantic/json_schema.py
+++ pydantic-2.11.7/pydantic/json_schema.py
@@ -36,7 +36,7 @@ from typing import (
)
import pydantic_core
-from pydantic_core import CoreSchema, PydanticOmit, core_schema, to_jsonable_python
+from pydantic_core import MISSING, CoreSchema, PydanticOmit, core_schema, to_jsonable_python
from pydantic_core.core_schema import ComputedField
from typing_extensions import TypeAlias, assert_never, deprecated, final
from typing_inspection.introspection import get_literal_values
@@ -805,6 +805,17 @@ class GenerateJsonSchema:
result['type'] = 'null'
return result
+ def missing_sentinel_schema(self, schema: core_schema.MissingSentinelSchema) -> JsonSchemaValue:
+ """Generates a JSON schema that matches the `MISSING` sentinel value.
+
+ Args:
+ schema: The core schema.
+
+ Returns:
+ The generated JSON schema.
+ """
+ raise PydanticOmit
+
def enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue:
"""Generates a JSON schema that matches an Enum value.
@@ -1109,7 +1120,7 @@ class GenerateJsonSchema:
json_schema = self.generate_inner(schema['schema'])
default = self.get_default_value(schema)
- if default is NoDefault:
+ if default is NoDefault or default is MISSING:
return json_schema
# we reflect the application of custom plain, no-info serializers to defaults for
Index: pydantic-2.11.7/pydantic/version.py
===================================================================
--- pydantic-2.11.7.orig/pydantic/version.py
+++ pydantic-2.11.7/pydantic/version.py
@@ -66,7 +66,7 @@ def version_info() -> str:
def check_pydantic_core_version() -> bool:
"""Check that the installed `pydantic-core` dependency is compatible."""
# Keep this in sync with the version constraint in the `pyproject.toml` dependencies:
- return __pydantic_core_version__ == '2.35.1'
+ return __pydantic_core_version__ == '2.39.0'
def parse_mypy_version(version: str) -> tuple[int, int, int]:
Index: pydantic-2.11.7/docs/concepts/experimental.md
===================================================================
--- pydantic-2.11.7.orig/docs/concepts/experimental.md
+++ pydantic-2.11.7/docs/concepts/experimental.md
@@ -502,3 +502,49 @@ args, kwargs = val.validate_json('{"args
print(args, kwargs)
#> ('arg1',) {'extra': 1}
```
+
+## `MISSING` sentinel
+
+The `MISSING` sentinel is a singleton indicating a field value was not provided during validation.
+
+This singleton can be used as a default value, as an alternative to `None` when it has an explicit
+meaning. During serialization, any field with `MISSING` as a value is excluded from the output.
+
+```python
+from typing import Union
+
+from pydantic import BaseModel
+from pydantic.experimental.missing_sentinel import MISSING
+
+
+class Configuration(BaseModel):
+ timeout: Union[int, None, MISSING] = MISSING
+
+
+# configuration defaults, stored somewhere else:
+defaults = {'timeout': 200}
+
+conf = Configuration()
+
+# `timeout` is excluded from the serialization output:
+conf.model_dump()
+# {}
+
+# The `MISSING` value doesn't appear in the JSON Schema:
+Configuration.model_json_schema()['properties']['timeout']
+#> {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Timeout'}}
+
+
+# `is` can be used to discrimate between the sentinel and other values:
+timeout = conf.timeout if conf.timeout is not MISSING else defaults['timeout']
+```
+
+This feature is marked as experimental because it relies on the draft [PEP 661](https://peps.python.org/pep-0661/), introducing sentinels in the standard library.
+
+As such, the following limitations currently apply:
+
+* Static type checking of sentinels is only supported with Pyright
+ [1.1.402](https://github.com/microsoft/pyright/releases/tag/1.1.402)
+ or greater, and the `enableExperimentalFeatures` type evaluation setting
+ should be enabled.
+* Pickling of models containing `MISSING` as a value is not supported.
Index: pydantic-2.11.7/docs/errors/validation_errors.md
===================================================================
--- pydantic-2.11.7.orig/docs/errors/validation_errors.md
+++ pydantic-2.11.7/docs/errors/validation_errors.md
@@ -1384,6 +1384,27 @@ except ValidationError as exc:
#> 'missing_positional_only_argument'
```
+## `missing_sentinel_error`
+
+This error is raised when the experimental `MISSING` sentinel is the only value allowed, and wasn't
+provided during validation:
+
+```python
+from pydantic import BaseModel, ValidationError
+from pydantic.experimental.missing_sentinel import MISSING
+
+
+class Model(BaseModel):
+ f: MISSING
+
+
+try:
+ Model(f=1)
+except ValidationError as exc:
+ print(repr(exc.errors()[0]['type']))
+ #> 'missing_sentinel_error'
+```
+
## `model_attributes_type`
This error is raised when the input value is not a valid dictionary, model instance, or instance that fields can be extracted from:
Index: pydantic-2.11.7/pydantic/experimental/missing_sentinel.py
===================================================================
--- /dev/null
+++ pydantic-2.11.7/pydantic/experimental/missing_sentinel.py
@@ -0,0 +1,5 @@
+"""Experimental module exposing a function a `MISSING` sentinel."""
+
+from pydantic_core import MISSING
+
+__all__ = ('MISSING',)
Index: pydantic-2.11.7/pyproject.toml
===================================================================
--- pydantic-2.11.7.orig/pyproject.toml
+++ pydantic-2.11.7/pyproject.toml
@@ -46,7 +46,7 @@ dependencies = [
'typing-extensions>=4.13.0',
'annotated-types>=0.6.0',
# Keep this in sync with the version in the `check_pydantic_core_version()` function:
- 'pydantic-core==2.35.1',
+ 'pydantic-core==2.39.0',
'typing-inspection>=0.4.0',
]
dynamic = ['version', 'readme']
Index: pydantic-2.11.7/tests/test_missing_sentinel.py
===================================================================
--- /dev/null
+++ pydantic-2.11.7/tests/test_missing_sentinel.py
@@ -0,0 +1,71 @@
+import pickle
+from typing import Union
+
+import pytest
+from pydantic_core import MISSING, PydanticSerializationUnexpectedValue
+
+from pydantic import BaseModel, TypeAdapter, ValidationError
+
+
+def test_missing_sentinel_model() -> None:
+ class Model(BaseModel):
+ f: Union[int, MISSING] = MISSING
+ g: MISSING = MISSING
+
+ m1 = Model()
+
+ assert m1.model_dump() == {}
+ assert m1.model_dump_json() == '{}'
+
+ m2 = Model.model_validate({'f': MISSING, 'g': MISSING})
+
+ assert m2.f is MISSING
+ assert m2.g is MISSING
+
+ m3 = Model(f=1)
+
+ assert m3.model_dump() == {'f': 1}
+ assert m3.model_dump_json() == '{"f":1}'
+
+
+def test_missing_sentinel_type_adapter() -> None:
+ """Note that this usage isn't explicitly supported (and useless in practice)."""
+
+ # TODO Remove annotation with PEP 747:
+ ta: TypeAdapter[object] = TypeAdapter(MISSING)
+
+ assert ta.validate_python(MISSING) is MISSING
+
+ with pytest.raises(ValidationError) as exc_info:
+ ta.validate_python(1)
+
+ assert exc_info.value.errors()[0]['type'] == 'missing_sentinel_error'
+
+ assert ta.dump_python(MISSING) is MISSING
+
+ with pytest.raises(PydanticSerializationUnexpectedValue):
+ ta.dump_python(1)
+
+
+# Defined in module to be picklable:
+class ModelPickle(BaseModel):
+ f: Union[int, MISSING] = MISSING
+
+
+@pytest.mark.xfail(reason="PEP 661 sentinels aren't picklable yet in the experimental typing-extensions implementation")
+def test_missing_sentinel_pickle() -> None:
+ m = ModelPickle()
+ m_reconstructed = pickle.loads(pickle.dumps(m))
+
+ assert m_reconstructed.f is MISSING
+
+
+def test_missing_sentinel_json_schema() -> None:
+ class Model(BaseModel):
+ f: Union[int, MISSING] = MISSING
+ g: MISSING = MISSING
+ h: MISSING
+
+ assert Model.model_json_schema()['properties'] == {
+ 'f': {'title': 'F', 'type': 'integer'},
+ }