Constants and Configuration

Content

  1. Constants

    1. Hard Coded

    2. Conigurable

    3. Where to put constants?

  2. Configuration

    1. Config files

    2. Accessing configuration values

    3. Default Configuration

    4. Test Configuration

1. Constants

Constants are values that, once initialized, are never changed during the runtime of a program. In Python constants are assigned to variables with capital letters by convention, and vice versa, variables with capital letters are supposed to be constants.

In principle there are about four ways to define a constant’s value: - hard coding: the value is defined in the python code directly - argument: the value is taken from an execution argument - context: the value is derived from the environmental context of the execution, e.g., the current working directory or the date-time of execution start. - configuration: read from a file or database

In CLIMADA, we only use hard coding and configuration to assign values to constants.

1.A. Hard Coded

Hard coding constants is the preferred way to deal with strings that are used to identify objects or files.

[1]:
# suboptimal
my_dict = {'x': 4}
if my_dict['x'] > 3:
    msg = 'well, arh, ...'
msg
[1]:
'well, arh, ...'
[2]:
# good
X = 'x'
my_dict = {X: 4}
if my_dict[X] > 3:
    msg = 'yeah!'
msg
[2]:
'yeah!'
[3]:
# possibly overdoing it
X = 'x'
Y = "this doesn't mean that every string must be a constant"
my_dict = {X: 4}
if my_dict[X] > 3:
    msg = Y
msg
[3]:
"this doesn't mean that every string must be a constant"
[4]:
import pandas as pd
X = 'x'
df = pd.DataFrame({'x':[1,2,3], 'y':[4,5,6]})
try:
    df.X
except:
    from sys import stderr; stderr.write("this does not work\n")
df[X] # this does work but it's less pretty
df.x
this does not work
[4]:
0    1
1    2
2    3
Name: x, dtype: int64

1.B. Configurable

When it comes to absolute paths, it is urgently suggested to not use hard coded constant values, for the obvious reasons. But also relative paths can cause problems. In particular, they may point to a location where the user has not sufficient access permissions. In order to avoid these problems, all paths constants in CLIMADA are supposed to be defined through configuration.
→ paths must be configurable
The same applies to urls to external resources, databases or websites. Since they may change at any time, there addresses are supposed to be defined through configuration. Like this it will be possible to access them without the need of tampering with the source code or waiting for a new release.
→ urls must be configurable
Another category of constants that should go into the configuration file are system specifications, such as number of CPU’s available for CLIMADA or memory settings.
→ OS settings must be configurable

1.C. Where to put constants?

As a general rule, constants are defined in the module where they intrinsically belong to. If they belong equally to different modules though or they are meant to be used globally, there is the module climada.util.constants which is compiling constants CLIMADA wide.

2. Configuration

2.A. Configuration files

The proper place to define constants that a user may want (or need) to change without changing the CLIMADA installation are the configuration files.
These are files in json format with the name climada.conf. There is a default config file that comes with the installation of CLIMADA. But it’s possible to have several of them. In this case they are complementing one another.

CLIMADA looks for configuration files upon import climada. There are four locations to look for configuration files: - climada/conf, the installation directory - ~/climada/conf, the user’s default climada directory - ~/.config, the user’s configuration directory, - ., the current working directory

At each location, the path is followed upwards until a file called climada.conf is found or the root of the path is reached. Hence, if e.g., ~/climada/climada.conf is missing but ~/climada.conf is present, the latter would be read.

When two config files are defining the same value, the priorities are:
[..]/./climada.conf > ~/.config/climada.conf > ~/climada/conf/climada.conf > installation_dir/climada/conf/climada.conf

277e092809c74c99a3a2c93050769c7c

A configuration file is a JSON file, with the additional restriction, that all keys must be strings without a ‘.’ (dot) character .
For configuration values that belong to a particular module it is suggested to reflect the code repositories file structure in the json object. For example if a configuration for my_config_value that belongs to the module climada.util.dates_times is wanted, it would be defined as
{
  "util": {
    "dates_times": {
      "my_config_value": 42
    }
  }
}

Configuration string values can be referenced from other configuration values. E.g.

{
  "a": "x",
  "b": "{a}y"
}

In this example “b” is eventually resolved to “xy”.

2.B. Accessing configuration values

Configuration values can be accessed through the (constant) CONFIG from the climada module:

[5]:
from climada import CONFIG
[6]:
CONFIG.hazard
[6]:
{relative_cropyield: {local_data: {_val: {exposures.crop_production.local_data}}, filename_wheat_mask: mask_winter_and_spring_wheat_areas_phase3.nc4}, trop_cyclone: {random_seed: 54}, storm_europe: {forecast_dir: ./results/forecast/hazards}, test_data: .../climada/hazard/test/data}

The configuration itself and its attributes have the data type climada.util.config.Config

[7]:
CONFIG.__class__, CONFIG.hazard.trop_cyclone.random_seed.__class__
[7]:
(climada.util.config.Config, climada.util.config.Config)

The actual configuration values can be accessed as basic types (float, int, str), provided the definition is according to the respective data type:

[8]:
CONFIG.hazard.trop_cyclone.random_seed.int()
[8]:
54
[9]:
try:
    CONFIG.hazard.trop_cyclone.random_seed.str()
except Exception as e:
    from sys import stderr; stderr.write(f"cannot convert random_seed to str: {e}\n")
cannot convert random_seed to str: <class 'int'>, not str

However, configuration string values can be converted to pathlib.Path objects if they are pointing to a directory.

[ ]:
CONFIG.hazard.relative_cropyield.local_data.dir()

Note that converting a configuration string to a Path object like this will create the specified directory on the fly, unless dir is called with the parameter create=False.

2.C. Default Configuration

The conifguration file climada/conf/climada.conf contains the default configuration.
On the top level it has the following attributes - local_data: definition of main paths for accessing and storing CLIMADA related data - system: top directory, where (persistent) climada data is stored
default: ~/climada/data - demo: top directory for data that is downloaded or created in the CLIMADA tutorials
default: ~/climada/demo/data - save_dir: directory where transient (non-persistent) data is stored
default: ./results - log_level: minimum log level showed by logging, one of DEBUG, INFO, WARNING, ERROR or CRITICAL.
default: INFO - max_matrix_size: maximum matrix size that can be used, can be decreased in order to avoid memory issues
default: 100000000 (1e8) - exposures: exposures modules specific configuration - hazard: hazard modules specific configuration
[10]:
CONFIG.__dict__.keys()
[10]:
dict_keys(['_root', '_comment', 'local_data', 'engine', 'exposures', 'hazard', 'util', 'log_level', 'max_matrix_size', 'data_api', 'test_directory', 'test_data', 'disc_rates', 'impact_funcs', 'measures'])
When import climada is executed in a python script or shell, data files from the installation directory are copied to the location specified in the current configuration.
This happens only when climada is used for the first time with the current configuration. Subsequent execution will only check for presence of files and won’t overwrite existing files.

ab86af749a8c44b98688016ef17d64b9

Thus, the home directory will automatically be populated with a climada directory and several files from the repository when climada is used.
To prevent this and keep the home directory clean, create a config file ~/.config/climada.conf with customized values for local_data.system and local_data.demo.
As an example, a file with the following content would suppress creation of directories and copying of files during execution of CLIMADA code:
{
    "local_data": {
        "system": "/path/to/installation-dir/climada/data/system",
        "demo": "/path/to/installation-dir/climada/data/demo",
    },
}

2.D. Test Configuration

The configuration values for unit and integration tests are not part of the default configuration (2.C), since they are irrelevant for the regular CLIMADA user and only aimed for developers.
The default test configuration is defined in the climada.conf file of the installation directory. This file contains paths to files that are read during tests. If they are part of the GitHub repository, their path i.g. starts with the climada folder within the installation directory:
{
    "_comment": "this is a climada configuration file meant to supersede the default configuration in climada/conf during test",
    "test_directory": "./climada",
    "test_data": "{test_directory}/test/data",
    "disc_rates": {
        "test_data": "{test_directory}/entity/disc_rates/test/data"
    },
    ...
}
Obviously, the default test_directory is given as the relative path to ./climada. This is fine if (but only if) unit or integration tests are started from the installation directory, which is the case in the automated tests on the CI server.
Developers who intend to start a test from another working directory may have to edit this file and replace the relative path with the absolute path to the installation directory:
{
    "_comment": "this is a climada configuration file meant to supersede the default configuration in climada/conf during test",
    "test_directory": "/path/to/installation-dir/climada",
    "test_data": "{test_directory}/test/data",
    "disc_rates": {
        "test_data": "{test_directory}/entity/disc_rates/test/data"
    },
    ...
}