mrkeyoor.com_
Thu 06 Aug 23:57 UTC
PyPIUtilsupdated 06 Aug 2026

omegaconf

OmegaConf is a hierarchical configuration library for Python. You build a config from YAML files, plain dicts, dataclasses, or command-line arguments, and get back one object with the same API regardless of the source. Its two signature features are merging (layer a base config, an experiment config, and CLI overrides into one tree, later sources winning) and interpolation (a value like ${server.port} or ${oc.env:DB_HOST} is resolved lazily when you read it). Back a config with a dataclass and it also type-checks every assignment and merge at runtime. It is the engine underneath Hydra, which is where most of its roughly 10.1M weekly downloads come from: the ML world configures experiments with it.

Verdict

The de facto config layer of the ML ecosystem via Hydra, with a genuinely good merge-and-interpolate model, but the stable branch has shipped one packaging fix since December 2022 while 2.4.0 has been a dev prerelease for three years. Fine to adopt where Hydra already decides for you; for plain application settings, pydantic-settings is the safer bet.

API stability4/5The 2.x API (create, merge, structured, resolvers) has been unchanged since 2.3.0 in 2022, partly because nothing stable has shipped since. History shows breaking moves at minors: 2.1 replaced the env: resolver with oc.env, and the 2.4 dev line drops old Python versions.
Docs4/5readthedocs covers usage, structured configs, resolvers, and the grammar with doctested examples that actually run in CI. Docked a point because docs are split across version branches (2.3_branch vs latest) and search often lands you on the wrong one.
Maintenance3/5The repo is active again (pushed August 2026, monthly 2.4.0.dev wheels through July 2026, 32 open issues counting PRs), but the stable channel got exactly one release between December 2022 and June 2026, and that was a pkg_resources packaging fix. 2.4.0 has been in dev since August 2023.
Ecosystem4/5Roughly 10.1M weekly downloads and it is the substrate of Hydra, so most PyTorch research configs run through it. The caveat: the ecosystem is essentially Hydra; outside ML tooling, adoption is thin next to pydantic-settings.

Use it if

  • You layer configuration from several sources: a defaults file, a per-environment file, and command-line overrides, and you want one merge call instead of hand-written dict recursion
  • You want values that reference other values: ${paths.root}/data, ${oc.env:API_KEY}, or a computed value via a custom resolver, all resolved lazily at access time
  • You want typed config without leaving dataclasses: OmegaConf.structured(MyConfig) gives runtime validation on every assignment and merge, plus IDE autocomplete through duck typing
  • You already use Hydra, in which case you are using OmegaConf whether you like it or not, and learning its flags (struct, readonly, MISSING) explains most confusing Hydra behavior
Skip it if

Setup reality

pip install omegaconf is pure Python and quick, but it drags in PyYAML and a hard-pinned antlr4-python3-runtime==4.9.*, which is the classic conflict when another package wants a different ANTLR runtime. Version signals are mixed: PyPI metadata for 2.3.1 still says Python >=3.6 with classifiers up to 3.11, while the README badge advertises 3.10 through 3.14 for the dev line, so on a new interpreter you may need pip install --pre to get the 2.4.0.dev wheels. If Hydra is in the picture, let Hydra pin the OmegaConf version; the two are released in lockstep and mismatches produce weird errors. Docs are versioned per branch, and Google often lands you on the wrong one.

Patterns

Create a config from a dict, YAML, or filecreate-config

from omegaconf import OmegaConf

cfg = OmegaConf.create({"server": {"port": 80}})
cfg = OmegaConf.create("server:\n  port: 80\n")  # YAML string
cfg = OmegaConf.load("config.yaml")               # file or Path

print(cfg.server.port)      # attribute style
print(cfg["server"]["port"])  # dict style

All three return the same DictConfig type, so downstream code never cares where config came from. cfg.get("key", default) works like dict.get; plain attribute access on a missing key raises.

Create a typed config from a dataclasscreate-from-dataclass

from dataclasses import dataclass
from omegaconf import OmegaConf

@dataclass
class ServerConfig:
    host: str = "localhost"
    port: int = 80

cfg = OmegaConf.structured(ServerConfig)
cfg.port = "8080"   # ok, converts to int 8080
cfg.port = "oops"   # raises omegaconf.errors.ValidationError

The result is a DictConfig duck-typed as ServerConfig, not an instance of it, so isinstance checks fail. Annotate variables as ServerConfig anyway and your IDE autocompletes; use OmegaConf.get_type(cfg) to see the backing class.

Merge base, override, and CLI configsmerge-configs

from omegaconf import OmegaConf

base = OmegaConf.load("base.yaml")
prod = OmegaConf.load("prod.yaml")
cli = OmegaConf.from_cli()  # parses sys.argv dotlist args

cfg = OmegaConf.merge(base, prod, cli)

Later arguments win, dicts merge recursively, but lists are replaced wholesale, not concatenated. If the first argument is a structured config, the merge is validated against its schema, which is the standard way to type-check raw YAML.

Reference one value from anotherinterpolate-values

cfg = OmegaConf.create({
    "paths": {"root": "/data"},
    "train_dir": "${paths.root}/train",
    "copy_of_paths": "${paths}",
})
print(cfg.train_dir)  # /data/train

Interpolations resolve lazily on every access, so changing paths.root later changes train_dir too. Non-string interpolations keep their type (an int stays an int); mixed into a string they concatenate. Cycles are forbidden and blow up at access time, not creation.

Pull environment variables into configread-env-vars

cfg = OmegaConf.create({
    "db": {
        "host": "${oc.env:DB_HOST,localhost}",
        "port": "${oc.decode:${oc.env:DB_PORT,5432}}",
        "password": "${oc.env:DB_PASSWORD}",
    }
})

oc.env always returns a string (defaults are str()-ed too, unless the default is null), so wrap it in oc.decode to get real ints, floats, or bools. The old env: resolver from pre-2.1 tutorials is gone; use oc.env. Missing var without a default raises on access.

Add your own ${func:...} resolverregister-custom-resolver

from omegaconf import OmegaConf

OmegaConf.register_new_resolver("mul", lambda x, y: x * y)

cfg = OmegaConf.create({"gpus": 4, "workers": "${mul:${gpus},2}"})
print(cfg.workers)  # 8

Registering the same name twice raises ValueError unless replace=True, which bites in pytest where each test re-imports your setup module. Resolvers run on every access; pass use_cache=True if the function is expensive or must return a stable value.

Type-check a YAML file against a dataclass schemavalidate-with-schema

from dataclasses import dataclass
from omegaconf import OmegaConf

@dataclass
class Schema:
    host: str = "localhost"
    port: int = 80

schema = OmegaConf.structured(Schema)
loaded = OmegaConf.load("config.yaml")
cfg = OmegaConf.merge(schema, loaded)  # validates types and keys

The schema must come first; merging schema into data does nothing useful. Wrong types raise ValidationError, unknown keys raise ConfigKeyError because structured configs act like struct mode. This merge-over-schema trick is exactly what Hydra does with its ConfigStore.

Require values with MISSING / ???mark-missing-values

from dataclasses import dataclass
from omegaconf import MISSING, OmegaConf

@dataclass
class Config:
    api_key: str = MISSING  # same as "???" in YAML

cfg = OmegaConf.structured(Config)
print(OmegaConf.missing_keys(cfg))  # {'api_key'}
cfg.api_key  # raises MissingMandatoryValue

MISSING is literally the string "???", so a YAML value of ??? means the same thing. The error fires at access, which can be deep inside a training run; call OmegaConf.missing_keys() right after your merges to fail at startup instead.

Make a config immutablefreeze-config-read-only

from omegaconf import OmegaConf, read_write

cfg = OmegaConf.create({"lr": 0.01})
OmegaConf.set_readonly(cfg, True)
cfg.lr = 0.1  # raises omegaconf.ReadonlyConfigError

with read_write(cfg):
    cfg.lr = 0.1  # allowed inside the context

The flag is recursive from the node you set it on. Freeze the config after composition so a stray assignment in library code cannot silently change an experiment; the read_write context is the sanctioned escape hatch, not a hack.

Reject unknown keys with struct modelock-keys-struct-mode

from omegaconf import OmegaConf, open_dict

cfg = OmegaConf.create({"model": {"lr": 0.01}})
OmegaConf.set_struct(cfg, True)
cfg.model.lrr = 0.1  # typo raises ConfigAttributeError

with open_dict(cfg):
    cfg.model.dropout = 0.5  # deliberately add a new key

Without struct mode a typo silently creates a new key and your real setting keeps its default. Hydra turns struct mode on for you, which is why 'Key X is not in struct' is one of the most googled Hydra errors; open_dict is the intended way to add keys anyway.

Override config from the command linecli-dotlist-overrides

from omegaconf import OmegaConf

cfg = OmegaConf.load("config.yaml")

# python train.py server.port=8080 log.level=debug
cfg.merge_with_dotlist(["server.port=8080", "log.level=debug"])
# or: cfg = OmegaConf.merge(cfg, OmegaConf.from_cli())

Values are parsed with the YAML-ish grammar, so port=8080 becomes an int and flag=true a bool. from_cli() eats all of sys.argv[1:], which fights argparse; if you mix both, split the args yourself or use from_dotlist on the leftovers.

Convert back to plain dicts or dataclass instancesexport-plain-containers

from omegaconf import OmegaConf

plain = OmegaConf.to_container(cfg, resolve=True)  # dicts and lists
obj = OmegaConf.to_object(cfg)  # real dataclass instances
OmegaConf.resolve(cfg)          # resolve interpolations in place
print(OmegaConf.to_yaml(cfg))   # serialize

Do this at the boundary before json.dumps, multiprocessing, or a hot loop: DictConfig is not a dict and attribute access is slow. resolve=True bakes interpolations in; to_object always resolves and also fails on any remaining MISSING values, which makes it a good final validation step.

Alternatives

PackageRegistryPick it when
pydantic-settingsPyPITyped application settings from env vars and .env files, especially if pydantic is already in the stack
dynaconfPyPILayered settings across toml/yaml/json/env with environment switching (dev/prod) and secrets files
hydra-corePyPIYou want the full application framework on top: config composition, CLI, multirun sweeps. It is built on OmegaConf
pyyamlPyPIOne YAML file, no merging or interpolation needed; parse it and feed a dataclass yourself