Given a set of site-packages paths, return a "clean" sys.modules. When importing site, modules within `sys.modules` have their `__path__`s populated with additional paths as defined by `*-nspkg.pth` in `site-packages`, or alternately by distribution metadata such as `*.dist-
(
cls,
isolated_sys_path, # type: IsolatedSysPath
modules=None, # type: Optional[Mapping[str, ModuleType]]
)
| 294 | |
| 295 | @classmethod |
| 296 | def minimum_sys_modules( |
| 297 | cls, |
| 298 | isolated_sys_path, # type: IsolatedSysPath |
| 299 | modules=None, # type: Optional[Mapping[str, ModuleType]] |
| 300 | ): |
| 301 | # type: (...) -> Mapping[str, ModuleType] |
| 302 | """Given a set of site-packages paths, return a "clean" sys.modules. |
| 303 | |
| 304 | When importing site, modules within `sys.modules` have their `__path__`s populated with |
| 305 | additional paths as defined by `*-nspkg.pth` in `site-packages`, or alternately by |
| 306 | distribution metadata such as `*.dist-info/namespace_packages.txt`. This can possibly cause |
| 307 | namespace packages to leak into imports despite being scrubbed from `sys.path`. |
| 308 | |
| 309 | NOTE: This method mutates modules' `__path__` attributes in `sys.modules`, so this is |
| 310 | currently an irreversible operation. |
| 311 | """ |
| 312 | |
| 313 | is_venv = isolated_sys_path.is_venv |
| 314 | modules = modules or sys.modules |
| 315 | new_modules = {} |
| 316 | |
| 317 | for module_name, module in modules.items(): |
| 318 | # Tainted modules should be dropped. |
| 319 | module_file = getattr(module, "__file__", None) |
| 320 | if ( |
| 321 | # The `_virtualenv` module is a known special case provided by the virtualenv |
| 322 | # project. It should not be un-imported or else the virtualenv importer it installs |
| 323 | # for performing needed patches to the `distutils` stdlib breaks. |
| 324 | # |
| 325 | # See: |
| 326 | # + https://github.com/pex-tool/pex/issues/992 |
| 327 | # + https://github.com/pypa/virtualenv/pull/1688 |
| 328 | (not is_venv or module_name != "_virtualenv") |
| 329 | and module_file |
| 330 | and module_file not in isolated_sys_path |
| 331 | ): |
| 332 | TRACER.log("Dropping %s" % (module_name,), V=3) |
| 333 | continue |
| 334 | |
| 335 | module_path = getattr(module, "__path__", None) |
| 336 | # Untainted non-packages (builtin modules) need no further special handling and can |
| 337 | # stay. |
| 338 | if module_path is None: |
| 339 | new_modules[module_name] = module |
| 340 | continue |
| 341 | |
| 342 | # Unexpected objects, e.g. PEP 420 namespace packages, should just be dropped. |
| 343 | if not isinstance(module_path, list): |
| 344 | TRACER.log("Dropping %s" % (module_name,), V=3) |
| 345 | continue |
| 346 | |
| 347 | # Drop tainted package paths. |
| 348 | for k in reversed(range(len(module_path))): |
| 349 | if module_path[k] not in isolated_sys_path: |
| 350 | TRACER.log("Scrubbing %s.__path__: %s" % (module_name, module_path[k]), V=3) |
| 351 | module_path.pop(k) |
| 352 | |
| 353 | # The package still contains untainted path elements, so it can stay. |