Skip to content

Reference

Defines the ProjectConfig and provides accessors to get config values.

Source code in src/maison/config.py
 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
class ProjectConfig:
    """Defines the `ProjectConfig` and provides accessors to get config values."""

    def __init__(
        self,
        project_name: str,
        starting_path: Optional[Path] = None,
        source_files: Optional[List[str]] = None,
        config_schema: Optional[Type[ConfigSchema]] = None,
        merge_configs: bool = False,
    ) -> None:
        """Initialize the config.

        Args:
            project_name: the name of the project, to be used to find the right section
                in the config file
            starting_path: an optional starting path to start the search for config
                file
            source_files: an optional list of source config filenames or absolute paths
                to search for. If none is provided then `pyproject.toml` will be used.
            config_schema: an optional `pydantic` model to define the config schema
            merge_configs: an optional boolean to determine whether configs should be
                merged if multiple are found
        """
        self.source_files = source_files or ["pyproject.toml"]
        self.merge_configs = merge_configs
        self._sources = _collect_configs(
            project_name=project_name,
            source_files=self.source_files,
            starting_path=starting_path,
        )
        self._config_dict = self._generate_config_dict()
        self._config_schema = config_schema

    def __repr__(self) -> str:
        """Return the __repr__.

        Returns:
            the representation
        """
        return f"<class '{self.__class__.__name__}'>"

    def __str__(self) -> str:
        """Return the __str__.

        Returns:
            the representation
        """
        return self.__repr__()

    @property
    def config_path(self) -> Optional[Union[Path, List[Path]]]:
        """Return a list of the path(s) to the config source(s).

        Returns:
            `None` is no config sources have been found, a list of the found config
            sources if `merge_configs` is `True`, or the path to the active config
            source if `False`
        """
        if len(self._sources) == 0:
            return None

        if self.merge_configs:
            return self.discovered_config_paths

        return self.discovered_config_paths[0]

    @property
    def discovered_config_paths(self) -> List[Path]:
        """Return a list of the paths to the config sources found on the filesystem.

        Returns:
            a list of the paths to the config sources
        """
        return [source.filepath for source in self._sources]

    def to_dict(self) -> Dict[str, Any]:
        """Return a dict of all the config options.

        Returns:
            a dict of the config options
        """
        return self._config_dict

    @property
    def config_schema(self) -> Optional[Type[ConfigSchema]]:
        """Return the `config_schema`.

        Returns:
            the `config_schema`
        """
        return self._config_schema

    @config_schema.setter
    def config_schema(self, config_schema: Type[ConfigSchema]) -> None:
        """Set the `config_schema`."""
        self._config_schema = config_schema

    def validate(
        self,
        config_schema: Optional[Type[ConfigSchema]] = None,
        use_schema_values: bool = True,
    ) -> Dict[str, Any]:
        """Validate the configuration.

        Warning:
            Using this method with `use_schema_values` set to `True` will cast values to
            whatever is defined in the schema. For example, for the following schema:

                class Schema(ConfigSchema):
                    foo: str

            Validating a config with:

                {"foo": 1}

            Will result in:

                {"foo": "1"}

        Args:
            config_schema: an optional `ConfigSchema` to define the schema. This
                takes precedence over a schema provided at object instantiation.
            use_schema_values: an optional boolean to indicate whether the result
                of passing the config through the schema should overwrite the existing
                config values, meaning values are cast to types defined in the schema as
                described above, and default values defined in the schema are used.

        Returns:
            the config values

        Raises:
            NoSchemaError: when validation is attempted but no schema has been provided
        """
        if not (config_schema or self.config_schema):
            raise NoSchemaError

        schema: Type[ConfigSchema] = config_schema or self.config_schema  # type: ignore

        validated_schema = schema(**self._config_dict)

        if use_schema_values:
            self._config_dict = validated_schema.dict()

        return self._config_dict

    def get_option(
        self, option_name: str, default_value: Optional[Any] = None
    ) -> Optional[Any]:
        """Return the value of a config option.

        Args:
            option_name: the config option for which to return the value
            default_value: an option default value if the option isn't set

        Returns:
            The value of the given config option or `None` if it doesn't exist
        """
        return self._config_dict.get(option_name, default_value)

    def _generate_config_dict(self) -> Dict[Any, Any]:
        """Generate the project config dict.

        If `merge_configs` is set to `False` then we use the first config. If `True`
        then the dicts of the sources are merged from right to left.

        Returns:
            the project config dict
        """
        if len(self._sources) == 0:
            return {}

        if not self.merge_configs:
            return self._sources[0].to_dict()

        source_dicts = [source.to_dict() for source in self._sources]
        return reduce(lambda a, b: deep_merge(a, b), source_dicts)

config_path: Optional[Union[Path, List[Path]]] property

Return a list of the path(s) to the config source(s).

Returns:

Type Description
Optional[Union[Path, List[Path]]]

None is no config sources have been found, a list of the found config

Optional[Union[Path, List[Path]]]

sources if merge_configs is True, or the path to the active config

Optional[Union[Path, List[Path]]]

source if False

config_schema: Optional[Type[ConfigSchema]] property writable

Return the config_schema.

Returns:

Type Description
Optional[Type[ConfigSchema]]

the config_schema

discovered_config_paths: List[Path] property

Return a list of the paths to the config sources found on the filesystem.

Returns:

Type Description
List[Path]

a list of the paths to the config sources

__init__(project_name, starting_path=None, source_files=None, config_schema=None, merge_configs=False)

Initialize the config.

Parameters:

Name Type Description Default
project_name str

the name of the project, to be used to find the right section in the config file

required
starting_path Optional[Path]

an optional starting path to start the search for config file

None
source_files Optional[List[str]]

an optional list of source config filenames or absolute paths to search for. If none is provided then pyproject.toml will be used.

None
config_schema Optional[Type[ConfigSchema]]

an optional pydantic model to define the config schema

None
merge_configs bool

an optional boolean to determine whether configs should be merged if multiple are found

False
Source code in src/maison/config.py
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
def __init__(
    self,
    project_name: str,
    starting_path: Optional[Path] = None,
    source_files: Optional[List[str]] = None,
    config_schema: Optional[Type[ConfigSchema]] = None,
    merge_configs: bool = False,
) -> None:
    """Initialize the config.

    Args:
        project_name: the name of the project, to be used to find the right section
            in the config file
        starting_path: an optional starting path to start the search for config
            file
        source_files: an optional list of source config filenames or absolute paths
            to search for. If none is provided then `pyproject.toml` will be used.
        config_schema: an optional `pydantic` model to define the config schema
        merge_configs: an optional boolean to determine whether configs should be
            merged if multiple are found
    """
    self.source_files = source_files or ["pyproject.toml"]
    self.merge_configs = merge_configs
    self._sources = _collect_configs(
        project_name=project_name,
        source_files=self.source_files,
        starting_path=starting_path,
    )
    self._config_dict = self._generate_config_dict()
    self._config_schema = config_schema

__repr__()

Return the repr.

Returns:

Type Description
str

the representation

Source code in src/maison/config.py
51
52
53
54
55
56
57
def __repr__(self) -> str:
    """Return the __repr__.

    Returns:
        the representation
    """
    return f"<class '{self.__class__.__name__}'>"

__str__()

Return the str.

Returns:

Type Description
str

the representation

Source code in src/maison/config.py
59
60
61
62
63
64
65
def __str__(self) -> str:
    """Return the __str__.

    Returns:
        the representation
    """
    return self.__repr__()

get_option(option_name, default_value=None)

Return the value of a config option.

Parameters:

Name Type Description Default
option_name str

the config option for which to return the value

required
default_value Optional[Any]

an option default value if the option isn't set

None

Returns:

Type Description
Optional[Any]

The value of the given config option or None if it doesn't exist

Source code in src/maison/config.py
163
164
165
166
167
168
169
170
171
172
173
174
175
def get_option(
    self, option_name: str, default_value: Optional[Any] = None
) -> Optional[Any]:
    """Return the value of a config option.

    Args:
        option_name: the config option for which to return the value
        default_value: an option default value if the option isn't set

    Returns:
        The value of the given config option or `None` if it doesn't exist
    """
    return self._config_dict.get(option_name, default_value)

to_dict()

Return a dict of all the config options.

Returns:

Type Description
Dict[str, Any]

a dict of the config options

Source code in src/maison/config.py
93
94
95
96
97
98
99
def to_dict(self) -> Dict[str, Any]:
    """Return a dict of all the config options.

    Returns:
        a dict of the config options
    """
    return self._config_dict

validate(config_schema=None, use_schema_values=True)

Validate the configuration.

Warning

Using this method with use_schema_values set to True will cast values to whatever is defined in the schema. For example, for the following schema:

class Schema(ConfigSchema):
    foo: str

Validating a config with:

{"foo": 1}

Will result in:

{"foo": "1"}

Parameters:

Name Type Description Default
config_schema Optional[Type[ConfigSchema]]

an optional ConfigSchema to define the schema. This takes precedence over a schema provided at object instantiation.

None
use_schema_values bool

an optional boolean to indicate whether the result of passing the config through the schema should overwrite the existing config values, meaning values are cast to types defined in the schema as described above, and default values defined in the schema are used.

True

Returns:

Type Description
Dict[str, Any]

the config values

Raises:

Type Description
NoSchemaError

when validation is attempted but no schema has been provided

Source code in src/maison/config.py
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
def validate(
    self,
    config_schema: Optional[Type[ConfigSchema]] = None,
    use_schema_values: bool = True,
) -> Dict[str, Any]:
    """Validate the configuration.

    Warning:
        Using this method with `use_schema_values` set to `True` will cast values to
        whatever is defined in the schema. For example, for the following schema:

            class Schema(ConfigSchema):
                foo: str

        Validating a config with:

            {"foo": 1}

        Will result in:

            {"foo": "1"}

    Args:
        config_schema: an optional `ConfigSchema` to define the schema. This
            takes precedence over a schema provided at object instantiation.
        use_schema_values: an optional boolean to indicate whether the result
            of passing the config through the schema should overwrite the existing
            config values, meaning values are cast to types defined in the schema as
            described above, and default values defined in the schema are used.

    Returns:
        the config values

    Raises:
        NoSchemaError: when validation is attempted but no schema has been provided
    """
    if not (config_schema or self.config_schema):
        raise NoSchemaError

    schema: Type[ConfigSchema] = config_schema or self.config_schema  # type: ignore

    validated_schema = schema(**self._config_dict)

    if use_schema_values:
        self._config_dict = validated_schema.dict()

    return self._config_dict