Welcome, guest | Sign In | My Account | Store | Cart

This is a rough analog to the import engine described in PEP 406. Here I've called it ImportState.The focus here is on using it as a context manager to limit changes to the import state to a block of code (in a with statement). Differences from PEP 406 are described below.

Python, 161 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"""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()

The import state defined here is not entirely true to PEP 406. One main thing the class is lacking is an import_module() method, which would be used any time to import a module relative to that import state. Also slightly different from PEP 406, ImportState.modules (sys.modules) is only a shallow copy, so the module objects themselves are shared between import states. In practice this should not be a big deal. We'll see how wrong I am. :)

Created by Eric Snow on Sun, 22 Apr 2012 (BSD)
Python recipes (4591)
Eric Snow's recipes (39)

Required Modules

  • (none specified)

Other Information and Tasks