Usage
Retrieving values¶
Once an instance of ProjectConfig
has been created, values can be retrieved through:
>>> config.get_option("foo")
'bar'
An optional second argument can be provided to get_option
which will be returned if the given option isn't found:
>>> config.get_option("baz", "default")
'default'
ProjectConfig
also exposes a to_dict()
method to return all the config options:
>>> config.to_dict()
{'foo': 'bar'}
Source files¶
By default, maison
will look for a pyproject.toml
file. If you prefer to look elsewhere, provide a source_files
list to ProjectConfig
and maison
will select the first source file it finds from the list.
from maison import ProjectConfig
config = ProjectConfig(
project_name="acme",
source_files=["acme.ini", "pyproject.toml"]
)
print(config.config_path)
#> PosixPath(/path/to/acme.ini)
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.{project_name}]
section. For other .toml
files maison
assumes the whole file is relevant.
To verify which source config file has been found, ProjectConfig
exposes a config_path
property:
>>> config.config_path
PosixPath('/path/to/pyproject.toml')
The source file can either be a filename or an absolute path to a config:
from maison import ProjectConfig
config = ProjectConfig(
project_name="acme",
source_files=["~/.config/acme.ini", "pyproject.toml"]
)
print(config.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 ProjectConfig
:
from maison import ProjectConfig
config = ProjectConfig(
project_name="acme",
source_files=["~/.config/acme.toml", "~/.acme.ini", "pyproject.toml"],
merge_configs=True
)
print(config.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"
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 ProjectConfig
:
from maison import ProjectConfig
config = ProjectConfig(
project_name="acme",
starting_path=Path("/some/other/path")
)
print(config.config_path)
#> PosixPath(/some/other/path/pyproject.toml)
Validation¶
maison
offers optional schema validation using pydantic.
To validate a configuration, first create a schema which subclasses ConfigSchema
:
from maison import ConfigSchema
class MySchema(ConfigSchema):
foo: str = "my_default"
ConfigSchema
offers all the same functionality as the pydantic
BaseModel
Then inject the schema when instantiating a ProjectConfig
:
from maison import ProjectConfig
config = ProjectConfig(project_name="acme", config_schema=MySchema)
To validate the config, simply run validate()
on the config instance:
config.validate()
If the configuration is invalid, 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:
config.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:
[tool.acme]
foo = 1
And a schema that looks like this:
class MySchema(ConfigSchema):
foo: str
bar: str = "my_default"
Running the config through validation will render the following:
config = ProjectConfig(project_name="acme", config_schema=MySchema)
print(config.to_dict())
#> {"foo": 1}
config.validate()
print(config.to_dict())
#> {"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.