Skip to content

Commit ac3c732

Browse files
authored
Merge pull request #174 from skogsbaer/sw/fix-wrappers
fix wrappers
2 parents c2cdc7a + fc945a5 commit ac3c732

File tree

13 files changed

+241
-123
lines changed

13 files changed

+241
-123
lines changed

.github/workflows/github-action-test-js.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ jobs:
1717
node-version: [14.x, 16.x, 18.x, 20.x]
1818

1919
steps:
20-
- uses: actions/checkout@v2
20+
- uses: actions/checkout@v4
2121
- name: Use Node.js ${{ matrix.node-version }}
22-
uses: actions/setup-node@v2
22+
uses: actions/setup-node@v4
2323
with:
2424
node-version: ${{ matrix.node-version }}
2525
cache: 'npm'

.github/workflows/github-action-test-python.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ jobs:
1414
# You need to change to branch protection rules if you change the versions here
1515
python-version: [3.12.1, 3.13.0]
1616
steps:
17-
- uses: actions/checkout@v2
17+
- uses: actions/checkout@v4
1818
- name: Set up Python ${{ matrix.python-version }}
19-
uses: actions/setup-python@v2
19+
uses: actions/setup-python@v5
2020
with:
2121
python-version: ${{ matrix.python-version }}
2222
- name: Test

python/deps/untypy/test/impl/test_simple.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def test_attributes(self):
5959
def m(x: A) -> None:
6060
self.assertEqual(x.foo, 42)
6161
x.foo = 43
62+
self.assertEqual(x.foo, 43)
6263

6364
m(a)
6465
self.assertEqual(a.foo, 43)

python/deps/untypy/test/util_test/test_wrapper.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,13 @@ def _test_api_complete(self, obj, ignore=[]):
7777
'__weakref__', '__wrapped__', '_DictWrapper__marker', '__setstate__',
7878
'__getstate__', '__firstlineno__', '__static_attributes__'
7979
] + ignore
80-
for x in dir(wrapped):
81-
if x in blacklist: continue
82-
m = getattr(wrapped, x)
83-
if not hasattr(m, '__module__'):
84-
self.fail(f'Attribute {x} not defined')
85-
elif m.__module__ != expectedModule:
80+
for x in dir(obj):
81+
if x in blacklist:
82+
continue
83+
a = getattr(wrapped, x)
84+
if not hasattr(a, '__module__'):
85+
self.fail(f'Attribute {x} not defined. obj={obj}, a={a}, wrapped={wrapped}')
86+
elif a.__module__ != expectedModule:
8687
self.fail(f'Attrribute {x} not defined in {expectedModule}')
8788

8889
def test_list_api_complete(self):

python/deps/untypy/untypy/util/wrapper.py

Lines changed: 78 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,11 @@
11
import typing
2-
import abc
32
import collections
4-
from untypy.error import UntypyError
53
from untypy.util.debug import debug
64

75
def _f():
86
yield 0
97
generatorType = type(_f())
108

11-
class WyppWrapError(Exception):
12-
pass
13-
14-
def _readonly(self, *args, **kwargs):
15-
raise RuntimeError("Cannot modify ReadOnlyDict")
16-
17-
class ReadOnlyDict(dict):
18-
__setitem__ = _readonly
19-
__delitem__ = _readonly
20-
pop = _readonly
21-
popitem = _readonly
22-
clear = _readonly
23-
update = _readonly
24-
setdefault = _readonly
25-
26-
def patch(self, ty, extra):
27-
# SW (2024-10-18): With python 3.13 there is the behavior that extra is modified after patching
28-
# the object. I never found out who is doing the modification. By wrapping extra with
29-
# ReadOnlyDict, everything works. Strangely, no error occurs somewhere.
30-
self.__extra__ = ReadOnlyDict(extra)
31-
w = self.__wrapped__
32-
m = None
33-
if hasattr(w, '__module__'):
34-
m = getattr(w, '__module__')
35-
ty.__module__ = m
36-
try:
37-
self.__class__ = ty
38-
except TypeError as e:
39-
raise WyppWrapError(f'Cannot wrap {self.__wrapped__} of type {type(self.__wrapped__)} ' \
40-
f'at type {ty}. Original error: {e}')
41-
429
class WrapperBase:
4310
def __eq__(self, other):
4411
if hasattr(other, '__wrapped__'):
@@ -49,17 +16,7 @@ def __ne__(self, other):
4916
return not self.__eq__(other)
5017
def __hash__(self):
5118
return hash(self.__wrapped__)
52-
def __patch__(self, ms, name=None, extra=None):
53-
if extra is None:
54-
extra = {}
55-
cls = self.__class__
56-
if name is None:
57-
name = cls.__name__
58-
ty = type(name, (cls,), ms)
59-
patch(self, ty, extra)
6019
def __repr__(self):
61-
#w = self.__wrapped__
62-
#return f"Wrapper(addr=0x{id(self):09x}, wrapped_addr=0x{id(w):09x}, wrapped={repr(w)}"
6320
return repr(self.__wrapped__)
6421
def __str__(self):
6522
return str(self.__wrapped__)
@@ -72,27 +29,6 @@ def __reduce__(self): return self.__wrapped__.__reduce__()
7229
def __reduce_ex__(self): return self.__wrapped__.__reduce_ex__()
7330
def __sizeof__(self): return self.__wrapped__.__sizeof__()
7431

75-
class ObjectWrapper(WrapperBase):
76-
def __init__(self, baseObject):
77-
self.__dict__ = baseObject.__dict__
78-
self.__wrapped__ = baseObject
79-
def __patch__(self, ms, name=None, extra=None):
80-
if extra is None:
81-
extra = {}
82-
cls = self.__class__
83-
if name is None:
84-
name = cls.__name__
85-
wrappedCls = type(self.__wrapped__)
86-
ty = type(name, (wrappedCls, cls), ms)
87-
patch(self, ty, extra)
88-
89-
class ABCObjectWrapper(abc.ABC, ObjectWrapper):
90-
pass
91-
92-
# Superclasses in reverse order.
93-
class ABCObjectWrapperRev(ObjectWrapper, abc.ABC):
94-
pass
95-
9632
# A wrapper for list such that the class is a subclass of the builtin list class.
9733
class ListWrapper(WrapperBase, list): # important: inherit from WrapperBase first
9834
def __new__(cls, content):
@@ -224,33 +160,10 @@ def __new__(cls, content):
224160
self.__wrapped__ = content
225161
return self
226162

227-
# These methods are not delegated to the wrapped object
228-
_blacklist = [
229-
'__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
230-
'__getattribute__', '__get_attr_', '__init_subclass__'
231-
'__init__', '__new__', '__del__', '__repr__', '__setattr__', '__str__',
232-
'__hash__', '__eq__', '__patch__',
233-
'__class_getitem__', '__subclasshook__',
234-
'__firstlineno__', '__static_attributes__']
235-
236-
_extra = ['__next__']
237-
238163
# SimpleWrapper is a fallback for types that cannot be used as base types
239164
class SimpleWrapper(WrapperBase):
240-
def __init__(self, baseObject):
241-
self.__wrapped__ = baseObject
242-
def __patch__(self, ms, name=None, extra=None):
243-
if extra is None:
244-
extra = {}
245-
cls = self.__class__
246-
if name is None:
247-
name = cls.__name__
248-
baseObject = self.__wrapped__
249-
for x in dir(baseObject) + _extra:
250-
if x not in ms and x not in _blacklist and hasattr(baseObject, x):
251-
ms[x] = getattr(baseObject, x)
252-
ty = type(name, (cls,), ms) #
253-
patch(self, ty, extra)
165+
def __init__(self, wrapped):
166+
self.__wrapped__ = wrapped
254167

255168
class ValuesViewWrapper(SimpleWrapper):
256169
pass
@@ -264,46 +177,99 @@ class KeysViewWrapper(SimpleWrapper):
264177
pass
265178
collections.abc.KeysView.register(KeysViewWrapper)
266179

180+
def _wrap(wrapped, methods, mod, name, extra, cls):
181+
if extra is None:
182+
extra = {}
183+
# Dynamically create a new class:
184+
# type(class_name, base_classes, class_dict)
185+
WrapperClass = type(
186+
name,
187+
(cls,),
188+
methods
189+
)
190+
WrapperClass.__module__ = mod
191+
w = WrapperClass(wrapped)
192+
w.__extra__ = extra
193+
return w
194+
195+
def wrapSimple(wrapped, methods, name, extra, cls=SimpleWrapper):
196+
if name is None:
197+
name = cls.__name__
198+
mod = None
199+
else:
200+
if hasattr(wrapped, '__module__'):
201+
mod = wrapped.__module__
202+
else:
203+
mod = None
204+
for x in ['__next__', '__iter__']:
205+
if x not in methods and hasattr(wrapped, x):
206+
attr = getattr(wrapped, x)
207+
methods[x] = attr
208+
return _wrap(wrapped, methods, mod, name, extra, cls)
209+
210+
def wrapObj(wrapped, methods, name, extra):
211+
class BaseWrapper(WrapperBase, wrapped.__class__):
212+
def __init__(self, wrapped):
213+
self.__dict__ = wrapped.__dict__
214+
self.__wrapped__ = wrapped
215+
if name is None:
216+
name = 'ObjectWrapper'
217+
if hasattr(wrapped, '__module__'):
218+
mod = getattr(wrapped, '__module__')
219+
else:
220+
mod = None
221+
return _wrap(wrapped, methods, mod, name, extra, BaseWrapper)
222+
223+
def wrapBuiltin(wrapped, methods, name, extra, cls):
224+
if name is None:
225+
name = cls.__name__
226+
return _wrap(wrapped, methods, None, name, extra, cls)
227+
267228
def wrap(obj, methods, name=None, extra=None, simple=False):
268229
if extra is None:
269230
extra = {}
231+
wrapper = None
270232
if simple:
271-
w = SimpleWrapper(obj)
233+
w = wrapSimple(obj, methods, name, extra)
234+
wrapper = 'SimpleWrapper'
272235
elif isinstance(obj, list):
273-
w = ListWrapper(obj)
236+
w = wrapBuiltin(obj, methods, name, extra, ListWrapper)
237+
wrapper = 'ListWrapper'
274238
elif isinstance(obj, tuple):
275-
w = TupleWrapper(obj)
239+
w = wrapBuiltin(obj, methods, name, extra, TupleWrapper)
240+
wrapper = 'TupleWrapper'
276241
elif isinstance(obj, dict):
277-
w = DictWrapper(obj)
242+
w = wrapBuiltin(obj, methods, name, extra, DictWrapper)
243+
wrapper = 'DictWrapper'
278244
elif isinstance(obj, str):
279-
w = StringWrapper(obj)
245+
w = wrapBuiltin(obj, methods, name, extra, StringWrapper)
246+
wrapper = 'StringWrapper'
280247
elif isinstance(obj, set):
281-
w = SetWrapper(obj)
248+
w = wrapBuiltin(obj, methods, name, extra, SetWrapper)
249+
wrapper = 'SetWrapper'
282250
elif isinstance(obj, collections.abc.ValuesView):
283-
w = ValuesViewWrapper(obj)
251+
w = wrapSimple(obj, methods, name, extra, ValuesViewWrapper)
252+
wrapper = 'ValuesViewWrapper'
284253
elif isinstance(obj, collections.abc.KeysView):
285-
w = KeysViewWrapper(obj)
254+
w = wrapSimple(obj, methods, name, extra, KeysViewWrapper)
255+
wrapper = 'KeysViewWrapper'
286256
elif isinstance(obj, collections.abc.ItemsView):
287-
w = ItemsViewWrapper(obj)
257+
w = wrapSimple(obj, methods, name, extra, ItemsViewWrapper)
258+
wrapper = 'ItemsViewWrapper'
288259
elif isinstance(obj, typing.Generic):
289-
w = SimpleWrapper(obj)
260+
w = wrapSimple(obj, methods, name, extra)
261+
wrapper = 'SimpleWrapper'
290262
elif isinstance(obj, generatorType):
291-
w = SimpleWrapper(obj)
292-
elif isinstance(obj, abc.ABC) and hasattr(obj, '__dict__'):
293-
try:
294-
w = ABCObjectWrapper(obj)
295-
except WyppWrapError:
296-
try:
297-
w = ABCObjectWrapperRev(obj)
298-
except WyppWrapError:
299-
w = SimpleWrapper(obj)
263+
w = wrapSimple(obj, methods, name, extra)
264+
wrapper = 'SimpleWrapper'
300265
elif hasattr(obj, '__dict__'):
301-
w = ObjectWrapper(obj)
266+
w = wrapObj(obj, methods, name, extra)
267+
wrapper = 'ObjectWrapper'
302268
else:
303-
w = SimpleWrapper(obj)
304-
w.__patch__(methods, name, extra)
269+
w = wrapSimple(obj, methods, name, extra)
270+
wrapper = 'SimpleWrapper'
305271
wname = name
306272
if wname is None:
307273
wname = str(type(w))
308-
debug(f"Wrapping {obj} at 0x{id(obj):09x} as {wname}, simple={simple}, wrapper=0x{id(w):09x}")
274+
debug(f"Wrapping {obj} at 0x{id(obj):09x} as {wname}, simple={simple}, wrapper=0x{id(w):09x} ({wrapper})")
309275
return w

python/fileTests

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ checkWithOutputAux yes 0 test-data/testForwardTypeInRecord.py
289289
checkWithOutputAux yes 0 test-data/testForwardTypeInRecord2.py
290290
checkWithOutputAux yes 0 test-data/testUnionOfUnion.py
291291
checkWithOutputAux yes 1 test-data/testRecordTypes.py
292+
checkWithOutputAux yes 0 test-data/testDisappearingObject_01.py
293+
checkWithOutputAux yes 0 test-data/testDisappearingObject_02.py
292294

293295
function is_min_version()
294296
{

python/run

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!/bin/bash
22

3+
PY=python3.13
34
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
45

56
OPTS="--quiet"
67
# OPTS="--verbose"
78

8-
PYTHONPATH="$SCRIPT_DIR"/site-lib:"$PYTHONPATH" python3 "$SCRIPT_DIR"/src/runYourProgram.py \
9+
PYTHONPATH="$SCRIPT_DIR"/site-lib:"$PYTHONPATH" $PY "$SCRIPT_DIR"/src/runYourProgram.py \
910
--no-clear $OPTS "$@"
1011
exit $?

python/test-data/testDisappearingObject_01.err

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
['subdir']
2+
Directory('stefan')

0 commit comments

Comments
 (0)