"""import_state.py
A rough implementation of PEP 405. This module centers on manipulating
the normal Python import machinery through its defined state. Any other
approach, such as replacing builtins.__import__ is certainly legal, but
not supported here.
"""
__all__ = ['ImportState', 'default_import_state', 'globalstate']
import sys
import builtins
import site
import importlib
import _imp
from collections import namedtuple
class GlobalImportLock:
# no need for a generic ImportLock type, since all import states
# use the same lock
@property
def acquire(self):
_imp.acquire_lock()
@property
def release(self):
_imp.release_lock()
@property
def lock_held(self):
_imp.lock_held()
_ImportState = namedtuple('_ImportState', (
'modules',
'meta_path',
'path',
'path_hooks',
'path_importer_cache',
))
class ImportState(_ImportState):
"""A container for the import state (a la PEP 406).
The dictionary in sys.modules is a special case, since it is part
of the CPython interpreter state. Binding a different dict there
is problematic, since the import machinery may use the internal
reference to the original dict, rather than looking up sys.modules.
The consequence is that the _contents_ of sys.modules must be
swapped in and out, rather than simply binding something else there.
ImportState objects may be used as context managers, to activate the
state temporarily. During a with statement the dict in self.modules
may not reflect the actual state. However, it _will_ be correct
before and after the with statement.
"""
# all import states use the same lock
lock = GlobalImportLock()
def __init__(self, *args, **kwargs):
self._saved = None
def __enter__(self):
self.lock.acquire()
self.activate()
def __exit__(self, *args, **kwargs):
self.deactivate()
self.lock.release()
def copy(self):
"""Return a shallow copy of the import state."""
return type(self)(self.modules.copy(), self.meta_path[:],
self.path[:], self.path_hooks[:],
self.path_importer_cache.copy())
def activate(self, force=False):
"""Have the interpreter use this import state, saving the old."""
if self._saved is not None and not force:
raise TypeError("Already activated; try using a copy")
self._saved = _ImportState(
sys.modules.copy(), # saving away the contents
sys.meta_path,
sys.path,
sys.path_hooks,
sys.path_importer_cache,
)
#sys.modules = self.modules
sys.meta_path = self.meta_path
sys.path = self.path
sys.path_hooks = self.meta_path
sys.path_importer_cache = self.path_importer_cache
# accommodate sys.module's quirkiness
sys.modules.clear()
sys.modules.update(self.modules)
def deactivate(self):
"""Restore the import state saved when this one activated."""
if not self._saved:
raise TypeError("Not activated yet")
# sys.modules = self.modules
sys.meta_path = self._saved.meta_path
sys.path = self._saved.path
sys.path_hooks = self._saved.path_hooks
sys.path_importer_cache = self._saved.path_importer_cache
# accommodate sys.module's quirkiness
self.modules.clear()
self.modules.update(sys.modules)
sys.modules.clear()
sys.modules.update(self._saved.modules)
self._saved = None
def default_import_state(**overrides):
"""Return an ImportState with defaults to the initial import state."""
state = {
'modules': {},
'meta_path': [],
'path': site.getsitepackages(),
'path_hooks': [],
'path_importer_cache': {},
}
state.update(overrides)
return ImportState(**state)
class GlobalImportState(ImportState):
"""An ImportState that wraps the current state"""
# The underlying ImportState values will be ignored.
def __new__(cls):
return super(GlobalImportState, cls).__new__(cls, *([None]*5))
@property
def modules(self):
"""The cache of modules that have already been imported."""
return sys.modules
@property
def meta_path(self):
"""The PEP 302 finders queried before 'path' is traversed."""
return sys.meta_path
@property
def path(self):
"""The directories in which top-level packages are located."""
return sys.path
@property
def path_hooks(self):
"""The PEP 302 path importers that are queried for a path."""
return sys.path_hooks
@property
def path_importer_cache(self):
"""The cache of finders previously found through path_hooks."""
return sys.path_importer_cache
globalstate = GlobalImportState()