Skip to content

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.