I wrote in python a config module for getting values from the env. The env is set by the docker-compose file. I want to test the config module, so i want to unset the variables in the test before importing the module.
Code:
test_config.py:
import os
from unittest import TestCase
def test_without_env():
os.unsetenv('GITLAB_URL')
os.unsetenv('GITLAB_PROJECT_ID')
os.unsetenv('GITLAB_API_TOKEN')
os.unsetenv('JIRA_API_TOKEN')
os.unsetenv('JIRA_BOARD_ID')
os.unsetenv('JIRA_URL')
os.unsetenv('ROCKET_CHAT_API_TOKEN')
os.unsetenv('ROCKET_CHAT_USER_ID')
os.unsetenv('ROCKET_CHAT_URL')
import src.config as conf
assert conf.GITLAB_URL == 'URL'
assert conf.GITLAB_PROJECT_ID == '1234'
assert conf.GITLAB_API_TOKEN == 'TOKEN'
assert conf.JIRA_API_TOKEN == 'TOKEN'
assert conf.JIRA_BOARD_ID == '1234'
assert conf.JIRA_URL == 'URL'
assert conf.ROCKET_CHAT_API_TOKEN == 'TOKEN'
assert conf.ROCKET_CHAT_URL == 'URL'
assert conf.ROCKET_CHAT_USER_ID == 'USER_ID'
config.py:
import os
# GitLab
if 'GITLAB_URL' in os.environ: GITLAB_URL = os.environ['GITLAB_URL']
else: GITLAB_URL = 'URL'
if 'GITLAB_PROJECT_ID' in os.environ: GITLAB_PROJECT_ID = os.environ['GITLAB_PROJECT_ID']
else: GITLAB_PROJECT_ID = 1234
if 'GITLAB_API_TOKEN' in os.environ: GITLAB_API_TOKEN = os.environ['GITLAB_API_TOKEN']
else: GITLAB_API_TOKEN = 'TOKEN'
# Jira
if 'JIRA_URL' in os.environ: JIRA_URL = os.environ['JIRA_URL']
else: JIRA_URL = 'URL'
if 'JIRA_BOARD_ID' in os.environ: JIRA_BOARD_ID = os.environ['JIRA_BOARD_ID']
else: JIRA_BOARD_ID = 1234
if 'JIRA_API_TOKEN' in os.environ: JIRA_API_TOKEN = os.environ['JIRA_API_TOKEN']
else: JIRA_API_TOKEN = 'TOKEN'
# RocketChat
if 'ROCKET_CHAT_API_TOKEN' in os.environ: ROCKET_CHAT_API_TOKEN = os.environ['ROCKET_CHAT_API_TOKEN']
else: ROCKET_CHAT_API_TOKEN = 'TOKEN'
if 'ROCKET_CHAT_URL' in os.environ: ROCKET_CHAT_URL = os.environ['ROCKET_CHAT_URL']
else: ROCKET_CHAT_URL = 'URL'
if 'ROCKET_CHAT_USER_ID' in os.environ: ROCKET_CHAT_USER_ID = os.environ['ROCKET_CHAT_USER_ID']
else: ROCKET_CHAT_USER_ID = 'USER_ID'
(Values from variables and assert are changed, because of security 😀 )
Am I doing it right? Or what can i do? I think that docker is rewrite the env if it changed, just a guess…
Greetings,
DasMoorhuhn
It does not change, I tried os.unsetenv('VAR')
, os.system('unset VAR')
and del os.environ['VAR']
.
2
Answers
You are doing it right by using os.unsetenv(‘VAR’) or del os.environ[‘VAR’], but the main issue you’re encountering is due to the fact that environment variables are loaded when a module is imported in Python. Once the module has been imported, any changes you make to the environment won’t affect the values that were set at the time of the import.
You are trying to unset the environment variables and then import the module. However, if your module is imported somewhere before the unsetting, the environment variables are already set for that instance of the module. That’s why unsetting them doesn’t seem to have any effect.
You can get around this problem by reloading the module after you’ve changed the environment. Python provides a built-in function, importlib.reload(), which you can use to reload a previously imported module. The argument must be a module object, so you must import the module first, then delete the environment variables, and then reload the module.
Here is how you could rewrite your test:
In this code, os.environ.pop(‘VAR’, None) is used to remove the environment variable. This method removes the item with the specified key and returns the value. If the key is not found, it returns the default specified (in this case, None), so it won’t raise a KeyError if the environment variable doesn’t exist.
Then, importlib.reload(conf) is used to reload the module, re-executing the module-level code and therefore picking up the updated environment variables.
This should give you the desired result, assuming that there are no other lingering references to the old module object.
What is likely taking place there is that your
src.config
file is being imported elsewhere in your tests routine, before this test is run.Python will only read and execute a module once in a single process. Further
import
statements targeting the same module will just alias the name after theimport
statement to the already imported module.But the lines in the config module that do all the environment cheking and setting had already run, in a moment when the environment variables were present.
One way to get this working without changing things would be to use
importlib.reload
on the config.src module – this will force the re-execution of the module, and theconf.*
attributes will change.The main issue with this test is that you are not restoring the previous values of
conf
after running it – this will likely break further tests (and even, in the worst case, make tests run with values pointing to production resources used as defaults) .Anyway, you can overcome that by using a try…finally clause, or setup and tear-down methods as proper. (this is easier if you are using pytest instead of unittest)
Anyway, for a self-contained test function;
And last, but not least: once a Python program is started, the OS enviroment variables are copied to the
os.environ
dictionary, which mostly behaves as a regular Python dictionary (although it may be the target of specialized functions). So, we are making dicionary changing things, and forcing code to run, and it is ok, as long as the changes are needed in the same process.If you were running other processes in the container, nothing of this kind would change the env variables for them – the only way to affect the environment variables of a process is when you are the parent process that spawns a sub-process: then you can set the child environment.