2010-01-13 10 views
13

J'utilise le code suivant pour modifier temporairement les variables d'environnement.Python - modifie temporairement l'environnement du processus en cours

@contextmanager 
def _setenv(**mapping): 
    """``with`` context to temporarily modify the environment variables""" 
    backup_values = {} 
    backup_remove = set() 
    for key, value in mapping.items(): 
     if key in os.environ: 
      backup_values[key] = os.environ[key] 
     else: 
      backup_remove.add(key) 
     os.environ[key] = value 

    try: 
     yield 
    finally: 
     # restore old environment 
     for k, v in backup_values.items(): 
      os.environ[k] = v 
     for k in backup_remove: 
      del os.environ[k] 

Ce contexte with est principalement utilisé dans les cas de test. Par exemple,

def test_myapp_respects_this_envvar(): 
    with _setenv(MYAPP_PLUGINS_DIR='testsandbox/plugins'): 
     myapp.plugins.register() 
     [...] 

Ma question: est-il un moyen simple/élégante d'écrire _setenv? Je pensais réellement faire backup = os.environ.copy() et puis os.environ = backup .. mais je ne suis pas sûr si cela affecterait le comportement du programme (par exemple: si os.environ est référencé ailleurs dans l'interpréteur Python).

Répondre

20
_environ = dict(os.environ) # or os.environ.copy() 
try: 

    ... 

finally: 
    os.environ.clear() 
    os.environ.update(_environ) 
+1

Bonne. J'utilise '.copy()' au lieu de 'dict()' bien. –

+0

juste voulu j'avais besoin, merci! – nnachefski

+2

Ok, mais en cas d'échec (exception) pendant [...], les variables d'environnement ne sont pas restaurées: un 'try ... finally ...' est requis pour cela. –

21

Je vous suggère la mise en œuvre suivante:

import contextlib 
import os 


@contextlib.contextmanager 
def set_env(**environ): 
    """ 
    Temporarily set the process environment variables. 

    >>> with set_env(PLUGINS_DIR=u'test/plugins'): 
    ... "PLUGINS_DIR" in os.environ 
    True 

    >>> "PLUGINS_DIR" in os.environ 
    False 

    :type environ: dict[str, unicode] 
    :param environ: Environment variables to set 
    """ 
    old_environ = dict(os.environ) 
    os.environ.update(environ) 
    try: 
     yield 
    finally: 
     os.environ.clear() 
     os.environ.update(old_environ) 

EDIT: mise en œuvre plus avancée

Le gestionnaire de contexte ci-dessous peut être utilisé pour ajouter/supprimer/mettre à jour vos variables d'environnement:

import contextlib 
import os 


@contextlib.contextmanager 
def modified_environ(*remove, **update): 
    """ 
    Temporarily updates the ``os.environ`` dictionary in-place. 

    The ``os.environ`` dictionary is updated in-place so that the modification 
    is sure to work in all situations. 

    :param remove: Environment variables to remove. 
    :param update: Dictionary of environment variables and values to add/update. 
    """ 
    env = os.environ 
    update = update or {} 
    remove = remove or [] 

    # List of environment variables being updated or removed. 
    stomped = (set(update.keys()) | set(remove)) & set(env.keys()) 
    # Environment variables and values to restore on exit. 
    update_after = {k: env[k] for k in stomped} 
    # Environment variables and values to remove on exit. 
    remove_after = frozenset(k for k in update if k not in env) 

    try: 
     env.update(update) 
     [env.pop(k, None) for k in remove] 
     yield 
    finally: 
     env.update(update_after) 
     [env.pop(k) for k in remove_after] 

Utilisation Exemples:

>>> with modified_environ('HOME', LD_LIBRARY_PATH='/my/path/to/lib'): 
...  home = os.environ.get('HOME') 
...  path = os.environ.get("LD_LIBRARY_PATH") 
>>> home is None 
True 
>>> path 
'/my/path/to/lib' 

>>> home = os.environ.get('HOME') 
>>> path = os.environ.get("LD_LIBRARY_PATH") 
>>> home is None 
False 
>>> path is None 
True 
+5

Pour les visiteurs de cette vieille question, je ne vois pas de défauts évidents dans cette réponse et il est plus complet et utile que l'original. – KobeJohn

+0

Cela devrait faire partie de python - ou quelque chose. Il est désagréable - mais parfois nécessaire - de manipuler l'environnement pour effectuer des tests, et cela peut sérieusement casser, invalider ou fnorder des tests situés en aval des fonctions de test env-messing :( – Chris

+0

C'est la meilleure réponse :) –

0

Pour les tests unitaires, je préfère utiliser une fonction de décorateur avec des paramètres optionnels. De cette façon, je peux utiliser les valeurs d'environnement modifiées pour une fonction de test complète. Le décorateur ci-dessous restaure aussi les valeurs de l'environnement d'origine au cas où la fonction déclenche une exception:

import os 

def patch_environ(new_environ=None, clear_orig=False): 
    if not new_environ: 
     new_environ = dict() 

    def actual_decorator(func): 
     from functools import wraps 

     @wraps(func) 
     def wrapper(*args, **kwargs): 
      original_env = dict(os.environ) 

      if clear_orig: 
       os.environ.clear() 

      os.environ.update(new_environ) 
      try: 
       result = func(*args, **kwargs) 
      except: 
       raise 
      finally: # restore even if Exception was raised 
       os.environ = original_env 

      return result 

     return wrapper 

    return actual_decorator 

Utilisation dans les tests unitaires:

class Something: 
    @staticmethod 
    def print_home(): 
     home = os.environ.get('HOME', 'unknown') 
     print("HOME = {0}".format(home)) 


class SomethingTest(unittest.TestCase): 
    @patch_environ({'HOME': '/tmp/test'}) 
    def test_environ_based_something(self): 
     Something.print_home() # prints: HOME = /tmp/test 

unittest.main() 
0

En utilisant l'essentiel ici, vous pouvez sauvegarder/restaurer la portée locale, mondiale variables et variables d'environnement: https://gist.github.com/earonesty/ac0617a5672ae1a41be1eaf316dd63e4

import os 
from varlib import vartemp, envtemp 

x = 3 
y = 4 

with vartemp({'x':93,'y':94}): 
    print(x) 
    print(y) 
print(x) 
print(y) 

with envtemp({'foo':'bar'}): 
    print(os.getenv('foo')) 

print(os.getenv('foo')) 

Ce sorties:

93 
94 
3 
4 
bar 
None