{"id":19630628,"url":"https://github.com/zwimer/module_env","last_synced_at":"2026-02-12T03:48:04.209Z","repository":{"id":64812419,"uuid":"578420380","full_name":"zwimer/module_env","owner":"zwimer","description":"Context manage-able module-environments for python!","archived":false,"fork":false,"pushed_at":"2024-08-26T06:08:39.000Z","size":32,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-18T08:50:54.397Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zwimer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-12-15T02:28:06.000Z","updated_at":"2024-08-26T06:08:42.000Z","dependencies_parsed_at":"2025-04-28T06:42:29.236Z","dependency_job_id":null,"html_url":"https://github.com/zwimer/module_env","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/zwimer/module_env","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zwimer%2Fmodule_env","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zwimer%2Fmodule_env/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zwimer%2Fmodule_env/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zwimer%2Fmodule_env/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zwimer","download_url":"https://codeload.github.com/zwimer/module_env/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zwimer%2Fmodule_env/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266420073,"owners_count":23925877,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T12:04:44.193Z","updated_at":"2026-02-12T03:47:59.191Z","avatar_url":"https://github.com/zwimer.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ModuleEnv\n\nContext manage-able module-environments for python!\nHave you ever needed multiple verions a python module installed?\nWanted to temprarily pollute `sys.path` or `import` some module but were afraid of polluting the global environment?\n\nWorry no more! `ModuleEnv` is like a runtime virtualenv for python modules!\nThe best part, it's just a context manager, `with ModuleEnv()` is all you need!\n\n### Install\n```bash\npip install module_env\n```\n\n# Usage\n\nThis module exposes three classes:\n1. `ModuleEnv`\n1. `InverseModuleEnv`\n1. `UsageError`\n\n#### ModuleEnv\n\nThis is the main class is `ModuleEnv`.\nThis class provides a context manager for a module environment.\n\n_Construction_: Upon construction, a `ModuleEnv` will save a copy of the current environment as its own.\nThat is, the env the `ModuleEnv` instance uses is initialized as a copy of the environment at time of construction\nThis does mean that if constructed within another `ModuleEnv`'s context, it will copy that installed context.\n\nGenerally users will want to default construct\nthis class, but this class does permit users to specify\nwhich attributes within `sys` are saved and restored during module setup and teardown.\nThis can be done as follows:\n```python\nstandard = ModuleEnv()\ncustom = ModuleEnv(sys_attrs=(\"meta_path\", \"path_hooks\", \"path\", \"path_importer_cache\"))\n```\nThese attributes are assigned and copies during setup and teardown.\n`sys.modules` is automatically updated; though its underlying object remains the same.\n\n_Context Manager_: The main use of this class is as a context manager.\nEntering the context applies the environment; exiting restores the previous environment.\nModule environments are not nestable!\nAn example usage:\n```python\ndef multi():\n    import multiprocessing\n\nwith ModuleEnv():\n    multi()  # Import in a function so globals() / locals() is not affected\n    assert \"multiprocessing\" in sys.modules  # Imported in env\n\nassert \"multiprocessing\" not in sys.modules  # Not outside of env\n```\n\n`.inverse()`: Module environment contexts can temporarily be escaped\nwithout exiting a context manager via a child `InverseModuleEnv`.\nIn this case, entering the context of a new `ModuleEnv` is permissible.\nFor example:\n```python\ndef multi():\n    import multiprocessing\n\nwith ModuleEnv() as env:\n    with env.inverse():  # Restore global env\n        multi()\n    assert \"multiprocessing\" not in sys.modules  # Not imported in env\n\nassert \"multiprocessing\" in sys.modules  # Imported global env\n```\n\n`__getitem__`: While modules imports a properly preserved by a `ModuleEnv`, it does not affect the variables in\nyour scope; that is `globals()` and `locals()` remain unchanged.\nFor this reason, it is recommended not to execute much code directly\nwithin a `with ModuleEnv()` but rather wrapped by a function.\nThat way, after exiting the env, any 'no longer imported' modules are not still reference-able via scoped variables.\nAlong these lines, entering an environment does not set up `globals()` nor `locals()` with your import modules.\nOne way to handle this is to just call `import` on the module again, since the module itself is already imported,\nthis should just be a variable assignment.\n`ModuleEnv`s expose a `__getitem__` function which is functionally just `__import__` for the given environment;\nthis function is only usable when the environment is active.\nThis is just syntactic sugar that might allow explicitness about which environment ought to be active\nat the time of the call, verifying this statement each use.\nFor example, here are three ways to import a module:\n```python\nwith ModuleEnv() as env:\n    # Three functionally identical ways of importing multiprocessing\n    m1 = env[\"multiprocessing\"]\n    m2 = __import__(\"multiprocessing\")\n    import multiprocessing as m3\n    assert m1 is m2 and m2 is m3\n```\n\n_Thread Saftey_: Editing `sys` attributes is inherently not thread-safe.\nIf using in a multithreaded environment, keep this in mind\nand do not use `ModuleEnv`'s concurrently in multiple threads.\n\n#### InverseModuleEnv\nThis context-manager class allows for escaping a `ModuleEnv` context without exiting the context manager.\nEntering the context an `InverModuleEnv` restores the module\nenvironment to the environment the parent `ModuleEnv` has active.\nAn `InverseModuleEnv` can exclusively invert the environment of the `ModuleEnv` which created it.\nEntering the context of a `ModuleEnv` when in the context of an `InverseModuleEnv` is allowed, as the\n`InverseModuleEnv` context between the two `ModuleEnv` functionally inverts the first,\nmeaning the second `ModuleEnv` context is not actually nested\n\nConstructing an `InverseModuleEnv` can _only_ be done via a `ModuleEnv`'s `.inverse()` function.\nThe `ModuleEnv` responsible for creating the `InverseModuleEnv` is considered the parent.\n`InverseModuleEnv`s contexts may only be entered if within the parents' environment is active.\nJust like `ModuleEnv`s, `InverseModuleEnv`s expose `.__getitem__`.\n\n`InverseModuleEnv`s themselves can be inverted via a call to `.invert()`.\nThis will return a child `ModuleEnv`; just like any child, this\nchild's context may only be entered when its parent context is active.\nA key point here is that given `inv=env.invert(); env2=inv.invert()`, `env2` is a distinct object from `env`.\nBoth apply the same environment once entered, but `env2` is a\nchild of `inv` and thus may only be entered when `inv` is active.\n\nA usage example:\n```python\n# Global environment\nwith ModuleEnv() as env:\n    # 'env' environment\n    with env.inverse() as inv:\n        # Global environment\n        with inv.inverse():  # Not the same object as 'env', but shares the same environment\n            # 'env' environment\n            pass\n        with env:\n            # 'env' environment\n            pass\n        with ModuleEnv() as env2:\n            # 'env2' environment\n            pass\n```\n\nInvalid uses examples:\n```python\nenv = ModuleEnv()\ninv = env.inverse()\n# with inv:  # INVALID\n#   InverseModuleEnv contexts may only be entered if the parent env is active\nwith env, inv:\n    # 'env' environment\n    with ModuleEnv() as env2:\n        # with inv:  # INVALID:\n            # Only env2.invert() can invert env2!\n        pass\n    # with env.inverse().inverse():  # INVALID\n        # A new env.inverse() is a different object than inv\n        # Thus env.inverse().inverse() is not inv's child\n        # Only children can invert their parent!\n```\n\n#### UsageError\nThis exception type is raised if a user misuses a `ModuleEnv` or `InverseModuleEnv`.\nFor example, if a user attempts to nest one ModuleEnv within another.\n\n### Practical Example:\n\nConsider two directory containing different versions of a module `foo`: `foo_v1` and `foo_v2`:\n```python\nfrom module_env import ModuleEnv\nimport sys\n\nv1 = ModuleEnv()\nwith v1:\n    sys.path.append(\"./foo_v1\")\n    import foo\n\nsys.path.append(\"./foo_v2\")\nimport foo\n\nassert foo.__version__ == \"2.0\"\nwith env:\n    assert foo.__version__ == \"1.0\"\n```\n\n# Development\n\n### Tests\n\nTests are available in the `./tests` directory.\nFrom the root directory, running them is as simple as:\n```bash\npip install .\ncd tests\npython -m unittest\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzwimer%2Fmodule_env","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzwimer%2Fmodule_env","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzwimer%2Fmodule_env/lists"}