Skip to main content

Context manage-able module-environments for python!

Project description

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

Project details


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.2.tar.gz (18.9 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.2-py3-none-any.whl (18.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: module_env-1.1.2.tar.gz
  • Upload date:
  • Size: 18.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.7

File hashes

Hashes for module_env-1.1.2.tar.gz
Algorithm Hash digest
SHA256 6370279d3fdc514fa0d2d1d2735dfbb33a429fd1188929652d06d5165bf2355c
MD5 731a6ba7171d297a7dcab9bd4e0a5f93
BLAKE2b-256 32decf315d88ad76a793552e7a6b159a5c78002ce44c50e127378b8b652434cf

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for module_env-1.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7beebc7b980554c51f50d67354d1aea74359cf9d95632a71d9dd935184b2db24
MD5 e2389089113bfc582018165737169ffe
BLAKE2b-256 c80d21c8465ff9854211b617bc6ab8b583c2bffbc71a1b6266be671756222884

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page