Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-debugpy for openSUSE:Factory 
checked in at 2026-03-14 22:20:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-debugpy (Old)
 and      /work/SRC/openSUSE:Factory/.python-debugpy.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-debugpy"

Sat Mar 14 22:20:40 2026 rev:25 rq:1338675 version:1.8.20

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-debugpy/python-debugpy.changes    
2025-12-20 21:46:15.906627656 +0100
+++ /work/SRC/openSUSE:Factory/.python-debugpy.new.8177/python-debugpy.changes  
2026-03-14 22:21:07.005954712 +0100
@@ -1,0 +2,12 @@
+Wed Mar  4 22:04:15 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.8.20:
+  * annotate in 3.14 causing exceptions:
+  * Use remote_exec if available
+  * Support more architectures
+- update to 1.8.19:
+  * More fixes for
+    https://github.com/microsoft/debugpy/issues/1980. Spaces in
+    python files are handled now too.
+
+-------------------------------------------------------------------

Old:
----
  debugpy-1.8.18.tar.gz

New:
----
  debugpy-1.8.20.tar.gz

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

Other differences:
------------------
++++++ python-debugpy.spec ++++++
--- /var/tmp/diff_new_pack.tD5zQl/_old  2026-03-14 22:21:07.613979882 +0100
+++ /var/tmp/diff_new_pack.tD5zQl/_new  2026-03-14 22:21:07.617980047 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-debugpy
 #
-# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -27,7 +27,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-debugpy%{psuffix}
-Version:        1.8.18
+Version:        1.8.20
 Release:        0
 Summary:        An implementation of the Debug Adapter Protocol for Python
 License:        MIT

++++++ debugpy-1.8.18.tar.gz -> debugpy-1.8.20.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/.devcontainer/Dockerfile 
new/debugpy-1.8.20/.devcontainer/Dockerfile
--- old/debugpy-1.8.18/.devcontainer/Dockerfile 1970-01-01 01:00:00.000000000 
+0100
+++ new/debugpy-1.8.20/.devcontainer/Dockerfile 2026-01-28 01:58:21.000000000 
+0100
@@ -0,0 +1,11 @@
+FROM mcr.microsoft.com/devcontainers/python:3.14
+
+# Install GDB
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends gdb \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/*
+
+# Switch to non-root user and install Python packages
+USER vscode
+RUN python -m pip install black flake8 tox
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/.devcontainer/devcontainer.json 
new/debugpy-1.8.20/.devcontainer/devcontainer.json
--- old/debugpy-1.8.18/.devcontainer/devcontainer.json  1970-01-01 
01:00:00.000000000 +0100
+++ new/debugpy-1.8.20/.devcontainer/devcontainer.json  2026-01-28 
01:58:21.000000000 +0100
@@ -0,0 +1,12 @@
+{
+  "name": "debugpy Development",
+  "build": {
+    "dockerfile": "Dockerfile"
+  },
+  
+  "features": {
+    "ghcr.io/devcontainers/features/git:1": {}
+  },
+
+  "remoteUser": "vscode"
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/.gdn/.gdnsuppress 
new/debugpy-1.8.20/.gdn/.gdnsuppress
--- old/debugpy-1.8.18/.gdn/.gdnsuppress        1970-01-01 01:00:00.000000000 
+0100
+++ new/debugpy-1.8.20/.gdn/.gdnsuppress        2026-01-28 01:58:21.000000000 
+0100
@@ -0,0 +1,42 @@
+{
+  "hydrated": true,
+  "properties": {
+    "helpUri": 
"https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/suppressions";
+  },
+  "version": "1.0.0",
+  "suppressionSets": {
+    "default": {
+      "name": "default",
+      "createdDate": "2026-01-28 00:33:53Z",
+      "lastUpdatedDate": "2026-01-28 00:33:53Z"
+    }
+  },
+  "results": {
+    "58a87fd4d7371ece41c06d9d8ef960a90be375cf25621fdddc0f69ed70a8b6f4": {
+      "signature": 
"58a87fd4d7371ece41c06d9d8ef960a90be375cf25621fdddc0f69ed70a8b6f4",
+      "alternativeSignatures": [
+        "86f6ccbed316821fbcca7e2f0daafcc164b6df645a491d850e1ac16b39eb747e"
+      ],
+      "target": "pydevd/inject_dll_amd64.exe",
+      "memberOf": [
+        "default"
+      ],
+      "tool": "binskim",
+      "ruleId": "BA2007",
+      "createdDate": "2026-01-28 00:33:53Z"
+    },
+    "2b82071ec3bb1a8e80b1615d79febb69084008a4b5a19e66dbeac590b39d4c55": {
+      "signature": 
"2b82071ec3bb1a8e80b1615d79febb69084008a4b5a19e66dbeac590b39d4c55",
+      "alternativeSignatures": [
+        "3e5a1d789a42983ae1541ba91e20f10f388886a5ba62833ec66e0a358adc1174"
+      ],
+      "target": "pydevd/inject_dll_x86.exe",
+      "memberOf": [
+        "default"
+      ],
+      "tool": "binskim",
+      "ruleId": "BA2007",
+      "createdDate": "2026-01-28 00:33:53Z"
+    }
+  }
+}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/build_attach_binaries.py 
new/debugpy-1.8.20/build_attach_binaries.py
--- old/debugpy-1.8.18/build_attach_binaries.py 2025-12-10 19:39:27.000000000 
+0100
+++ new/debugpy-1.8.20/build_attach_binaries.py 2026-01-28 01:58:21.000000000 
+0100
@@ -2,6 +2,7 @@
 import argparse
 import os
 import platform
+import re
 
 def build_pydevd_binaries(force: bool):
     os.environ["PYDEVD_USE_CYTHON"] = "yes"
@@ -25,7 +26,12 @@
         if not os.path.exists(os.path.join(pydevd_attach_to_process_root, 
"attach_amd64.dll")) or force:
             os.system(os.path.join(pydevd_attach_to_process_root, "windows", 
"compile_windows.bat"))
     elif platform.system() == "Linux":
-        if not os.path.exists(os.path.join(pydevd_attach_to_process_root, 
"attach_linux_amd64.so")) or force:
+        arch = platform.machine()
+        if re.match(r'^i.*86$', arch):
+            arch = 'x86'
+        if arch == "x86_64":
+            arch = "amd64"
+        if not os.path.exists(os.path.join(pydevd_attach_to_process_root, 
f"attach_linux_{arch}.so")) or force:
             os.system(os.path.join(pydevd_attach_to_process_root, 
"linux_and_mac", "compile_linux.sh"))
     elif platform.system() == "Darwin":
         if not os.path.exists(os.path.join(pydevd_attach_to_process_root, 
"attach.dylib")) or force:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd.py 
new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd.py
--- old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd.py   2025-12-10 
19:39:27.000000000 +0100
+++ new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd.py   2026-01-28 
01:58:21.000000000 +0100
@@ -93,6 +93,7 @@
     PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING,
     PYDEVD_IPYTHON_CONTEXT,
     PYDEVD_USE_SYS_MONITORING,
+    IS_PY314_OR_GREATER,
 )
 from _pydevd_bundle.pydevd_defaults import PydevdCustomization  # Note: import 
alias used on pydev_monkey.
 from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, 
custom_frames_container_init
@@ -1292,6 +1293,16 @@
             if file_type == self.PYDEV_FILE:
                 cache[cache_key] = False
 
+            elif IS_PY314_OR_GREATER and frame.f_code.co_name == 
"__annotate__":
+                # Special handling for __annotate__ functions (PEP 649 in 
Python 3.14+).
+                # These are compiler-generated functions that can raise 
NotImplementedError
+                # when called with unsupported format arguments by 
inspect.call_annotate_function.
+                # They should be treated as library code to avoid false 
positives in exception handling.
+                # Note: PEP 649 reserves the __annotate__ name for 
compiler-generated functions,
+                # so user-defined functions with this name are discouraged and 
will also be treated
+                # as library code to maintain consistency with the language 
design.
+                cache[cache_key] = False
+
             elif absolute_filename == "<string>":
                 # Special handling for '<string>'
                 if file_type == self.LIB_FILE:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh
 
new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh
--- 
old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh
     2025-12-10 19:39:27.000000000 +0100
+++ 
new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh
     2026-01-28 01:58:21.000000000 +0100
@@ -4,7 +4,7 @@
 case $ARCH in
     i*86) SUFFIX=x86;;
     x86_64*) SUFFIX=amd64;;
-    *) echo >&2 "unsupported: $ARCH"; exit 1;;
+    *) echo >&2 "unsupported: $ARCH, this script may not work";;
 esac
 
 SRC="$(dirname "$0")/.."
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/windows/compile_windows.bat
 
new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/windows/compile_windows.bat
--- 
old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/windows/compile_windows.bat
        2025-12-10 19:39:27.000000000 +0100
+++ 
new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/windows/compile_windows.bat
        2026-01-28 01:58:21.000000000 +0100
@@ -10,29 +10,29 @@
                                  
 call "%VSDIR%\VC\Auxiliary\Build\vcvarsall.bat" x86 
-vcvars_spectre_libs=spectre
 
-cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /Qspectre attach.cpp /link 
/PROFILE /GUARD:CF /CETCOMPAT /out:attach_x86.dll
+cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /GL /Qspectre attach.cpp 
/link /LTCG /PROFILE /GUARD:CF /CETCOMPAT /out:attach_x86.dll
 copy attach_x86.dll ..\attach_x86.dll /Y
 copy attach_x86.pdb ..\attach_x86.pdb /Y
 
-cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /D BITS_32 /Qspectre 
run_code_on_dllmain.cpp /link /PROFILE /GUARD:CF /CETCOMPAT 
/out:run_code_on_dllmain_x86.dll
+cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /GL /D BITS_32 /Qspectre 
run_code_on_dllmain.cpp /link /LTCG /PROFILE /GUARD:CF /CETCOMPAT 
/out:run_code_on_dllmain_x86.dll
 copy run_code_on_dllmain_x86.dll ..\run_code_on_dllmain_x86.dll /Y
 copy run_code_on_dllmain_x86.pdb ..\run_code_on_dllmain_x86.pdb /Y
 
-cl /EHsc /Zi /O1 /W3 /Qspectre inject_dll.cpp /link /PROFILE /GUARD:CF 
/CETCOMPAT /out:inject_dll_x86.exe
+cl /EHsc /Zi /O1 /W3 /GL /Qspectre inject_dll.cpp /link /LTCG /PROFILE 
/GUARD:CF /CETCOMPAT /out:inject_dll_x86.exe
 copy inject_dll_x86.exe ..\inject_dll_x86.exe /Y
 copy inject_dll_x86.pdb ..\inject_dll_x86.pdb /Y
 
 call "%VSDIR%\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 
-vcvars_spectre_libs=spectre
 
-cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /Qspectre attach.cpp /link 
/PROFILE /GUARD:CF /CETCOMPAT /out:attach_amd64.dll
+cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /GL /Qspectre attach.cpp 
/link /LTCG /PROFILE /GUARD:CF /CETCOMPAT /out:attach_amd64.dll
 copy attach_amd64.dll ..\attach_amd64.dll /Y
 copy attach_amd64.pdb ..\attach_amd64.pdb /Y
 
-cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /D BITS_64 /Qspectre 
run_code_on_dllmain.cpp /link /PROFILE /GUARD:CF /CETCOMPAT 
/out:run_code_on_dllmain_amd64.dll
+cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /GL /D BITS_64 /Qspectre 
run_code_on_dllmain.cpp /link /LTCG /PROFILE /GUARD:CF /CETCOMPAT 
/out:run_code_on_dllmain_amd64.dll
 copy run_code_on_dllmain_amd64.dll ..\run_code_on_dllmain_amd64.dll /Y
 copy run_code_on_dllmain_amd64.pdb ..\run_code_on_dllmain_amd64.pdb /Y
 
-cl /EHsc /Zi /O1 /W3 /Qspectre inject_dll.cpp /link /PROFILE /GUARD:CF 
/CETCOMPAT /out:inject_dll_amd64.exe
+cl /EHsc /Zi /O1 /W3 /GL /Qspectre inject_dll.cpp /link /LTCG /PROFILE 
/GUARD:CF /CETCOMPAT /out:inject_dll_amd64.exe
 copy inject_dll_amd64.exe ..\inject_dll_amd64.exe /Y
 copy inject_dll_amd64.pdb ..\inject_dll_amd64.pdb /Y
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/_version.py 
new/debugpy-1.8.20/src/debugpy/_version.py
--- old/debugpy-1.8.18/src/debugpy/_version.py  2025-12-10 19:39:27.000000000 
+0100
+++ new/debugpy-1.8.20/src/debugpy/_version.py  2026-01-28 01:58:21.000000000 
+0100
@@ -25,9 +25,9 @@
     # setup.py/versioneer.py will grep for the variable names, so they must
     # each be defined on a line of their own. _version.py will just call
     # get_keywords().
-    git_refnames = " (HEAD -> main, tag: v1.8.18)"
-    git_full = "e5017d736052d8d84484cdfe05750bd61cc7c50f"
-    git_date = "2025-12-10 10:39:27 -0800"
+    git_refnames = " (tag: v1.8.20)"
+    git_full = "7ac3d1f2c65ccf6b2e62a762863093f0242179f0"
+    git_date = "2026-01-27 16:58:21 -0800"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/adapter/launchers.py 
new/debugpy-1.8.20/src/debugpy/adapter/launchers.py
--- old/debugpy-1.8.18/src/debugpy/adapter/launchers.py 2025-12-10 
19:39:27.000000000 +0100
+++ new/debugpy-1.8.20/src/debugpy/adapter/launchers.py 2026-01-28 
01:58:21.000000000 +0100
@@ -160,15 +160,16 @@
                 quote_char = arguments["terminalQuoteCharacter"] if 
"terminalQuoteCharacter" in arguments else default_quote
 
                 # VS code doesn't quote arguments if 
`argsCanBeInterpretedByShell` is true,
-                # so we need to do it ourselves for the arguments up to the 
call to the adapter.
+                # so we need to do it ourselves for the arguments up to the 
first argument passed to
+                # debugpy (this should be the python file to run).
                 args = request_args["args"]
                 for i in range(len(args)):
-                    if args[i] == "--":
-                        break
                     s = args[i]
                     if " " in s and not ((s.startswith('"') and 
s.endswith('"')) or (s.startswith("'") and s.endswith("'"))):
                         s = f"{quote_char}{s}{quote_char}"
                     args[i] = s
+                    if i > 0 and args[i-1] == "--":
+                        break
 
             try:
                 # It is unspecified whether this request receives a response 
immediately, or only
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/server/cli.py 
new/debugpy-1.8.20/src/debugpy/server/cli.py
--- old/debugpy-1.8.18/src/debugpy/server/cli.py        2025-12-10 
19:39:27.000000000 +0100
+++ new/debugpy-1.8.20/src/debugpy/server/cli.py        2026-01-28 
01:58:21.000000000 +0100
@@ -37,6 +37,7 @@
                [--log-to <path>] [--log-to-stderr]
                [--parent-session-pid <pid>]]
                [--adapter-access-token <token>]
+               [--disable-sys-remote-exec]]
                {1}
                [<arg>]...
 """.format(
@@ -56,6 +57,7 @@
     adapter_access_token = None
     config: Dict[str, Any] = {}
     parent_session_pid: Union[int, None] = None
+    disable_sys_remote_exec = False
 
 
 options = Options()
@@ -186,6 +188,7 @@
     ("--configure-.+",          "<value>",          set_config),
     ("--parent-session-pid",    "<pid>",            
set_arg("parent_session_pid", lambda x: int(x) if x else None)),
     ("--adapter-access-token",   "<token>",         
set_arg("adapter_access_token")),
+    ("--disable-sys-remote-exec", None,             
set_const("disable_sys_remote_exec", True)),
 
     # Targets. The "" entry corresponds to positional command line arguments,
     # i.e. the ones not preceded by any switch name.
@@ -447,6 +450,37 @@
         .replace("\n", "")
         .format(script_dir=script_dir, setup=setup)
     )
+
+    # attempt pep 768 style code injection
+    if (not options.disable_sys_remote_exec) and hasattr(sys, "remote_exec"):
+        tmp_file_path = ""
+        try:
+            import tempfile
+
+            with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
+                tmp_file_path = tmp_file.name
+                log.info(
+                    "Attempting to inject code at '{tmp_file_path}' using 
sys.remote_exec()",
+                    tmp_file_path=tmp_file_path,
+                )
+                tmp_file.write(python_code.encode())
+                tmp_file.write(
+                    """import os;os.remove("{tmp_file_path}");""".format(
+                        tmp_file_path=tmp_file_path
+                    ).encode()
+                )
+                tmp_file.flush()
+                tmp_file.close()
+                sys.remote_exec(pid, tmp_file_path)
+            return
+        except Exception as e:
+            if os.path.exists(tmp_file_path):
+                os.remove(tmp_file_path)
+            log.warning(
+                'Injecting code using sys.remote_exec() failed with 
error:\n"{e}"\nWill reattempt using pydevd.\n',
+                e=e,
+            )
+
     log.info("Code to be injected: \n{0}", python_code.replace(";", ";\n"))
 
     # pydevd restriction on characters in injected code.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/tests/debug/session.py 
new/debugpy-1.8.20/tests/debug/session.py
--- old/debugpy-1.8.18/tests/debug/session.py   2025-12-10 19:39:27.000000000 
+0100
+++ new/debugpy-1.8.20/tests/debug/session.py   2026-01-28 01:58:21.000000000 
+0100
@@ -594,25 +594,78 @@
 
     def run_in_terminal(self, args, cwd, env):
         exe = args.pop(0)
+        if getattr(self, "_run_in_terminal_args_can_be_interpreted_by_shell", 
False):
+            exe = self._shell_unquote(exe)
+            args = [self._shell_unquote(a) for a in args]
         self.spawn_debuggee.env.update(env)
         self.spawn_debuggee(args, cwd, exe=exe)
         return {}
 
+    @staticmethod
+    def _shell_unquote(s):
+        s = str(s)
+        if len(s) >= 2 and s[0] == s[-1] and s[0] in ("\"", "'"):
+            return s[1:-1]
+        return s
+
+    @classmethod
+    def _split_shell_arg_string(cls, s):
+        """Split a shell argument string into args, honoring simple 
single/double quotes.
+
+        This is intentionally minimal: it matches how terminals remove 
surrounding quotes
+        before passing args to the spawned process, which our tests need to 
emulate.
+        """
+        s = str(s)
+        args = []
+        current = []
+        quote = None
+
+        def flush():
+            if current:
+                args.append("".join(current))
+                current.clear()
+
+        for ch in s:
+            if quote is None:
+                if ch.isspace():
+                    flush()
+                    continue
+                if ch in ("\"", "'"):
+                    quote = ch
+                    continue
+                current.append(ch)
+            else:
+                if ch == quote:
+                    quote = None
+                    continue
+                current.append(ch)
+        flush()
+
+        return [cls._shell_unquote(a) for a in args]
+
     def _process_request(self, request):
         self.timeline.record_request(request, block=False)
         if request.command == "runInTerminal":
             args = request("args", json.array(str, vectorize=True))
-            if len(args) > 0 and request("argsCanBeInterpretedByShell", False):
+            args_can_be_interpreted_by_shell = 
request("argsCanBeInterpretedByShell", False)
+            if len(args) > 0 and args_can_be_interpreted_by_shell:
                 # The final arg is a string that contains multiple actual 
arguments.
+                # Split it like a shell would, but keep the rest of the args 
(including
+                # any quoting) intact so tests can inspect the raw 
runInTerminal argv.
                 last_arg = args.pop()
-                args += last_arg.split()
+                args += self._split_shell_arg_string(last_arg)
             cwd = request("cwd", ".")
             env = request("env", json.object(str))
             try:
+                self._run_in_terminal_args_can_be_interpreted_by_shell = (
+                    args_can_be_interpreted_by_shell
+                )
                 return self.run_in_terminal(args, cwd, env)
             except Exception as exc:
                 log.swallow_exception('"runInTerminal" failed:')
                 raise request.cant_handle(str(exc))
+            finally:
+                self._run_in_terminal_args_can_be_interpreted_by_shell = False
 
         elif request.command == "startDebugging":
             pid = request("configuration", dict)("subProcessId", int)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/tests/debugpy/test_args.py 
new/debugpy-1.8.20/tests/debugpy/test_args.py
--- old/debugpy-1.8.18/tests/debugpy/test_args.py       2025-12-10 
19:39:27.000000000 +0100
+++ new/debugpy-1.8.20/tests/debugpy/test_args.py       2026-01-28 
01:58:21.000000000 +0100
@@ -113,3 +113,91 @@
             f"Expected 'python with space' in python path: {python_arg}"
         if expansion == "expand":
             assert (python_arg.startswith('"') or python_arg.startswith("'")), 
f"Python_arg is not quoted: {python_arg}"
+
+
[email protected]("run", runners.all_launch_terminal)
[email protected]("expansion", ["preserve", "expand"])
+def test_debuggee_filename_with_space(tmpdir, run, expansion):
+    """Test that a debuggee filename with a space gets properly quoted in 
runInTerminal."""
+    
+    # Create a script file with a space in both directory and filename
+    
+    # Create a Python script with a space in the filename
+    script_dir = tmpdir / "test dir"
+    script_dir.mkdir()
+    script_file = script_dir / "script with space.py"
+    
+    script_content = """import sys
+import debuggee
+from debuggee import backchannel
+
+debuggee.setup()
+backchannel.send(sys.argv)
+
+import time
+time.sleep(2)
+"""
+    script_file.write(script_content)
+    
+    captured_run_in_terminal_request = []
+    captured_run_in_terminal_args = []
+    
+    class Session(debug.Session):
+        def _process_request(self, request):
+            if request.command == "runInTerminal":
+                # Capture the raw runInTerminal request before any processing
+                args_from_request = list(request.arguments.get("args", []))
+                captured_run_in_terminal_request.append({
+                    "args": args_from_request,
+                    "argsCanBeInterpretedByShell": 
request.arguments.get("argsCanBeInterpretedByShell", False)
+                })
+            return super()._process_request(request)
+        
+        def run_in_terminal(self, args, cwd, env):
+            # Capture the processed args after the framework has handled them
+            captured_run_in_terminal_args.append(args[:])
+            return super().run_in_terminal(args, cwd, env)
+    
+    argslist = ["arg1", "arg2"]
+    args = argslist if expansion == "preserve" else " ".join(argslist)
+    
+    with Session() as session:
+        backchannel = session.open_backchannel()
+        target = targets.Program(script_file, args=args)
+        with run(session, target):
+            pass
+        
+        argv = backchannel.receive()
+    
+    assert argv == [some.str] + argslist
+    
+    # Verify that runInTerminal was called
+    assert captured_run_in_terminal_request, "Expected runInTerminal request 
to be sent"
+    request_data = captured_run_in_terminal_request[0]
+    terminal_request_args = request_data["args"]
+    args_can_be_interpreted_by_shell = 
request_data["argsCanBeInterpretedByShell"]
+    
+    log.info("Captured runInTerminal request args: {0}", terminal_request_args)
+    log.info("argsCanBeInterpretedByShell: {0}", 
args_can_be_interpreted_by_shell)
+    
+    # With expansion="expand", argsCanBeInterpretedByShell should be True
+    if expansion == "expand":
+        assert args_can_be_interpreted_by_shell, \
+            "Expected argsCanBeInterpretedByShell=True for expansion='expand'"
+    
+    # Find the script path in the arguments (it should be after the debugpy 
launcher args)
+    script_path_found = False
+    for arg in terminal_request_args:
+        if "script with space.py" in arg:
+            script_path_found = True
+            log.info("Found script path argument: {0}", arg)
+            
+            # NOTE: With shell expansion enabled, we currently have a 
limitation:
+            # The test framework splits the last arg by spaces when 
argsCanBeInterpretedByShell=True,
+            # which makes it incompatible with quoting individual args. This 
causes issues with
+            # paths containing spaces. This is a known limitation that needs 
investigation.
+            # For now, just verify the script path is found.
+            break
+    
+    assert script_path_found, \
+        f"Expected to find 'script with space.py' in runInTerminal args: 
{terminal_request_args}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/tests/debugpy/test_exception.py 
new/debugpy-1.8.20/tests/debugpy/test_exception.py
--- old/debugpy-1.8.18/tests/debugpy/test_exception.py  2025-12-10 
19:39:27.000000000 +0100
+++ new/debugpy-1.8.20/tests/debugpy/test_exception.py  2026-01-28 
01:58:21.000000000 +0100
@@ -5,7 +5,7 @@
 import pytest
 import sys
 
-from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER
+from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER, 
IS_PY314_OR_GREATER
 from tests import debug
 from tests.debug import runners, targets
 from tests.patterns import some
@@ -390,3 +390,54 @@
         assert min_expected_lines <= stack_line_count <= max_expected_lines
 
         session.request_continue()
+
+
[email protected](not IS_PY314_OR_GREATER, reason="Test requires Python 
3.14+")
+def test_annotate_function_not_treated_as_user_exception(pyfile, target, run):
+    """
+    Test that __annotate__ functions (PEP 649) are treated as library code.
+    In Python 3.14+, compiler-generated __annotate__ functions can raise
+    NotImplementedError when called by inspect.call_annotate_function with
+    unsupported format arguments. These should not be reported as user 
exceptions.
+    """
+    @pyfile
+    def code_to_debug():
+        import debuggee
+        from typing import get_type_hints
+
+        debuggee.setup()
+
+        # Define a class with annotations that will trigger __annotate__ 
function generation
+        class AnnotatedClass:
+            value: int = 42
+            name: str = "test"
+
+        # This will trigger the __annotate__ function to be called by the 
runtime
+        # which may raise NotImplementedError internally (expected behavior)
+        try:
+            hints = get_type_hints(AnnotatedClass)
+            print(f"Type hints: {hints}")  # @bp
+        except Exception as e:
+            print(f"Exception: {e}")
+
+    with debug.Session() as session:
+        session.config["justMyCode"] = True
+
+        with run(session, target(code_to_debug)):
+            # Set exception breakpoints for user uncaught exceptions
+            session.request(
+                "setExceptionBreakpoints",
+                {"filters": ["userUnhandled"]}
+            )
+            session.set_breakpoints(code_to_debug, all)
+
+        # Wait for the breakpoint
+        session.wait_for_stop(
+            "breakpoint",
+            expected_frames=[some.dap.frame(code_to_debug, "bp")]
+        )
+
+        # The test passes if we reach here without stopping on a 
NotImplementedError
+        # from __annotate__ function
+        session.request_continue()
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/debugpy-1.8.18/tests/debugpy/test_threads.py 
new/debugpy-1.8.20/tests/debugpy/test_threads.py
--- old/debugpy-1.8.18/tests/debugpy/test_threads.py    2025-12-10 
19:39:27.000000000 +0100
+++ new/debugpy-1.8.20/tests/debugpy/test_threads.py    2026-01-28 
01:58:21.000000000 +0100
@@ -102,7 +102,7 @@
 
         stop = session.wait_for_stop()
         threads = session.request("threads")
-        assert len(threads["threads"]) == 3
+        assert len(threads["threads"]) >= 3
 
         thread_name_to_id = {t["name"]: t["id"] for t in threads["threads"]}
         assert stop.thread_id == thread_name_to_id["thread1"]

Reply via email to