@@ -105,6 +105,39 @@ def _save_and_remove_modules(names):
105105 return orig_modules
106106
107107
108+ _PARENT_ATTR_MISSING = object ()
109+
110+
111+ def _save_parent_attrs (names ):
112+ parent_attrs = {}
113+ prefixes = tuple (name + '.' for name in names )
114+ removed = {modname for modname in sys .modules
115+ if modname in names or modname .startswith (prefixes )}
116+ for modname in set (names ) | removed :
117+ parent_name , _ , attr = modname .rpartition ('.' )
118+ if not parent_name :
119+ continue
120+ if parent_name in removed or parent_name .startswith (prefixes ):
121+ continue
122+ if (parent := sys .modules .get (parent_name )) is None :
123+ continue
124+ parent_attrs [parent_name , attr ] = getattr (parent , attr , _PARENT_ATTR_MISSING )
125+ return parent_attrs
126+
127+
128+ def _restore_parent_attrs (parent_attrs ):
129+ for (parent_name , attr ), value in parent_attrs .items ():
130+ if (parent := sys .modules .get (parent_name )) is None :
131+ continue
132+ if value is _PARENT_ATTR_MISSING :
133+ try :
134+ delattr (parent , attr )
135+ except AttributeError :
136+ pass
137+ else :
138+ setattr (parent , attr , value )
139+
140+
108141@contextlib .contextmanager
109142def frozen_modules (enabled = True ):
110143 """Force frozen modules to be used (or not).
@@ -179,6 +212,7 @@ def import_fresh_module(name, fresh=(), blocked=(), *,
179212 fresh = list (fresh )
180213 blocked = list (blocked )
181214 names = {name , * fresh , * blocked }
215+ orig_parent_attrs = _save_parent_attrs (names )
182216 orig_modules = _save_and_remove_modules (names )
183217 for modname in blocked :
184218 sys .modules [modname ] = None
@@ -195,6 +229,7 @@ def import_fresh_module(name, fresh=(), blocked=(), *,
195229 finally :
196230 _save_and_remove_modules (names )
197231 sys .modules .update (orig_modules )
232+ _restore_parent_attrs (orig_parent_attrs )
198233
199234
200235class CleanImport (object ):
0 commit comments