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'},
+    }
openSUSE Build Service is sponsored by