Skip to main content

ModuleEnv

Context manage-able module-environments for python! Have you ever needed multiple verions a python module installed? Wanted to temprarily pollute sys.path or import some module but were afraid of polluting the global environment?

Worry no more! ModuleEnv is like a runtime virtualenv for python modules! The best part, it's just a context manager, with ModuleEnv() is all you need!

Install

pip install module_env

Usage

This module exposes three classes:

  1. ModuleEnv
  2. InverseModuleEnv
  3. UsageError

ModuleEnv

This is the main class is ModuleEnv. This class provides a context manager for a module environment.

Construction: Upon construction, a ModuleEnv will save a copy of the current environment as its own. That is, the env the ModuleEnv instance uses is initialized as a copy of the environment at time of construction This does mean that if constructed within another ModuleEnv's context, it will copy that installed context.

Generally users will want to default construct this class, but this class does permit users to specify which attributes within sys are saved and restored during module setup and teardown. This can be done as follows:

standard = ModuleEnv()
custom = ModuleEnv(sys_attrs=("meta_path", "path_hooks", "path", "path_importer_cache"))

These attributes are assigned and copies during setup and teardown. sys.modules is automatically updated; though its underlying object remains the same.

Context Manager: The main use of this class is as a context manager. Entering the context applies the environment; exiting restores the previous environment. Module environments are not nestable! An example usage:

def multi():
    import multiprocessing

with ModuleEnv():
    multi()  # Import in a function so globals() / locals() is not affected
    assert "multiprocessing" in sys.modules  # Imported in env

assert "multiprocessing" not in sys.modules  # Not outside of env

.inverse(): Module environment contexts can temporarily be escaped without exiting a context manager via a child InverseModuleEnv. In this case, entering the context of a new ModuleEnv is permissible. For example:

def multi():
    import multiprocessing

with ModuleEnv() as env:
    with env.inverse():  # Restore global env
        multi()
    assert "multiprocessing" not in sys.modules  # Not imported in env

assert "multiprocessing" in sys.modules  # Imported global env

__getitem__: While modules imports a properly preserved by a ModuleEnv, it does not affect the variables in your scope; that is globals() and locals() remain unchanged. For this reason, it is recommended not to execute much code directly within a with ModuleEnv() but rather wrapped by a function. That way, after exiting the env, any 'no longer imported' modules are not still reference-able via scoped variables. Along these lines, entering an environment does not set up globals() nor locals() with your import modules. One way to handle this is to just call import on the module again, since the module itself is already imported, this should just be a variable assignment. ModuleEnvs expose a __getitem__ function which is functionally just __import__ for the given environment; this function is only usable when the environment is active. This is just syntactic sugar that might allow explicitness about which environment ought to be active at the time of the call, verifying this statement each use. For example, here are three ways to import a module:

with ModuleEnv() as env:
    # Three functionally identical ways of importing multiprocessing
    m1 = env["multiprocessing"]
    m2 = __import__("multiprocessing")
    import multiprocessing as m3
    assert m1 is m2 and m2 is m3

Thread Saftey: Editing sys attributes is inherently not thread-safe. If using in a multithreaded environment, keep this in mind and do not use ModuleEnv's concurrently in multiple threads.

InverseModuleEnv

This context-manager class allows for escaping a ModuleEnv context without exiting the context manager. Entering the context an InverModuleEnv restores the module environment to the environment the parent ModuleEnv has active. An InverseModuleEnv can exclusively invert the environment of the ModuleEnv which created it. Entering the context of a ModuleEnv when in the context of an InverseModuleEnv is allowed, as the InverseModuleEnv context between the two ModuleEnv functionally inverts the first, meaning the second ModuleEnv context is not actually nested

Constructing an InverseModuleEnv can only be done via a ModuleEnv's .inverse() function. The ModuleEnv responsible for creating the InverseModuleEnv is considered the parent. InverseModuleEnvs contexts may only be entered if within the parents' environment is active. Just like ModuleEnvs, InverseModuleEnvs expose .__getitem__.

InverseModuleEnvs themselves can be inverted via a call to .invert(). This will return a child ModuleEnv; just like any child, this child's context may only be entered when its parent context is active. A key point here is that given inv=env.invert(); env2=inv.invert(), env2 is a distinct object from env. Both apply the same environment once entered, but env2 is a child of inv and thus may only be entered when inv is active.

A usage example:

# Global environment
with ModuleEnv() as env:
    # 'env' environment
    with env.inverse() as inv:
        # Global environment
        with inv.inverse():  # Not the same object as 'env', but shares the same environment
            # 'env' environment
            pass
        with env:
            # 'env' environment
            pass
        with ModuleEnv() as env2:
            # 'env2' environment
            pass

Invalid uses examples:

env = ModuleEnv()
inv = env.inverse()
# with inv:  # INVALID
#   InverseModuleEnv contexts may only be entered if the parent env is active
with env, inv:
    # 'env' environment
    with ModuleEnv() as env2:
        # with inv:  # INVALID:
            # Only env2.invert() can invert env2!
        pass
    # with env.inverse().inverse():  # INVALID
        # A new env.inverse() is a different object than inv
        # Thus env.inverse().inverse() is not inv's child
        # Only children can invert their parent!

UsageError

This exception type is raised if a user misuses a ModuleEnv or InverseModuleEnv. For example, if a user attempts to nest one ModuleEnv within another.

Practical Example:

Consider two directory containing different versions of a module foo: foo_v1 and foo_v2:

from module_env import ModuleEnv
import sys

v1 = ModuleEnv()
with v1:
    sys.path.append("./foo_v1")
    import foo

sys.path.append("./foo_v2")
import foo

assert foo.__version__ == "2.0"
with env:
    assert foo.__version__ == "1.0"

Development

Tests

Tests are available in the ./tests directory. From the root directory, running them is as simple as:

pip install .
cd tests
python -m unittest

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

module_env-1.1.3.tar.gz (19.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

module_env-1.1.3-py3-none-any.whl (18.2 kB view details)

Uploaded Python 3

File details

Details for the file module_env-1.1.3.tar.gz.

File metadata

  • Download URL: module_env-1.1.3.tar.gz
  • Upload date:
  • Size: 19.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for module_env-1.1.3.tar.gz
Algorithm Hash digest
SHA256 cda02e9b952e3a5ed95bcca0f619f2a0c1547f8fbd94e9751e83b5d72dc145cf
MD5 93e38b8336763d6e47ab9182e3b1d699
BLAKE2b-256 2463e96f9b8c400fa9e2629f1a622de05011e0943cde4e6675f92165808c3f67

See more details on using hashes here.

File details

Details for the file module_env-1.1.3-py3-none-any.whl.

File metadata

  • Download URL: module_env-1.1.3-py3-none-any.whl
  • Upload date:
  • Size: 18.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for module_env-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 af67898a7d5d001fe8ef26040ef4d36b7ceef88c71deed0aa90ca711b53da42e
MD5 f2002e08187c75e719c5c2d4315aa895
BLAKE2b-256 6627fc3d3a499c0d4a274c5966b8a2dc009504843846104fc83123dfee4cd3ed

See more details on using hashes here.

Release history Release notifications | RSS feed

1.2.0

2 files

This release

1.1.3 This release

2 files

1.1.2

2 files

1.1.1

2 files

1.1.0

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page