Skip to content

Commit 7a19c1f

Browse files
committed
Merge remote-tracking branch 'uqfoundation/master' into enums
2 parents 7046170 + df6ab36 commit 7a19c1f

File tree

7 files changed

+448
-87
lines changed

7 files changed

+448
-87
lines changed

dill/_dill.py

Lines changed: 178 additions & 82 deletions
Large diffs are not rendered by default.

dill/detect.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,19 @@ def nestedglobals(func, recurse=True):
173173
"""get the names of any globals found within func"""
174174
func = code(func)
175175
if func is None: return list()
176+
import sys
176177
from .temp import capture
178+
CAN_NULL = sys.hexversion >= 51052711 #NULL may be prepended >= 3.11a7
177179
names = set()
178180
with capture('stdout') as out:
179181
dis.dis(func) #XXX: dis.dis(None) disassembles last traceback
180182
for line in out.getvalue().splitlines():
181183
if '_GLOBAL' in line:
182184
name = line.split('(')[-1].split(')')[0]
183-
names.add(name)
185+
if CAN_NULL:
186+
names.add(name.replace('NULL + ', ''))
187+
else:
188+
names.add(name)
184189
for co in getattr(func, 'co_consts', tuple()):
185190
if co and recurse and iscode(co):
186191
names.update(nestedglobals(co, recurse=True))

tests/__main__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,3 @@
2828
if not p:
2929
print('.', end='')
3030
print('')
31-

tests/test_abc.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,9 @@ def baz(self):
128128
assert False
129129

130130
labc2, pik = dill.copy((labc, Real()))
131+
assert 'Real' == type(pik).__name__
131132
if dill._dill.PY3:
132-
assert '.Real' in type(pik).__name__
133-
else:
134-
assert 'Real' == type(pik).__name__
133+
assert '.Real' in type(pik).__qualname__
135134
assert type(pik) is not Real
136135
assert labc2 is not LocalABC
137136
assert labc2 is not labc

tests/test_classdef.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,20 @@ def test_namedtuple():
122122
assert Bad._fields == dill.loads(dill.dumps(Bad))._fields
123123
assert tuple(Badi) == tuple(dill.loads(dill.dumps(Badi)))
124124

125+
class A:
126+
class B(namedtuple("C", ["one", "two"])):
127+
'''docstring'''
128+
B.__module__ = 'testing'
129+
130+
a = A()
131+
assert dill.copy(a)
132+
133+
assert dill.copy(A.B).__name__ == 'B'
134+
if dill._dill.PY3:
135+
assert dill.copy(A.B).__qualname__.endswith('.<locals>.A.B')
136+
assert dill.copy(A.B).__doc__ == 'docstring'
137+
assert dill.copy(A.B).__module__ == 'testing'
138+
125139
def test_dtype():
126140
try:
127141
import numpy as np

tests/test_functions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ def function_c(c, c1=1):
2828

2929

3030
def function_d(d, d1, d2=1):
31+
"""doc string"""
3132
return d + d1 + d2
3233

34+
function_d.__module__ = 'a module'
35+
3336

3437
if is_py3():
3538
exec('''
@@ -63,6 +66,8 @@ def test_functions():
6366
assert dill.loads(dumped_func_c)(1, 2) == 3
6467

6568
dumped_func_d = dill.dumps(function_d)
69+
assert dill.loads(dumped_func_d).__doc__ == function_d.__doc__
70+
assert dill.loads(dumped_func_d).__module__ == function_d.__module__
6671
assert dill.loads(dumped_func_d)(1, 2) == 4
6772
assert dill.loads(dumped_func_d)(1, 2, 3) == 6
6873
assert dill.loads(dumped_func_d)(1, 2, d2=3) == 6

tests/test_session.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
#!/usr/bin/env python
2+
3+
# Author: Leonardo Gama (@leogama)
4+
# Copyright (c) 2022 The Uncertainty Quantification Foundation.
5+
# License: 3-clause BSD. The full license text is available at:
6+
# - https://github.com/uqfoundation/dill/blob/master/LICENSE
7+
8+
from __future__ import print_function
9+
import atexit, dill, os, sys, __main__
10+
11+
session_file = os.path.join(os.path.dirname(__file__), 'session-byref-%s.pkl')
12+
13+
def test_modules(main, byref):
14+
main_dict = main.__dict__
15+
16+
try:
17+
for obj in ('json', 'url', 'local_mod', 'sax', 'dom'):
18+
assert main_dict[obj].__name__ in sys.modules
19+
20+
for obj in ('Calendar', 'isleap'):
21+
assert main_dict[obj] is sys.modules['calendar'].__dict__[obj]
22+
assert main.day_name.__module__ == 'calendar'
23+
if byref:
24+
assert main.day_name is sys.modules['calendar'].__dict__['day_name']
25+
26+
assert main.complex_log is sys.modules['cmath'].__dict__['log']
27+
28+
except AssertionError:
29+
import traceback
30+
error_line = traceback.format_exc().splitlines()[-2].replace('[obj]', '['+repr(obj)+']')
31+
print("Error while testing (byref=%s):" % byref, error_line, sep="\n", file=sys.stderr)
32+
raise
33+
34+
35+
# Test session loading in a fresh interpreter session.
36+
if __name__ == '__main__' and len(sys.argv) >= 3 and sys.argv[1] == '--child':
37+
byref = sys.argv[2] == 'True'
38+
dill.load_session(session_file % byref)
39+
test_modules(__main__, byref)
40+
sys.exit()
41+
42+
del test_modules
43+
44+
45+
def _clean_up_cache(module):
46+
cached = module.__file__.split('.', 1)[0] + '.pyc'
47+
cached = module.__cached__ if hasattr(module, '__cached__') else cached
48+
pycache = os.path.join(os.path.dirname(module.__file__), '__pycache__')
49+
for remove, file in [(os.remove, cached), (os.removedirs, pycache)]:
50+
try:
51+
remove(file)
52+
except OSError:
53+
pass
54+
55+
56+
# To clean up namespace before loading the session.
57+
original_modules = set(sys.modules.keys()) - \
58+
set(['json', 'urllib', 'xml.sax', 'xml.dom.minidom', 'calendar', 'cmath'])
59+
original_objects = set(__main__.__dict__.keys())
60+
original_objects.add('original_objects')
61+
62+
63+
# Create various kinds of objects to test different internal logics.
64+
65+
## Modules.
66+
import json # top-level module
67+
import urllib as url # top-level module under alias
68+
from xml import sax # submodule
69+
import xml.dom.minidom as dom # submodule under alias
70+
import test_dictviews as local_mod # non-builtin top-level module
71+
atexit.register(_clean_up_cache, local_mod)
72+
73+
## Imported objects.
74+
from calendar import Calendar, isleap, day_name # class, function, other object
75+
from cmath import log as complex_log # imported with alias
76+
77+
## Local objects.
78+
x = 17
79+
empty = None
80+
names = ['Alice', 'Bob', 'Carol']
81+
def squared(x): return x**2
82+
cubed = lambda x: x**3
83+
class Person:
84+
def __init__(self, name, age):
85+
self.name = name
86+
self.age = age
87+
person = Person(names[0], x)
88+
class CalendarSubclass(Calendar):
89+
def weekdays(self):
90+
return [day_name[i] for i in self.iterweekdays()]
91+
cal = CalendarSubclass()
92+
selfref = __main__
93+
94+
95+
def test_objects(main, copy_dict, byref):
96+
main_dict = main.__dict__
97+
98+
try:
99+
for obj in ('json', 'url', 'local_mod', 'sax', 'dom'):
100+
assert main_dict[obj].__name__ == copy_dict[obj].__name__
101+
102+
#FIXME: In the second test call, 'calendar' is not included in
103+
# sys.modules, independent of the value of byref. Tried to run garbage
104+
# collection before with no luck. This block fails even with
105+
# "import calendar" before it. Needed to restore the original modules
106+
# with the 'copy_modules' object. (Moved to "test_session_{1,2}.py".)
107+
108+
#for obj in ('Calendar', 'isleap'):
109+
# assert main_dict[obj] is sys.modules['calendar'].__dict__[obj]
110+
#assert main_dict['day_name'].__module__ == 'calendar'
111+
#if byref:
112+
# assert main_dict['day_name'] is sys.modules['calendar'].__dict__['day_name']
113+
114+
for obj in ('x', 'empty', 'names'):
115+
assert main_dict[obj] == copy_dict[obj]
116+
117+
globs = '__globals__' if dill._dill.PY3 else 'func_globals'
118+
for obj in ['squared', 'cubed']:
119+
assert getattr(main_dict[obj], globs) is main_dict
120+
assert main_dict[obj](3) == copy_dict[obj](3)
121+
122+
assert main.Person.__module__ == main.__name__
123+
assert isinstance(main.person, main.Person)
124+
assert main.person.age == copy_dict['person'].age
125+
126+
assert issubclass(main.CalendarSubclass, main.Calendar)
127+
assert isinstance(main.cal, main.CalendarSubclass)
128+
assert main.cal.weekdays() == copy_dict['cal'].weekdays()
129+
130+
assert main.selfref is main
131+
132+
except AssertionError:
133+
import traceback
134+
error_line = traceback.format_exc().splitlines()[-2].replace('[obj]', '['+repr(obj)+']')
135+
print("Error while testing (byref=%s):" % byref, error_line, sep="\n", file=sys.stderr)
136+
raise
137+
138+
139+
if __name__ == '__main__':
140+
141+
# Test dump_session() and load_session().
142+
for byref in (False, True):
143+
if byref:
144+
# Test unpickleable imported object in main.
145+
from sys import flags
146+
147+
#print(sorted(set(sys.modules.keys()) - original_modules))
148+
dill._test_file = dill._dill.StringIO()
149+
try:
150+
# For the subprocess.
151+
dill.dump_session(session_file % byref, byref=byref)
152+
153+
dill.dump_session(dill._test_file, byref=byref)
154+
dump = dill._test_file.getvalue()
155+
dill._test_file.close()
156+
157+
import __main__
158+
copy_dict = __main__.__dict__.copy()
159+
copy_modules = sys.modules.copy()
160+
del copy_dict['dump']
161+
del copy_dict['__main__']
162+
for name in copy_dict.keys():
163+
if name not in original_objects:
164+
del __main__.__dict__[name]
165+
for module in list(sys.modules.keys()):
166+
if module not in original_modules:
167+
del sys.modules[module]
168+
169+
dill._test_file = dill._dill.StringIO(dump)
170+
dill.load_session(dill._test_file)
171+
#print(sorted(set(sys.modules.keys()) - original_modules))
172+
173+
# Test session loading in a new session.
174+
from dill.tests.__main__ import python, shell, sp
175+
error = sp.call([python, __file__, '--child', str(byref)], shell=shell)
176+
if error: sys.exit(error)
177+
del python, shell, sp
178+
179+
finally:
180+
dill._test_file.close()
181+
try:
182+
os.remove(session_file % byref)
183+
except OSError:
184+
pass
185+
186+
test_objects(__main__, copy_dict, byref)
187+
__main__.__dict__.update(copy_dict)
188+
sys.modules.update(copy_modules)
189+
del __main__, copy_dict, copy_modules, dump
190+
191+
192+
# This is for code coverage, tests the use case of dump_session(byref=True)
193+
# without imported objects in the namespace. It's a contrived example because
194+
# even dill can't be in it.
195+
from types import ModuleType
196+
modname = '__test_main__'
197+
main = ModuleType(modname)
198+
main.x = 42
199+
200+
_main = dill._dill._stash_modules(main)
201+
if _main is not main:
202+
print("There are objects to save by referenece that shouldn't be:",
203+
_main.__dill_imported, _main.__dill_imported_as, _main.__dill_imported_top_level,
204+
file=sys.stderr)
205+
206+
test_file = dill._dill.StringIO()
207+
try:
208+
dill.dump_session(test_file, main=main, byref=True)
209+
dump = test_file.getvalue()
210+
test_file.close()
211+
212+
sys.modules[modname] = ModuleType(modname) # empty
213+
# This should work after fixing https://github.com/uqfoundation/dill/issues/462
214+
test_file = dill._dill.StringIO(dump)
215+
dill.load_session(test_file)
216+
finally:
217+
test_file.close()
218+
219+
assert x == 42
220+
221+
222+
# Dump session for module that is not __main__:
223+
import test_classdef as module
224+
atexit.register(_clean_up_cache, module)
225+
module.selfref = module
226+
dict_objects = [obj for obj in module.__dict__.keys() if not obj.startswith('__')]
227+
228+
test_file = dill._dill.StringIO()
229+
try:
230+
dill.dump_session(test_file, main=module)
231+
dump = test_file.getvalue()
232+
test_file.close()
233+
234+
for obj in dict_objects:
235+
del module.__dict__[obj]
236+
237+
test_file = dill._dill.StringIO(dump)
238+
dill.load_session(test_file, main=module)
239+
finally:
240+
test_file.close()
241+
242+
assert all(obj in module.__dict__ for obj in dict_objects)
243+
assert module.selfref is module

0 commit comments

Comments
 (0)