diff --git a/python-pydantic.changes b/python-pydantic.changes index b305261..b0a2640 100644 --- a/python-pydantic.changes +++ b/python-pydantic.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Fri Aug 15 04:23:15 UTC 2025 - Steve Kowalik + +- Add patch support-pydantic-core-2.39.0.patch: + * Support pydantic-core 2.39.0. + ------------------------------------------------------------------- Mon Jun 23 05:56:23 UTC 2025 - Daniel Garcia diff --git a/python-pydantic.spec b/python-pydantic.spec index 6d98bcd..ba69218 100644 --- a/python-pydantic.spec +++ b/python-pydantic.spec @@ -1,7 +1,7 @@ # # spec file for package python-pydantic # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2025 SUSE LLC and contributors # Copyright (c) 2019, Martin Hauke # # All modifications and additions to the file contributed by third parties @@ -37,11 +37,13 @@ Source: https://github.com/pydantic/pydantic/archive/v%{version}.tar.gz# Patch0: bump-pydantic-core-2.35.1.patch # PATCH-FIX-UPSTREAM field-name-validator-core-schemas.patch gh#pydantic/pydantic#11761 Patch1: field-name-validator-core-schemas.patch +# PATCH-FIX-UPSTREAM Based on gh#pydantic/pydantic#11883 +Patch2: support-pydantic-core-2.39.0.patch BuildRequires: %{python_module hatch-fancy-pypi-readme} BuildRequires: %{python_module hatchling} BuildRequires: %{python_module packaging} BuildRequires: %{python_module pip} -BuildRequires: %{python_module pydantic-core = 2.35.1} +BuildRequires: %{python_module pydantic-core = 2.39.0} BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros @@ -62,10 +64,7 @@ BuildRequires: %{python_module rich} BuildRequires: %{python_module typing-inspection} %endif Requires: python-annotated-types >= 0.4.0 -%if 0%{?python_version_nodots} < 310 -Requires: python-eval-type-backport -%endif -Requires: python-pydantic-core = 2.35.1 +Requires: python-pydantic-core = 2.39.0 Requires: python-typing-extensions >= 4.12.2 Requires: python-typing-inspection BuildArch: noarch diff --git a/support-pydantic-core-2.39.0.patch b/support-pydantic-core-2.39.0.patch new file mode 100644 index 0000000..c2e2d12 --- /dev/null +++ b/support-pydantic-core-2.39.0.patch @@ -0,0 +1,293 @@ +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'}, ++ }