43
43
tornado = None
44
44
45
45
import cloudpickle
46
- from cloudpickle .cloudpickle import _is_dynamic
46
+ from cloudpickle .cloudpickle import _is_importable
47
47
from cloudpickle .cloudpickle import _make_empty_cell , cell_set
48
48
from cloudpickle .cloudpickle import _extract_class_dict , _whichmodule
49
49
from cloudpickle .cloudpickle import _lookup_module_and_qualname
52
52
from .testutils import assert_run_python_script
53
53
from .testutils import subprocess_worker
54
54
55
+ from _cloudpickle_testpkg import relative_imports_factory
56
+
55
57
56
58
_TEST_GLOBAL_VARIABLE = "default_value"
57
59
@@ -678,25 +680,62 @@ def my_small_function(x, y):
678
680
assert b'unwanted_function' not in b
679
681
assert b'math' not in b
680
682
681
- def test_is_dynamic_module (self ):
683
+ def test_module_importability (self ):
682
684
import pickle # decouple this test from global imports
683
685
import os .path
684
686
import distutils
685
687
import distutils .ccompiler
686
688
687
- assert not _is_dynamic (pickle )
688
- assert not _is_dynamic (os .path ) # fake (aliased) module
689
- assert not _is_dynamic (distutils ) # package
690
- assert not _is_dynamic (distutils .ccompiler ) # module in package
689
+ assert _is_importable (pickle )
690
+ assert _is_importable (os .path ) # fake (aliased) module
691
+ assert _is_importable (distutils ) # package
692
+ assert _is_importable (distutils .ccompiler ) # module in package
691
693
692
- # user-created module without using the import machinery are also
693
- # dynamic
694
694
dynamic_module = types .ModuleType ('dynamic_module' )
695
- assert _is_dynamic (dynamic_module )
695
+ assert not _is_importable (dynamic_module )
696
696
697
697
if platform .python_implementation () == 'PyPy' :
698
698
import _codecs
699
- assert not _is_dynamic (_codecs )
699
+ assert _is_importable (_codecs )
700
+
701
+ # #354: Check that modules created dynamically during the import of
702
+ # their parent modules are considered importable by cloudpickle.
703
+ # See the mod_with_dynamic_submodule documentation for more
704
+ # details of this use case.
705
+ import _cloudpickle_testpkg .mod .dynamic_submodule as m
706
+ assert _is_importable (m )
707
+ assert pickle_depickle (m , protocol = self .protocol ) is m
708
+
709
+ # Check for similar behavior for a module that cannot be imported by
710
+ # attribute lookup.
711
+ from _cloudpickle_testpkg .mod import dynamic_submodule_two as m2
712
+ # Note: import _cloudpickle_testpkg.mod.dynamic_submodule_two as m2
713
+ # works only for Python 3.7+
714
+ assert _is_importable (m2 )
715
+ assert pickle_depickle (m2 , protocol = self .protocol ) is m2
716
+
717
+ # Submodule_three is a dynamic module only importable via module lookup
718
+ with pytest .raises (ImportError ):
719
+ import _cloudpickle_testpkg .mod .submodule_three # noqa
720
+ from _cloudpickle_testpkg .mod import submodule_three as m3
721
+ assert not _is_importable (m3 )
722
+
723
+ # This module cannot be pickled using attribute lookup (as it does not
724
+ # have a `__module__` attribute like classes and functions.
725
+ assert not hasattr (m3 , '__module__' )
726
+ depickled_m3 = pickle_depickle (m3 , protocol = self .protocol )
727
+ assert depickled_m3 is not m3
728
+ assert m3 .f (1 ) == depickled_m3 .f (1 )
729
+
730
+ # Do the same for an importable dynamic submodule inside a dynamic
731
+ # module inside a file-backed module.
732
+ import _cloudpickle_testpkg .mod .dynamic_submodule .dynamic_subsubmodule as sm # noqa
733
+ assert _is_importable (sm )
734
+ assert pickle_depickle (sm , protocol = self .protocol ) is sm
735
+
736
+ expected = "cannot check importability of object instances"
737
+ with pytest .raises (TypeError , match = expected ):
738
+ _is_importable (object ())
700
739
701
740
def test_Ellipsis (self ):
702
741
self .assertEqual (Ellipsis ,
@@ -1181,11 +1220,14 @@ def func(x):
1181
1220
func .__module__ = None
1182
1221
1183
1222
class NonModuleObject (object ):
1223
+ def __ini__ (self ):
1224
+ self .some_attr = None
1225
+
1184
1226
def __getattr__ (self , name ):
1185
- # We whitelist func so that a _whichmodule(func, None) call returns
1186
- # the NonModuleObject instance if a type check on the entries
1187
- # of sys.modules is not carried out, but manipulating this
1188
- # instance thinking it really is a module later on in the
1227
+ # We whitelist func so that a _whichmodule(func, None) call
1228
+ # returns the NonModuleObject instance if a type check on the
1229
+ # entries of sys.modules is not carried out, but manipulating
1230
+ # this instance thinking it really is a module later on in the
1189
1231
# pickling process of func errors out
1190
1232
if name == 'func' :
1191
1233
return func
@@ -1200,7 +1242,7 @@ def __getattr__(self, name):
1200
1242
# Any manipulation of non_module_object relying on attribute access
1201
1243
# will raise an Exception
1202
1244
with pytest .raises (AttributeError ):
1203
- _is_dynamic ( non_module_object )
1245
+ _ = non_module_object . some_attr
1204
1246
1205
1247
try :
1206
1248
sys .modules ['NonModuleObject' ] = non_module_object
@@ -1966,20 +2008,9 @@ def check_positive(x):
1966
2008
1967
2009
def test_relative_import_inside_function (self ):
1968
2010
# Make sure relative imports inside round-tripped functions is not
1969
- # broken.This was a bug in cloudpickle versions <= 0.5.3 and was
2011
+ # broken. This was a bug in cloudpickle versions <= 0.5.3 and was
1970
2012
# re-introduced in 0.8.0.
1971
-
1972
- # Both functions living inside modules and packages are tested.
1973
- def f ():
1974
- # module_function belongs to mypkg.mod1, which is a module
1975
- from .mypkg import module_function
1976
- return module_function ()
1977
-
1978
- def g ():
1979
- # package_function belongs to mypkg, which is a package
1980
- from .mypkg import package_function
1981
- return package_function ()
1982
-
2013
+ f , g = relative_imports_factory ()
1983
2014
for func , source in zip ([f , g ], ["module" , "package" ]):
1984
2015
# Make sure relative imports are initially working
1985
2016
assert func () == "hello from a {}!" .format (source )
@@ -2028,7 +2059,7 @@ def f(a, /, b=1):
2028
2059
def test___reduce___returns_string (self ):
2029
2060
# Non regression test for objects with a __reduce__ method returning a
2030
2061
# string, meaning "save by attribute using save_global"
2031
- from . mypkg import some_singleton
2062
+ from _cloudpickle_testpkg import some_singleton
2032
2063
assert some_singleton .__reduce__ () == "some_singleton"
2033
2064
depickled_singleton = pickle_depickle (
2034
2065
some_singleton , protocol = self .protocol )
@@ -2100,7 +2131,7 @@ def test_pickle_dynamic_typevar_memoization(self):
2100
2131
assert depickled_T1 is depickled_T2
2101
2132
2102
2133
def test_pickle_importable_typevar (self ):
2103
- from . mypkg import T
2134
+ from _cloudpickle_testpkg import T
2104
2135
T1 = pickle_depickle (T , protocol = self .protocol )
2105
2136
assert T1 is T
2106
2137
@@ -2230,12 +2261,12 @@ def test_lookup_module_and_qualname_dynamic_typevar():
2230
2261
2231
2262
2232
2263
def test_lookup_module_and_qualname_importable_typevar ():
2233
- from . import mypkg
2234
- T = mypkg .T
2264
+ import _cloudpickle_testpkg
2265
+ T = _cloudpickle_testpkg .T
2235
2266
module_and_name = _lookup_module_and_qualname (T , name = T .__name__ )
2236
2267
assert module_and_name is not None
2237
2268
module , name = module_and_name
2238
- assert module is mypkg
2269
+ assert module is _cloudpickle_testpkg
2239
2270
assert name == 'T'
2240
2271
2241
2272
0 commit comments