Skip to content

Commit 78a9631

Browse files
committed
bpo-39336: Allow setattr to fail on modules which aren't assignable
Fix whitespace Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags:
1 parent 45cf5db commit 78a9631

File tree

3 files changed

+391
-341
lines changed

3 files changed

+391
-341
lines changed

Lib/importlib/_bootstrap.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,13 @@ def _find_and_load_unlocked(name, import_):
978978
if parent:
979979
# Set the module as an attribute on its parent.
980980
parent_module = sys.modules[parent]
981-
setattr(parent_module, name.rpartition('.')[2], module)
981+
child = name.rpartition('.')[2]
982+
try:
983+
setattr(parent_module, child, module)
984+
except AttributeError:
985+
msg = (f"Can't set child package '{child}' on "
986+
f"parent '{parent}'")
987+
_warnings.warn(msg, ImportWarning)
982988
return module
983989

984990

Lib/test/test_import/__init__.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import errno
44
import glob
55
import importlib.util
6+
import inspect
67
from importlib._bootstrap_external import _get_sourcefile
78
import marshal
89
import os
@@ -26,7 +27,7 @@
2627
temp_dir, DirsOnSysPath)
2728
from test.support import script_helper
2829
from test.test_importlib.util import uncache
29-
30+
from types import ModuleType
3031

3132
skip_if_dont_write_bytecode = unittest.skipIf(
3233
sys.dont_write_bytecode,
@@ -49,6 +50,8 @@ def _ready_to_import(name=None, source=""):
4950
# reverts or removes the module when cleaning up
5051
name = name or "spam"
5152
with temp_dir() as tempdir:
53+
if os.sep in name:
54+
os.makedirs(os.path.join(tempdir, name.rpartition(os.sep)[0]))
5255
path = script_helper.make_script(tempdir, name, source)
5356
old_module = sys.modules.pop(name, None)
5457
try:
@@ -1339,6 +1342,39 @@ def test_circular_from_import(self):
13391342
str(cm.exception),
13401343
)
13411344

1345+
def test_unwritable_module(self):
1346+
package = inspect.cleandoc('''
1347+
import sys
1348+
1349+
class MyMod(object):
1350+
__slots__ = ['__builtins__', '__cached__', '__doc__',
1351+
'__file__', '__loader__', '__name__',
1352+
'__package__', '__path__', '__spec__']
1353+
def __init__(self):
1354+
for attr in self.__slots__:
1355+
setattr(self, attr, globals()[attr])
1356+
1357+
1358+
sys.modules['spam'] = MyMod()
1359+
''')
1360+
1361+
init = os.path.join('spam', '__init__')
1362+
with _ready_to_import(init, package) as (name, path):
1363+
with open(os.path.join(os.path.dirname(path), 'x.py'), 'w+'):
1364+
pass
1365+
1366+
try:
1367+
import spam
1368+
with self.assertWarns(ImportWarning):
1369+
from spam import x
1370+
1371+
self.assertNotEqual(type(spam), ModuleType)
1372+
self.assertEqual(type(x), ModuleType)
1373+
with self.assertRaises(AttributeError):
1374+
spam.x = 42
1375+
finally:
1376+
del sys.modules['spam.x']
1377+
13421378

13431379
if __name__ == '__main__':
13441380
# Test needs to be a package, so we can do relative imports.

0 commit comments

Comments
 (0)