File: usage.md

package info (click to toggle)
python-maison 2.0.0-3
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 464 kB
  • sloc: python: 816; makefile: 17; sh: 5
file content (214 lines) | stat: -rw-r--r-- 5,257 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# Usage

Suppose a `pyproject.toml` file lives in the user's directory:

```toml
[tool.acme]
foo = "bar"
```

## Retrieving values

`UserConfig` objects have a `values` property that behaves as a dict which
allows config values to be retrieved:

```python
>>> from maison import UserConfig
>>> config = UserConfig(package_name="acme")
>>> config.values
"{'foo': 'bar'}"
>>> config.values["foo"]
'bar'
>>> "baz" in config.values
False
>>> config.values.get("baz", "qux")
'qux'
```

## Source files

By default, `maison` will look for a `pyproject.toml` file. If you prefer to look
elsewhere, provide a `source_files` list to `UserConfig` and `maison` will select the
first source file it finds from the list.

```python
from maison import UserConfig

config = UserConfig(
  package_name="acme",
  source_files=["acme.ini", "pyproject.toml"]
)

print(config.path)
#> PosixPath(/path/to/acme.ini)
```

```{caution}
Currently only `.toml` and `.ini` files are supported. For `.ini` files,
`maison` assumes that the whole file is relevant. For `pyproject.toml` files,
`maison` assumes that the relevant section will be in a
`[tool.{package_name}]` section. For other `.toml` files `maison` assumes the whole
file is relevant.
```

To verify which source config file has been found, `UserConfig` exposes a
`path` property:

```python
>>> config.path
PosixPath('/path/to/pyproject.toml')
```

The source file can either be a filename or an absolute path to a config:

```python
from maison import UserConfig

config = UserConfig(
  package_name="acme",
  source_files=["~/.config/acme.ini", "pyproject.toml"]
)

print(config.path)
#> PosixPath(/Users/tom.jones/.config/acme.ini)
```

## Merging configs

`maison` offers support for merging multiple configs. To do so, set the `merge_configs`
flag to `True` in the constructor for `UserConfig`:

```python
from maison import UserConfig

config = UserConfig(
  package_name="acme",
  source_files=["~/.config/acme.toml", "~/.acme.ini", "pyproject.toml"],
  merge_configs=True
)

print(config.path)
"""
[
  PosixPath(/Users/tom.jones/.config/acme.toml),
  PosixPath(/Users/tom.jones/.acme.ini),
  PosixPath(/path/to/pyproject.toml),
]
"""

print(config.get_option("foo"))
#> "bar"
```

```{warning}
When merging configs, `maison` merges from **right to left**, ie. rightmost sources
take precedence. So in the above example, if `~/config/.acme.toml` and
`pyproject.toml` both set `nice_option`, the value from `pyproject.toml` will be
returned from `config.get_option("nice_option")`.
```

## Search paths

By default, `maison` searches for config files by starting at `Path.cwd()` and moving up
the tree until it finds the relevant config file or there are no more parent paths.

You can start searching from a different path by providing a `starting_path` property to
`UserConfig`:

```python
from maison import UserConfig

config = UserConfig(
  package_name="acme",
  starting_path=Path("/some/other/path")
)

print(config.path)
#> PosixPath(/some/other/path/pyproject.toml)
```

## Validation

`maison` offers optional schema validation.

To validate a configuration, first create a schema. The schema should implement
a method called `model_dump`. This can be achieved by writing the schema as a
`pydantic` model:

```python
from pydantic import BaseModel

class MySchema(BaseModel):
  foo: str = "my_default"
```

```{note}
`maison` validation was built with using `pydantic` models as schemas in mind
but this package doesn't explicitly declare `pydantic` as a dependency so you
are free to use another validation package if you wish, you just need to ensure
that your schema follows the `maison.config._IsSchema` protocol.
```

Then inject the schema when instantiating a `UserConfig`:

```python
from maison import UserConfig

config = UserConfig(package_name="acme", schema=MySchema)
```

To validate the config, simply run `validate()` on the config instance:

```python
config.validate()
```

If the configuration is invalid and if you are using a `pydantic` base model as
your schema, a `pydantic` `ValidationError` will be raised. If the configuration
is valid, the validated values are returned.

If `validate` is invoked but no schema has been provided, a `NoSchemaError` will
be raised. A schema can be added after instantiation through a setter:

```python
config.schema = MySchema
```

## Casting and default values

By default, `maison` will replace the values in the config with whatever comes back from
the validation. For example, for a config file that looks like this:

```toml
[tool.acme]
foo = 1
```

And a schema that looks like this:

```python
class MySchema(BaseModel):
  foo: str
  bar: str = "my_default"
```

Running the config through validation will render the following:

```python
config = UserConfig(package_name="acme", schema=MySchema)

print(config)
#> {"foo": 1}

config.validate()
print(config)
#> {"foo": "1", "bar": "my_default"}
```

If you prefer to keep the config values untouched and just perform simple validation,
add a `use_schema_values=False` argument to the `validate` method.

### Schema precedence

The `validate` method also accepts a `config_schema` is an argument. If one is provided here,
it will be used instead of a schema passed as an init argument.