```{currentmodule} typed_settings
```

# Post-processing Settings

Processors are applied after all settings have been loaded but before the loaded settings are converted to your settings class.

```{note}
This approach allows you to, e.g., use template strings where an `int` is expected –
as long as the template results in a valid `int`.
```

As with loaders, you can configure a list of processors that are applied one after another.

## Interpolation

*Interpolation* is basically a simple form of templating.
The stdlib's [configparser] uses old-school string formatting for this.
Typed Settings uses new style format strings:

[configparser]: https://docs.python.org/3/library/configparser.html#interpolation-of-values

```{code-block} python
:caption: example.py
:emphasize-lines: 7,13

import typed_settings as ts


@ts.settings
class Settings:
    a: str
    b: str = "{a}{a}"


settings = ts.load_settings(
    cls=Settings,
    loaders=ts.default_loaders("example", ["example.toml"]),
    processors=[ts.processors.FormatProcessor()],
)
print(settings)
```

```{code-block} toml
:caption: example.toml

[example]
a = "spam"
```

```{code-block} console
$ python example.py
Settings(a='spam', b='spamspam')
```

You can access nested settings like dictionary/list items but you have to leave out the quotes.
It is also okay to refer to values that are themselves format strings:

```{code-block} python
:caption: example.py
:emphasize-lines: 6,11-13,19

import typed_settings as ts


@ts.settings
class Nested:
    x: str = "{b}"


@ts.settings
class Settings:
    a: str = "{nested[x]}"
    b: str = "spam"
    c: str = "{d[1]}"
    d: list[str] = ["foo", "bar"]
    nested: Nested = Nested()


settings = ts.load_settings(
    cls=Settings,
    loaders=[],
    processors=[ts.processors.FormatProcessor()],
)
print(settings)
```
```{code-block} console
$ python example.py
Settings(a='spam', b='spam', c='bar', d=['foo', 'bar'], nested=Nested(x='spam'))
```

## Templating

If interpolation via format strings is not expressive enough,
you can also use templating via [Jinja].
This works very similarly to how [Ansible] templates its variables.

[ansible]: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#simple-variables
[jinja]: http://jinja.pocoo.org/

The package {program}`jinja2` is required for this feature:

```bash
$ pip install -U jinja2
```

You can now use Jinja variables inside your settings:

```{code-block} python
:caption: example.py
:emphasize-lines: 6-7,13

import typed_settings as ts


@ts.settings
class Settings:
    a: str = "spam"
    b: str = "{{ a }}{{ a }}"


settings = ts.load_settings(
    cls=Settings,
    loaders=[],
    processors=[ts.processors.JinjaProcessor()],
)
print(settings)
```
```{code-block} console
$ python example.py
Settings(a='spam', b='spamspam')
```

In contrast to format strings, you can access nested settings like attributes via `.`.
It is also okay to refer to values that are themselves templates:

```{code-block} python
:caption: example.py
:emphasize-lines: 6,11-13,19

import typed_settings as ts


@ts.settings
class Nested:
    x: str = "{% if b == 'spam'%}True{% else %}False{% endif %}"


@ts.settings
class Settings:
    a: bool = "{{ nested.x }}"
    b: str = "spam"
    nested: Nested = Nested()


settings = ts.load_settings(
    cls=Settings,
    loaders=[],
    processors=[ts.processors.JinjaProcessor()],
)
print(settings)
```
```{code-block} console
$ python example.py
Settings(a=True, b='spam', nested=Nested(x='True'))
```

## Loading Secrets via Helper Scripts

The {class}`~typed_settings.processors.UrlProcessor` allows you to reference and query external resources,
e.g., scripts or secrets in a given location.

You pass a mapping of URL schemas and handlers to it.
If a settings value starts with one of the configured schemas,
the corresponding handler is invoked and the original settings value is replaced with the handler's result.

```{hint}
The URL processor is not very strict in what "schemas" it accepts.
You can pass any string to it as the following example shows.
```

Let's create a settings class and define some exemplary default values,
and a corresponding {class}`~typed_settings.processors.UrlProcessor`:

```{code-block} python
:caption: example.py

import typed_settings as ts


@ts.settings
class Settings:
    a: str = "script://echo password"
    b: str = "helper: echo password"
    c: str = "raw://script://echo password"


# UrlProcessor takes a dict mapping schemas/prefixes to handler funcs:
url_processor = ts.processors.UrlProcessor({
    "raw://": ts.processors.handle_raw,
    "script://": ts.processors.handle_script,
    "helper: ": ts.processors.handle_script,
})
settings = ts.load_settings(
    cls=Settings,
    loaders=[],
    processors=[url_processor],
)
print(settings)
```

The first two values indicate that the script {code}`echo password` should be run and its output (`"password"`) be used as new settings value.

The `raw://` schema for the setting `c` works like an escape sequence if the literal value should be `script://echo password`.

Let's take a look at the resulting settings:

```{code-block} console
$ python example.py
Settings(a='password', b='password', c='script://echo password')
```
