Skip to content

Instantly share code, notes, and snippets.

@igniteflow
Last active December 12, 2023 16:43
Show Gist options
  • Save igniteflow/7267431 to your computer and use it in GitHub Desktop.
Save igniteflow/7267431 to your computer and use it in GitHub Desktop.
A Python context manager for setting/unsetting environment variables
from contextlib import contextmanager
"""
Usage:
with env_var('MY_VAR', 'foo'):
# is set here
# does not exist here
"""
@contextmanager
def env_var(key, value):
os.environ[key] = value
yield
del os.environ[key]
@twidi
Copy link

twidi commented Feb 25, 2015

So if you update an existing value, the previous is not restored.

I guess that you know, but as it's a top result in google, it may serves someone ;)

@sidprak
Copy link

sidprak commented Oct 24, 2016

I needed a version of this that restored old values. Now posted here: https://gist.github.com/sidprak/a3571943bcf6df0565c09471ab2f90b8.

@ArnaudPel
Copy link

ArnaudPel commented Apr 11, 2018

With some improvements:

  • support multiple keys
  • restore old values
  • rollback imune to crash in yield
from contextlib import contextmanager
@contextmanager
def environ(**kwargs):
    orig = dict()
    todel = []
    try:
        for k, newval in kwargs.items():
            if k in os.environ:
                orig[k] = os.environ[k]
            else:
                todel.append(k)
            os.environ[k] = newval
        yield
    finally:
        for k, oldval in orig.items():
            os.environ[k] = oldval
        for k in todel:
            del os.environ[k]

# Usage
import unittest
class TestEnvMgr(unittest.TestCase):
    def test_with_environ(self):
       with environ(USER='xxx', HOME='42', NONEXISTENT='?'):
            self.assertEqual(os.environ['USER'], 'xxx')
            self.assertEqual(os.environ['HOME'], '42')
            self.assertEqual(os.environ['NONEXISTENT'], '?')
        self.assertEqual(os.environ['USER'], user)
        self.assertEqual(os.environ['HOME'], home)
        self.assertTrue('NONEXISTENT' not in os.environ)

@jdemaeyer
Copy link

jdemaeyer commented Apr 12, 2018

Building on @ArnaudPel's version above, a little more concise (and using that existing environment vars can never be None, only ''):

from contextlib import contextmanager


@contextmanager
def environ(env):
    """Temporarily set environment variables inside the context manager and
    fully restore previous environment afterwards
    """
    original_env = {key: os.getenv(key) for key in env}
    os.environ.update(env)
    try:
        yield
    finally:
        for key, value in original_env.items():
            if value is None:
                del os.environ[key]
            else:
                os.environ[key] = value

Whether to use env or **env in the signature is a bit up to your taste, I prefer passing a dictionary rather than keyword args :)

@tbradley-sans
Copy link

This looks great, and seems like exactly what I need. But why not use the built-in patch.dict?
https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.dict

@m000
Copy link

m000 commented Sep 25, 2022

This further improves over @jdemaeyer's snippet. It allows using the context manager to temporarily unset an env variable (e.g. with environ(HOME=None)).

Also, because it uses pop(), it will not choke if an environment variable that was added by the context manager has been deleted by the wrapped code.

import os
from contextlib import contextmanager


@contextmanager
def environ(**env):
    originals = {k: os.environ.get(k) for k in env}
    for k, val in env.items():
        if val is None:
            os.environ.pop(k, None)
        else:
            os.environ[k] = val
    try:
        yield
    finally:
        for k, val in originals.items():
            if val is None:
                os.environ.pop(k, None)
            else:
                os.environ[k] = val

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment