Skip to main content
Version: 2.4 (prerelease)

Upgrade from 2.3 to 2.4

OmegaConf 2.4 is a prerelease with breaking changes. Check the affected APIs and config inputs below before upgrading from 2.3.

Breaking changes​

Python 3.6–3.9 are no longer supported​

Upgrade application, CI, and deployment environments to Python 3.10 or newer before installing 2.4.

Tuple inputs no longer become mutable lists​

Native tuples create structurally immutable TupleConfig values instead of ListConfig values. Element assignment, insertion, and removal fail; OmegaConf.is_list() returns False, and OmegaConf.to_container() returns a tuple rather than a list for tuple input.

Pass a list when mutation is required. Use OmegaConf.is_sequence() for code that accepts either a list or a tuple.

Tuple migration examples and type checks

For a mutable sequence, use a list. For a tuple, replace the whole value through its mutable parent:

>>> from omegaconf import OmegaConf
>>> # Use a list for mutation.
>>> cfg = OmegaConf.create({"coords": [1, 2]})
>>> cfg.coords.append(3)
>>> list(cfg.coords)
[1, 2, 3]
>>> cfg = OmegaConf.create({"coords": (1, 2)})
>>> # Replace the whole tuple.
>>> cfg.coords = (1, 2, 3)
>>> tuple(cfg.coords)
(1, 2, 3)

In structured configs, use list[T] for mutable sequences and a tuple annotation for tuples. A fixed annotation such as tuple[int, int] requires two elements; tuple[int, ...] accepts any number of integers. Nested mutable containers inside a tuple remain mutable.

Use OmegaConf.is_tuple() for tuple-specific code or OmegaConf.is_sequence() when either sequence type is accepted, including checks that previously used isinstance(value, ListConfig) for general sequence handling. Tuple semantics are experimental in 2.4.

Config containers cannot be dictionary keys or set elements​

DictConfig and ListConfig are unhashable in 2.4. Calling hash(cfg), using a config as a dictionary key, or adding one to a set raises TypeError, including for read-only configs.

Use an explicit immutable key derived from the fields your application needs to identify a config.

OmegaConf.create(None) returns None​

In 2.3, this call returned a DictConfig wrapping None. In 2.4, it returns literal None. Handle None before calling container methods. If you intended to create an empty dictionary config, pass {} explicitly.

OmegaConf.get_type() reports NoneType for null nodes​

For a config node containing None, OmegaConf.get_type() now returns type(None). Update code that branches on the previous type result. To check whether a field's value is null, read the field and use is None.

Resolving an interpolation to a missing value raises​

OmegaConf.resolve() raises InterpolationToMissingValueError when an interpolation targets ???. In 2.3, it replaced the interpolation with the missing marker.

Populate the referenced value before calling resolve(), or defer resolution until the required values are available.

A resolver returning ??? produces a missing value​

A resolver that returns plain "???" now makes its result missing on access. Return r"\???" when you intend those characters as literal text. See missing values for escaping in Python and YAML and for checking detached values.

Typed interpolations validate on access​

Interpolations into typed containers and union fields now validate and convert their results against the destination type during lazy access. Code that previously read an incompatible value may now raise InterpolationValidationError; compatible container elements may be converted to the destination's declared types.

Check the source values and destination annotations. See validation and conversion.

Resolver evaluation during export runs fewer times​

OmegaConf.to_container(..., resolve=True) evaluates each custom resolver node at most once per conversion pass, including when several interpolations reference that node. Code that depends on repeated resolver side effects or different results from those references must be updated. Move required side effects out of resolvers and make distinct evaluations explicit.

Key paths interpret backslashes before delimiters as escapes​

In select(), update(), from_dotlist(), and from_cli(), a backslash before ., [, ], or = now escapes that character. For example, r"a\.b" addresses the single key "a.b"; it no longer navigates through a key ending in a backslash to a nested key "b".

For nested access through a key ending in a backslash, use direct item access or rename the key. Doubling the backslash does not restore the old path behavior. See key paths. Configs containing both an integer key and its string spelling, such as 1 and "1", are also rejected. Rename or normalize conflicting keys.

YAML loading enforces alias expansion limits​

YAML that exceeds the expansion limits is rejected. Simplify excessive anchors, aliases, or merge keys. For trusted input that needs a larger limit, set max_yaml_expanded_nodes on OmegaConf.load() or OmegaConf.create(). See YAML alias limits for the limits and environment override.

New warnings and deprecations​

  • Implicit conversion: direct assignment and typed-container mutation emit FutureWarning when they convert a value to another type. This can fail applications or tests that treat warnings as errors. Assign the declared type directly or use OmegaConf.update() for explicit conversion.
  • Resolver annotations: argument and return annotation mismatches emit UserWarning by default. Correct the annotations or values, or choose an explicit annotation_validation policy when registering the resolver. See resolver annotation validation.
  • Resolver registration: register_new_resolver() and legacy_register_resolver() are deprecated. Migrate to OmegaConf.register_resolver() and review argument parsing and validation in the custom resolver guide.

See the 2.4.0rc1 release notes for the complete release history.