pydantic: FTBFS against python 3.15rc1

Maximiliano Curia <[email protected]>
Newsgroups gmane.linux.debian.devel.python
Message-ID <[email protected]>
Package: src:pydantic
Version: 2.13.4-3
User: [email protected]
Usertags: python3.15
Tags: patch

Hi!

While rebuilding the python related packages against the Python 3.15rc1
version we found that pydantic fails to build from source [1]. 

We found that the necessary fixes have recently been applied in the
upstream repository [2].

I applied the upstream fix in the sandbox [3] to be able to build the
packages that depend on pydantic, please consider applying the patch to
support the upcoming 3.15 version.

Happy hacking,

[1]: https://debusine.debian.net/debian/r-python-python3.15/artifact/4439432/
[2]: https://github.com/pydantic/pydantic/pull/13587
[3]: https://debusine.debian.net/debian/r-python-python3.15/

-- 
"Can you imagine what I would do if I could do all I can?" -- Sun Tzu
Saludos /\/\ /\ >< `/
fix-compat-with-base64.patch (text/x-diff, 6.4 KB)
commit b348cf0dce91e1cf1a3e918402e4bb8e4e6203b2
Author: Victorien <[email protected]>
Date:   Thu Aug 6 13:34:53 2026 +0200

    Add initial support for Python 3.15 (#13587)

Index: pydantic/pydantic/_internal/_model_construction.py
===================================================================
--- pydantic.orig/pydantic/_internal/_model_construction.py
+++ pydantic/pydantic/_internal/_model_construction.py
@@ -8,6 +8,7 @@ import typing
 import warnings
 import weakref
 from abc import ABCMeta
+from collections.abc import MutableMapping
 from functools import cache, partial, wraps
 from types import FunctionType
 from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, cast
@@ -812,7 +813,7 @@ class _PydanticWeakRef:
         return _PydanticWeakRef, (self(),)
 
 
-def build_lenient_weakvaluedict(d: dict[str, Any] | None) -> dict[str, Any] | None:
+def build_lenient_weakvaluedict(d: MutableMapping[str, Any] | None) -> dict[str, Any] | None:
     """Takes an input dictionary, and produces a new value that (invertibly) replaces the values with weakrefs.
 
     We can't just use a WeakValueDictionary because many types (including int, str, etc.) can't be stored as values
Index: pydantic/pydantic/_internal/_typing_extra.py
===================================================================
--- pydantic.orig/pydantic/_internal/_typing_extra.py
+++ pydantic/pydantic/_internal/_typing_extra.py
@@ -7,6 +7,7 @@ import re
 import sys
 import types
 import typing
+from collections.abc import MutableMapping
 from functools import partial
 from inspect import Signature, signature
 from typing import TYPE_CHECKING, Any, Callable, cast
@@ -212,7 +213,7 @@ typing_base: Any = typing._Final  # pyri
 ### Annotation evaluations functions:
 
 
-def parent_frame_namespace(*, parent_depth: int = 2, force: bool = False) -> dict[str, Any] | None:
+def parent_frame_namespace(*, parent_depth: int = 2, force: bool = False) -> MutableMapping[str, Any] | None:
     """Fetch the local namespace of the parent frame where this function is called.
 
     Using this function is mostly useful to resolve forward annotations pointing to members defined in a local namespace,
Index: pydantic/pydantic/types.py
===================================================================
--- pydantic.orig/pydantic/types.py
+++ pydantic/pydantic/types.py
@@ -5,6 +5,7 @@ from __future__ import annotations as _a
 import base64
 import dataclasses as _dataclasses
 import re
+import sys
 from collections.abc import Hashable, Iterator
 from datetime import date, datetime
 from decimal import Decimal
@@ -2434,6 +2435,9 @@ class Base64Encoder(EncoderProtocol):
         return 'base64'
 
 
+_urlsafe_translation = bytes.maketrans(b'+/', b'-_')
+
+
 class Base64UrlEncoder(EncoderProtocol):
     """URL-safe Base64 encoder."""
 
@@ -2448,7 +2452,15 @@ class Base64UrlEncoder(EncoderProtocol):
             The decoded data.
         """
         try:
-            return base64.urlsafe_b64decode(data)
+            if sys.version_info >= (3, 15):
+                # In Python >= 3.15, `urlsafe_b64decode()` doesn't require padded input anymore.
+                # It also raises a `FutureWarning` if '+' or '/' is found in input (and translates it
+                # to '-' and '_'). We can't have this warning raised while validating, so we do the translation
+                # ourselves.
+                data = data.translate(_urlsafe_translation)
+                return base64.urlsafe_b64decode(data, padded=True)
+            else:
+                return base64.urlsafe_b64decode(data)
         except ValueError as e:
             raise PydanticCustomError('base64_decode', "Base64 decoding error: '{error}'", {'error': str(e)})
 
@@ -2799,11 +2811,8 @@ Base64UrlBytes = Annotated[bytes, Encode
 """A bytes type that is encoded and decoded using the URL-safe base64 encoder.
 
 Note:
-    Under the hood, `Base64UrlBytes` use standard library `base64.urlsafe_b64encode` and `base64.urlsafe_b64decode`
-    functions.
-
-    As a result, the `Base64UrlBytes` type can be used to faithfully decode "vanilla" base64 data
-    (using `'+'` and `'/'`).
+    Under the hood, `Base64UrlBytes` uses the standard library [`base64.urlsafe_b64encode()`][base64.urlsafe_b64encode]
+    and [`base64.urlsafe_b64decode()`][base64.urlsafe_b64decode] functions.
 
 ```python
 from pydantic import Base64UrlBytes, BaseModel
@@ -2821,10 +2830,8 @@ Base64UrlStr = Annotated[str, EncodedStr
 """A str type that is encoded and decoded using the URL-safe base64 encoder.
 
 Note:
-    Under the hood, `Base64UrlStr` use standard library `base64.urlsafe_b64encode` and `base64.urlsafe_b64decode`
-    functions.
-
-    As a result, the `Base64UrlStr` type can be used to faithfully decode "vanilla" base64 data (using `'+'` and `'/'`).
+    Under the hood, `Base64UrlStr` uses the standard library [`base64.urlsafe_b64encode()`][base64.urlsafe_b64encode]
+    and [`base64.urlsafe_b64decode()`][base64.urlsafe_b64decode] functions.
 
 ```python
 from pydantic import Base64UrlStr, BaseModel
Index: pydantic/pyproject.toml
===================================================================
--- pydantic.orig/pyproject.toml
+++ pydantic/pyproject.toml
@@ -34,6 +34,7 @@ classifiers = [
     'Programming Language :: Python :: 3.12',
     'Programming Language :: Python :: 3.13',
     'Programming Language :: Python :: 3.14',
+    'Programming Language :: Python :: 3.15',
     'Intended Audience :: Developers',
     'Intended Audience :: Information Technology',
     'Operating System :: OS Independent',
@@ -117,7 +118,8 @@ testing-extra = [
     'devtools',
     # used in docs tests
     'sqlalchemy',
-    'pytest-memray; platform_python_implementation == "CPython" and platform_system != "Windows"',
+    'pytest-memray; platform_python_implementation == "CPython" and platform_system != "Windows" and python_version < "3.15"',
+    'memray; platform_python_implementation == "CPython" and platform_system != "Windows" and python_version < "3.15"',
 ]
 typechecking = [
     'mypy',
Index: pydantic/tests/test_generics.py
===================================================================
--- pydantic.orig/tests/test_generics.py
+++ pydantic/tests/test_generics.py
@@ -2295,6 +2295,7 @@ def test_parse_generic_json():
     }
 
 
[email protected](sys.version_info >= (3, 15), reason="memray doesn't yet support Python 3.15")
 def memray_limit_memory(limit):
     if '--memray' in sys.argv:
         return pytest.mark.limit_memory(limit)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.