Skip to content

Quickstart

Install Coyaml:

pip install coyaml

Loading a configuration

The heart of Coyaml is the YSettings object — a thin wrapper around a nested dict that gives you dot-notation access, Pydantic conversion and much more.

from coyaml import YSettings
from coyaml.sources.yaml import YamlFileSource
from coyaml.sources.env import EnvFileSource

cfg = (
    YSettings()
    .add_source(YamlFileSource('config.yaml'))  # ↩ YAML file
    .add_source(EnvFileSource('.env'))          # ↩ environment overrides
)

# 🔄  Replace templates like `${{ env:DB_USER }}`
cfg.resolve_templates()

Alternatively, you can build a config with a single call using URI helpers:

from coyaml import YRegistry

cfg = YRegistry.create_from_uri_list([
    'yaml://config.yaml',
    'env://.env',
])

Notes: - Sources are applied in order: later sources override earlier ones. - Call resolve_templates() after all sources are added to process ${{ env|file|config|yaml:… }}. - You can mix URIs and manual sources as needed.

Example YAML with templates

index: 9
stream: true
llm: "path/to/llm/config"
debug:
  db:
    url: "postgres://user:password@localhost/dbname"
    user: ${{ env:DB_USER }}            # ← env variable
    password: ${{ env:DB_PASSWORD:dev }} # ← with default
    init_script: ${{ file:init.sql }}   # ← embed file content
app:
  db_url: "postgresql://${{ config:debug.db.user }}:${{ config:debug.db.password }}@localhost/app"
  extra_settings: ${{ yaml:extra.yaml }} # ← include another YAML

After resolve_templates() every placeholder is replaced by its real value.

Using the config

# Simple attribute access
print(cfg.debug.db.url)

# Convert a node to a Pydantic model
from pydantic import BaseModel

class DBConfig(BaseModel):
    url: str
    user: str
    password: str

print(cfg.debug.db.to(DBConfig))

Zero-boilerplate injection

Coyaml ships with a tiny helper to inject configuration values into any function:

from typing import Annotated
from coyaml import YResource, coyaml

@coyaml
def handler(
    user: Annotated[str, YResource('debug.db.user')],
    pwd: Annotated[str, YResource('debug.db.password')],
):
    print(user, pwd)

handler()  # arguments are taken from cfg that lives in YRegistry ("default")

Quick injection taste: by name with mask

You can also inject by parameter name. Add an optional mask on the decorator to constrain the search to a subtree.

from typing import Annotated
from coyaml import YResource, coyaml

@coyaml(mask='debug.**')
def connect(user: Annotated[str | None, YResource()] = None) -> str | None:
    return user  # 'debug.db.user' will be found by name within the masked subtree

print(connect())

Notes: - If nothing is found and the parameter is Optional[...] or has default None, None is injected. - If multiple matches are found and unique=True (default), an error points to candidate paths; restrict the mask or use an explicit path.

Merge semantics at a glance

  • Dictionaries are deep-merged (nested keys are merged recursively).
  • Lists are replaced by the later source (no per-item merge).

See the dedicated tutorial: Merging.

That's it — happy coding!