Package: src:python-enaml
Version: 0.19.0-1
User: [email protected]
Usertags: python3.15
Tags: patch, ftbfs, forky, sid
Severity: important
Hi!
While rebuilding the python related packages against the Python 3.15rc2
version we found that TODO fails to build from source [1].
I had to do various changes to get it to work:
- PyWeakref_GET_OBJECT is deprecated since 3.13 and removed in 3.15,
replace it by PyWeakref_GetRef
- Update pegen related code for 3.15 changes:
- Add `LOAD_COMMON_CONSTANT` as the return type when returning None.
- Add `is_lazy` field.
- Force the `__cached__` field on modules, as the code assumes it will
be there.
There's now also a PR in the upstream tracker to add 3.15 support, with
a different approach as the one I took [2].
I've applied my fixes in the sandbox [3] to verify that it builds
successfully, please consider applying the patch to support the upcoming
3.15 version.
Setting the severity to important for now. Once Python 3.15 is released,
it will be added to python3-defaults and this bug will become release
critical.
Happy hacking,
[1]: https://debusine.debian.net/debian/r-python-python3.15/artifact/4670760/
[2]: https://github.com/nucleic/enaml/pull/630
[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 /\/\ /\ >< `/
Subject: Fix build with Python 3.15
Author: Maximiliano Curia <[email protected]>
Forwarded: not-needed
PyWeakref_GET_OBJECT (and PyWeakref_GetObject) were removed in Python 3.15,
deprecated since Python 3.13 in favor of PyWeakref_GetRef.
Index: python-enaml/enaml/src/callableref.cpp
===================================================================
--- python-enaml.orig/enaml/src/callableref.cpp
+++ python-enaml/enaml/src/callableref.cpp
@@ -8,6 +8,7 @@
#include <iostream>
#include <sstream>
#include <cppy/cppy.h>
+#include "pyweakref_compat.h"
#ifdef __clang__
#pragma clang diagnostic ignored "-Wdeprecated-writable-strings"
@@ -99,7 +100,11 @@ PyObject*
CallableRef_call( CallableRef* self, PyObject* args, PyObject* kwargs )
{
cppy::ptr objrefptr( cppy::incref( self->objref ) );
- cppy::ptr objptr( cppy::incref( PyWeakref_GET_OBJECT( objrefptr.get() ) ) );
+ cppy::ptr objptr( weakref_get_object( objrefptr.get() ) );
+ if( !objptr )
+ {
+ return 0;
+ }
if( objptr.is_none() )
{
Py_RETURN_NONE;
Index: python-enaml/enaml/src/signaling.cpp
===================================================================
--- python-enaml.orig/enaml/src/signaling.cpp
+++ python-enaml/enaml/src/signaling.cpp
@@ -9,6 +9,7 @@
#include <sstream>
#include <vector>
#include <cppy/cppy.h>
+#include "pyweakref_compat.h"
#ifdef __clang__
@@ -398,7 +399,11 @@ _Disconnector_call( _Disconnector* self,
}
cppy::ptr objref( cppy::incref( self->objref ) );
- cppy::ptr obj( cppy::incref( PyWeakref_GET_OBJECT( objref.get() ) ) );
+ cppy::ptr obj( weakref_get_object( objref.get() ) );
+ if( !obj )
+ {
+ return 0;
+ }
if( obj.is_none() )
{
Py_RETURN_NONE;
@@ -633,7 +638,11 @@ PyObject*
BoundSignal_emit( BoundSignal* self, PyObject* args, PyObject* kwargs )
{
cppy::ptr objref( cppy::incref( self->objref ) );
- cppy::ptr obj( cppy::incref( PyWeakref_GET_OBJECT( objref.get() ) ) );
+ cppy::ptr obj( weakref_get_object( objref.get() ) );
+ if( !obj )
+ {
+ return 0;
+ }
if( obj.is_none() )
{
Py_RETURN_NONE;
@@ -720,7 +729,11 @@ PyObject*
BoundSignal_connect( BoundSignal* self, PyObject* slot )
{
cppy::ptr objref( cppy::incref( self->objref ) );
- cppy::ptr obj( cppy::incref( PyWeakref_GET_OBJECT( objref.get() ) ) );
+ cppy::ptr obj( weakref_get_object( objref.get() ) );
+ if( !obj )
+ {
+ return 0;
+ }
if( obj.is_none() )
{
Py_RETURN_NONE;
Index: python-enaml/enaml/src/weakmethod.cpp
===================================================================
--- python-enaml.orig/enaml/src/weakmethod.cpp
+++ python-enaml/enaml/src/weakmethod.cpp
@@ -9,6 +9,7 @@
#include <sstream>
#include <cppy/cppy.h>
#include <structmember.h> // Included to access offsetof
+#include "pyweakref_compat.h"
namespace enaml
@@ -222,7 +223,11 @@ PyObject*
WeakMethod_call( WeakMethod* self, PyObject* args, PyObject* kwargs )
{
cppy::ptr selfref( cppy::incref( self->selfref ) );
- cppy::ptr mself( cppy::incref( PyWeakref_GET_OBJECT( selfref.get() ) ) );
+ cppy::ptr mself( weakref_get_object( selfref.get() ) );
+ if( !mself )
+ {
+ return 0;
+ }
if( mself.is_none() )
{
Py_RETURN_NONE;
Index: python-enaml/enaml/src/pyweakref_compat.h
===================================================================
--- /dev/null
+++ python-enaml/enaml/src/pyweakref_compat.h
@@ -0,0 +1,32 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) 2013-2025, Nucleic Development Team.
+|
+| Distributed under the terms of the Modified BSD License.
+|
+| The full license is in the file LICENSE, distributed with this software.
+|----------------------------------------------------------------------------*/
+#pragma once
+
+#include <cppy/cppy.h>
+
+
+namespace enaml
+{
+
+
+// PyWeakref_GET_OBJECT was removed in Python 3.15; use PyWeakref_GetRef there.
+static inline PyObject*
+weakref_get_object( PyObject* ref )
+{
+#if PY_VERSION_HEX >= 0x030d0000
+ PyObject* obj;
+ if( PyWeakref_GetRef( ref, &obj ) < 0 )
+ return 0;
+ return obj ? obj : cppy::incref( Py_None );
+#else
+ return cppy::incref( PyWeakref_GET_OBJECT( ref ) );
+#endif
+}
+
+
+} // namespace enaml
Index: python-enaml/enaml/compat.py
===================================================================
--- python-enaml.orig/enaml/compat.py
+++ python-enaml/enaml/compat.py
@@ -20,6 +20,8 @@ PY313 = sys.version_info >= (3, 13)
PY314 = sys.version_info >= (3, 14)
+PY315 = sys.version_info >= (3, 15)
+
# Functions used to update the co_filename slot of a code object
# Available in Python 3.5+ (tested up to 3.8)
from _imp import _fix_co_filename
Index: python-enaml/enaml/core/code_generator.py
===================================================================
--- python-enaml.orig/enaml/core/code_generator.py
+++ python-enaml/enaml/core/code_generator.py
@@ -10,8 +10,9 @@ from contextlib import contextmanager
import bytecode as bc
from atom.api import Atom, Bool, Int, List, Str
+from bytecode.instr import CommonConstant
-from ..compat import PY310, PY311, PY312, PY313, PY314
+from ..compat import PY310, PY311, PY312, PY313, PY314, PY315
class _ReturnNoneIdentifier(ast.NodeVisitor):
@@ -567,8 +568,15 @@ class CodeGenerator(Atom):
(rc := (block[-1].name == "RETURN_CONST" and block[-1].arg is None))
or (
block[-1].name == "RETURN_VALUE"
- and block[-2].name == "LOAD_CONST"
- and block[-2].arg is None
+ and (
+ (block[-2].name == "LOAD_CONST" and block[-2].arg is None)
+ # Python 3.15 loads None via LOAD_COMMON_CONSTANT instead
+ or (
+ PY315
+ and block[-2].name == "LOAD_COMMON_CONSTANT"
+ and block[-2].arg == CommonConstant.CONSTANT_NONE
+ )
+ )
and block[-1].lineno not in _inspector.lines
)
):
Index: python-enaml/enaml/core/import_hooks.py
===================================================================
--- python-enaml.orig/enaml/core/import_hooks.py
+++ python-enaml/enaml/core/import_hooks.py
@@ -136,6 +136,11 @@ class AbstractEnamlImporter(object, meta
if code is None:
code, _ = self.get_code()
+ # Python 3.15 stopped setting __cached__ from spec.cached (GH-65961).
+ spec = module.__spec__
+ if spec is not None and spec.cached is not None:
+ module.__cached__ = spec.cached
+
# Even though the import hook is already installed, this is a
# safety net to avoid potentially hard to find bugs if code has
# manually installed and removed a hook. The contract here is
Index: python-enaml/enaml/core/parser/enaml.gram
===================================================================
--- python-enaml.orig/enaml/core/parser/enaml.gram
+++ python-enaml/enaml/core/parser/enaml.gram
@@ -597,14 +597,22 @@ import_stmt[ast.Import]:
# Import statements
# -----------------
-import_name[ast.Import]: 'import' a=dotted_as_names { ast.Import(names=a, LOCATIONS) }
+import_name[ast.Import]: 'import' a=dotted_as_names {
+ ast.Import(names=a, is_lazy=0, LOCATIONS)
+ if sys.version_info >= (3, 15) else
+ ast.Import(names=a, LOCATIONS)
+}
# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS
import_from[ast.ImportFrom]:
| 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets {
+ ast.ImportFrom(module=b, names=c, level=self.extract_import_level(a), is_lazy=0, LOCATIONS)
+ if sys.version_info >= (3, 15) else
ast.ImportFrom(module=b, names=c, level=self.extract_import_level(a), LOCATIONS)
}
| 'from' a=('.' | '...')+ 'import' b=import_from_targets {
+ ast.ImportFrom(names=b, level=self.extract_import_level(a), is_lazy=0, LOCATIONS)
+ if sys.version_info >= (3, 15) else
ast.ImportFrom(names=b, level=self.extract_import_level(a), LOCATIONS)
if sys.version_info >= (3, 9) else
ast.ImportFrom(module=None, names=b, level=self.extract_import_level(a), LOCATIONS)
Index: python-enaml/enaml/core/parser/enaml_parser.py
===================================================================
--- python-enaml.orig/enaml/core/parser/enaml_parser.py
+++ python-enaml/enaml/core/parser/enaml_parser.py
@@ -1561,7 +1561,7 @@ class EnamlParser(Parser):
):
tok = self._tokenizer.get_last_non_whitespace_token()
end_lineno, end_col_offset = tok.end
- return ast . Import ( names = a , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset );
+ return ast . Import ( names = a , is_lazy = 0 , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset ) if sys . version_info >= ( 3 , 15 ) else ast . Import ( names = a , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset );
self._reset(mark)
return None;
@@ -1584,7 +1584,7 @@ class EnamlParser(Parser):
):
tok = self._tokenizer.get_last_non_whitespace_token()
end_lineno, end_col_offset = tok.end
- return ast . ImportFrom ( module = b , names = c , level = self . extract_import_level ( a ) , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset );
+ return ast . ImportFrom ( module = b , names = c , level = self . extract_import_level ( a ) , is_lazy = 0 , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset ) if sys . version_info >= ( 3 , 15 ) else ast . ImportFrom ( module = b , names = c , level = self . extract_import_level ( a ) , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset );
self._reset(mark)
if (
(self.expect('from'))
@@ -1597,7 +1597,7 @@ class EnamlParser(Parser):
):
tok = self._tokenizer.get_last_non_whitespace_token()
end_lineno, end_col_offset = tok.end
- return ast . ImportFrom ( names = b , level = self . extract_import_level ( a ) , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset ) if sys . version_info >= ( 3 , 9 ) else ast . ImportFrom ( module = None , names = b , level = self . extract_import_level ( a ) , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset );
+ return ast . ImportFrom ( names = b , level = self . extract_import_level ( a ) , is_lazy = 0 , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset ) if sys . version_info >= ( 3 , 15 ) else ast . ImportFrom ( names = b , level = self . extract_import_level ( a ) , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset ) if sys . version_info >= ( 3 , 9 ) else ast . ImportFrom ( module = None , names = b , level = self . extract_import_level ( a ) , lineno=start_lineno, col_offset=start_col_offset, end_lineno=end_lineno, end_col_offset=end_col_offset );
self._reset(mark)
return None;