Skip to content

API Reference

BaseConfig dataclass

Bases: ConfigHolder

Base class for the root of your configuration. Use it as a dataclass.

Supported types for configuration values: str, int, float, bool, and BaseSectionConfig.

BaseSectionConfig allows you to define another dataclass and use it as a section within the configuration.

To load the configuration, call the init class method. You could use __init__, but then you'd have to manage some logic by yourself. Try to avoid that.

Source code in yaucl/base_config.py
@dataclass
class BaseConfig(ConfigHolder):
    """
    Base class for the root of your configuration. Use it as a dataclass.

    Supported types for configuration values: str, int, float, bool, and `BaseSectionConfig`.

    `BaseSectionConfig` allows you to define another dataclass and use it as a section within the configuration.

    To load the configuration, call the `init` class method. You could use `__init__`, but then you'd
    have to manage some logic by yourself. Try to avoid that.
    """

    _app_name: str | None
    _load_from: LOAD_FROM_TYPE
    _conf_location: str | Path | None
    _conf_file_name: str

    @classmethod
    def init(
        cls,
        app_name: str | None = None,
        load_from: LOAD_FROM_TYPE | None = None,
        conf_location: Path | str | None = None,
        conf_file_name: str = "config.toml",
    ) -> Self:
        """
        Initialization of the Config Class.

        Args:
            app_name: The name of the application.
                Used in default paths and env variables.
            load_from: A list of file types to load configuration from.
                Defaults to ["toml", "env"] if not provided.
                Order matters, rightmost takes precedence.
            conf_location: The location of the configuration file.
                If not provided, location will be determined based on the OS.

                - Windows: `%APPDATA%/<app_name>/`
                - Linux: `~/.config/<app_name>/`
                - macOS: `~/Library/Application Support/<app_name>/`
            conf_file_name: The name of the configuration file.

        Returns:
            Self: Configuration ready to go.
        """
        # For some of these, we don't need properties
        _load_from: LOAD_FROM_TYPE = load_from if load_from is not None else ["toml", "env"]

        self: Self = cls(
            _app_name=app_name,
            _load_from=_load_from,
            _conf_location=conf_location,
            _conf_file_name=conf_file_name,
        )
        self.remember_as_defaults()
        self.load()
        return self

    @property
    def conf_file_path(self) -> Path:
        """
        Gets the configuration file path based on the provided configuration location
        and file name. This property determines the path dynamically by considering
        whether the provided configuration location is a `Path` object or a string,
        or defaults to a user-specific configuration directory if not explicitly set.

        Returns:
            Path: The resolved configuration file path.

        """
        if self._conf_location and isinstance(self._conf_location, Path):
            return self._conf_location / self._conf_file_name
        elif isinstance(self._conf_location, str):
            return Path(self._conf_location) / self._conf_file_name
        else:
            return user_config_path() / self.app_name / self._conf_file_name

    @property
    def app_name(self) -> str:
        """
        Property to retrieve the application name.

        This property attempts to determine the application name automatically if it
        is not explicitly set. If the name cannot be derived from the configuration
        class name, a ValueError is raised. In cases where the name is guessed, a
        warning is logged to indicate potential unpredictability in the deduced name.

        Raises:
            ValueError: If the application name is missing and cannot be guessed.

        Returns:
            str: The name of the application.
        """
        if self._app_name is None:
            guessed_app_name = type(self).__name__.lower().replace("config", "")
            if not guessed_app_name:
                raise ValueError("Please provide an app name, unable to guess it from the config class name.")
            LOGGER.warning(
                "No app name provided, guessed %s from config class name. This might be dangerous.",
                guessed_app_name,
            )
            self._app_name = guessed_app_name
        return self._app_name

    def load(self, reset: bool = False) -> None:
        """
        Loads the data from specified sources and optionally resets before loading. This method loops
        through internal data sources and attempts to invoke corresponding update methods dynamically.
        If no update method is found for a source, a warning is logged.

        Args:
            reset: Whether to reset the current data before loading from the sources. Defaults to False.
        """
        if reset:
            self.reset()
        for source in self._load_from:
            try:
                getattr(self, f"update_from_{source}")()
            except AttributeError:
                LOGGER.warning("No method to update from %s, skipping.", source)

    def update_from_toml(self) -> None:
        """Updates the config from a TOML file."""
        if self.conf_file_path.exists():
            with self.conf_file_path.open("rb") as f:
                config_data = tomllib.load(f)

            self.update_from_dict(config_data)

    def update_from_env(self) -> None:
        """Updates the config from environment variables."""
        for key, expected_type in self.__annotations__.items():
            if key in self.sections:
                section = getattr(self, key)
                section.update_from_env(key, prefix=[self.app_name])
            else:
                env_value = get_env_value(f"{self.app_name}_{key}", expected_type)
                if env_value is not None:
                    setattr(self, key, env_value)

    def generate_markdown_skeleton(self) -> str:
        """
        Helper function that generates a Markdown skeleton for your documentation.

        Consider adding descriptions to your options, examples, and more.
        """
        doc = f"""
# Configuring {self.app_name}

## Available options

### General

| Option name | Description | Type | Default |
|--------|-------------|------|---------|
"""
        for option, default in self._defaults.items():
            doc += f"""| `{option}` | -- | `{self.__annotations__[option].__name__}` | `{default}` |\n"""
        if self.sections:
            doc += """\n### Sections\n\n"""
        for section_name, section in self.sections.items():
            section = cast(BaseSectionConfig, section)
            doc += f"#### {section_name}\n{section.generate_markdown_skeleton(section_name)}"
        doc += """\n## Configuration methods\n"""
        for source in self._load_from:
            doc += getattr(self, f"_{source}_doc")
        return doc

    @property
    def _toml_doc(self) -> str:
        doc = f"""
### TOML file

File location:{self._conf_location_all}
"""
        return doc

    @property
    def _conf_location_all(self) -> str:
        if self._conf_location:
            return f"`{self.conf_file_path}`"
        else:
            return f"""
- Windows: `%APPDATA%/{self.app_name}/{self._conf_file_name}`
- Linux: `~/.config/{self.app_name}/{self._conf_file_name}`
- macOS: `~/Library/Application Support/{self.app_name}/{self._conf_file_name}`"""

    @property
    def _env_doc(self):
        doc = f"""
### Environment variables

Environment variables are uppercase option names, with a prefix `{self.app_name.upper()}_`.
"""
        if self.sections:
            doc += """Options outside of General are then prefixed further with the section name."""
        return doc

app_name property

Property to retrieve the application name.

This property attempts to determine the application name automatically if it is not explicitly set. If the name cannot be derived from the configuration class name, a ValueError is raised. In cases where the name is guessed, a warning is logged to indicate potential unpredictability in the deduced name.

Raises:

Type Description
ValueError

If the application name is missing and cannot be guessed.

Returns:

Name Type Description
str str

The name of the application.

conf_file_path property

Gets the configuration file path based on the provided configuration location and file name. This property determines the path dynamically by considering whether the provided configuration location is a Path object or a string, or defaults to a user-specific configuration directory if not explicitly set.

Returns:

Name Type Description
Path Path

The resolved configuration file path.

generate_markdown_skeleton()

Helper function that generates a Markdown skeleton for your documentation.

Consider adding descriptions to your options, examples, and more.

Source code in yaucl/base_config.py
    def generate_markdown_skeleton(self) -> str:
        """
        Helper function that generates a Markdown skeleton for your documentation.

        Consider adding descriptions to your options, examples, and more.
        """
        doc = f"""
# Configuring {self.app_name}

## Available options

### General

| Option name | Description | Type | Default |
|--------|-------------|------|---------|
"""
        for option, default in self._defaults.items():
            doc += f"""| `{option}` | -- | `{self.__annotations__[option].__name__}` | `{default}` |\n"""
        if self.sections:
            doc += """\n### Sections\n\n"""
        for section_name, section in self.sections.items():
            section = cast(BaseSectionConfig, section)
            doc += f"#### {section_name}\n{section.generate_markdown_skeleton(section_name)}"
        doc += """\n## Configuration methods\n"""
        for source in self._load_from:
            doc += getattr(self, f"_{source}_doc")
        return doc

init(app_name=None, load_from=None, conf_location=None, conf_file_name='config.toml') classmethod

Initialization of the Config Class.

Parameters:

Name Type Description Default
app_name str | None

The name of the application. Used in default paths and env variables.

None
load_from LOAD_FROM_TYPE | None

A list of file types to load configuration from. Defaults to ["toml", "env"] if not provided. Order matters, rightmost takes precedence.

None
conf_location Path | str | None

The location of the configuration file. If not provided, location will be determined based on the OS.

  • Windows: %APPDATA%/<app_name>/
  • Linux: ~/.config/<app_name>/
  • macOS: ~/Library/Application Support/<app_name>/
None
conf_file_name str

The name of the configuration file.

'config.toml'

Returns:

Name Type Description
Self Self

Configuration ready to go.

Source code in yaucl/base_config.py
@classmethod
def init(
    cls,
    app_name: str | None = None,
    load_from: LOAD_FROM_TYPE | None = None,
    conf_location: Path | str | None = None,
    conf_file_name: str = "config.toml",
) -> Self:
    """
    Initialization of the Config Class.

    Args:
        app_name: The name of the application.
            Used in default paths and env variables.
        load_from: A list of file types to load configuration from.
            Defaults to ["toml", "env"] if not provided.
            Order matters, rightmost takes precedence.
        conf_location: The location of the configuration file.
            If not provided, location will be determined based on the OS.

            - Windows: `%APPDATA%/<app_name>/`
            - Linux: `~/.config/<app_name>/`
            - macOS: `~/Library/Application Support/<app_name>/`
        conf_file_name: The name of the configuration file.

    Returns:
        Self: Configuration ready to go.
    """
    # For some of these, we don't need properties
    _load_from: LOAD_FROM_TYPE = load_from if load_from is not None else ["toml", "env"]

    self: Self = cls(
        _app_name=app_name,
        _load_from=_load_from,
        _conf_location=conf_location,
        _conf_file_name=conf_file_name,
    )
    self.remember_as_defaults()
    self.load()
    return self

load(reset=False)

Loads the data from specified sources and optionally resets before loading. This method loops through internal data sources and attempts to invoke corresponding update methods dynamically. If no update method is found for a source, a warning is logged.

Parameters:

Name Type Description Default
reset bool

Whether to reset the current data before loading from the sources. Defaults to False.

False
Source code in yaucl/base_config.py
def load(self, reset: bool = False) -> None:
    """
    Loads the data from specified sources and optionally resets before loading. This method loops
    through internal data sources and attempts to invoke corresponding update methods dynamically.
    If no update method is found for a source, a warning is logged.

    Args:
        reset: Whether to reset the current data before loading from the sources. Defaults to False.
    """
    if reset:
        self.reset()
    for source in self._load_from:
        try:
            getattr(self, f"update_from_{source}")()
        except AttributeError:
            LOGGER.warning("No method to update from %s, skipping.", source)

update_from_env()

Updates the config from environment variables.

Source code in yaucl/base_config.py
def update_from_env(self) -> None:
    """Updates the config from environment variables."""
    for key, expected_type in self.__annotations__.items():
        if key in self.sections:
            section = getattr(self, key)
            section.update_from_env(key, prefix=[self.app_name])
        else:
            env_value = get_env_value(f"{self.app_name}_{key}", expected_type)
            if env_value is not None:
                setattr(self, key, env_value)

update_from_toml()

Updates the config from a TOML file.

Source code in yaucl/base_config.py
def update_from_toml(self) -> None:
    """Updates the config from a TOML file."""
    if self.conf_file_path.exists():
        with self.conf_file_path.open("rb") as f:
            config_data = tomllib.load(f)

        self.update_from_dict(config_data)

BaseSectionConfig

Bases: ConfigHolder

Source code in yaucl/section_config.py
class BaseSectionConfig(ConfigHolder):
    def update_from_env(self, section_key: str, prefix: list[str]) -> None:
        """
        Looks for env variables that correspond to the section passed

        Args:
            section_key: key(name) of this section in the parent config
            prefix: prefixes from parents to construct env variable name

        Returns:

        """
        for key, expected_type in self.__annotations__.items():
            if key in self.sections:
                section = self.sections[key]
                new_prefix = [*prefix, section_key]
                section.update_from_env(key, prefix=new_prefix)
            else:
                all_key_parts = [*prefix, section_key, key]
                env_var_name = "_".join(all_key_parts)
                env_value = get_env_value(env_var_name, expected_type)
                if env_value is not None:
                    setattr(self, key, env_value)

    def generate_markdown_skeleton(self, name: str) -> str:
        """
        Generates a table with variables belonging to this section.
        Args:
            name: name under which this section is registered

        Returns: string containing a Markdown table

        """
        doc = """
| Option name | Description | Type | Default |
|--------|-------------|------|---------|
"""
        for option, default in self._defaults.items():
            doc += f"""| `{option}` | -- | `{self.__annotations__[option].__name__}` | `{default}` |\n"""

        for section_name, section in self.sections.items():
            subsection_name = f"{name}.{section_name}"
            doc += f"\n#### {subsection_name}\n{section.generate_markdown_skeleton(subsection_name)}"
        return doc

generate_markdown_skeleton(name)

Generates a table with variables belonging to this section. Args: name: name under which this section is registered

Returns: string containing a Markdown table

Source code in yaucl/section_config.py
    def generate_markdown_skeleton(self, name: str) -> str:
        """
        Generates a table with variables belonging to this section.
        Args:
            name: name under which this section is registered

        Returns: string containing a Markdown table

        """
        doc = """
| Option name | Description | Type | Default |
|--------|-------------|------|---------|
"""
        for option, default in self._defaults.items():
            doc += f"""| `{option}` | -- | `{self.__annotations__[option].__name__}` | `{default}` |\n"""

        for section_name, section in self.sections.items():
            subsection_name = f"{name}.{section_name}"
            doc += f"\n#### {subsection_name}\n{section.generate_markdown_skeleton(subsection_name)}"
        return doc

update_from_env(section_key, prefix)

Looks for env variables that correspond to the section passed

Parameters:

Name Type Description Default
section_key str

key(name) of this section in the parent config

required
prefix list[str]

prefixes from parents to construct env variable name

required

Returns:

Source code in yaucl/section_config.py
def update_from_env(self, section_key: str, prefix: list[str]) -> None:
    """
    Looks for env variables that correspond to the section passed

    Args:
        section_key: key(name) of this section in the parent config
        prefix: prefixes from parents to construct env variable name

    Returns:

    """
    for key, expected_type in self.__annotations__.items():
        if key in self.sections:
            section = self.sections[key]
            new_prefix = [*prefix, section_key]
            section.update_from_env(key, prefix=new_prefix)
        else:
            all_key_parts = [*prefix, section_key, key]
            env_var_name = "_".join(all_key_parts)
            env_value = get_env_value(env_var_name, expected_type)
            if env_value is not None:
                setattr(self, key, env_value)