English | 简体中文
A small, reusable configuration framework for Python, built on
pydantic. It lets every component declare a single
config object and be constructed uniformly — from Python, from a YAML file, or from
Hydra — with validation happening once at the boundary.
The core depends only on pydantic; the Hydra bridge is optional.
- Python 3.8 or newer
- pydantic 2.x
- Hydra support requires the optional
hydraextra
pip install cfgable
pip install "cfgable[hydra]"From a local checkout, use pip install . for the core package or pip install ".[hydra]" for the optional Hydra bridge.
- A component's settings are a pydantic model (
*Config), documented with attribute docstrings. - Every class takes one
configparameter and reads fields off it. - Inheriting
ConfigurableBasislets the same class be built from an explicit config, a plain mapping, or flat keyword arguments — the shape Hydra'sinstantiatepasses — because theInitConfigMetametaclass assembles and validates the config for you, then callsconfig_post_init().
from typing import Optional
from pydantic import BaseModel, ConfigDict, PositiveInt
from cfgable import ConfigurableBasis
class CameraConfig(BaseModel, frozen=True):
"""Configuration for a camera."""
model_config = ConfigDict(use_attribute_docstrings=True, extra="forbid")
device: str = "/dev/video0"
"""Video device path."""
fps: PositiveInt = 30
"""Capture rate in frames per second."""
serial: Optional[str] = None
"""Optional camera serial number for disambiguation."""
class Camera(ConfigurableBasis):
"""A camera built from a single validated config."""
def __init__(self, config: CameraConfig):
self.config = config
def config_post_init(self):
super().config_post_init()
self._opened = False
def on_configure(self) -> bool:
self._opened = True
return True
# All three build the same object:
cam = Camera(CameraConfig(fps=60)) # explicit config
cam = Camera({"fps": 60}) # mapping
cam = Camera(fps=60) # flat kwargs (Hydra-style)Use ConfigurableBasis when the component has a configure lifecycle. For plain
objects that only need config assembly, inherit from InitConfigMixin.
Point _target_ at the class and list the config fields as siblings; the metaclass turns
them into the config model:
# camera.yaml
_target_: my_pkg.Camera
fps: 60
device: /dev/video2from cfgable.hydra_utils import init_hydra_config, hydra_instance
cam = hydra_instance(init_hydra_config("camera.yaml"))For plain classes you can also nest the config under its own _target_ so standard
hydra.utils.instantiate builds Camera(config=CameraConfig(...)).
Because a component keeps its whole config, it can serialize back to the config that
would rebuild it:
cam.dump() # -> dict of fields + a "_target_" pointing at the class
cam.save_config("cam.yaml")The saved YAML can be loaded again by passing the path back to the component:
cam = Camera("cam.yaml")ConfigurableBasis,InitConfigMixin,InitConfigABCMixin— base classes for the single-configconstruction protocol.InitConfigMeta/InitConfigABCMeta— the metaclass that assembles configs.NoConfig— placeholder for components that need no settings.StrEnum,ReprEnum— a string-enum backport (use thisStrEnum, notenum.StrEnum, for consistent behavior across Python 3.8–3.13).ForceSetAttr/force_set_attr— controlled mutation of otherwise-frozen configs.import_string,get_fully_qualified_class_name,dump_or_repr,fetch_config.cfgable.hydra_utils—init_hydra_config,hydra_instance,hydra_instance_from_dict,hydra_instance_from_config_path(needs[hydra]).
import cfgable never imports Hydra — the bridge is loaded only when you import
cfgable.hydra_utils.
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytestIssues and pull requests are welcome — see CONTRIBUTING.md for the development setup and conventions, and please follow the Code of Conduct. To report a security issue, see SECURITY.md. Release notes are tracked in CHANGELOG.md, and support options are listed in SUPPORT.md.
Released under the MIT License.
