Python’s configparser module reads and writes Windows-style INI configuration files. from configparser import ConfigParser, ExtendedInterpolation, RawConfigParser. ConfigParser: cfg = ConfigParser(); cfg.read("app.ini"). Sections: cfg.sections() — list of section names (excludes DEFAULT). has_section: cfg.has_section("database"). has_option: cfg.has_option("server", "port"). get: cfg.get("server", "host", fallback="localhost"). getint: cfg.getint("server", "port", fallback=8080). getfloat: cfg.getfloat("cache", "timeout", fallback=30.0). getboolean: cfg.getboolean("features", "debug", fallback=False) — handles “yes/no/true/false/on/off/1/0”. DEFAULT: [DEFAULT] values are inherited by all sections. interpolation: cfg.get("database", "url") expands %(host)s via BasicInterpolation; ${host} via ExtendedInterpolation. read_string: cfg.read_string("[s]\nk=v"). read_dict: cfg.read_dict({"s": {"k": "v"}}). items: cfg.items("database") → list of (key, value) pairs. write: cfg.write(open("app.ini", "w")). converters: ConfigParser(converters={"list": lambda s: s.split(",")}). RawConfigParser: skip interpolation entirely. NoSectionError / NoOptionError / MissingSectionHeaderError exceptions. Claude Code generates layered config loaders, environment-variable overrides, config validators, and settings dataclasses.
CLAUDE.md for configparser
## configparser Stack
- Stdlib: from configparser import ConfigParser, ExtendedInterpolation
- Load: cfg = ConfigParser(); cfg.read(["defaults.ini", "local.ini"])
- Typed get: cfg.getint/getfloat/getboolean(section, key, fallback=...)
- Interpolation: ExtendedInterpolation() for ${section:key} syntax
- Env override: cfg.read_dict({"section": {k: os.environ[k] for k in KEYS}})
- Write: cfg.write(open("out.ini", "w"))
configparser Configuration Pipeline
# app/config.py — ConfigParser, typed access, ENV override, validation, dataclass
from __future__ import annotations
import os
from configparser import (
ConfigParser,
ExtendedInterpolation,
MissingSectionHeaderError,
NoOptionError,
NoSectionError,
RawConfigParser,
)
from dataclasses import dataclass
from pathlib import Path
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 1. Loader helpers
# ─────────────────────────────────────────────────────────────────────────────
def load_config(
*paths: str | Path,
defaults: dict[str, str] | None = None,
interpolation: bool = True,
) -> ConfigParser:
"""
Load one or more INI files in priority order (last file wins).
Missing files are silently skipped.
Example:
cfg = load_config("defaults.ini", "local.ini", "~/.myapp.ini")
host = cfg.get("server", "host", fallback="localhost")
"""
cfg = ConfigParser(
defaults=defaults or {},
interpolation=ExtendedInterpolation() if interpolation else None,
)
cfg.read([str(p) for p in paths], encoding="utf-8")
return cfg
def load_config_string(ini_text: str, interpolation: bool = True) -> ConfigParser:
"""
Parse an INI string directly — useful for testing.
Example:
cfg = load_config_string('''
[server]
host = localhost
port = 8080
''')
"""
cfg = ConfigParser(
interpolation=ExtendedInterpolation() if interpolation else None
)
cfg.read_string(ini_text)
return cfg
def config_to_dict(cfg: ConfigParser) -> dict[str, dict[str, str]]:
"""
Convert all sections (excluding DEFAULT) to a plain dict-of-dicts.
Example:
d = config_to_dict(cfg)
d["server"]["host"]
"""
return {
section: dict(cfg.items(section))
for section in cfg.sections()
}
# ─────────────────────────────────────────────────────────────────────────────
# 2. Typed accessor helpers
# ─────────────────────────────────────────────────────────────────────────────
def get_list(
cfg: ConfigParser,
section: str,
key: str,
fallback: list[str] | None = None,
sep: str = ",",
) -> list[str]:
"""
Read a comma-separated value as a list of stripped strings.
Example:
# INI: allowed_hosts = localhost, example.com, api.example.com
hosts = get_list(cfg, "server", "allowed_hosts")
# ["localhost", "example.com", "api.example.com"]
"""
raw = cfg.get(section, key, fallback=None)
if raw is None:
return fallback if fallback is not None else []
return [item.strip() for item in raw.split(sep) if item.strip()]
def get_path(
cfg: ConfigParser,
section: str,
key: str,
fallback: str | Path | None = None,
) -> Path | None:
"""
Read a value and return it as an expanded Path.
Example:
log_dir = get_path(cfg, "logging", "dir", fallback="/var/log/app")
"""
raw = cfg.get(section, key, fallback=str(fallback) if fallback is not None else None)
if raw is None:
return None
return Path(raw).expanduser()
def safe_get(
cfg: ConfigParser,
section: str,
key: str,
fallback: Any = None,
cast=None,
) -> Any:
"""
Get a value with an optional type cast; return fallback on missing key.
Example:
timeout = safe_get(cfg, "cache", "timeout", fallback=30.0, cast=float)
workers = safe_get(cfg, "app", "workers", fallback=4, cast=int)
"""
raw = cfg.get(section, key, fallback=None)
if raw is None:
return fallback
if cast is None:
return raw
try:
return cast(raw)
except (ValueError, TypeError):
return fallback
# ─────────────────────────────────────────────────────────────────────────────
# 3. Environment variable overlay
# ─────────────────────────────────────────────────────────────────────────────
def overlay_env(
cfg: ConfigParser,
mapping: dict[str, tuple[str, str]],
) -> ConfigParser:
"""
Override config values from environment variables.
mapping: {ENV_VAR_NAME: (section, key)}
Example:
overlay_env(cfg, {
"DATABASE_URL": ("database", "url"),
"SERVER_PORT": ("server", "port"),
"DEBUG": ("app", "debug"),
})
"""
for env_var, (section, key) in mapping.items():
value = os.environ.get(env_var)
if value is not None:
if not cfg.has_section(section):
cfg.add_section(section)
cfg.set(section, key, value)
return cfg
def from_env_prefix(
prefix: str,
cfg: ConfigParser | None = None,
) -> ConfigParser:
"""
Build or extend a ConfigParser from environment variables matching PREFIX_SECTION_KEY.
Converts PREFIX_DATABASE_URL → section "database", key "url".
Example:
# APP_SERVER_HOST=api.example.com APP_SERVER_PORT=9000 python ...
cfg = from_env_prefix("APP")
cfg.get("server", "host") # "api.example.com"
"""
cfg = cfg or ConfigParser()
prefix_lower = prefix.lower() + "_"
for env_key, value in os.environ.items():
lower = env_key.lower()
if not lower.startswith(prefix_lower):
continue
remainder = lower[len(prefix_lower):]
parts = remainder.split("_", 1)
if len(parts) != 2:
continue
section, key = parts
if not cfg.has_section(section):
cfg.add_section(section)
cfg.set(section, key, value)
return cfg
# ─────────────────────────────────────────────────────────────────────────────
# 4. Config writing
# ─────────────────────────────────────────────────────────────────────────────
def write_config(cfg: ConfigParser, path: str | Path, encoding: str = "utf-8") -> Path:
"""
Write ConfigParser to an INI file; create parent dirs if needed.
Example:
write_config(cfg, "config/app.ini")
"""
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
with p.open("w", encoding=encoding) as f:
cfg.write(f)
return p
def config_from_dict(data: dict[str, dict[str, Any]]) -> ConfigParser:
"""
Build a ConfigParser from a nested dict.
Example:
cfg = config_from_dict({
"server": {"host": "localhost", "port": "8080"},
"database": {"url": "sqlite:///app.db"},
})
"""
cfg = ConfigParser()
cfg.read_dict({s: {k: str(v) for k, v in items.items()} for s, items in data.items()})
return cfg
# ─────────────────────────────────────────────────────────────────────────────
# 5. Settings dataclass
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class AppSettings:
"""
Typed settings object loaded from INI + environment variables.
Example:
settings = AppSettings.load("app.ini")
print(settings.server_host, settings.server_port)
"""
server_host: str
server_port: int
debug: bool
database_url: str
allowed_hosts: list[str]
log_level: str
log_dir: Path
workers: int
cache_timeout: float
@classmethod
def load(
cls,
*config_paths: str | Path,
env_prefix: str = "APP",
) -> "AppSettings":
"""
Load settings from INI files overridden by APP_* env vars.
Example:
settings = AppSettings.load("defaults.ini", "local.ini")
"""
cfg = load_config(*config_paths)
overlay_env(cfg, {
"DATABASE_URL": ("database", "url"),
"SERVER_HOST": ("server", "host"),
"SERVER_PORT": ("server", "port"),
"DEBUG": ("app", "debug"),
"LOG_LEVEL": ("logging", "level"),
})
return cls(
server_host=cfg.get("server", "host", fallback="127.0.0.1"),
server_port=cfg.getint("server", "port", fallback=8000),
debug=cfg.getboolean("app", "debug", fallback=False),
database_url=cfg.get("database", "url", fallback="sqlite:///app.db"),
allowed_hosts=get_list(cfg, "server", "allowed_hosts", fallback=["localhost"]),
log_level=cfg.get("logging", "level", fallback="INFO"),
log_dir=get_path(cfg, "logging", "dir", fallback="logs") or Path("logs"),
workers=cfg.getint("app", "workers", fallback=1),
cache_timeout=cfg.getfloat("cache", "timeout", fallback=300.0),
)
def to_config(self) -> ConfigParser:
"""Serialize settings back to a ConfigParser."""
return config_from_dict({
"server": {
"host": self.server_host,
"port": str(self.server_port),
"allowed_hosts": ", ".join(self.allowed_hosts),
},
"app": {
"debug": str(self.debug).lower(),
"workers": str(self.workers),
},
"database": {"url": self.database_url},
"logging": {
"level": self.log_level,
"dir": str(self.log_dir),
},
"cache": {"timeout": str(self.cache_timeout)},
})
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== configparser demo ===")
INI = """
[DEFAULT]
env = development
[server]
host = 127.0.0.1
port = 8080
allowed_hosts = localhost, 127.0.0.1, example.com
debug = %(env)s == development
[database]
url = sqlite:///%(env)s.db
[logging]
level = DEBUG
dir = /tmp/logs
[app]
debug = false
workers = 4
[cache]
timeout = 120.0
"""
print("\n--- load_config_string ---")
cfg = load_config_string(INI)
print(f" sections: {cfg.sections()}")
print(f" server.host: {cfg.get('server', 'host')!r}")
print(f" server.port: {cfg.getint('server', 'port')!r}")
print(f" app.debug: {cfg.getboolean('app', 'debug')!r}")
print(f" cache.timeout:{cfg.getfloat('cache', 'timeout')!r}")
print("\n--- get_list ---")
hosts = get_list(cfg, "server", "allowed_hosts")
print(f" allowed_hosts: {hosts}")
print("\n--- get_path ---")
log_dir = get_path(cfg, "logging", "dir")
print(f" log_dir: {log_dir}")
print("\n--- safe_get ---")
workers = safe_get(cfg, "app", "workers", fallback=1, cast=int)
missing = safe_get(cfg, "app", "nonexistent", fallback="default")
print(f" workers: {workers} missing: {missing!r}")
print("\n--- overlay_env ---")
os.environ["SERVER_PORT"] = "9999"
overlay_env(cfg, {"SERVER_PORT": ("server", "port")})
print(f" server.port after env override: {cfg.getint('server', 'port')}")
del os.environ["SERVER_PORT"]
print("\n--- from_env_prefix ---")
os.environ["DEMO_APP_WORKERS"] = "8"
os.environ["DEMO_APP_DEBUG"] = "true"
prefix_cfg = from_env_prefix("DEMO")
print(f" app.workers: {prefix_cfg.get('app', 'workers')!r}")
print(f" app.debug: {prefix_cfg.get('app', 'debug')!r}")
del os.environ["DEMO_APP_WORKERS"], os.environ["DEMO_APP_DEBUG"]
print("\n--- config_to_dict ---")
d = config_to_dict(cfg)
for section, vals in list(d.items())[:2]:
print(f" [{section}]")
for k, v in list(vals.items())[:2]:
print(f" {k} = {v!r}")
print("\n--- write_config ---")
with tempfile.TemporaryDirectory() as td:
out = write_config(cfg, f"{td}/saved.ini")
size = out.stat().st_size
print(f" written to: {out.name}, size={size} bytes")
print("\n--- AppSettings.load ---")
with tempfile.NamedTemporaryFile(
mode="w", suffix=".ini", delete=False, encoding="utf-8"
) as f:
f.write(INI)
tmp_path = f.name
settings = AppSettings.load(tmp_path)
print(f" host={settings.server_host}:{settings.server_port}")
print(f" debug={settings.debug} workers={settings.workers}")
print(f" db={settings.database_url!r}")
print(f" allowed_hosts={settings.allowed_hosts}")
print(f" cache_timeout={settings.cache_timeout}")
import pathlib; pathlib.Path(tmp_path).unlink()
print("\n=== done ===")
For the dynaconf alternative — dynaconf (PyPI, seen in claude-code-dynaconf) supports layered config from TOML/YAML/JSON/INI/.env files and environment variables with a fluent settings.SERVER.HOST API, secret vaults integration, and per-environment switching (ENV_FOR_DYNACONF=production); configparser is single-format INI-only with no env-switching built in — use dynaconf for applications needing multi-format layered config with secret injection and environment switching, configparser for tools, CLIs, and services where INI is the required format or zero-dependency is mandatory. For the python-decouple alternative — python-decouple (PyPI) reads settings from environment variables with .env file fallback via config("KEY", default=...), casting to int/bool automatically; configparser organizes settings into INI sections with typed accessors — use python-decouple for twelve-factor apps where environment variables are the primary config source and .env files are the local override, configparser for applications shipping with INI files as the documented configuration format. The Claude Skills 360 bundle includes configparser skill sets covering load_config()/load_config_string()/config_to_dict() loaders, get_list()/get_path()/safe_get() typed accessors, overlay_env()/from_env_prefix() environment-variable overlay, write_config()/config_from_dict() writers, and AppSettings dataclass with typed load() and to_config() round-trip. Start with the free tier to try INI configuration and configparser pipeline code generation.