Package: src:cython
Version: 3.1.6+dfsg-3
User: [email protected]
Usertags: python3.15

Hi!

While rebuilding packages against python 3.15 we found that cython
produces some errors when used against it. Some of this caught by the
autopkgtest
(https://debusine.debian.net/debian/r-python-python3.15/work-request/972899/)
.

I'm attaching a debdiff that adds the upstream fixes related to python
3.15, these fix the build against python 3.15, these fixes are in the
history of the upcoming 3.3.0 release.

I'll also send a merge request for this changes to salsa which might be
easier to work with depending on your workflow.

Happy hacking,

--
"The most important thing in the programming language is the name. A language will not succeed without a good name. I have recently invented a very good name and now I am looking for a suitable language."
-- Donald Knuth
Saludos /\/\ /\ >< `/
diff -Nru cython-3.1.6+dfsg/debian/changelog cython-3.1.6+dfsg/debian/changelog
--- cython-3.1.6+dfsg/debian/changelog  2026-06-29 01:19:58.000000000 +0200
+++ cython-3.1.6+dfsg/debian/changelog  2026-08-06 12:49:22.000000000 +0200
@@ -1,3 +1,14 @@
+cython (3.1.6+dfsg-3.1) UNRELEASED; urgency=medium
+
+  * Non-maintainer upload.
+  * Backport upstream fixes for Python 3.15 support:
+    - py315-fix-tests.patch,
+    - py315-update-builtins.patch
+    - py315-array-declarations.patch
+    - py315-cpdef-enum-negatives.patch
+
+ -- Maximiliano Curia <[email protected]>  Thu, 06 Aug 2026 12:49:22 +0200
+
 cython (3.1.6+dfsg-3) unstable; urgency=medium
 
   * Team upload.
diff -Nru cython-3.1.6+dfsg/debian/patches/py315-array-declarations.patch 
cython-3.1.6+dfsg/debian/patches/py315-array-declarations.patch
--- cython-3.1.6+dfsg/debian/patches/py315-array-declarations.patch     
1970-01-01 01:00:00.000000000 +0100
+++ cython-3.1.6+dfsg/debian/patches/py315-array-declarations.patch     
2026-08-06 12:49:22.000000000 +0200
@@ -0,0 +1,54 @@
+From: da-woods <[email protected]>
+Date: Fri, 8 May 2026 16:30:31 +0100
+Subject: Adapt array module declarations to Python 3.15 changes
+
+In https://github.com/python/cpython/issues/148675 the format of the
+"arraydescr" struct was changed, which meant that our representation was
+outdated and crashed.
+
+The new format is partly compatible and we can provide help to use either e
+of the two formats, but user code that manually accesses the internals of
+the "arraydescr" needs to be adapted in order to make it work correctly.
+
+Origin: upstream, 
https://github.com/cython/cython/commit/a470483da2274ca272e70280fa27b5f7e90c9e09
+Bug: https://github.com/cython/cython/issues/7659
+Applied-Upstream: 3.2.9
+Last-Update: 2026-08-06
+---
+diff --git a/Cython/Includes/cpython/array.pxd 
b/Cython/Includes/cpython/array.pxd
+index b5e888089..de1f7d51e 100644
+--- a/Cython/Includes/cpython/array.pxd
++++ b/Cython/Includes/cpython/array.pxd
+@@ -70,7 +70,9 @@ cdef extern from *:  # Hard-coded utility code hack.
+     ctypedef object GETF(array a, Py_ssize_t ix)
+     ctypedef object SETF(array a, Py_ssize_t ix, object o)
+     ctypedef struct arraydescr:  # [object arraydescr]:
+-            char typecode
++            char typecode "typecode_char"  # backwards compatibility only
++            char typecode_char             # Python <= 3.14
++            char typecode_array[3]         # Python 3.15+
+             int itemsize
+             GETF getitem    # PyObject * (*getitem)(struct arrayobject *, 
Py_ssize_t);
+             SETF setitem    # int (*setitem)(struct arrayobject *, 
Py_ssize_t, PyObject *);
+diff --git a/Cython/Utility/arrayarray.h b/Cython/Utility/arrayarray.h
+index d410e66c8..507d97a7f 100644
+--- a/Cython/Utility/arrayarray.h
++++ b/Cython/Utility/arrayarray.h
+@@ -23,11 +23,16 @@
+ // below.  That's defined later because the appropriate get and set
+ // functions aren't visible yet.
+ typedef struct arraydescr {
+-    int typecode;
++    union {
++        char typecode_char;  // pre-3.15
++        char typecode_array[3]; // post-3.15
++    };
+     int itemsize;
+     PyObject * (*getitem)(struct arrayobject *, Py_ssize_t);
+     int (*setitem)(struct arrayobject *, Py_ssize_t, PyObject *);
++#if PY_VERSION_HEX <= 0x030F00a8
+     char *formats;
++#endif
+ } arraydescr;
+ 
+ 
diff -Nru cython-3.1.6+dfsg/debian/patches/py315-cpdef-enum-negatives.patch 
cython-3.1.6+dfsg/debian/patches/py315-cpdef-enum-negatives.patch
--- cython-3.1.6+dfsg/debian/patches/py315-cpdef-enum-negatives.patch   
1970-01-01 01:00:00.000000000 +0100
+++ cython-3.1.6+dfsg/debian/patches/py315-cpdef-enum-negatives.patch   
2026-08-06 12:49:22.000000000 +0200
@@ -0,0 +1,214 @@
+From: da-woods <[email protected]>
+Date: Mon, 20 Jul 2026 12:23:34 +0100
+Subject: Fix cpdef enums with negative values on Python 3.15 (#7703)
+
+Fixes #7701
+
+It seems like Python 3.15 has tightened the requirements for `IntFlag`
+and now converts negative numbers into positive numbers (because it
+doesn't make sense to do bitwise operations on negative numbers).
+
+We only really use `IntFlag` because people *might* want to do bitwise
+operations with wrapped enums and it gives them a slightly better
+experience. I've created a new enum-type that's is less strict than
+`IntEnum` and which does allow unknown values. However for simplicity
+I'm skipped all the `IntFlag` flag-like behaviour.
+
+Origin: upstream, 
https://github.com/cython/cython/commit/42830b85c8ae7489dac9d143f521e392d966d8c8
+Bug: https://github.com/cython/cython/issues/7701
+Last-Update: 2026-08-06
+---
+diff --git a/Cython/Compiler/Code.py b/Cython/Compiler/Code.py
+index 4dca40ffd..be131968d 100644
+--- a/Cython/Compiler/Code.py
++++ b/Cython/Compiler/Code.py
+@@ -562,7 +562,7 @@ class UtilityCodeBase(AbstractUtilityCode):
+         return cls(**kwargs)
+ 
+     @classmethod
+-    def load_cached(cls, utility_code_name, from_file, __cache={}):
++    def load_cached(cls, utility_code_name, from_file, *, __cache={}):
+         """
+         Calls .load(), but using a per-type cache based on utility name and 
file name.
+         """
+diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
+index 2f67d774c..f5a162224 100644
+--- a/Cython/Compiler/PyrexTypes.py
++++ b/Cython/Compiler/PyrexTypes.py
+@@ -4480,6 +4480,8 @@ class CppScopedEnumType(CType, EnumMixin):
+ 
+     def create_type_wrapper(self, env):
+         from .UtilityCode import CythonUtilityCode
++        env.use_utility_code(CythonUtilityCode.load_cached(
++            "CppScopedEnumBase", "CpdefEnums.pyx"))
+         rst = CythonUtilityCode.load(
+             "CppScopedEnumType", "CpdefEnums.pyx",
+             context={
+@@ -4595,6 +4597,8 @@ class CEnumType(CIntLike, CType, EnumMixin):
+         enum_to_pyint_func = self.to_py_function
+         self.to_py_function = old_to_py_function  # we don't actually want to 
overwrite this
+ 
++        env.use_utility_code(CythonUtilityCode.load_cached(
++            "EnumBase", "CpdefEnums.pyx"))
+         env.use_utility_code(CythonUtilityCode.load(
+             "EnumType", "CpdefEnums.pyx",
+             context={"name": self.name,
+diff --git a/Cython/Utility/CpdefEnums.pyx b/Cython/Utility/CpdefEnums.pyx
+index efd391f61..a69a78e84 100644
+--- a/Cython/Utility/CpdefEnums.pyx
++++ b/Cython/Utility/CpdefEnums.pyx
+@@ -1,46 +1,62 @@
+ #################### EnumBase ####################
+ 
+-cimport cython
+-
+ cdef extern from *:
+-    int PY_VERSION_HEX
++    object PyImport_Import(object)
++
++cdef object __Pyx_FlexibleEnumBase
++class __Pyx_FlexibleEnumBase(PyImport_Import("enum").IntEnum):
++    @classmethod
++    def _missing_(cls, value):
++        # This is a trimmed down version of "EnumFlag._missing_" with
++        # the flag-specific details removed.
++        pseudo_member = int.__new__(cls, value)
++        if not hasattr(pseudo_member, '_value_'):
++            pseudo_member._value_ = value
++        pseudo_member._name_ = None
++        # _value2member_map_ is an undocumented detail of enum so don't fail
++        # if we can't use it to cache pseudo-members
++        value2member_map = getattr(cls, '_value2member_map_', None)
++        if value2member_map is not None:
++            pseudo_member = value2member_map.setdefault(value, pseudo_member)
++        return pseudo_member
++
++    def __repr__(self):
++        if self._name_ is None:
++            # arbitrary value pseudo member
++            return f"<{self.__class__.__name__}: {self._value_!r}>"
++        return super().__repr__()
+ 
+-# @cython.internal
+-cdef object __Pyx_EnumBase
+-from enum import IntEnum as __Pyx_EnumBase
+-
+-cdef object __Pyx_FlagBase
+-from enum import IntFlag as __Pyx_FlagBase
+ 
+ #################### EnumType ####################
+-#@requires: EnumBase
++# requires EnumBase but this is done manually to avoid duplication
+ 
+ cdef extern from *:
+     object {{enum_to_pyint_func}}({{name}} value)
+ 
+-# create new IntFlag() - the assumption is that C enums are sufficiently 
commonly
+-# used as flags that this is the most appropriate base class
+-{{name}} = __Pyx_FlagBase('{{name}}', [
++
++# Create new IntFlag()-like enums:
++# the assumption is that C enums are sufficiently commonly
++# used as flags that this is the most appropriate base class.
++# On Python 3.15+ IntFlag doesn't accept negative numbers however.
++{{name}} = __Pyx_FlexibleEnumBase('{{name}}',  [
+     {{for item in items}}
+     ('{{item}}', {{enum_to_pyint_func}}({{item}})),
+     {{endfor}}
+     # Try to look up the module name dynamically if possible
+ ], module=globals().get("__module__", '{{static_modname}}'))
+ 
+-if PY_VERSION_HEX >= 0x030B0000:
+-    # Python 3.11 starts making the behaviour of flags stricter
+-    # (only including powers of 2 when iterating). Since we're using
+-    # "flag" because C enums *might* be used as flags, not because
+-    # we want strict flag behaviour, manually undo some of this.
+-    {{name}}._member_names_ = list({{name}}.__members__)
+-
+ {{if enum_doc is not None}}
+ {{name}}.__doc__ = {{ repr(enum_doc) }}
+ {{endif}}
+ 
+ 
++#################### CppScopedEnumBase ####################
++
++cdef object __Pyx_EnumBase
++from enum import IntEnum as __Pyx_EnumBase
++
+ #################### CppScopedEnumType ####################
+-#@requires: EnumBase
++# requires CppScopedEnumBase (but this is done manually to avoid duplication)
+ cdef dict __Pyx_globals = globals()
+ 
+ __Pyx_globals["{{name}}"] = __Pyx_EnumBase('{{name}}', [
+diff --git a/tests/run/cpdef_enums.pyx b/tests/run/cpdef_enums.pyx
+index 2cff7c2b5..8e6d700b9 100644
+--- a/tests/run/cpdef_enums.pyx
++++ b/tests/run/cpdef_enums.pyx
+@@ -141,6 +141,23 @@ cpdef enum CyDefinedHasDuplicates3:
+     CY_DUP3_B = 0
+     CY_DUP3_C  # = 1
+ 
++cdef extern from *:
++    """
++    enum ExternHasNegatives {
++        EX_NEG_A = -1,
++        EX_NEG_B = 1,
++        EX_NEG_C = 2
++    };
++    """
++    cpdef enum ExternHasNegatives:
++        EX_NEG_A
++        EX_NEG_B
++        EX_NEG_C
++
++cpdef enum CyDefinedHasNegatives:
++    CY_NEG_A = -1
++    CY_NEG_B = 1
++    CY_NEG_C = 2
+ 
+ def test_as_variable_from_cython():
+     """
+@@ -285,3 +302,42 @@ def test_special_attributes():
+         cpdefPyxDocLineEnum.__module__,
+         cpdefPyxDocLineEnum.__doc__,
+     )
++
++
++def test_extern_negatives(ExternHasNegatives val=ExternHasNegatives.EX_NEG_A):
++    """
++    >>> val = test_extern_negatives()
++    >>> type(val) == ExternHasNegatives
++    True
++    >>> val < 0
++    True
++
++    # It's still possible to do bitwise operations of numbers and convert 
them to/from Cython
++    # whether Cython chooses to represent it as an IntEnum or an IntFlag.
++    >>> test_extern_negatives(ExternHasNegatives.EX_NEG_B) == 1 == 
ExternHasNegatives.EX_NEG_B
++    True
++    >>> test_extern_negatives(3) == 3 == (ExternHasNegatives.EX_NEG_B | 
ExternHasNegatives.EX_NEG_C)
++    True
++    >>> test_extern_negatives(ExternHasNegatives.EX_NEG_A) == -1 == 
ExternHasNegatives.EX_NEG_A
++    True
++    """
++    return val
++
++def test_cy_negatives(CyDefinedHasNegatives 
val=CyDefinedHasNegatives.CY_NEG_A):
++    """
++    >>> val = test_cy_negatives()
++    >>> type(val) == CyDefinedHasNegatives
++    True
++    >>> val < 0
++    True
++
++    # It's still possible to do bitwise operations of numbers and convert 
them to/from Cython
++    # whether Cython chooses to represent it as an IntEnum or an IntFlag.
++    >>> test_cy_negatives(CyDefinedHasNegatives.CY_NEG_B) == 1 == 
CyDefinedHasNegatives.CY_NEG_B
++    True
++    >>> test_cy_negatives(3) == 3 == (CyDefinedHasNegatives.CY_NEG_B | 
CyDefinedHasNegatives.CY_NEG_C)
++    True
++    >>> test_cy_negatives(CyDefinedHasNegatives.CY_NEG_A) == -1 == 
CyDefinedHasNegatives.CY_NEG_A
++    True
++    """
++    return val
diff -Nru cython-3.1.6+dfsg/debian/patches/py315-fix-tests.patch 
cython-3.1.6+dfsg/debian/patches/py315-fix-tests.patch
--- cython-3.1.6+dfsg/debian/patches/py315-fix-tests.patch      1970-01-01 
01:00:00.000000000 +0100
+++ cython-3.1.6+dfsg/debian/patches/py315-fix-tests.patch      2026-08-06 
12:49:22.000000000 +0200
@@ -0,0 +1,93 @@
+From: Stefan Behnel <[email protected]>
+Date: Sat, 24 Jan 2026 20:33:29 +0100
+Subject: Fix tests in Py3.15
+
+Origin: upstream, 
https://github.com/cython/cython/commit/46ee38dd0ed8d5fe90fb31690e6e9a39ba1e2a4b
+Applied-Upstream: 3.2.9
+Last-Update: 2026-08-06
+---
+diff --git a/tests/run/strmethods.pyx b/tests/run/strmethods.pyx
+index 7714eb7f9..45c516c6d 100644
+--- a/tests/run/strmethods.pyx
++++ b/tests/run/strmethods.pyx
+@@ -152,9 +152,9 @@ def mod_format_tuple(*values):
+     """
+     >>> mod_format_tuple('sa') == 'abcsadef'  or  mod_format(format1, 'sa')
+     True
+-    >>> mod_format_tuple()
++    >>> mod_format_tuple()  # doctest: +ELLIPSIS
+     Traceback (most recent call last):
+-    TypeError: not enough arguments for format string
++    TypeError: not enough arguments for format string...
+     """
+     assert cython.typeof('abc%sdef' % values) == "str object", 
cython.typeof('abc%sdef' % values)
+     return 'abc%sdef' % values
+diff --git a/tests/run/test_unicode.pyx b/tests/run/test_unicode.pyx
+index 937039218..2139b7779 100644
+--- a/tests/run/test_unicode.pyx
++++ b/tests/run/test_unicode.pyx
+@@ -1446,11 +1446,43 @@ class UnicodeTest(CommonTest,
+         self.assertEqual('%X' % letter_m, '6D')
+         self.assertEqual('%o' % letter_m, '155')
+         self.assertEqual('%c' % letter_m, 'm')
+-        self.assertRaisesRegex(TypeError, '%x format: an integer is required, 
not float', operator.mod, '%x', 3.14),
+-        self.assertRaisesRegex(TypeError, '%X format: an integer is required, 
not float', operator.mod, '%X', 2.11),
+-        self.assertRaisesRegex(TypeError, '%o format: an integer is required, 
not float', operator.mod, '%o', 1.79),
+-        self.assertRaisesRegex(TypeError, '%x format: an integer is required, 
not PseudoFloat', operator.mod, '%x', pi),
+-        self.assertRaises(TypeError, operator.mod, '%c', pi),
++        """
++        # Error message differs in Py3.15+
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %x requires an integer, not float'):
++            '%x' % 3.14
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %X requires an integer, not float'):
++            '%X' % 2.11
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %o requires an integer, not float'):
++            '%o' % 1.79
++        with self.assertRaisesRegex(TypeError,
++                r'format argument: %x requires an integer, not 
.*\.PseudoFloat'):
++            '%x' % pi
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %x requires an integer, not complex'):
++            '%x' % 3j
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %X requires an integer, not complex'):
++            '%X' % 2j
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %o requires an integer, not complex'):
++            '%o' % 1j
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %u requires a real number, not complex'):
++            '%u' % 3j
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %i requires a real number, not complex'):
++            '%i' % 2j
++        with self.assertRaisesRegex(TypeError,
++                'format argument: %d requires a real number, not complex'):
++            '%d' % 1j
++        with self.assertRaisesRegex(TypeError,
++                r'format argument: %c requires an integer or a unicode 
character, '
++                r'not .*\.PseudoFloat'):
++            '%c' % pi
++        """
+ 
+     def test_formatting_with_enum(self):
+         # issue18780
+diff --git a/tests/run/unicodemethods.pyx b/tests/run/unicodemethods.pyx
+index f893a7100..7aa2317b0 100644
+--- a/tests/run/unicodemethods.pyx
++++ b/tests/run/unicodemethods.pyx
+@@ -581,9 +581,9 @@ def mod_format_tuple(*values):
+     """
+     >>> mod_format_tuple('sa') == 'abcsadef'  or  mod_format(format1, 'sa')
+     True
+-    >>> mod_format_tuple()
++    >>> mod_format_tuple()  # doctest: +ELLIPSIS
+     Traceback (most recent call last):
+-    TypeError: not enough arguments for format string
++    TypeError: not enough arguments for format string...
+     """
+     assert cython.typeof(u'abc%sdef' % values) == "str object", 
cython.typeof(u'abc%sdef' % values)
+     return u'abc%sdef' % values
diff -Nru cython-3.1.6+dfsg/debian/patches/py315-update-builtins.patch 
cython-3.1.6+dfsg/debian/patches/py315-update-builtins.patch
--- cython-3.1.6+dfsg/debian/patches/py315-update-builtins.patch        
1970-01-01 01:00:00.000000000 +0100
+++ cython-3.1.6+dfsg/debian/patches/py315-update-builtins.patch        
2026-08-06 12:49:22.000000000 +0200
@@ -0,0 +1,83 @@
+From: Stefan Behnel <[email protected]>
+Date: Wed, 29 Apr 2026 05:40:07 +0200
+Subject: Update builtins to Python 3.15b1
+
+ 736a2c360 Update builtins to Py3.15.0a6.
+ 42d13596c Update builtins to Py3.15.0a6+.
+ 414ef7834 Update builtins for Python 3.15b1.
+
+Origin: upstrea, 
https://github.com/cython/cython/commit/414ef78348a6aa1f3abd310ded388a10348b67c9
+Bug: https://github.com/cython/cython/issues/6405
+Applied-Upstream: 3.2.9
+Last-Update: 2026-08-06
+---
+diff --git a/Cython/Compiler/Code.py b/Cython/Compiler/Code.py
+index 4dca40ffd..b0960d5fe 100644
+--- a/Cython/Compiler/Code.py
++++ b/Cython/Compiler/Code.py
+@@ -54,7 +54,7 @@ basicsize_builtins_map = {
+ }
+ 
+ # Builtins as of Python version ...
+-KNOWN_PYTHON_BUILTINS_VERSION = (3, 13, 0, 'alpha', 5)
++KNOWN_PYTHON_BUILTINS_VERSION = (3, 15, 0, 'beta', 1)
+ KNOWN_PYTHON_BUILTINS = frozenset([
+     'ArithmeticError',
+     'AssertionError',
+@@ -84,6 +84,7 @@ KNOWN_PYTHON_BUILTINS = frozenset([
+     'FutureWarning',
+     'GeneratorExit',
+     'IOError',
++    'ImportCycleError',
+     'ImportError',
+     'ImportWarning',
+     '_IncompleteInputError',
+@@ -135,6 +136,7 @@ KNOWN_PYTHON_BUILTINS = frozenset([
+     'ZeroDivisionError',
+     '__build_class__',
+     '__debug__',
++    '__lazy_import__',
+     '__import__',
+     'abs',
+     'aiter',
+@@ -165,6 +167,7 @@ KNOWN_PYTHON_BUILTINS = frozenset([
+     'filter',
+     'float',
+     'format',
++    'frozendict',
+     'frozenset',
+     'getattr',
+     'globals',
+@@ -199,6 +202,7 @@ KNOWN_PYTHON_BUILTINS = frozenset([
+     'repr',
+     'reversed',
+     'round',
++    'sentinel',
+     'set',
+     'setattr',
+     'slice',
+@@ -216,6 +220,11 @@ KNOWN_PYTHON_BUILTINS = frozenset([
+ uncachable_builtins = [
+     # Global/builtin names that cannot be cached because they may or may not
+     # be available at import time, for various reasons:
++    ## Python 3.15+
++    'frozendict',
++    'sentinel',
++    'ImportCycleError',
++    '__lazy_import__',
+     ## Python 3.13+
+     '_IncompleteInputError',
+     'PythonFinalizationError',
+@@ -226,11 +235,10 @@ uncachable_builtins = [
+     'aiter',
+     'anext',
+     'EncodingWarning',
+-    ## - Py3.7+
+-    'breakpoint',  # might deserve an implementation in Cython
+     ## - platform specific
+     'WindowsError',
+     ## - others
++    'breakpoint',  # Probably best left alone.
+     '_',  # e.g. used by gettext
+ ]
+ 
diff -Nru cython-3.1.6+dfsg/debian/patches/series 
cython-3.1.6+dfsg/debian/patches/series
--- cython-3.1.6+dfsg/debian/patches/series     2026-06-28 22:15:28.000000000 
+0200
+++ cython-3.1.6+dfsg/debian/patches/series     2026-08-06 12:49:22.000000000 
+0200
@@ -1,3 +1,7 @@
 honour_SOURCE_DATE_EPOCH_for_copyright_year
 debup_workaround_verify_resolution_GH1533
 disable_tests.patch
+py315-fix-tests.patch
+py315-update-builtins.patch
+py315-array-declarations.patch
+py315-cpdef-enum-negatives.patch

Attachment: signature.asc
Description: PGP signature

Reply via email to