user [email protected]
usertags 1123355 python3.15
tags 1123355 patch
thanks

Hi!

While rebuilding the python related packages against the Python 3.15rc2
version I ran into this issue [1].

I have picked a few patches from upstream that take care of the problem:
 - Upstream commit 886bc9d: Use curframe.f_locals instead of
   curframe_locals in debugger for newer Python versions [2]
 - Upstream commit 155fa01: Fix test_functions_with_locals_in_pdb in
   Python 3.14 [3]

Additionally, I had to add one patch of my own, overriding the get_trace
function to workaround a recent change in the pdb behaviour. This patch
should be shared with upstream in
https://github.com/spyder-ide/spyder-kernels/issues/583

And finally, the autopkgtest test named pytest had a hardcoded
PYS=python3.13, replacing the use of py3version -s, not sure why that
was there, but since we are removing python3.13 from the archive I
assume that you don't want it anymore. Using the original py3versions -s
call, the autopkgtest is green again.

I've applied these fixes in the sandbox [4] to verify that it builds
successfully, please consider applying them to support the upcoming 3.15
version.

Happy hacking,

[1]: https://debusine.debian.net/debian/r-python-python3.15/artifact/4589710/
[2]: 
https://github.com/spyder-ide/spyder-kernels/commit/886bc9d2c613f96ea9b323ee4a1f7e827c29cc00
[3]: 
https://github.com/spyder-ide/spyder-kernels/commit/155fa010d96a3436c575cefb0ea6eb313e1c280c
[4]: https://debusine.debian.net/debian/r-python-python3.15/

--
"UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity."
-- Dennis Ritchie
Saludos /\/\ /\ >< `/
From cffff55b9190d23a71d33122eec8ab9d611d26e1 Mon Sep 17 00:00:00 2001
From: "Lumberbot (aka Jack)"
 <[email protected]>
Date: Mon, 8 Jun 2026 01:34:00 +0200
Subject: [PATCH] Backport PR #591 on branch 3.x (PR: Use generic args/kwargs
 in `SpyderPdb` init method) (#592)

---
 spyder_kernels/console/tests/test_console_kernel.py | 15 ++++++++++++---
 spyder_kernels/customize/spyderpdb.py              |  3 +--
 2 files changed, 13 insertions(+), 5 deletions(-)

Index: spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py
===================================================================
--- spyder-kernels.orig/spyder_kernels/console/tests/test_console_kernel.py
+++ spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py
@@ -1070,9 +1070,18 @@ def test_functions_with_locals_in_pdb(ke
     This is a regression test for spyder-ide/spyder-kernels#345
     """
     pdb_obj = SpyderPdb()
-    Frame = namedtuple("Frame", ["f_globals"])
-    pdb_obj.curframe = Frame(f_globals=kernel.shell.user_ns)
-    pdb_obj.curframe_locals = kernel.shell.user_ns
+
+    if sys.version_info[:2] >=(3, 14):
+        Frame = namedtuple("Frame", ["f_globals", "f_locals"])
+        pdb_obj.curframe = Frame(
+            f_globals=kernel.shell.user_ns,
+            f_locals=kernel.shell.user_ns
+        )
+    else:
+        Frame = namedtuple("Frame", ["f_globals"])
+        pdb_obj.curframe = Frame(f_globals=kernel.shell.user_ns)
+        pdb_obj.curframe_locals = kernel.shell.user_ns
+
     kernel.shell._namespace_stack = [pdb_obj]
 
     # Create a local function.
Index: spyder-kernels/spyder_kernels/customize/spyderpdb.py
===================================================================
--- spyder-kernels.orig/spyder_kernels/customize/spyderpdb.py
+++ spyder-kernels/spyder_kernels/customize/spyderpdb.py
@@ -81,8 +81,7 @@ class SpyderPdb(ipyPdb):
      - Add completion to non-command code.
     """
 
-    def __init__(self, completekey='tab', stdin=None, stdout=None,
-                 skip=None, nosigint=False):
+    def __init__(self, *args, **kwargs):
         """Init Pdb."""
         self.curframe_locals = None
         # Only set to true when calling debugfile
From 886bc9d2c613f96ea9b323ee4a1f7e827c29cc00 Mon Sep 17 00:00:00 2001
From: Nowa Ammerlaan <[email protected]>
Date: Fri, 12 Jun 2026 16:56:09 +0200
Subject: [PATCH] spyderpdb.py: replace curframe_locals with curframe.f_locals

From the python 3.14 changelog:

The undocumented pdb.Pdb.curframe_locals attribute is now a deprecated
read-only property, which will be removed in a future version of
Python. The low overhead dynamic frame locals access added in Python
3.13 by PEP 667 means the frame locals cache reference previously
stored in this attribute is no longer needed. Derived debuggers should
access pdb.Pdb.curframe.f_locals directly in Python 3.13 and later
versions.

Signed-off-by: Nowa Ammerlaan <[email protected]>
---
 spyder_kernels/console/shell.py                    |  5 ++-
 .../console/tests/test_console_kernel.py          | 34 ++++++++++++++--------
 spyder_kernels/customize/spyderpdb.py             | 24 ++++++++++++---
 3 files changed, 46 insertions(+), 17 deletions(-)

Index: spyder-kernels/spyder_kernels/console/shell.py
===================================================================
--- spyder-kernels.orig/spyder_kernels/console/shell.py
+++ spyder-kernels/spyder_kernels/console/shell.py
@@ -297,7 +297,10 @@ class SpyderShell(ZMQInteractiveShell):
         for session in self._namespace_stack[::-1]:
             if isinstance(session, SpyderPdb) and session.curframe is not None:
                 if frame is None or frame == session.curframe:
-                    return session.curframe_locals
+                    if sys.version_info[:2] <= (3, 12):
+                        return session.curframe_locals
+                    else:
+                        return session.curframe.f_locals
             elif frame is None and isinstance(session, NamespaceManager):
                 return session.ns_locals
 
Index: spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py
===================================================================
--- spyder-kernels.orig/spyder_kernels/console/tests/test_console_kernel.py
+++ spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py
@@ -975,7 +975,8 @@ def test_comprehensions_with_locals_in_p
     """
     pdb_obj = SpyderPdb()
     pdb_obj.curframe = inspect.currentframe()
-    pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
     kernel.shell._namespace_stack = [pdb_obj]
 
     # Create a local variable.
@@ -991,7 +992,8 @@ def test_comprehensions_with_locals_in_p
     assert kernel.get_value('in_globals') is False
 
     pdb_obj.curframe = None
-    pdb_obj.curframe_locals = None
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = None
 
 
 def test_comprehensions_with_locals_in_pdb_2(kernel):
@@ -1002,7 +1004,8 @@ def test_comprehensions_with_locals_in_p
     """
     pdb_obj = SpyderPdb()
     pdb_obj.curframe = inspect.currentframe()
-    pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
     kernel.shell._namespace_stack = [pdb_obj]
 
     # Create a local variable.
@@ -1016,7 +1019,8 @@ def test_comprehensions_with_locals_in_p
     assert kernel.get_value('res') == [[(1, 3), (1, 4)], [(2, 3), (2, 4)]]
 
     pdb_obj.curframe = None
-    pdb_obj.curframe_locals = None
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = None
 
 
 def test_namespaces_in_pdb(kernel):
@@ -1028,7 +1032,8 @@ def test_namespaces_in_pdb(kernel):
     kernel.shell.user_ns["test"] = 0
     pdb_obj = SpyderPdb()
     pdb_obj.curframe = inspect.currentframe()
-    pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
     kernel.shell._namespace_stack = [pdb_obj]
 
     # Check adding something to globals works
@@ -1051,7 +1056,10 @@ def test_namespaces_in_pdb(kernel):
     assert not pdb_obj._error_occured
 
     # Test locals are visible
-    pdb_obj.curframe_locals["test4"] = 0
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals["test4"] = 0
+    else:
+        pdb_obj.curframe.f_locals["test4"] = 0
     pdb_obj.default("%timeit test4")
     assert not pdb_obj._error_occured
 
@@ -1060,7 +1068,8 @@ def test_namespaces_in_pdb(kernel):
     assert pdb_obj._error_occured
 
     pdb_obj.curframe = None
-    pdb_obj.curframe_locals = None
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = None
 
 
 def test_functions_with_locals_in_pdb(kernel):
@@ -1071,7 +1080,7 @@ def test_functions_with_locals_in_pdb(ke
     """
     pdb_obj = SpyderPdb()
 
-    if sys.version_info[:2] >=(3, 14):
+    if sys.version_info[:2] >= (3, 13):
         Frame = namedtuple("Frame", ["f_globals", "f_locals"])
         pdb_obj.curframe = Frame(
             f_globals=kernel.shell.user_ns,
@@ -1100,7 +1109,8 @@ def test_functions_with_locals_in_pdb(ke
     assert kernel.get_value('zz') == 1
 
     pdb_obj.curframe = None
-    pdb_obj.curframe_locals = None
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = None
 
 
 def test_functions_with_locals_in_pdb_2(kernel):
@@ -1112,7 +1122,8 @@ def test_functions_with_locals_in_pdb_2(
     baba = 1  # noqa
     pdb_obj = SpyderPdb()
     pdb_obj.curframe = inspect.currentframe()
-    pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
     kernel.shell._namespace_stack = [pdb_obj]
 
     # Create a local function.
@@ -1139,7 +1150,8 @@ def test_functions_with_locals_in_pdb_2(
     assert "baba" not in kernel.get_value('gg')
 
     pdb_obj.curframe = None
-    pdb_obj.curframe_locals = None
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = None
 
 
 def test_locals_globals_in_pdb(kernel):
@@ -1149,7 +1161,8 @@ def test_locals_globals_in_pdb(kernel):
     a = 1  # noqa
     pdb_obj = SpyderPdb()
     pdb_obj.curframe = inspect.currentframe()
-    pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
     kernel.shell._namespace_stack = [pdb_obj]
 
     assert kernel.get_value('a') == 1
@@ -1181,7 +1194,8 @@ def test_locals_globals_in_pdb(kernel):
     assert kernel.get_value('test') is True
 
     pdb_obj.curframe = None
-    pdb_obj.curframe_locals = None
+    if sys.version_info[:2] <= (3, 12):
+        pdb_obj.curframe_locals = None
 
 
 @pytest.mark.flaky(max_runs=3)
Index: spyder-kernels/spyder_kernels/customize/spyderpdb.py
===================================================================
--- spyder-kernels.orig/spyder_kernels/customize/spyderpdb.py
+++ spyder-kernels/spyder_kernels/customize/spyderpdb.py
@@ -83,7 +83,8 @@ class SpyderPdb(ipyPdb):
 
     def __init__(self, *args, **kwargs):
         """Init Pdb."""
-        self.curframe_locals = None
+        if sys.version_info[:2] <= (3, 12):
+            self.curframe_locals = None
         # Only set to true when calling debugfile
         self.continue_if_has_breakpoints = False
         self.pdb_ignore_lib = False
@@ -159,7 +160,10 @@ class SpyderPdb(ipyPdb):
             if cmd == "debug":
                 return self.do_debug(arg)
 
-        local_ns = self.curframe_locals
+        if sys.version_info[:2] >= (3, 13):
+            local_ns = self.curframe.f_locals
+        else:
+            local_ns = self.curframe_locals
         global_ns = self.curframe.f_globals
 
         if self.pdb_use_exclamation_mark:
@@ -424,12 +428,22 @@ class SpyderPdb(ipyPdb):
         if ipython_do_complete:
             # Make complete call with current frame
             if self.curframe:
-                if self.curframe_locals:
-                    Frame = namedtuple("Frame", ["f_locals", "f_globals"])
-                    frame = Frame(self.curframe_locals,
-                                  self.curframe.f_globals)
+                if sys.version_info[:2] >= (3, 13):
+                    if self.curframe.f_locals:
+                        Frame = namedtuple("Frame", ["f_locals", "f_globals"])
+                        frame = Frame(
+                            self.curframe.f_locals, self.curframe.f_globals
+                        )
+                    else:
+                        frame = self.curframe
                 else:
-                    frame = self.curframe
+                    if self.curframe_locals:
+                        Frame = namedtuple("Frame", ["f_locals", "f_globals"])
+                        frame = Frame(
+                            self.curframe_locals, self.curframe.f_globals
+                        )
+                    else:
+                        frame = self.curframe
                 self.shell.set_completer_frame(frame)
             result = await self.shell.kernel._do_complete(code, cursor_pos)
             # Reset frame
Subject: Allow set_trace to accept generic args and kwargs

In Python 3.14+, pdb.set_trace() calls set_trace(..., commands=commands).
Override set_trace in SpyderPdb to accept *args and **kwargs and forward to pdb.Pdb,
preventing a TypeError when called with commands=None.
---
 spyder_kernels/customize/spyderpdb.py | 10 ++++++++++
 1 file changed, 10 insertions(+)

Index: spyder-kernels/spyder_kernels/customize/spyderpdb.py
===================================================================
--- spyder-kernels.orig/spyder_kernels/customize/spyderpdb.py
+++ spyder-kernels/spyder_kernels/customize/spyderpdb.py
@@ -265,6 +265,16 @@ class SpyderPdb(ipyPdb):
         self.message("\nProgram interrupted. (Use 'cont' to resume).")
         self.set_step()
 
+    def set_trace(self, frame=None, *args, **kwargs):
+        """Start debugging from frame."""
+        if frame is None:
+            frame = sys._getframe().f_back
+        self.initial_frame = frame
+        try:
+            return super(ipyPdb, self).set_trace(frame, *args, **kwargs)
+        except TypeError:
+            return super(ipyPdb, self).set_trace(frame)
+
     def set_quit(self):
         """Register that debugger is not tracing."""
         self.shell.remove_pdb_session(self)

Attachment: signature.asc
Description: PGP signature

Reply via email to