
Habrapitonam hello!
From time to time I come across development patterns that exist not because they solve a problem well, but because it is done in the popular framework X, therefore, many people think that this is good.
')
Now I want to celebrate the “all settings - in settings.py” pattern. It is clear that he gained popularity thanks to Django. I now and again meet in projects that are not tied to this framework the same story: a large code base, small, pretty unrelated components, and here you are: all together from arbitrary places climb into the magic nedomodul settings for their constants.
So, why is this approach in my opinion disgusting.
Problems with cascading settings
In real-life projects, as a rule, you need at least three sets of settings: to drive a project to localhost, to launch unittest, and to turn everything on combat servers. In this case, most of the settings usually coincide in all cases, and some are different.
For example, you use MongoDB as storage. In general, you need to
my_project
to it on localhost and use a DB called
my_project
. However, to launch unittest, you need to take DB with a different name so as not to hurt the combat data: say,
unittests
. And in the case of production, you need to connect not to localhost, but to a well-defined IP, to a server sent under Mong.
So how, depending on the external
settings.MONGODB_ADDRESS
of settings.py should take on different values? Usually a voodoo-construction at the end consisting of
__import__
,
__dict__
,
vars()
,
try/except ImportError
, which tries to add and block the namespace with all the giblets of another module like settings_local.py.
The fact that it is additionally necessary to load
_local.py
is specified either by a hardcode or through an environment variable. In any case, so that the same unit tests, for example, enable their settings only at the time of launch, you have to dance with a tambourine and break the Zen of Python: Explicit is better than implicit.
In addition, this solution involves another problem, described below.
Executable code
To store settings in the form of an executable py-code is creepy. In fact, the entire pattern, apparently, originally appeared as a supposedly simple and elegant solution: “Why do we need some cfg parsers if you can do everything right on the python? And there are more opportunities! ” In scenarios a little harder than a trivial solution turns sideways. Consider, for example, such a snippet:
Do you understand what the problem is? The fact that we have overlapped the value of
PROJECT_HOSTNAME
absolutely on the drum for the total value of
SOME_JOB_COMMAND
. We could grit our teeth with copying the definition of
SOME_JOB_COMMAND
after overlapping, but even this is not possible:
BASE_PATH
is in another module. Copy-paste it too? Is it too?
I'm not saying that the executable code as a configuration can simply lead to the difficult to
ImportError
when the application starts in a new environment.
Therefore, I am sure that flies should be separately, cutlets separately: the basic values ​​in the text file, calculated in the py-module.
High-coupling
A good project is one project that can be disassembled into small cubes, and each cube is put on github as a full-fledged open-source project.
When everything is the same, but with one BUT: “be so kind as to have settings.py in the root of the project and so that it contains the settings
FOO_BAR
,
FOO_BAZ
and
FOO_QUX
” it looks like something absurd, isn't it? And when something sounds ridiculous, it usually means that there are situations in which this nonsense is auditioned.
In our case, the example does not force itself to invent for a long time. Let our application work with the VKontakte API, and we have something like
VKontakteProfileCache
, which uses
settings.VK_API_KEY
and
settings.VK_API_SECRET
in the forehead. Well used and enjoyed, and then again, and our project should start working immediately with several VKontakte applications. And everything,
VKontakteProfileCache
designed so that it works only with one pair of credentials.
Therefore, slimmer and more expedient to never ever access the settings module directly. Let all consumers accept the necessary settings through the parameters of the constructor, through the dependency injection framework, whatever you like, but not directly. And let the concrete settings let them pull out the very lowest level, like code in
if __name__ == '__main__'
. And how could he take them - his personal problems. With this approach, unit-testing is also extremely simplified: with what settings you need to run, with those we create.
Possible Solution
So, the “settings.py” pattern was covered with mud. I feel better, thank you. Now about a possible solution. I have used a similar approach in several projects and find it convenient and devoid of the problems listed.
Settings stored in text ini-style files. For parsing,
we use
ConfigObj : it has richer possibilities in comparison with the standard ConfigParser, in particular, it is very easy to make cascades with it.
In the project, we create the base settings file
default_settings.cfg
with all possible settings and their values ​​with a reasonable default.
We create the utils.config module with functions like
configure_from_files()
,
configure_for_unittests()
, which return an object with settings for different situations.
configure_from_files()
organizes a cascading file search:
default_settings.cfg
,
~/.my-project.cfg
,
/etc/my-project.cfg
and probably somewhere else. It all depends on the project.
The calculated settings are evaluated by the last step of the assembly of the configuration object.
The module itself is only used for launching processes or tests. All classes interested in the settings receive ready-made values ​​via injections, that is, they do not communicate with the settings directly. In fact, it is not always convenient when the darkness is still better to transfer the entire configuration object, but this does not negate the fact that it needs
to be
transmitted - no appeal to the forehead.
Perhaps I have written too much about such a “trifle” as settings. But if at least someone, after reading, thinks about blindly copying a far from perfect approach to something and makes it better, simpler, more fun, I will consider the mission of this post accomplished.