Skip to content

Commit 31ecdd6

Browse files
pierreglaserogrisel
authored andcommitted
Fix relative imports inside function body (#254)
1 parent da4f332 commit 31ecdd6

File tree

5 files changed

+48
-0
lines changed

5 files changed

+48
-0
lines changed

Diff for: CHANGES.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
0.8.1
2+
=====
3+
4+
- Fix a bug (already present before 0.5.3 and re-introduced in 0.8.0)
5+
affecting relative import instructions inside depickled functions
6+
([issue #254](https://github.com/cloudpipe/cloudpickle/pull/254))
7+
18
0.8.0
29
=====
310

Diff for: cloudpickle/cloudpickle.py

+9
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,15 @@ def extract_func_data(self, func):
666666
# multiple invokations are bound to the same Cloudpickler.
667667
base_globals = self.globals_ref.setdefault(id(func.__globals__), {})
668668

669+
if base_globals == {}:
670+
# Add module attributes used to resolve relative imports
671+
# instructions inside func.
672+
for k in ["__package__", "__name__", "__path__", "__file__"]:
673+
# Some built-in functions/methods such as object.__new__ have
674+
# their __globals__ set to None in PyPy
675+
if func.__globals__ is not None and k in func.__globals__:
676+
base_globals[k] = func.__globals__[k]
677+
669678
return (code, f_globals, defaults, closure, dct, base_globals)
670679

671680
def save_builtin_function(self, obj):

Diff for: tests/cloudpickle_test.py

+24
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,30 @@ def test_dataclass(self):
13681368
pickle_depickle(DataClass, protocol=self.protocol)
13691369
assert data.x == pickle_depickle(data, protocol=self.protocol).x == 42
13701370

1371+
def test_relative_import_inside_function(self):
1372+
# Make sure relative imports inside round-tripped functions is not
1373+
# broken.This was a bug in cloudpickle versions <= 0.5.3 and was
1374+
# re-introduced in 0.8.0.
1375+
1376+
# Both functions living inside modules and packages are tested.
1377+
def f():
1378+
# module_function belongs to mypkg.mod1, which is a module
1379+
from .mypkg import module_function
1380+
return module_function()
1381+
1382+
def g():
1383+
# package_function belongs to mypkg, which is a package
1384+
from .mypkg import package_function
1385+
return package_function()
1386+
1387+
for func, source in zip([f, g], ["module", "package"]):
1388+
# Make sure relative imports are initially working
1389+
assert func() == "hello from a {}!".format(source)
1390+
1391+
# Make sure relative imports still work after round-tripping
1392+
cloned_func = pickle_depickle(func, protocol=self.protocol)
1393+
assert cloned_func() == "hello from a {}!".format(source)
1394+
13711395

13721396
class Protocol2CloudPickleTest(CloudPickleTest):
13731397

Diff for: tests/mypkg/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .mod import module_function
2+
3+
4+
def package_function():
5+
"""Function living inside a package, not a simple module"""
6+
return "hello from a package!"

Diff for: tests/mypkg/mod.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def module_function():
2+
return "hello from a module!"

0 commit comments

Comments
 (0)