Files and file location
Files for storing parameters
A Container class defines a root section and provides for behavior to load parameter values from files and, in case of settings, store updated values to files.
Currently, two formats are supported for persistent storage of parameters:
toml
and json
.
The toml
format is human-oriented, flexible, standardardized and not overly complex.
It supports comments, and hence parameters can be easily documented in a toml file.
However, when parameters are initialized by reading from a toml file, then changed
(because it concerns settings) and subsequently written to file again for persistance,
the comments will be lost. Therefore, it does not make much sense to add documenting
comments to a settings file. Because config parameters are read-only, comments in a
config file will not get lost and do make sense. Because of this, the default format for
storing config parameters is toml
. The name of a config file equals config.toml
by
default.
JSON is a standardized, lightweight data-interchange format that is easy for machines to
parse and generate. It is a bit less straightforward to document parameters in this
format, which makes it less human-oriented than toml
, but it is used widely for data
interchange between automated systems. Because of this, the default format for storing
settings parameters is json
. The name of a settings file equals settings.json
by
default.
The examples introduced in the previous chapter can for example be initialized with the following files.
# Use this file to set the config that you prefer by editing the file
name = "application specific name"
[section1]
# field1 has default value 0.5
field1 = -0.5
# field2 has default value 2
field2 = 22
{
"name": "the stored name",
"basics": {
"totals": 3
}
}
The field names of a Container class are found as:
- the root parameter names and the section names in the
toml
file - the member names of the root object in the
json
file
The field names of a Section class are found as:
- the parameter names inside the corresponding section in the
toml
file - the member names of the object that is the value of the name that represents the
section in the
json
file
The order of sections and/or fields in the file does not have to adhere to the order in which fields have been specified in the Container - or Section classes.
Presence of sections and/or fields in the files that are not defined in the classes goes by silently.
Fields or complete sections defined in the classes can be absent in the files as long as default values have been specified for all fields that have been left out. For more info on data validation click here
Location, name and type of the file
By default, the config and settings files are located in a subfolder of the home folder of
the user running the application. The default name of that subfolder is provided by the
default_foldername()
method and consists of a dot, followed by a name derived from your
container class: the word Config
/Settings
is removed, underscores are put in front of
capitals (except for the first letter) and all letters are made lower case. For example,
the Container class MyExampleConfig
by default will store its config in
~/.my_example/config.toml
. And MyExampleSettings
will default to a settings file
~/.my_example/settings.json
.
If you want the files to be stored in a different location and/or have a different name
and/or change the format, then you can use the method set_filepath
. If you invoke this
method but you have already instantiated the parameters via load()
or get()
, then you
most likely want to reload them. You can do so by setting an argument load=True
in
set_filepath
. If you invoke this function after parameters have been instantiated and
do not set load=True
, then a warning is printed. Example:
# the next statement sets the location, name and format of the settings file
# the argument can be eiter a string or a Path
MyExampleSettings.set_filepath(r"C:\ProgramData\testsettings.toml")
# the next statement loads the settings
MyExampleSettings.load()
# the next statement sets a new name for the settings file and reloads it
MyExampleSettings.set_filepath(r"C:\ProgramData\productionsettings.toml", load=True)
# the next statement resets the filepath to the default, doesn't load but generates a warning
MyExampleSettings.set_filepath("")
The extension of the file is used to select the format for parsing and hence has to be
either json
, JSON
, toml
or TOML
.
Setting the filepath via command-line arguments
A quite common scenario is to launch an application from the command-line and to specify the config file and/or settings file as argument(s). Convenience functions are available to support this using the argparse module from the standard library:
- a function
config_filepath_from_cli
is available that will define a command-line argument that takes exactly one additional argument, namely the config filepath. You have to specify the Config class when calling this function, and you may pass a parser (default: the main argument parser) and your own short option (default:"-c"
) and long option (default:"--config_filepath"
) and you may setload=True
(default:False
). If the option is indeed supplied when the application is launched, then the config filepath is set usingset_filepath
and the value forload
is passed into this function. - a function
settings_filepath_from_cli
is available that will define a command-line argument that takes exactly one additional argument, namely the settings filepath. You have to specify the Settings class when calling this function, and you may pass a parser (default: the main argument parser) and your own short option (default:"-s"
) and long option (default:"--settings_filepath"
) and you may setload=True
(default:False
). If the option is indeed supplied when the application is launched, then the config filepath is set usingset_filepath
and the value forload
is passed into this function. - a function
parameters_folderpath_from_cli
is also available and comes in handy when you have a config file and a settings file in the same folder. This function will define a command-line argument that takes exactly one additional argument, namely the path of the folder that holds both files. Note that this implies that the config- and settings file have to have the default filename. You have to specify both the Settings class and the Config class when calling this function, and you may pass a parser (default: the main argument parser) and your own short option (default:"-p"
) and long option (default:"--parameters_folderpath"
) and you may setload=True
(default:False
). If the option is indeed supplied when the application is launched, then the config and the settings filepath are set usingset_filepath
and the value forload
is passed into these functions.
from application_settings import config_filepath_from_cli
# The next line defines a cli argument "-c" and "--config_filepath"
# and specifies that the config should be loaded when it is specified
config_filepath_from_cli(MyExampleConfig, load=True)
# the application launch with config file spec could be something like:
# application_name -c C:\ProgramData\productionconfig.toml
from application_settings import settings_filepath_from_cli
# The next line defines a cli argument "-s" and "--settings_filepath"
# and specifies that the settings should be loaded when it is specified
settings_filepath_from_cli(MyExampleSettings, load=True)
# the application launch with settings file spec could be something like:
# application_name -s C:\ProgramData\productionsettings.json
from application_settings import parameters_folderpath_from_cli
# The next line defines a cli argument "-p" and "--parameters_folderpath"
# and specifies that the config and settings should be loaded when it is specified
parameters_folderpath_from_cli(MyExampleConfig, MyExampleSettings, load=True)
# the application launch with folder spec could be something like:
# application_name -p C:\ProgramData
It is good practice make a separate module that defines the container class and the
sections and to add both the convenience function for setting the filepath via the
cli and the load
statement in that module.
This may help to prevent initialization problems.
Handling FileNotFoundError
When loading a parameter file, you have a choice what should happen when the parameter file is not found in the location that has been specified:
- if you
load(throw_if_file_not_found = False)
, then theFileNotFoundError
is catched byapplication_settings
, an error message is generated and program flow is continued by trying to instantiate the config / settings using default values. If you have defined parameters without default values, a TypeError exception will be raised. - if you
load(throw_if_file_not_found = True)
, then theFileNotFoundError
exception is thrown and the application can decide how this situation should be handled.
The default value for throw_if_file_not_found
is False
, hence load()
will not throw
an exception when the parameter file is not found. Note that if you do not explicitly
use load
but rather implicitly call it via get()
or set_filepath()
, then this
default behavior will also be obtained.
Sharing parameters over different configs via file inclusion
Another common scenario is that you work with different configurations for your
application but these different configurations are partially the same. To prevent
inconsistencies and config duplication, it is desirable to be able to share the common
part. For this purpose, application_settings
provides a file inclusion mechanism
for configuration files.
Above, an example of such a config file was given. Suppose now that you want to create
several configurations that have a varying name
but they will share the parametrization
section1
. Then we can put that part in a file config_common.toml
and include that
file from the different configurations.
# Use this file to set the config that you prefer by editing the file
name = "application specific name"
__include__ = "./config_common.toml"
[section1]
# field1 has default value 0.5
field1 = -0.5
# field2 has default value 2
field2 = 22
The file inclusion mechanism has been kept simple; the following rules apply:
- The key to use for specifying a file to include is
__include__
; hence, this key is to be treated as a keyword and is not available as field name. - The value can be either a single path string or an array of path strings.
- File inclusion can only be specified at the top level, not inside a section.
- File inclusion is only available for configuration, not for settings.
- File inclusion can be nested, i.e., in the included config file one can again specify another file to include (albeit at the top level only).
- If the included file specifies a key that was already specified in the file that does the inclusion, then it is disregarded and the key-value pair of the latter file is kept.