Skip to content

Goodconf

goodconf is a thin wrapper over Pydantic's settings management. Allows you to define configuration variables and load them from environment or JSON/YAML file. Also generates initial configuration files and documentation for your defined configuration.

Installation

pip install goodconf or pip install goodconf[yaml] if parsing/generating YAML files is required.

Basic Usage

Define the configuration object in config.py:

import base64
import os

from goodconf import GoodConf, Field
from pydantic import PostgresDsn


class AppConfig(GoodConf):  # type: ignore
    """Configure my application."""

    debug: bool
    database_url: PostgresDsn = "postgres://localhost:5432/mydb"
    secret_key: str = Field(
        initial=lambda: base64.b64encode(os.urandom(60)).decode(),
        description="Used for cryptographic signing. "
        "https://docs.djangoproject.com/en/2.0/ref/settings/#secret-key",
    )

    class Config:
        """Define the default files to check."""

        default_files = [
            os.path.expanduser("~/.local/share/your_program/config.yaml"),
            "config.yaml",
        ]


config = AppConfig()

To load the configuration use config.load(). If you don't pass any file to load(), then the default_files will be read in order.

Remember that environment variables always take precedence over variables in the configuration files.

For more details see Pydantic's docs for examples of loading:

Initialize the config with a default value if the file doesn't exist

    def load(self, filename: Optional[str] = None) -> None:
        self._config_file = filename
        if not self.store_dir.is_dir():
            log.warning("The store directory doesn't exist. Creating it")
            os.makedirs(str(self.store_dir))
        if not Path(self.config_file).is_file():
            log.warning("The yaml store file doesn't exist. Creating it")
            self.save()
        super().load(filename)

Config saving

So far goodconf doesn't support saving the config. Until it's ready you can use the next snippet:

class YamlStorage(GoodConf):
    """Adapter to store and load information from a yaml file."""

    @property
    def config_file(self) -> str:
        """Return the path to the config file."""
        return str(self._config_file)

    @property
    def store_dir(self) -> Path:
        """Return the path to the store directory."""
        return Path(self.config_file).parent

    def reload(self) -> None:
        """Reload the contents of the authentication store."""
        self.load(self.config_file)

    def load(self, filename: Optional[str] = None) -> None:
        """Load a configuration file."""
        if not filename:
            filename = f"{self.store_dir}/data.yaml"
        super().load(self.config_file)

    def save(self) -> None:
        """Save the contents of the authentication store."""
        with open(self.config_file, "w+", encoding="utf-8") as file_cursor:

            yaml = YAML()
            yaml.default_flow_style = False
            yaml.dump(self.dict(), file_cursor)

References