Skip to content

Configuration Cookbook

This section provides practical recipes for advanced configuration patterns using @configured, configuration(...), and the Value(...) inline override mechanism.


đź§© Pin a Single Field While Loading the Rest from ENV / YAML

You can use Annotated[..., Value(...)] to lock a field value regardless of the environment or configuration source. This is useful for constants that must remain fixed (e.g., internal timeouts, embedded defaults, or CI-only overrides).

from dataclasses import dataclass
from typing import Annotated
from pico_ioc import configured, configuration, EnvSource, Value, init

@configured(prefix="APP_", mapping="auto")
@dataclass
class AppConfig:
    name: str
    # This field is always 60, ignoring APP_TIMEOUT in env or other sources
    timeout: Annotated[int, Value(60)]
    retries: int = 3

# --- Example ENV ---
# APP_NAME="pico-service"
# APP_TIMEOUT="10"
# APP_RETRIES="5"

ctx = configuration(EnvSource(prefix=""))
container = init(modules=[__name__], config=ctx)
cfg = container.get(AppConfig)

print(cfg.name)     # pico-service
print(cfg.timeout)  # 60  (forced by Value)
print(cfg.retries)  # 5   (loaded from ENV)

Key idea: - Value(...) has the highest precedence in the configuration chain. - Once applied, the field is no longer looked up in ENV, YAML, or other sources — it is treated as a literal.


⚙️ Combine Value(...) with Discriminated Unions

You can mix Value(...) and Discriminator(...) to fix the subtype of a union field while still sourcing the subtype’s internal fields dynamically.

This is particularly useful when your system supports multiple backends (e.g., Postgres or Sqlite), but you want to lock one in a specific deployment without changing the source structure.

from dataclasses import dataclass
from typing import Annotated, Union
from pico_ioc import configured, configuration, DictSource, Discriminator, Value, init

@dataclass
class Postgres:
    kind: str
    host: str
    port: int

@dataclass
class Sqlite:
    kind: str
    path: str

@configured(prefix="db", mapping="tree")
@dataclass
class DbConfig:
    # Force the discriminator to always use Postgres
    model: Annotated[Union[Postgres, Sqlite], Discriminator("kind"), Value("Postgres")]

# --- Example Source ---
config_data = {
    "db": {
        "model": { "host": "db.example.com", "port": 5432 }
    }
}

ctx = configuration(DictSource(config_data))
container = init(modules=[__name__], config=ctx)
cfg = container.get(DbConfig)

print(cfg.model.kind)  # Postgres
print(cfg.model.host)  # db.example.com
print(cfg.model.port)  # 5432

How it works: - The Value("Postgres") annotation pins the union discriminator to "Postgres". - Discriminator("kind") directs the runtime to build the correct subtype (Postgres), allowing its other fields (host, port) to load normally from the configuration source.


đź§± Pin Nested Fields in Tree-Mapped Configs

You can also pin specific fields inside nested dataclasses when using hierarchical mapping. This lets you lock internal defaults (like TTLs) while still loading the rest from your configuration sources.

from dataclasses import dataclass
from typing import Annotated
from pico_ioc import configured, configuration, DictSource, Value, init

@dataclass
class CacheConfig:
    host: str
    port: int
    # Always enforce TTL of 60 seconds, regardless of sources
    ttl: Annotated[int, Value(60)]

@configured(prefix="app", mapping="tree")
@dataclass
class AppConfig:
    debug: bool
    cache: CacheConfig

# --- Example Source ---
data = {
    "app": {
        "debug": True,
        "cache": {
            "host": "cache.internal",
            "port": 6379,
            "ttl": 5  # will be ignored due to Value(60)
        }
    }
}

ctx = configuration(DictSource(data))
container = init(modules=[__name__], config=ctx)
cfg = container.get(AppConfig)

print(cfg.debug)       # True
print(cfg.cache.host)  # cache.internal
print(cfg.cache.port)  # 6379
print(cfg.cache.ttl)   # 60  (forced by Value)

Notes: - The Value(...) annotation can be used on any field, including nested dataclasses. - When mapping="tree", nested structures align with the source shape naturally.


✅ Takeaways: - Annotated[..., Value(...)] provides a clean, declarative way to hard-code configuration decisions while keeping the rest of your configuration dynamic and environment-driven. - Value(...) has the highest precedence and disables further lookups for that field. - With Discriminator(...), you can lock a union’s discriminator while still sourcing the subtype’s fields from your configuration.