Skip to content

Instantly share code, notes, and snippets.

@mmorton
Last active August 29, 2025 01:15
Show Gist options
  • Select an option

  • Save mmorton/eccf27bf6ac61df466969c2140b924c6 to your computer and use it in GitHub Desktop.

Select an option

Save mmorton/eccf27bf6ac61df466969c2140b924c6 to your computer and use it in GitHub Desktop.
A super simple implementation of a "typed" configuration in Python.
import inspect
import json
import os
from dataclasses import dataclass, field, is_dataclass
from typing import Any, Callable, Dict, List, Type, Union, get_type_hints
import toml
_META = "__configurable_meta__"
_COMP = "__configurable_oncomplete__"
def convert_keys_to_int(d: Dict) -> Dict:
return (
{
int(k): v if not isinstance(v, dict) else convert_keys_to_int(v)
for k, v in d.items()
}
if d is not None
else {}
)
@dataclass
class ConfigurableMeta:
attr_helpers: Dict[str, Callable[[Any], Any]] = field(default_factory=lambda: {})
_NONE = ConfigurableMeta(attr_helpers={})
def configurable(cls=None, attr_helpers: Dict[str, Callable[[Any], Any]] = None):
def wrap(cls):
setattr(cls, _META, ConfigurableMeta(attr_helpers=attr_helpers))
return cls
if cls is None:
return wrap
return wrap(cls)
def configurable_oncomplete(func):
setattr(func, _COMP, True)
return func
def is_configurable(obj):
cls = obj if isinstance(obj, type) else type(obj)
return hasattr(cls, _META)
def _get_meta(obj) -> ConfigurableMeta:
cls = obj if isinstance(obj, type) else type(obj)
return getattr(cls, _META) if hasattr(cls, _META) else _NONE
def _def_attr_helper(v):
return v
def _get_data_name(attr_name: str) -> str:
return attr_name.replace("_", "").lower()
def _join_env_names(*args: List[str], separator: str = "_") -> str:
return separator.join(
map(lambda v: v.upper(), filter(lambda v: v is not None and len(v) > 0, args))
)
def _deserialize_env(prop_type: Type, v: str) -> Any:
if prop_type == str:
return v
if prop_type == bool:
if v == "0":
return False
if v == "1":
return True
return json.loads(v)
def _create_data_map(data: Dict[str, Any]) -> Dict[str, Any]:
data_map = (
{_get_data_name(str(k)): v for k, v in data.items()} if data is not None else {}
)
data_map.update(data)
return data_map
def marshal_config(target: Any, data: Dict[str, Any] = None, env_prefix: str = ""):
meta = _get_meta(target)
data = _create_data_map(data)
for attr_name, attr_type in get_type_hints(target).items():
data_name = _get_data_name(attr_name)
if is_configurable(attr_type) or is_dataclass(attr_type):
v = getattr(target, attr_name)
v = marshal_config(
v if v is not None else attr_type(),
data[data_name] if data_name in data else {},
env_prefix=_join_env_names(env_prefix, attr_name),
)
setattr(target, attr_name, v)
else:
env_name = _join_env_names(env_prefix, attr_name)
v = (
meta.attr_helpers[attr_name]
if attr_name in meta.attr_helpers
else _def_attr_helper
)(
_deserialize_env(attr_type, os.environ[env_name])
if env_name in os.environ
else data[data_name]
if data_name in data
else None
)
if v is not None:
setattr(target, attr_name, v)
for method_name, _ in inspect.getmembers(
target, predicate=lambda x: inspect.ismethod(x) and getattr(x, _COMP, False)
):
func = getattr(target, method_name)
func()
return target
@dataclass
class LogConfig:
level: str = "INFO"
@dataclass
class SomeValue:
name: str = "is something"
@dataclass
@configurable(
attr_helpers={
"values": lambda v: [marshal_config(SomeValue(), e) for e in v]
if v is not None
else []
},
)
class SomeConfig:
enable: bool = True
values: List[SomeValue] = field(default_factory=lambda: [])
@dataclass
class Config:
log: LogConfig
some: SomeConfig
__ok: bool = False
def __init__(self):
self.log = LogConfig()
self.some = SomeConfig()
@configurable_oncomplete
def setup(self):
self.__ok = True
def get_config_from(path: str = None, **kwargs) -> Config:
data = toml.load(path) if os.path.exists(path) else None
return marshal_config(Config(**kwargs), data)
def get_config(search_paths: List[str] = None, **kwargs) -> Config:
for path in search_paths:
if os.path.exists(path):
return get_config_from(path, **kwargs)
conf = Config(**kwargs)
conf.setup()
return conf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment