Skip to content

Commit 1381ae8

Browse files
Merge branch 'release/2.1.2'
2 parents 1332d2d + 26ab4fd commit 1381ae8

File tree

7 files changed

+118
-11
lines changed

7 files changed

+118
-11
lines changed

.github/workflows/build-test-release.yml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,18 +250,14 @@ jobs:
250250
strategy:
251251
matrix:
252252
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, macos-15-intel, macos-latest]
253-
# arch: [auto]
254-
# include:
255-
# - os: ubuntu-latest
256-
# arch: aarch64
257-
# - os: macos-latest
258-
# arch: universal2
259-
# - os: ubuntu-latest
260-
# arch: riscv64
253+
arch: [auto]
254+
include:
255+
- os: ubuntu-latest
256+
arch: riscv64
261257
steps:
262258
- uses: actions/checkout@v5
263259
- name: Set up QEMU
264-
if: ${{ matrix.arch == 'aarch64' || matrix.arch == 'riscv64' }}
260+
if: ${{ matrix.arch == 'riscv64' }}
265261
uses: docker/setup-qemu-action@v3
266262
- name: Build wheels
267263
uses: pypa/cibuildwheel@v3.3.0

docs/changes.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
Release Notes
22
=============
33

4+
Version 2.1.2
5+
-------------
6+
7+
**Bugs Fixed**
8+
9+
* Building of Python wheels for riscv64 Linux platform had been accidentally
10+
removed from the build configuration. This has now been added back in.
11+
12+
* When a weak function proxy was created for a bound method and the instance
13+
it was bound to was garbage collected, calling the proxy would silently
14+
call the function as unbound instead of raising a ``ReferenceError``.
15+
16+
* When deleting an attribute named ``__annotations__`` on an object proxy, the
17+
attribute was only being deleted from the proxy and not also from the wrapped
18+
object.
19+
420
Version 2.1.1
521
-------------
622

src/wrapt/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Wrapt is a library for decorators, wrappers and monkey patching.
33
"""
44

5-
__version_info__ = ("2", "1", "1")
5+
__version_info__ = ("2", "1", "2")
66
__version__ = ".".join(__version_info__)
77

88
from .__wrapt__ import (

src/wrapt/weakrefs.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ def _unpack_self(self, *args):
103103
instance = self._self_instance and self._self_instance()
104104
function = self.__wrapped__ and self.__wrapped__
105105

106+
# If the wrapped function was originally a bound method but the
107+
# instance it was bound to has been garbage collected, raise a
108+
# ReferenceError rather than silently calling it as unbound.
109+
110+
if self._self_instance is not None and instance is None:
111+
raise ReferenceError("weakly-referenced object no longer exists")
112+
106113
# If the wrapped function was originally a bound function, for
107114
# which we retained a reference to the instance and the unbound
108115
# function we need to rebind the function and then call it. If

src/wrapt/wrappers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ def __delattr__(self, name):
283283
object.__delattr__(self, name)
284284
delattr(self.__wrapped__, name)
285285

286+
elif name == "__annotations__":
287+
object.__delattr__(self, name)
288+
delattr(self.__wrapped__, name)
289+
286290
elif hasattr(type(self), name):
287291
object.__delattr__(self, name)
288292

tests/core/test_update_attributes.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,44 @@ def wrapper(wrapped, instance, args, kwargs):
7474
self.assertEqual(function.__qualname__, "override_qualname")
7575
self.assertEqual(instance.__qualname__, "override_qualname")
7676

77+
def test_delete_qualname(self):
78+
79+
@passthru_decorator
80+
def function():
81+
pass
82+
83+
function.__qualname__ = "override_qualname"
84+
85+
self.assertEqual(function.__qualname__, "override_qualname")
86+
87+
# CPython raises TypeError when deleting __qualname__ from a function
88+
# because the C-level setter rejects a NULL value. PyPy raises
89+
# AttributeError instead. Both indicate that deletion is not supported.
90+
self.assertRaises(
91+
(TypeError, AttributeError), delattr, function, "__qualname__"
92+
)
93+
94+
def test_delete_qualname_modified_on_original(self):
95+
def function():
96+
pass
97+
98+
def wrapper(wrapped, instance, args, kwargs):
99+
return wrapped(*args, **kwargs)
100+
101+
instance = wrapt.FunctionWrapper(function, wrapper)
102+
103+
instance.__qualname__ = "override_qualname"
104+
105+
self.assertEqual(function.__qualname__, "override_qualname")
106+
self.assertEqual(instance.__qualname__, "override_qualname")
107+
108+
# CPython raises TypeError when deleting __qualname__ from a function
109+
# because the C-level setter rejects a NULL value. PyPy raises
110+
# AttributeError instead. Both indicate that deletion is not supported.
111+
self.assertRaises(
112+
(TypeError, AttributeError), delattr, instance, "__qualname__"
113+
)
114+
77115
def test_update_module(self):
78116
@passthru_decorator
79117
def function():
@@ -160,6 +198,40 @@ def wrapper(wrapped, instance, args, kwargs):
160198
self.assertEqual(function.__annotations__, override_annotations)
161199
self.assertEqual(instance.__annotations__, override_annotations)
162200

201+
def test_delete_annotations(self):
202+
@passthru_decorator
203+
def function():
204+
pass
205+
206+
override_annotations = {"override_annotations": ""}
207+
function.__annotations__ = override_annotations
208+
209+
self.assertEqual(function.__annotations__, override_annotations)
210+
211+
del function.__annotations__
212+
213+
self.assertEqual(function.__annotations__, {})
214+
215+
def test_delete_annotations_modified_on_original(self):
216+
def function():
217+
pass
218+
219+
def wrapper(wrapped, instance, args, kwargs):
220+
return wrapped(*args, **kwargs)
221+
222+
instance = wrapt.FunctionWrapper(function, wrapper)
223+
224+
override_annotations = {"override_annotations": ""}
225+
instance.__annotations__ = override_annotations
226+
227+
self.assertEqual(function.__annotations__, override_annotations)
228+
self.assertEqual(instance.__annotations__, override_annotations)
229+
230+
del instance.__annotations__
231+
232+
self.assertEqual(function.__annotations__, {})
233+
self.assertEqual(instance.__annotations__, {})
234+
163235

164236
if __name__ == "__main__":
165237
unittest.main()

tests/core/test_weak_function_proxy.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import unittest
21
import gc
2+
import unittest
33

44
import wrapt
55

@@ -82,6 +82,9 @@ def callback(proxy):
8282
self.assertEqual(len(result), 1)
8383
self.assertEqual(id(proxy), result[0])
8484

85+
with self.assertRaises(ReferenceError):
86+
proxy(1, 2)
87+
8588
def test_instancemethod_delete_function(self):
8689
class Class:
8790
def function(self, a, b):
@@ -105,6 +108,9 @@ def callback(proxy):
105108
self.assertEqual(len(result), 1)
106109
self.assertEqual(id(proxy), result[0])
107110

111+
with self.assertRaises(ReferenceError):
112+
proxy(1, 2)
113+
108114
def test_instancemethod_delete_function_and_instance(self):
109115
class Class:
110116
def function(self, a, b):
@@ -128,6 +134,9 @@ def callback(proxy):
128134
self.assertEqual(len(result), 1)
129135
self.assertEqual(id(proxy), result[0])
130136

137+
with self.assertRaises(ReferenceError):
138+
proxy(1, 2)
139+
131140
def test_classmethod(self):
132141
class Class:
133142
@classmethod
@@ -151,6 +160,9 @@ def callback(proxy):
151160
self.assertEqual(len(result), 1)
152161
self.assertEqual(id(proxy), result[0])
153162

163+
with self.assertRaises(ReferenceError):
164+
proxy(1, 2)
165+
154166
def test_staticmethod(self):
155167
class Class:
156168
@staticmethod

0 commit comments

Comments
 (0)