|
| 1 | +# Configurable |
| 2 | + |
| 3 | +Marking a class as `@configurable` allows for the class to be configured via command line arguments or environment variables. |
| 4 | + |
| 5 | +This is done by analyzing the parameters to the class' `__init__` method and its `__dataclass_fields__` attribute if it is a `@dataclass`. |
| 6 | +As having a `@configurable` also be a `@dataclass` makes it easier to extend it, it is usually recommended to define a configurable as a `@dataclass`. |
| 7 | +Furthermore, using a dataclass allows more natural use of the `parameter()` definition. |
| 8 | + |
| 9 | +All [use-cases](use_case.md) are automatically configurable. |
| 10 | + |
| 11 | +## Parameter Definition |
| 12 | + |
| 13 | +Parameters can either be defined using type hints and default values, or by using the `parameter()` method. |
| 14 | + |
| 15 | +```python |
| 16 | +from dataclasses import dataclass |
| 17 | +from utils.configurable import configurable, parameter |
| 18 | + |
| 19 | + |
| 20 | +@configurable("inner-example", "Inner Example Configurable for documentation") |
| 21 | +@dataclass |
| 22 | +class InnerConfigurableExample: |
| 23 | + text_value: str |
| 24 | + |
| 25 | + |
| 26 | +@configurable("example", "Example Configurable for documentation") |
| 27 | +@dataclass |
| 28 | +class ConfigurableExample: |
| 29 | + inner_configurable: InnerConfigurableExample |
| 30 | + text_value: str |
| 31 | + number_value_with_description: int = parameter(desc="This is a number value", default=42) |
| 32 | + number_value_without_description: int = 43 |
| 33 | +``` |
| 34 | + |
| 35 | +As can be seen, the `parameter` method allows additionally setting a description for the parameter, while returning a `dataclasses.Field` to allow interoperability with existing tools. |
| 36 | + |
| 37 | +The type of a configurable parameter may only be a primitive type (`int`, `str`, `bool`) or another configurable. |
| 38 | + |
| 39 | +## Usage |
| 40 | + |
| 41 | +When a class is marked as `@configurable`, it can be configured via command line arguments or environment variables. |
| 42 | +The name of the parameter is automatically built from the field name (in the case of the example to be `text_value`, `number_with_description` and `number_value_without_description`). |
| 43 | + |
| 44 | +If a configurable has other configurable fields as parameters, they can be recursively configured, the name of the parameter is built from the field name and the field name of the inner configurable (here `inner_configurable.text_value`). |
| 45 | + |
| 46 | +These parameters are looked up in the following order: |
| 47 | + |
| 48 | +1. Command line arguments |
| 49 | +2. Environment variables (with `.` being replaced with `_`) |
| 50 | +3. .env file |
| 51 | +4. Default values |
| 52 | + |
| 53 | +When you have a simple use case as follows: |
| 54 | + |
| 55 | +```python |
| 56 | +from dataclasses import dataclass |
| 57 | +from usecases import use_case, UseCase |
| 58 | + |
| 59 | +@use_case("example", "Example Use Case") |
| 60 | +@dataclass |
| 61 | +class ExampleUseCase(UseCase): |
| 62 | + conf: ConfigurableExample |
| 63 | + |
| 64 | + def run(self): |
| 65 | + print(self.conf) |
| 66 | +``` |
| 67 | + |
| 68 | +You can configure the `ConfigurableExample` class as follows: |
| 69 | + |
| 70 | +```bash |
| 71 | +echo "conf.text_value = 'Hello World'" > .env |
| 72 | +export CONF_NUMBER_VALUE_WITH_DESCRIPTION=120 |
| 73 | +export CONF_INNER_CONFIGURABLE_TEXT_VALUE="Inner Hello World" |
| 74 | + |
| 75 | +python3 wintermute.py example --conf.inner_configurable.text_value "Inner Hello World Overwrite" |
| 76 | +``` |
| 77 | + |
| 78 | +This results in |
| 79 | + |
| 80 | +``` |
| 81 | +ConfigurableExample( |
| 82 | + inner_configurable=InnerConfigurableExample(text_value='Inner Hello World Overwrite'), |
| 83 | + text_value='Hello World', |
| 84 | + number_value_with_description=120, |
| 85 | + number_value_without_description=43 |
| 86 | +) |
| 87 | +``` |
| 88 | + |
0 commit comments