Skip to main content

Dataclass companion for config metadata and parsing, with a ConfigParser that generates and loads configs from files.

Project description

codecov PyPI - Version

classconf

A declarative configuration library that transforms classes into typed dataclasses with metadata for seamless file-based parsing and serialization.

Core ideas

  • Automatic Dataclasses: Use @configclass to transform a class into a dataclass.
  • Integrated Metadata: Attach section names, field mappings, and custom logic directly to your schema.
  • Type-Safe Parsing: Use ConfigParser to load and generate typed instances from configuration files.

Basic usage

from pathlib import Path

from classconf import ConfigParser, configclass
from classconf.format import JSONFormat


@configclass
class PathsConfig:
    output_dir: Path = Path("./out")


@configclass(top_level=True)
class AppConfig:
    name: str = "demo"
    paths: PathsConfig = field(default_factory=PathsConfig)


parser = ConfigParser(
    "config.json",
    AppConfig,
    format=JSONFormat(),
    create_noexist=True,
)

config = parser.get(AppConfig)

Generated config

{
  "name": "demo",
  "paths": {
    "output_dir": "out"
  }
}

Formats

  • TOMLFormat (default if format is None)
  • JSONFormat
from classconf.format import TOMLFormat

parser = ConfigParser("config.toml", AppConfig, PathsConfig, format=TOMLFormat())

TOMLFormat accepts none_value to control how None is written. Use none_value=None to omit None fields entirely.

Custom formats

To add a new format, implement ConfigFormat with read and write methods. read should return None when the file does not exist.

from pathlib import Path
from typing import Any

from classconf.format import ConfigFormat


class YAMLFormat(ConfigFormat):
    def read(self, path: Path) -> dict[str, Any] | None:
        ...

    def write(self, path: Path, data: dict[str, Any]) -> None:
        ...

Field mappings, serializers, deserializers

from classconf import ConfigParser, configclass
from classconf.format import JSONFormat


def deserialize_num(value: str, **_) -> int:
    return int(value.rstrip("x"))


def serialize_num(value: int) -> str:
    return f"{value}x"


@configclass(
    name="metrics",
    field_deserialzers={"count": deserialize_num},
    field_serializers={"count": serialize_num},
    field_name_mappings={"count": "count_value"},
)
class MetricsConfig:
    count: int = 3


parser = ConfigParser(
    "config.json",
    MetricsConfig,
    format=JSONFormat(),
    create_noexist=True,
)

metrics = parser.get(MetricsConfig)

Generated config file

{
  "metrics": {
    "count_value": "3x"
  }
}

Deserializers can also accept a parser to resolve other configs. This is useful when a field is typed as a protocol/ABC and a string selects which config section to load.

from typing import Protocol, runtime_checkable

from classconf import ConfigParser, configclass
from classconf.format import TOMLFormat


@runtime_checkable
class DatabaseConfig(Protocol):
    driver: ClassVar[str]


@configclass(name="sqlite")
class SQLiteConfig:
    driver: ClassVar[str] = "sqlite"
    path: str = "app.db"


@configclass(name="postgres")
class PostgresConfig:
    driver: ClassVar[str] = "postgres"
    host: str = "localhost"
    port: int = 5432


def resolve_database(name: str, parser: ConfigParser) -> DatabaseConfig:
    return parser.get(SQLiteConfig if name == "sqlite" else PostgresConfig)


def serialize_database(db: DatabaseConfig) -> str:
    return db.driver


@configclass(
    top_level=True,
    field_deserialzers={"database": resolve_database},
    field_serializers={"database": serialize_database},
)
class AppConfig:
    database: DatabaseConfig = field(default_factory=SQLiteConfig)


parser = ConfigParser(
    "config.json",
    AppConfig,
    SQLiteConfig,
    PostgresConfig,
    format=TOMLFormat(),
    create_noexist=True,
)

config = parser.get(AppConfig)
print(config.database.driver)

Generated config

database = "sqlite"

[postgres]
driver = "postgres"
host = "localhost"
port = 5432

[sqlite]
driver = "sqlite"
path = "app.db"

Generating configs from instances

ConfigParser.generate_config writes a config file from config class instances. This is useful for preset generation when a CLI or UI offers a few known configurations and only the selected one should be saved.

from classconf import ConfigParser, configclass
from classconf.format import JSONFormat


@configclass(name="logging")
class LoggingConfig:
    level: str = "INFO"
    file: str = "app.log"


preset = "debug"  # could come from CLI/UI

if preset == "debug":
    config = LoggingConfig(level="DEBUG", file="debug.log")
else:
    config = LoggingConfig(level="INFO", file="app.log")

ConfigParser.generate_config(
    "logging_preset.json",
    config,
    format=JSONFormat(),
    override_existing=True,
)

Adding configs later

parser.add(OtherConfig)
other = parser.get(OtherConfig)

Quirks and constraints

  • Only one top-level config class is allowed per parser.
  • create_noexist=False requires the file to exist.
  • Missing config keys raise KeyError during parsing.
  • get() raises if the class was not provided to the parser.
  • With JSON/TOML, fields without defaults are written as null/None placeholders.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

classconf-0.3.2.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

classconf-0.3.2-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

File details

Details for the file classconf-0.3.2.tar.gz.

File metadata

  • Download URL: classconf-0.3.2.tar.gz
  • Upload date:
  • Size: 23.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for classconf-0.3.2.tar.gz
Algorithm Hash digest
SHA256 63eba235a702927f747ae833d5008c2c4cc9c757a6abaaa5c08967b58b5e7d2b
MD5 d3d5e2b64e54f1983f5f433497d0cad2
BLAKE2b-256 019abe38338ed0b29f529936f022bccb38c007b07e8072df7db0f68ec1042926

See more details on using hashes here.

File details

Details for the file classconf-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: classconf-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 9.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for classconf-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9b8c63940150f73a1977c29edc4fb6b7bd9eb2b42eb158ef09a7b1df761cd6c0
MD5 5ea0164c442c813113ad99b24ca613da
BLAKE2b-256 118d41e3a39c287fc022a72d229a8df99e0af232ceb3ed47f161694388d0b0e8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page