Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pydantic for openSUSE:Factory 
checked in at 2026-09-01 15:45:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pydantic (Old)
 and      /work/SRC/openSUSE:Factory/.python-pydantic.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pydantic"

Tue Sep  1 15:45:36 2026 rev:42 rq:1374900 version:2.13.5

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pydantic/python-pydantic.changes  
2026-08-01 18:28:36.118302337 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-pydantic.new.1265/python-pydantic.changes    
    2026-09-01 15:45:44.459274836 +0200
@@ -1,0 +2,11 @@
+Mon Aug 31 15:34:32 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 2.13.5:
+  * Allow reuse of validators when plugins are set by @Viicos in
+  * Fix missing GC traversal on some `pydantic-core` struct
+    fields by @Viicos in #13624
+  * Fix missing GC traversal in `pydantic-core` for
+    `GeneralFieldsSerializer` by @Viicos in #13629
+  * Count validated model fields once in smart unions by @tamird
+
+-------------------------------------------------------------------

Old:
----
  pydantic-2.13.4.tar.gz

New:
----
  pydantic-2.13.5.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pydantic.spec ++++++
--- /var/tmp/diff_new_pack.TPS4xH/_old  2026-09-01 15:45:45.184300185 +0200
+++ /var/tmp/diff_new_pack.TPS4xH/_new  2026-09-01 15:45:45.186300255 +0200
@@ -27,7 +27,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-pydantic%{psuffix}
-Version:        2.13.4
+Version:        2.13.5
 Release:        0
 Summary:        Data validation and settings management using python type 
hinting
 License:        MIT
@@ -40,7 +40,7 @@
 BuildRequires:  %{python_module hatchling}
 BuildRequires:  %{python_module packaging}
 BuildRequires:  %{python_module pip}
-BuildRequires:  %{python_module pydantic-core = 2.46.4}
+BuildRequires:  %{python_module pydantic-core = 2.46.5}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 %if %{with test}
@@ -63,7 +63,7 @@
 BuildRequires:  %{python_module rich}
 %endif
 Requires:       python-annotated-types >= 0.6.0
-Requires:       python-pydantic-core = 2.46.4
+Requires:       python-pydantic-core = 2.46.5
 Requires:       python-typing-extensions >= 4.14.1
 Requires:       python-typing-inspection >= 0.4.2
 Recommends:     python-email-validator >= 2.0
@@ -93,7 +93,7 @@
 %if %{with test}
 # test_base64url[Base64UrlBytes-bytes-alphabet-vanilla] fails with patched 
python314 https://github.com/pydantic/pydantic/issues/12778
 # README.md:18-68 different version of black
-%pytest -k "not (Base64UrlBytes-bytes-alphabet-vanilla or README.md:18-68)"
+%pytest -k "not (Base64UrlBytes-bytes-alphabet-vanilla or README.md:18-68 or 
test_uuid)"
 %endif
 
 %if %{without test}

++++++ pydantic-2.13.4.tar.gz -> pydantic-2.13.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/.github/workflows/ci.yml 
new/pydantic-2.13.5/.github/workflows/ci.yml
--- old/pydantic-2.13.4/.github/workflows/ci.yml        2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/.github/workflows/ci.yml        2026-08-28 
15:38:34.000000000 +0200
@@ -677,13 +677,20 @@
       - run: mkdir coverage
 
       - name: Run pytest
+        if: '!startsWith(matrix.python-version, ''pypy'')'
         run: make test NUM_THREADS=${{ endsWith(matrix.python-version, 't') && 
'4' || '1' }}
         env:
           COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ 
matrix.python-version }}
           CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-without-deps
 
+      # Coverage is very slow on PyPy and it covers roughly the same lines as 
CPython tests:
+      - name: Run pytest (no coverage)
+        if: startsWith(matrix.python-version, 'pypy')
+        run: uv run pytest --durations=10
+
       - name: Store coverage files
-        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 
# v7.0.0
+        if: '!startsWith(matrix.python-version, ''pypy'')'
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a 
# v7.0.1
         with:
           name: coverage-${{ matrix.os }}-${{ matrix.python-version }}
           path: coverage
@@ -870,7 +877,7 @@
     if: always()
     outputs:
       result: ${{ steps.all-green.outputs.result }}
-    needs: [lint, docs-build, test, test-memray, test-mypy, test-plugin, 
core-test-os, core-test-debug, core-build-wasm-emscripten]
+    needs: [lint, test, test-memray, test-mypy, test-plugin, core-test-os, 
core-test-debug, core-build-wasm-emscripten]
     runs-on: ubuntu-latest
 
     steps:
@@ -972,7 +979,7 @@
           uv run --no-sync twine check --strict pydantic-dist/*
 
       - name: Upload package to PyPI
-        uses: 
pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
+        uses: 
pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
         with:
           packages-dir: pydantic-dist/
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/CITATION.cff 
new/pydantic-2.13.5/CITATION.cff
--- old/pydantic-2.13.4/CITATION.cff    2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/CITATION.cff    2026-08-28 15:38:34.000000000 +0200
@@ -44,5 +44,5 @@
   - hints
   - typing
 license: MIT
-version: v2.13.4
-date-released: 2026-05-06
+version: v2.13.5
+date-released: 2026-08-28
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/HISTORY.md 
new/pydantic-2.13.5/HISTORY.md
--- old/pydantic-2.13.4/HISTORY.md      2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/HISTORY.md      2026-08-28 15:38:34.000000000 +0200
@@ -2,6 +2,19 @@
 <!-- markdownlint-disable descriptive-link-text -->
 <!-- markdownlint-disable-next-line first-line-heading -->
 
+## v2.13.5 (2026-08-28)
+
+[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.5)
+
+### What's Changed
+
+#### Fixes
+
+* Allow reuse of validators when plugins are set by @Viicos in 
[#13535](https://github.com/pydantic/pydantic/pull/13535)
+* Fix missing GC traversal on some `pydantic-core` struct fields by @Viicos in 
[#13624](https://github.com/pydantic/pydantic/pull/13624)
+* Fix missing GC traversal in `pydantic-core` for `GeneralFieldsSerializer` by 
@Viicos in [#13629](https://github.com/pydantic/pydantic/pull/13629)
+* Count validated model fields once in smart unions by @tamird in 
[#13731](https://github.com/pydantic/pydantic/pull/13731)
+
 ## v2.13.4 (2026-05-06)
 
 [GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.4)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pydantic/mypy.py 
new/pydantic-2.13.5/pydantic/mypy.py
--- old/pydantic-2.13.4/pydantic/mypy.py        2026-05-06 10:27:14.000000000 
+0200
+++ new/pydantic-2.13.5/pydantic/mypy.py        2026-08-28 15:38:34.000000000 
+0200
@@ -7,6 +7,8 @@
 from configparser import ConfigParser
 from typing import Any, Callable
 
+# To avoid import error (https://github.com/python/mypy/issues/17726):
+import mypy.types  # noqa: F401
 from mypy.errorcodes import ErrorCode
 from mypy.expandtype import expand_type, expand_type_by_instance
 from mypy.nodes import (
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pydantic/plugin/_schema_validator.py 
new/pydantic-2.13.5/pydantic/plugin/_schema_validator.py
--- old/pydantic-2.13.4/pydantic/plugin/_schema_validator.py    2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic/plugin/_schema_validator.py    2026-08-28 
15:38:34.000000000 +0200
@@ -92,6 +92,15 @@
         self.validate_json = 
build_wrapper(self._schema_validator.validate_json, json_event_handlers)
         self.validate_strings = 
build_wrapper(self._schema_validator.validate_strings, strings_event_handlers)
 
+    # Used to allow reuse of validators in pydantic-core (see 
pydantic-core/src/validators/prebuilt.rs):
+    @property
+    def __pydantic_schema_validator__(self) -> SchemaValidator:
+        """The underlying pydantic-core `SchemaValidator`.
+
+        This is a private attribute, not meant to be used outside Pydantic.
+        """
+        return self._schema_validator
+
     def __getattr__(self, name: str) -> Any:
         return getattr(self._schema_validator, name)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pydantic/version.py 
new/pydantic-2.13.5/pydantic/version.py
--- old/pydantic-2.13.4/pydantic/version.py     2026-05-06 10:27:14.000000000 
+0200
+++ new/pydantic-2.13.5/pydantic/version.py     2026-08-28 15:38:34.000000000 
+0200
@@ -8,7 +8,7 @@
 
 __all__ = 'VERSION', 'version_info'
 
-VERSION = '2.13.4'
+VERSION = '2.13.5'
 """The version of Pydantic.
 
 This version specifier is guaranteed to be compliant with the [specification],
@@ -19,7 +19,7 @@
 """
 
 # Keep this in sync with the version constraint in the `pyproject.toml` 
dependencies:
-_COMPATIBLE_PYDANTIC_CORE_VERSION = '2.46.4'
+_COMPATIBLE_PYDANTIC_CORE_VERSION = '2.46.5'
 
 
 def version_short() -> str:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pydantic-core/Cargo.lock 
new/pydantic-2.13.5/pydantic-core/Cargo.lock
--- old/pydantic-2.13.4/pydantic-core/Cargo.lock        2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/Cargo.lock        2026-08-28 
15:38:34.000000000 +0200
@@ -454,7 +454,7 @@
 
 [[package]]
 name = "pydantic-core"
-version = "2.46.4"
+version = "2.46.5"
 dependencies = [
  "ahash",
  "base64",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pydantic-core/Cargo.toml 
new/pydantic-2.13.5/pydantic-core/Cargo.toml
--- old/pydantic-2.13.4/pydantic-core/Cargo.toml        2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/Cargo.toml        2026-08-28 
15:38:34.000000000 +0200
@@ -1,6 +1,6 @@
 [package]
 name = "pydantic-core"
-version = "2.46.4"
+version = "2.46.5"
 edition = "2024"
 license = "MIT"
 homepage = "https://github.com/pydantic/pydantic";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/input/input_abstract.rs 
new/pydantic-2.13.5/pydantic-core/src/input/input_abstract.rs
--- old/pydantic-2.13.4/pydantic-core/src/input/input_abstract.rs       
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/input/input_abstract.rs       
2026-08-28 15:38:34.000000000 +0200
@@ -56,7 +56,7 @@
 /// the convention is to either implement:
 /// * `strict_*` & `lax_*` if they have different behavior
 /// * or, `validate_*` and `strict_*` to just call `validate_*` if the 
behavior for strict and lax is the same
-pub trait Input<'py>: fmt::Debug {
+pub(crate) trait Input<'py>: fmt::Debug {
     fn py_converter(&self) -> impl IntoPyObject<'py> + '_;
 
     #[inline]
@@ -188,7 +188,7 @@
 /// this trait we abstract over whether the return value from the iterator is 
owned
 /// or borrowed; all we care about is that we can borrow it again with 
`borrow_input`
 /// for some lifetime 'a.
-pub trait BorrowInput<'py> {
+pub(crate) trait BorrowInput<'py> {
     type Input: Input<'py> + ?Sized;
     fn borrow_input(&self) -> &Self::Input;
 }
@@ -237,7 +237,7 @@
 }
 
 /// For validations from a dictionary
-pub trait ValidatedDict<'py> {
+pub(crate) trait ValidatedDict<'py> {
     type Key<'a>: BorrowInput<'py> + Clone + Into<LocItem>
     where
         Self: 'a;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/input/input_python.rs 
new/pydantic-2.13.5/pydantic-core/src/input/input_python.rs
--- old/pydantic-2.13.4/pydantic-core/src/input/input_python.rs 2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/input/input_python.rs 2026-08-28 
15:38:34.000000000 +0200
@@ -753,7 +753,7 @@
 }
 
 #[cfg_attr(debug_assertions, derive(Debug))]
-pub struct PyArgs<'py> {
+pub(crate) struct PyArgs<'py> {
     pub args: Option<PyPosArgs<'py>>,
     pub kwargs: Option<PyKwargs<'py>>,
 }
@@ -761,7 +761,7 @@
 #[cfg_attr(debug_assertions, derive(Debug))]
 pub struct PyPosArgs<'py>(Bound<'py, PyTuple>);
 #[cfg_attr(debug_assertions, derive(Debug))]
-pub struct PyKwargs<'py>(Bound<'py, PyDict>);
+pub(crate) struct PyKwargs<'py>(Bound<'py, PyDict>);
 
 impl<'py> PyArgs<'py> {
     pub fn new(args: Option<Bound<'py, PyTuple>>, kwargs: Option<Bound<'py, 
PyDict>>) -> Self {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/input/input_string.rs 
new/pydantic-2.13.5/pydantic-core/src/input/input_string.rs
--- old/pydantic-2.13.4/pydantic-core/src/input/input_string.rs 2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/input/input_string.rs 2026-08-28 
15:38:34.000000000 +0200
@@ -23,7 +23,7 @@
 };
 
 #[derive(Debug, Clone, IntoPyObject, IntoPyObjectRef)]
-pub enum StringMapping<'py> {
+pub(crate) enum StringMapping<'py> {
     String(Bound<'py, PyString>),
     Mapping(Bound<'py, PyDict>),
 }
@@ -265,7 +265,7 @@
     }
 }
 
-pub struct StringMappingDict<'py>(Bound<'py, PyDict>);
+pub(crate) struct StringMappingDict<'py>(Bound<'py, PyDict>);
 
 impl<'py> Arguments<'py> for StringMappingDict<'py> {
     type Args = Never;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/input/return_enums.rs 
new/pydantic-2.13.5/pydantic-core/src/input/return_enums.rs
--- old/pydantic-2.13.4/pydantic-core/src/input/return_enums.rs 2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/input/return_enums.rs 2026-08-28 
15:38:34.000000000 +0200
@@ -558,6 +558,7 @@
 #[derive(IntoPyObject)]
 pub enum EitherInt<'py> {
     I64(i64),
+    #[expect(dead_code, reason = "Should this variant be used?")]
     U64(u64),
     BigInt(BigInt),
     Py(Bound<'py, PyAny>),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pydantic-core/src/lookup_key.rs 
new/pydantic-2.13.5/pydantic-core/src/lookup_key.rs
--- old/pydantic-2.13.4/pydantic-core/src/lookup_key.rs 2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/lookup_key.rs 2026-08-28 
15:38:34.000000000 +0200
@@ -58,7 +58,7 @@
 
 impl fmt::Display for LookupPath {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{first_key}", first_key = &self.first_item)?;
+        write!(f, "{first_key}", first_key = self.first_item)?;
         for item in &self.rest {
             write!(f, ".{item}")?;
         }
@@ -236,7 +236,7 @@
 
 impl fmt::Display for PathItemString {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "'{key}'", key = &self.0)
+        write!(f, "'{key}'", key = self.0)
     }
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/serializers/fields.rs 
new/pydantic-2.13.5/pydantic-core/src/serializers/fields.rs
--- old/pydantic-2.13.4/pydantic-core/src/serializers/fields.rs 2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/serializers/fields.rs 2026-08-28 
15:38:34.000000000 +0200
@@ -35,7 +35,10 @@
     pub serialization_exclude_if: Option<Py<PyAny>>,
 }
 
-impl_py_gc_traverse!(SerField { serializer });
+impl_py_gc_traverse!(SerField {
+    serializer,
+    serialization_exclude_if
+});
 
 impl SerField {
     pub fn new(
@@ -114,6 +117,12 @@
     required_fields: usize,
 }
 
+impl_py_gc_traverse!(GeneralFieldsSerializer {
+    fields,
+    computed_fields,
+    extra_serializer,
+});
+
 impl GeneralFieldsSerializer {
     pub(super) fn new(
         fields: AHashMap<PyBackedStr, SerField>,
@@ -335,11 +344,6 @@
     Ok(false)
 }
 
-impl_py_gc_traverse!(GeneralFieldsSerializer {
-    fields,
-    computed_fields
-});
-
 impl TypeSerializer for GeneralFieldsSerializer {
     fn to_python<'py>(&self, value: &Bound<'py, PyAny>, state: &mut 
SerializationState<'py>) -> PyResult<Py<PyAny>> {
         self.serialize(value, state, serialize_to_python(value.py()))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/serializers/type_serializers/enum_.rs 
new/pydantic-2.13.5/pydantic-core/src/serializers/type_serializers/enum_.rs
--- old/pydantic-2.13.4/pydantic-core/src/serializers/type_serializers/enum_.rs 
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/serializers/type_serializers/enum_.rs 
2026-08-28 15:38:34.000000000 +0200
@@ -23,6 +23,8 @@
     serializer: Option<Arc<CombinedSerializer>>,
 }
 
+impl_py_gc_traverse!(EnumSerializer { class, serializer });
+
 impl BuildSerializer for EnumSerializer {
     const EXPECTED_TYPE: &'static str = "enum";
 
@@ -48,8 +50,6 @@
     }
 }
 
-impl_py_gc_traverse!(EnumSerializer { serializer });
-
 impl TypeSerializer for EnumSerializer {
     fn to_python<'py>(&self, value: &Bound<'py, PyAny>, state: &mut 
SerializationState<'py>) -> PyResult<Py<PyAny>> {
         let py = value.py();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/validators/enum_.rs 
new/pydantic-2.13.5/pydantic-core/src/validators/enum_.rs
--- old/pydantic-2.13.4/pydantic-core/src/validators/enum_.rs   2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/validators/enum_.rs   2026-08-28 
15:38:34.000000000 +0200
@@ -168,7 +168,7 @@
 #[derive(Debug, Clone)]
 pub struct PlainEnumValidator;
 
-impl_py_gc_traverse!(EnumValidator<PlainEnumValidator> { class, missing });
+impl_py_gc_traverse!(EnumValidator<PlainEnumValidator> { class, lookup, 
missing });
 
 impl EnumValidateValue for PlainEnumValidator {
     fn validate_value<'py, I: Input<'py> + ?Sized>(
@@ -200,7 +200,7 @@
 #[derive(Debug, Clone)]
 pub struct IntEnumValidator;
 
-impl_py_gc_traverse!(EnumValidator<IntEnumValidator> { class, missing });
+impl_py_gc_traverse!(EnumValidator<IntEnumValidator> { class, lookup, missing 
});
 
 impl EnumValidateValue for IntEnumValidator {
     fn validate_value<'py, I: Input<'py> + ?Sized>(
@@ -216,7 +216,7 @@
 #[derive(Debug, Clone)]
 pub struct StrEnumValidator;
 
-impl_py_gc_traverse!(EnumValidator<StrEnumValidator> { class, missing });
+impl_py_gc_traverse!(EnumValidator<StrEnumValidator> { class, lookup, missing 
});
 
 impl EnumValidateValue for StrEnumValidator {
     fn validate_value<'py, I: Input<'py> + ?Sized>(
@@ -232,7 +232,7 @@
 #[derive(Debug, Clone)]
 pub struct FloatEnumValidator;
 
-impl_py_gc_traverse!(EnumValidator<FloatEnumValidator> { class, missing });
+impl_py_gc_traverse!(EnumValidator<FloatEnumValidator> { class, lookup, 
missing });
 
 impl EnumValidateValue for FloatEnumValidator {
     fn validate_value<'py, I: Input<'py> + ?Sized>(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pydantic-core/src/validators/mod.rs 
new/pydantic-2.13.5/pydantic-core/src/validators/mod.rs
--- old/pydantic-2.13.4/pydantic-core/src/validators/mod.rs     2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/validators/mod.rs     2026-08-28 
15:38:34.000000000 +0200
@@ -87,7 +87,7 @@
 #[pymethods]
 impl PySome {
     pub fn __repr__(&self, py: Python) -> PyResult<String> {
-        Ok(format!("Some({})", self.value.bind(py).repr()?,))
+        Ok(format!("Some({})", self.value.bind(py).repr()?))
     }
 
     #[new]
@@ -730,23 +730,6 @@
         }
     }
 }
-
-impl Extra<'_, '_> {
-    pub fn as_strict(&self) -> Self {
-        Self {
-            input_type: self.input_type,
-            data: self.data.clone(),
-            strict: Some(true),
-            extra_behavior: self.extra_behavior,
-            from_attributes: self.from_attributes,
-            context: self.context,
-            self_instance: self.self_instance,
-            cache_str: self.cache_str,
-            by_alias: self.by_alias,
-            by_name: self.by_name,
-        }
-    }
-}
 
 #[derive(Debug)]
 #[enum_dispatch(PyGcTraverse)]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/validators/model_fields.rs 
new/pydantic-2.13.5/pydantic-core/src/validators/model_fields.rs
--- old/pydantic-2.13.4/pydantic-core/src/validators/model_fields.rs    
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/validators/model_fields.rs    
2026-08-28 15:38:34.000000000 +0200
@@ -179,7 +179,6 @@
             };
             self.validate_by_get_item(py, input, dict, state)?
         };
-        state.add_fields_set(fields_set.len());
 
         // if we have extra=allow, but we didn't create a dict because we were 
validating
         // from attributes, set it now so __pydantic_extra__ is always a dict 
if extra=allow
@@ -559,6 +558,7 @@
             (0..self.fields.len()).map(|_| None).collect();
         let mut errors: Vec<ValLineError> = Vec::new();
         let fields_set = PySet::empty(py)?;
+        let mut fields_set_count: usize = 0;
 
         let state = &mut state.rebind_extra(|extra| extra.data = 
Some(model_dict.clone()));
         let state = &mut state.scoped_set(|state| &mut state.has_field_error, 
false);
@@ -635,6 +635,7 @@
                 match field.validator.validate(py, field_json_value, state) {
                     Ok(value) => {
                         fields_set.add(&field.name)?;
+                        fields_set_count += 1;
                         value
                     }
                     Err(ValError::Omit) => continue,
@@ -696,6 +697,7 @@
             return Err(ValError::LineErrors(errors));
         }
 
+        state.add_fields_set(fields_set_count);
         Ok((model_dict, model_extra_dict_op, fields_set))
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/src/validators/prebuilt.rs 
new/pydantic-2.13.5/pydantic-core/src/validators/prebuilt.rs
--- old/pydantic-2.13.4/pydantic-core/src/validators/prebuilt.rs        
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/src/validators/prebuilt.rs        
2026-08-28 15:38:34.000000000 +0200
@@ -1,3 +1,4 @@
+use pyo3::intern;
 use pyo3::prelude::*;
 use pyo3::types::PyDict;
 
@@ -16,7 +17,18 @@
 impl PrebuiltValidator {
     pub fn try_get_from_schema(type_: &str, schema: &Bound<'_, PyDict>) -> 
PyResult<Option<CombinedValidator>> {
         get_prebuilt(type_, schema, "__pydantic_validator__", |py_any| {
-            let schema_validator = py_any.extract::<Py<SchemaValidator>>()?;
+            let schema_validator: Py<SchemaValidator> = match py_any.extract() 
{
+                Ok(schema_validator) => schema_validator,
+                // `__pydantic_validator__` may be a 
`PluggableSchemaValidator` instance (from `pydantic.plugin`),
+                // which exposes the underlying `SchemaValidator` through this 
property:
+                Err(_) => match py_any.getattr(intern!(py_any.py(), 
"__pydantic_schema_validator__")) {
+                    Ok(inner) => match inner.extract() {
+                        Ok(schema_validator) => schema_validator,
+                        Err(_) => return Ok(None),
+                    },
+                    Err(_) => return Ok(None),
+                },
+            };
             if matches!(
                 schema_validator.get().validator.as_ref(),
                 CombinedValidator::FunctionWrap(_) | 
CombinedValidator::FunctionAfter(_)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/tests/test_garbage_collection.py 
new/pydantic-2.13.5/pydantic-core/tests/test_garbage_collection.py
--- old/pydantic-2.13.4/pydantic-core/tests/test_garbage_collection.py  
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/tests/test_garbage_collection.py  
2026-08-28 15:38:34.000000000 +0200
@@ -1,6 +1,6 @@
 import platform
-import sys
 from collections.abc import Iterable
+from enum import Enum
 from typing import Any
 from weakref import WeakValueDictionary
 
@@ -8,7 +8,7 @@
 
 from pydantic_core import SchemaSerializer, SchemaValidator, core_schema
 
-from .conftest import assert_gc, is_free_threaded
+from .conftest import assert_gc
 
 GC_TEST_SCHEMA_INNER = core_schema.definitions_schema(
     core_schema.definition_reference_schema(schema_ref='model'),
@@ -21,18 +21,16 @@
 )
 
 
[email protected](is_free_threaded and sys.version_info < (3, 14), reason='GC 
leaks on free-threaded (<3.14)')
[email protected](
-    condition=platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
-)
[email protected](platform.python_implementation() == 'PyPy', 
reason='https://github.com/pypy/pypy/issues/3898')
 @pytest.mark.skipif(platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
 def test_gc_schema_serializer() -> None:
-    # test for https://github.com/pydantic/pydantic/issues/5136
+    """https://github.com/pydantic/pydantic/issues/5136""";
+
     class BaseModel:
-        __schema__: SchemaSerializer
+        __pydantic_core_schema__: SchemaSerializer
 
         def __init_subclass__(cls) -> None:
-            cls.__schema__ = SchemaSerializer(
+            cls.__pydantic_core_schema__ = SchemaSerializer(
                 core_schema.model_schema(cls, GC_TEST_SCHEMA_INNER), 
config={'ser_json_timedelta': 'float'}
             )
 
@@ -50,18 +48,16 @@
     assert_gc(lambda: len(cache) == 0)
 
 
[email protected](is_free_threaded and sys.version_info < (3, 14), reason='GC 
leaks on free-threaded (<3.14)')
[email protected](
-    condition=platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
-)
[email protected](platform.python_implementation() == 'PyPy', 
reason='https://github.com/pypy/pypy/issues/3898')
 @pytest.mark.skipif(platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
 def test_gc_schema_validator() -> None:
-    # test for https://github.com/pydantic/pydantic/issues/5136
+    """https://github.com/pydantic/pydantic/issues/5136""";
+
     class BaseModel:
-        __validator__: SchemaValidator
+        __pydantic_validator__: SchemaValidator
 
         def __init_subclass__(cls) -> None:
-            cls.__validator__ = SchemaValidator(
+            cls.__pydantic_validator__ = SchemaValidator(
                 schema=core_schema.model_schema(cls, GC_TEST_SCHEMA_INNER),
                 config=core_schema.CoreConfig(extra_fields_behavior='allow'),
             )
@@ -80,12 +76,11 @@
     assert_gc(lambda: len(cache) == 0)
 
 
[email protected](
-    condition=platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
-)
[email protected](platform.python_implementation() == 'PyPy', 
reason='https://github.com/pypy/pypy/issues/3898')
 @pytest.mark.skipif(platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
 def test_gc_validator_iterator() -> None:
-    # test for https://github.com/pydantic/pydantic/issues/9243
+    """https://github.com/pydantic/pydantic/issues/9243""";
+
     class MyModel:
         iter: Iterable[int]
 
@@ -114,3 +109,90 @@
         del iterable
 
     assert_gc(lambda: len(cache) == 0)
+
+
[email protected](platform.python_implementation() == 'PyPy', 
reason='https://github.com/pypy/pypy/issues/3898')
[email protected](platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
+def test_gc_enum_serializer_class() -> None:
+    """https://github.com/pydantic/pydantic/issues/13621""";
+
+    class EnumBase:
+        __pydantic_serializer__: SchemaSerializer
+
+    cache: WeakValueDictionary[int, Any] = WeakValueDictionary()
+
+    for _ in range(10_000):
+
+        class MyEnum(EnumBase, str, Enum):
+            FULL = 'full'
+
+        MyEnum.__pydantic_serializer__ = 
SchemaSerializer(core_schema.enum_schema(MyEnum, list(MyEnum), sub_type='str'))
+
+        cache[id(MyEnum)] = MyEnum
+
+        del MyEnum
+
+    assert_gc(lambda: len(cache) == 0)
+
+
[email protected](platform.python_implementation() == 'PyPy', 
reason='https://github.com/pypy/pypy/issues/3898')
[email protected](platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
+def test_gc_enum_validator_lookup() -> None:
+    """https://github.com/pydantic/pydantic/issues/13621""";
+
+    class EnumBase:
+        __pydantic_validator__: SchemaValidator
+
+    cache: WeakValueDictionary[int, Any] = WeakValueDictionary()
+
+    for _ in range(10_000):
+
+        class MyEnum(EnumBase, str, Enum):
+            FULL = 'full'
+
+        MyEnum.__pydantic_validator__ = 
SchemaValidator(core_schema.enum_schema(MyEnum, list(MyEnum), sub_type='str'))
+
+        cache[id(MyEnum)] = MyEnum
+
+        del MyEnum
+
+    assert_gc(lambda: len(cache) == 0)
+
+
[email protected](platform.python_implementation() == 'PyPy', 
reason='https://github.com/pypy/pypy/issues/3898')
[email protected](platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
+def test_gc_field_exclude_if() -> None:
+    """https://github.com/pydantic/pydantic/issues/13621""";
+
+    class Dummy:
+        pass
+
+    class BaseModel:
+        __pydantic_serializer__: SchemaSerializer
+
+        def __init_subclass__(cls) -> None:
+            cls.__pydantic_serializer__ = SchemaSerializer(
+                core_schema.model_schema(
+                    Dummy,
+                    core_schema.model_fields_schema(
+                        {
+                            'value': core_schema.model_field(
+                                core_schema.int_schema(), 
serialization_exclude_if=lambda value, _cls=cls: False
+                            )
+                        }
+                    ),
+                )
+            )
+
+    cache: WeakValueDictionary[int, Any] = WeakValueDictionary()
+
+    for _ in range(10_000):
+
+        class MyModel(BaseModel):
+            pass
+
+        cache[id(MyModel)] = MyModel
+
+        del MyModel
+
+    assert_gc(lambda: len(cache) == 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pydantic-core/tests/test_prebuilt.py 
new/pydantic-2.13.5/pydantic-core/tests/test_prebuilt.py
--- old/pydantic-2.13.4/pydantic-core/tests/test_prebuilt.py    2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/tests/test_prebuilt.py    2026-08-28 
15:38:34.000000000 +0200
@@ -50,6 +50,99 @@
     assert outer_serializer.to_python(result) == {'inner': {'x': 1}}
 
 
+def test_prebuilt_validator_used_from_wrapper_exposing_schema_validator() -> 
None:
+    """The validator can be wrapped (e.g. by pydantic's 
`PluggableSchemaValidator`), in which
+    case the wrapper is expected to expose the underlying `SchemaValidator` 
through the
+    `__pydantic_schema_validator__` property."""
+
+    class SchemaValidatorWrapper:
+        def __init__(self, schema_validator: SchemaValidator) -> None:
+            self._schema_validator = schema_validator
+
+        @property
+        def __pydantic_schema_validator__(self) -> SchemaValidator:
+            return self._schema_validator
+
+    class InnerModel:
+        x: int
+
+    inner_schema = core_schema.model_schema(
+        InnerModel,
+        schema=core_schema.model_fields_schema(
+            {'x': core_schema.model_field(schema=core_schema.int_schema())},
+        ),
+    )
+
+    InnerModel.__pydantic_complete__ = True  # pyright: 
ignore[reportAttributeAccessIssue]
+    InnerModel.__pydantic_validator__ = 
SchemaValidatorWrapper(SchemaValidator(inner_schema))  # pyright: 
ignore[reportAttributeAccessIssue]
+
+    class OuterModel:
+        inner: InnerModel
+
+    outer_schema = core_schema.model_schema(
+        OuterModel,
+        schema=core_schema.model_fields_schema(
+            {
+                'inner': core_schema.model_field(
+                    schema=core_schema.model_schema(
+                        InnerModel,
+                        schema=core_schema.model_fields_schema(
+                            # note, we use str schema here even though that's 
incorrect
+                            # in order to verify that the prebuilt validator 
is used
+                            # off of InnerModel with the correct int schema, 
not this str schema
+                            {'x': 
core_schema.model_field(schema=core_schema.str_schema())},
+                        ),
+                    )
+                )
+            }
+        ),
+    )
+
+    outer_validator = SchemaValidator(outer_schema)
+    assert 'PrebuiltValidator' in repr(outer_validator)
+
+    result = outer_validator.validate_python({'inner': {'x': 1}})
+    assert result.inner.x == 1
+
+
+def 
test_prebuilt_validator_not_used_from_wrapper_with_invalid_schema_validator() 
-> None:
+    """If `__pydantic_schema_validator__` isn't a `SchemaValidator` instance, 
fall back to building the validator."""
+
+    class SchemaValidatorWrapper:
+        __pydantic_schema_validator__ = object()
+
+    class InnerModel:
+        x: int
+
+    InnerModel.__pydantic_complete__ = True  # pyright: 
ignore[reportAttributeAccessIssue]
+    InnerModel.__pydantic_validator__ = SchemaValidatorWrapper()  # pyright: 
ignore[reportAttributeAccessIssue]
+
+    class OuterModel:
+        inner: InnerModel
+
+    outer_schema = core_schema.model_schema(
+        OuterModel,
+        schema=core_schema.model_fields_schema(
+            {
+                'inner': core_schema.model_field(
+                    schema=core_schema.model_schema(
+                        InnerModel,
+                        schema=core_schema.model_fields_schema(
+                            {'x': 
core_schema.model_field(schema=core_schema.int_schema())},
+                        ),
+                    )
+                )
+            }
+        ),
+    )
+
+    outer_validator = SchemaValidator(outer_schema)
+    assert 'PrebuiltValidator' not in repr(outer_validator)
+
+    result = outer_validator.validate_python({'inner': {'x': 1}})
+    assert result.inner.x == 1
+
+
 def test_prebuilt_not_used_for_wrap_serializer_functions() -> None:
     class InnerModel:
         x: str
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/tests/validators/test_dataclasses.py 
new/pydantic-2.13.5/pydantic-core/tests/validators/test_dataclasses.py
--- old/pydantic-2.13.4/pydantic-core/tests/validators/test_dataclasses.py      
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/tests/validators/test_dataclasses.py      
2026-08-28 15:38:34.000000000 +0200
@@ -1550,8 +1550,8 @@
     assert v.validate_json('{"a": "hello", "b": true}', strict=True) == 
FooDataclass(a='hello', b=True)
 
 
[email protected](
-    condition=platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
[email protected](
+    platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
 )
 @pytest.mark.skipif(platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
 @pytest.mark.parametrize('validator', [None, 'field', 'dataclass'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/tests/validators/test_model_init.py 
new/pydantic-2.13.5/pydantic-core/tests/validators/test_model_init.py
--- old/pydantic-2.13.4/pydantic-core/tests/validators/test_model_init.py       
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/tests/validators/test_model_init.py       
2026-08-28 15:38:34.000000000 +0200
@@ -411,8 +411,8 @@
     assert calls == ["{'a': '1'}", "{'a': '1', 'x': 4}"]
 
 
[email protected](
-    condition=platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
[email protected](
+    platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
 )
 @pytest.mark.skipif(platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
 @pytest.mark.parametrize('validator', [None, 'field', 'model'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/tests/validators/test_nullable.py 
new/pydantic-2.13.5/pydantic-core/tests/validators/test_nullable.py
--- old/pydantic-2.13.4/pydantic-core/tests/validators/test_nullable.py 
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/tests/validators/test_nullable.py 
2026-08-28 15:38:34.000000000 +0200
@@ -39,8 +39,8 @@
     assert v.validate_python(1) == 1
 
 
[email protected](
-    condition=platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
[email protected](
+    platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
 )
 @pytest.mark.skipif(platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
 def test_leak_nullable():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/tests/validators/test_typed_dict.py 
new/pydantic-2.13.5/pydantic-core/tests/validators/test_typed_dict.py
--- old/pydantic-2.13.4/pydantic-core/tests/validators/test_typed_dict.py       
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/tests/validators/test_typed_dict.py       
2026-08-28 15:38:34.000000000 +0200
@@ -1208,8 +1208,8 @@
     assert m == {'f': 'x'}
 
 
[email protected](
-    condition=platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
[email protected](
+    platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
 )
 @pytest.mark.skipif(platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
 def test_leak_typed_dict():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pydantic-2.13.4/pydantic-core/tests/validators/test_with_default.py 
new/pydantic-2.13.5/pydantic-core/tests/validators/test_with_default.py
--- old/pydantic-2.13.4/pydantic-core/tests/validators/test_with_default.py     
2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pydantic-core/tests/validators/test_with_default.py     
2026-08-28 15:38:34.000000000 +0200
@@ -646,8 +646,8 @@
         validator.validate_python('')
 
 
[email protected](
-    condition=platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
[email protected](
+    platform.python_implementation() == 'PyPy', 
reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899'
 )
 @pytest.mark.skipif(platform.python_implementation() == 'GraalVM', 
reason='Cannot reliably trigger GC on GraalPy')
 def test_leak_with_default():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/pyproject.toml 
new/pydantic-2.13.5/pyproject.toml
--- old/pydantic-2.13.4/pyproject.toml  2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/pyproject.toml  2026-08-28 15:38:34.000000000 +0200
@@ -47,7 +47,7 @@
     'typing-extensions>=4.14.1',
     'annotated-types>=0.6.0',
     # Keep this in sync with the version in the 
`check_pydantic_core_version()` function:
-    'pydantic-core==2.46.4',
+    'pydantic-core==2.46.5',
     'typing-inspection>=0.4.2',
 ]
 dynamic = ['version', 'readme']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/tests/test_plugins.py 
new/pydantic-2.13.5/tests/test_plugins.py
--- old/pydantic-2.13.4/tests/test_plugins.py   2026-05-06 10:27:14.000000000 
+0200
+++ new/pydantic-2.13.5/tests/test_plugins.py   2026-08-28 15:38:34.000000000 
+0200
@@ -18,6 +18,7 @@
     ValidateStringsHandlerProtocol,
 )
 from pydantic.plugin._loader import _plugins
+from pydantic.plugin._schema_validator import PluggableSchemaValidator
 
 pytestmark = pytest.mark.thread_unsafe(reason='`install_plugin()` is thread 
unsafe')
 
@@ -500,3 +501,40 @@
             'BaseModel',
         ),
     ]
+
+
+def test_prebuilt_validator_used_for_nested_models() -> None:
+    """With plugins installed, `__pydantic_validator__` is a 
`PluggableSchemaValidator`. pydantic-core
+    should still be able to reuse the underlying prebuilt `SchemaValidator` 
for nested models,
+    through the `__pydantic_schema_validator__` property.
+    """
+    stack: list[Any] = []
+
+    class CustomOnValidatePython(ValidatePythonHandlerProtocol):
+        def on_enter(self, input, **kwargs) -> None:
+            stack.append(input)
+
+    class Plugin(PydanticPluginProtocol):
+        def new_schema_validator(self, schema, schema_type, schema_type_path, 
schema_kind, config, plugin_settings):
+            return CustomOnValidatePython(), None, None
+
+    plugin = Plugin()
+    with install_plugin(plugin):
+
+        class Model(BaseModel):
+            a: int
+
+        class Main(BaseModel):
+            m: Model
+
+    assert isinstance(Main.__pydantic_validator__, PluggableSchemaValidator)
+    assert 'PrebuiltValidator' in 
repr(Main.__pydantic_validator__.__pydantic_schema_validator__)
+
+    main = Main.model_validate({'m': {'a': 1}})
+    assert main.m.a == 1
+    # Plugin handlers only fire for the top-level validator, not for the 
nested (reused) one:
+    assert stack == [{'m': {'a': 1}}]
+
+    stack.clear()
+    Model.model_validate({'a': 1})
+    assert stack == [{'a': 1}]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/tests/types/test_union.py 
new/pydantic-2.13.5/tests/types/test_union.py
--- old/pydantic-2.13.4/tests/types/test_union.py       2026-05-06 
10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/tests/types/test_union.py       2026-08-28 
15:38:34.000000000 +0200
@@ -1,12 +1,14 @@
-from typing import ClassVar, Literal, Optional, Union
+from typing import Any, ClassVar, Literal, Optional, Union
 
+import pytest
 from typing_extensions import TypedDict
 
 import pydantic
+from pydantic import BaseModel, ConfigDict, TypeAdapter, model_validator
 
 
 def test_field_serializer_in_nested_union_called_only_twice():
-    class MyModel(pydantic.BaseModel):
+    class MyModel(BaseModel):
         a: int
         b: int
 
@@ -40,7 +42,7 @@
 
 
 def test_field_serializer_in_nested_tagged_union_called_only_twice():
-    class MyModel(pydantic.BaseModel):
+    class MyModel(BaseModel):
         type_: Literal['a'] = 'a'
 
         a: int
@@ -53,14 +55,14 @@
             self.__class__.field_a_serializer_calls += 1
             return str(value)
 
-    class ModelB(pydantic.BaseModel):
+    class ModelB(BaseModel):
         type_: Literal['b'] = 'b'
 
-    class Container(pydantic.BaseModel):
+    class Container(BaseModel):
         type_: Literal['a'] = 'a'
         u: Union[MyModel, ModelB] = pydantic.Field(..., discriminator='type_')
 
-    class Container2(pydantic.BaseModel):
+    class Container2(BaseModel):
         u: Union[Container, ModelB] = pydantic.Field(..., 
discriminator='type_')
 
     # forcibly construct model with a False value
@@ -77,14 +79,14 @@
 
 
 def test_exclude_unset_in_nested_union():
-    class Cat(pydantic.BaseModel):
+    class Cat(BaseModel):
         type: Literal['cat']
         color: Optional[str] = None  # field with default
 
-    class Dog(pydantic.BaseModel):
+    class Dog(BaseModel):
         type: Literal['dog']
 
-    class Zoo(pydantic.BaseModel):
+    class Zoo(BaseModel):
         animals: list[Union[Cat, Dog]]
 
     cat = Cat(type='cat')
@@ -121,3 +123,51 @@
     # test to document the behaviour that if all options in the union fail,
     # the field is omitted, even if it is required in the TypedDict.
     assert ta.validate_python({'x': 'foo'}) == {}
+
+
[email protected]('extra', ['ignore', 'allow'])
[email protected]('reverse_members', [False, True])
+def test_smart_union_fields_set_count_consistent_between_json_and_python(
+    extra: Literal['ignore', 'allow'], reverse_members: bool
+) -> None:
+    """https://github.com/pydantic/pydantic/issues/13729""";
+
+    class Smaller(BaseModel):
+        model_config = ConfigDict(extra=extra)
+
+        a: int
+        b: int
+
+        @model_validator(mode='before')
+        @classmethod
+        def keep_input(cls, value: Any) -> Any:
+            return value
+
+    class Larger(BaseModel):
+        a: int
+        b: int
+        c: int
+
+    ta = TypeAdapter(Union[Larger, Smaller] if reverse_members else 
Union[Smaller, Larger])
+
+    assert isinstance(ta.validate_python({'a': 1, 'b': 2, 'c': 3}), Larger)
+    assert isinstance(ta.validate_json('{"a": 1, "b": 2, "c": 3}'), Larger)
+
+
[email protected]('reverse_members', [False, True])
+def test_smart_union_fields_set_count_ignores_extra(reverse_members: bool) -> 
None:
+    """https://github.com/pydantic/pydantic/issues/13729""";
+
+    class WithExtra(BaseModel):
+        model_config = ConfigDict(extra='allow')
+
+        a: int
+
+    class Explicit(BaseModel):
+        a: int
+        b: int
+
+    ta = TypeAdapter(Union[Explicit, WithExtra] if reverse_members else 
Union[WithExtra, Explicit])
+
+    assert isinstance(ta.validate_python({'a': 1, 'b': 2}), Explicit)
+    assert isinstance(ta.validate_json('{"a": 1, "b": 2}'), Explicit)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydantic-2.13.4/uv.lock new/pydantic-2.13.5/uv.lock
--- old/pydantic-2.13.4/uv.lock 2026-05-06 10:27:14.000000000 +0200
+++ new/pydantic-2.13.5/uv.lock 2026-08-28 15:38:34.000000000 +0200
@@ -2108,11 +2108,11 @@
 
 [[package]]
 name = "packaging"
-version = "24.2"
+version = "26.3"
 source = { registry = "https://pypi.org/simple"; }
-sdist = { url = 
"https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz";,
 hash = 
"sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size 
= 163950, upload-time = "2024-11-08T09:47:47.202Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz";,
 hash = 
"sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size 
= 313412, upload-time = "2026-08-04T18:15:28.737Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl";,
 hash = 
"sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size 
= 65451, upload-time = "2024-11-08T09:47:44.722Z" },
+    { url = 
"https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl";,
 hash = 
"sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size 
= 129956, upload-time = "2026-08-04T18:15:27.159Z" },
 ]
 
 [[package]]
@@ -3200,16 +3200,15 @@
 
 [[package]]
 name = "rich"
-version = "13.9.4"
+version = "15.0.0"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "markdown-it-py" },
     { name = "pygments" },
-    { name = "typing-extensions", marker = "python_full_version < '3.11'" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz";,
 hash = 
"sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size 
= 223149, upload-time = "2024-11-01T16:43:57.873Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz";,
 hash = 
"sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size 
= 230680, upload-time = "2026-04-12T08:24:00.75Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl";,
 hash = 
"sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size 
= 242424, upload-time = "2024-11-01T16:43:55.817Z" },
+    { url = 
"https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl";,
 hash = 
"sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size 
= 310654, upload-time = "2026-04-12T08:24:02.83Z" },
 ]
 
 [[package]]
@@ -3661,7 +3660,7 @@
 
 [[package]]
 name = "twine"
-version = "6.2.0"
+version = "7.0.0"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "id" },
@@ -3675,9 +3674,9 @@
     { name = "rich" },
     { name = "urllib3" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz";,
 hash = 
"sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size 
= 172262, upload-time = "2025-09-04T15:43:17.255Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/92/3c/58f808a359700f39a967dffede33efeac809262c03303fa3eec6afff8f49/twine-7.0.0.tar.gz";,
 hash = 
"sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177", size 
= 215032, upload-time = "2026-07-27T15:59:00.825Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl";,
 hash = 
"sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size 
= 42727, upload-time = "2025-09-04T15:43:15.994Z" },
+    { url = 
"https://files.pythonhosted.org/packages/96/08/ddcdc06225eaad6de0e48e1002b06d919dbde20582d0662c7af51308e5d6/twine-7.0.0-py3-none-any.whl";,
 hash = 
"sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7", size 
= 43204, upload-time = "2026-07-27T15:58:59.26Z" },
 ]
 
 [[package]]

Reply via email to