Config loaders¶
Load registry configuration from TOML, YAML, and Pydantic models, with env-var interpolation.
"""Config loaders — Load registry configuration from TOML, YAML, and Pydantic models, with env-var interpolation.
Demonstrates loading RegistryConfig from TOML files, YAML files, and Pydantic
models, plus env-var interpolation. All loaders delegate to from_dict() for
Secret wrapping and validation.
---
see_also:
- label: Extensions
url: ../../guides/extensions.md
note: extension modules overview
"""
from __future__ import annotations
import json
import os
import tempfile
from pathlib import Path
from remote_store import Registry, RegistryConfig, resolve_env
def _posix(p: Path) -> str:
"""Return a forward-slash path string safe for TOML/YAML values."""
return p.as_posix()
def demo() -> dict[str, object]:
"""Run config-loader examples and return results for test verification."""
results: dict[str, object] = {}
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
# --- from_toml(): standalone TOML file ---
toml_file = root / "remote-store.toml"
toml_file.write_text(
"[backends.local]\n"
'type = "local"\n\n'
"[backends.local.options]\n"
f'root = "{_posix(root / "toml-data")}"\n\n'
"[stores.docs]\n"
'backend = "local"\n'
'root_path = "docs"\n'
)
config = RegistryConfig.from_toml(toml_file)
print(f"from_toml(): {len(config.backends)} backend(s), {len(config.stores)} store(s)")
with Registry(config) as reg:
docs = reg.get_store("docs")
docs.write("readme.txt", b"Hello from TOML config!")
toml_content = docs.read_bytes("readme.txt")
results["toml_content"] = toml_content
print(f" wrote: {toml_content.decode()}")
# --- from_toml(): pyproject.toml with table extraction ---
pyproject = root / "pyproject.toml"
pyproject.write_text(
"[project]\n"
'name = "my-app"\n\n'
"[tool.remote-store.backends.local]\n"
'type = "local"\n\n'
"[tool.remote-store.backends.local.options]\n"
f'root = "{_posix(root / "pyproject-data")}"\n\n'
"[tool.remote-store.stores.cache]\n"
'backend = "local"\n'
'root_path = "cache"\n'
)
config = RegistryConfig.from_toml(pyproject, table=("tool", "remote-store"))
print(f"\nfrom_toml(table=...): {len(config.stores)} store(s) from pyproject.toml")
with Registry(config) as reg:
cache = reg.get_store("cache")
cache.write("data.bin", b"\x00\x01\x02")
results["pyproject_bytes"] = len(cache.read_bytes("data.bin"))
print(f" wrote {results['pyproject_bytes']} bytes to cache")
# --- from_yaml() (ext.yaml) ---
yaml_file = root / "remote-store.yaml"
yaml_file.write_text(
"backends:\n"
" local:\n"
" type: local\n"
" options:\n"
f" root: '{_posix(root / 'yaml-data')}'\n"
"stores:\n"
" logs:\n"
" backend: local\n"
" root_path: logs\n"
)
from remote_store.ext.yaml import from_yaml
config = from_yaml(yaml_file)
print(f"\nfrom_yaml(): {len(config.backends)} backend(s), {len(config.stores)} store(s)")
with Registry(config) as reg:
logs = reg.get_store("logs")
logs.write("app.log", b"[INFO] started\n")
yaml_content = logs.read_bytes("app.log")
results["yaml_content"] = yaml_content
print(f" wrote: {yaml_content.decode().strip()}")
# --- from_pydantic() ---
try:
from pydantic import BaseModel
from remote_store.ext.pydantic import from_pydantic
class BackendEntry(BaseModel):
type: str
options: dict[str, object] = {}
class StoreEntry(BaseModel):
backend: str
root_path: str = ""
class MyConfig(BaseModel):
backends: dict[str, BackendEntry] = {}
stores: dict[str, StoreEntry] = {}
model = MyConfig(
backends={
"local": BackendEntry(
type="local",
options={"root": _posix(root / "pydantic-data")},
)
},
stores={"notes": StoreEntry(backend="local", root_path="notes")},
)
config = from_pydantic(model)
results["pydantic_stores"] = len(config.stores)
print(f"\nfrom_pydantic(): {results['pydantic_stores']} store(s)")
with Registry(config) as reg:
notes = reg.get_store("notes")
notes.write("todo.txt", b"Ship config loaders!")
pydantic_content = notes.read_bytes("todo.txt")
results["pydantic_content"] = pydantic_content
print(f" wrote: {pydantic_content.decode()}")
except ImportError:
print("\n(pydantic not installed -- skipping pydantic example)")
results["pydantic_stores"] = None
# --- resolve_env(): env-var interpolation for YAML ---
_prev_root = os.environ.get("DEMO_STORE_ROOT")
os.environ["DEMO_STORE_ROOT"] = _posix(root / "env-data")
yaml_env_file = root / "env-config.yaml"
yaml_env_file.write_text(
"backends:\n"
" local:\n"
" type: local\n"
" options:\n"
" root: ${DEMO_STORE_ROOT}\n"
"stores:\n"
" data:\n"
" backend: local\n"
" root_path: resolved\n"
)
# Option A: resolve_env_vars=True on the loader
config = from_yaml(yaml_env_file, resolve_env_vars=True)
resolved_root = config.backends["local"].options["root"]
results["resolved_root"] = resolved_root
print(f"\nfrom_yaml(resolve_env_vars=True): root={resolved_root}")
# Option B: standalone resolve_env() for any dict
raw = json.loads('{"backends": {"mem": {"type": "memory"}}, "stores": {}}')
resolved = resolve_env(raw)
config2 = RegistryConfig.from_dict(resolved)
results["resolve_env_backends"] = len(config2.backends)
print(f"resolve_env() standalone: {results['resolve_env_backends']} backend(s)")
# Default values: ${VAR:-fallback} syntax
data_with_default: dict[str, object] = {"greeting": "${UNSET_VAR:-hello world}"}
results["default_value"] = resolve_env(data_with_default)["greeting"]
print(f"Default value: {results['default_value']}")
# Clean up env var
if _prev_root is None:
os.environ.pop("DEMO_STORE_ROOT", None)
else:
os.environ["DEMO_STORE_ROOT"] = _prev_root
print("\nDone!")
return results
if __name__ == "__main__":
demo()
See also¶
- Extensions — extension modules overview
- Source:
examples/configuration/config_loaders.py