I have been working with a project for which I give the choice of using two backends (backends 1 and 2, let's say) following what they do in this project. However, that project counts on having already defined environment variables for deciding which backend to use before executing the code. It would not be the case for the code I'm writing.
I would like to know if there is any alternative to use environment variables in this scenario so that, on execution time, I can load one backend or another depending on the value of a variable. The general structure of my project is as follows:
I thought of directly setting the environment variable directly in the python code (os.environ['NAME_OF_ENV_VARIABLE'] = 'BACKEND 1'
), but that feels potentially insecure and I really dislike the idea, even if the variable name is... unique. Given this need, I would like to know if it is possible to have some kind of variable spanning different files so that, when I do import a module, the __init__.py
file can disambiguate between the backends.
PS: Maybe what I'm doing makes no sense whatsoever.
[UPDATE] Some more information about the problem, reduced to its minimal extension. My main file processes some data and is the following:
from argparse import ArgumentParser
from utils.loader import load_data
from utils.do import do_stuff
def main(config):
data = load_data(config)
do_stuff(config, data)
if __name__ == '__main__':
# Retrieve input data
parser = ArgumentParser()
parser.add_argument('--backend', type=str, default='backend 1', help='backend to use')
inputs = parser.parse_args()
config = "backend 1" if inputs.backend == "1" else "backend 2"
# Call main function
main(config)
The data loader load_data(config)
I guess is not important for this. Then, the file containing do_stuff(data)
is the following:
import backend
def do_stuff(config, data):
# Do some really important stuff that is coded in backend 1 and backend 2
a = backend.do_something(data)
print(a)
It simply loads the backend (!!!) and does something. The do_stuff(data)
function itself does something coded in either backend 1 or backend 2:
def do_something(data):
data.values = "Value obtained through functions of 'BACKEND 1' (same function names and inputs, different backends used)"
and
def do_something(data):
data.values = "Value obtained through functions of 'BACKEND 2' (same function names and inputs, different backends used)"
Finally, the backend module has in itself the following __init__.py
file:
from .load_backend import do_something
Which loads from the load_backend.py
file, which simply disambiguates the backend given an environmental variable:
from __future__ import absolute_import
from __future__ import print_function
import os
import sys
# Default backend: backend 1
if 'ENVIRONMENT_VARIABLE' in os.environ:
_BACKEND = os.environ['ENVIRONMENT_VARIABLE']
else:
_BACKEND = 'backend 1'
# Import backend functions.
if _BACKEND == "backend 1":
sys.stderr.write('Using backend 1\n')
from .backend_1 import *
elif _BACKEND == "backend 2":
sys.stderr.write('Using backend 2\n')
from .backend_2 import *
else:
raise ValueError('Unable to import backend : ' + str(_BACKEND))
def backend():
"""Publicly accessible method
for determining the current backend.
# Returns
String, the name of the backend
# Example
```python
>>> backend.backend()
'backend 1'
```
"""
return _BACKEND
What I want is to reduce this last environment variable with anything else, but I don't know what can I use.
Like @DanielRoseman asked, I would just pass the backend argument around. For example in load_backend
, while changing your code as litte as possible:
from __future__ import absolute_import
from __future__ import print_function
import os
import sys
def backend(backend):
"""Returns the wanted backend module"""
# Import backend functions.
if backend == "backend 1":
sys.stderr.write('Using backend 1\n')
from . import backend_1 as backend_module
elif backend == "backend 2":
sys.stderr.write('Using backend 2\n')
from . import backend_2 as backend_module
else:
raise ValueError('Unable to import backend : ' + str(_BACKEND))
return backend_module
An improvement could be to use importlib
to dynamically import the backend and move the magic strings to a constant:
...
import importlib
BACKENDS = {
"backend 1": "backend_1",
"backend 2": "backend_2"
}
def load_backend(backend):
try:
module = importlib.import_module(
BACKENDS[backend]
)
except KeyError:
raise ImportError('Unable to import backend : %s' % backend)
sys.stderr.write('Using %s\n' % backend)
return module
So you can do this in the do_stuff
file:
import load_backend
def do_stuff(config, data):
# Do some really important stuff that is coded in backend 1 and backend 2
backend = load_backend.backend(config)
a = backend.do_something(data)
print(a)
Another way to go about this is to use a singleton pattern, where you set the backend variable once (and other settings you want broadly available):
in a settings.py
or whereever:
class SettingSingleton(object):
_backend = None
def __new__(cls, backend=None, *args, **kwargs):
cls._backend = cls._backend or backend
return super(SettingsSingleton, cls).__new__(cls, *args, **kwargs)
@property
def backend(self):
return self._backend
You can initialize that in the main.
from argparse import ArgumentParser
from utils.loader import load_data
from utils.do import do_stuff
from settings import SettingSingleton
def main(config):
SettingsSingleton(backend=config)
data = load_data(config)
do_stuff(config, data)
...
Now you can do something like:
from __future__ import absolute_import
from __future__ import print_function
import os
import sys
from settings import SettingsSingleton
_BACKEND = SettingsSingleton().backend
# Import backend functions.
if _BACKEND == "backend 1":
sys.stderr.write('Using backend 1\n')
from .backend_1 import *
elif _BACKEND == "backend 2":
sys.stderr.write('Using backend 2\n')
from .backend_2 import *
else:
raise ValueError('Unable to import backend : ' + str(_BACKEND))
Downside to this is that it is somewhat implicit.