Skip to content

Migrating from 3.* to 4.*

How to Read This Guide

This guide is intended to help you migrate existing functionality from that-depends version 3.* to 4.*.
The goal is to enable you to migrate as quickly as possible while making only the minimal necessary changes to your codebase.

This migration intentionally focuses only on the simplest change needed to preserve existing behaviour.

If you want to learn more about the new internals introduced in 4.*, please refer to the documentation and the release notes.


Changes in the API

Collection providers now return read-only container types

In 4.*, collection providers no longer resolve to mutable built-in containers:

  • providers.List(...) now resolves to a Sequence implemented as a tuple
  • providers.Dict(...) now resolves to a Mapping implemented as a mappingproxy

If your existing code only reads from these values, you likely do not need to change anything.

If your existing code mutates the resolved value, the simplest way to preserve the old behaviour is to convert the result at the call site:

items = list(MyContainer.items.resolve_sync())
mapping = dict(MyContainer.mapping.resolve_sync())

Behaviour-Preserving Migration Examples

providers.List(...)

Previously in 3.*, code like this returned a list:

items = MyContainer.items.resolve_sync()
items.append("new-item")

In 4.*, items is a read-only sequence, so mutating it directly will no longer work.

To preserve the previous behaviour, change it to:

items = list(MyContainer.items.resolve_sync())
items.append("new-item")

The same applies to async resolution:

items = list(await MyContainer.items.resolve())
items.append("new-item")

providers.Dict(...)

Previously in 3.*, code like this returned a mutable dict:

mapping = MyContainer.mapping.resolve_sync()
mapping["extra"] = "value"

In 4.*, mapping is a read-only mapping, so item assignment will no longer work.

To preserve the previous behaviour, change it to:

mapping = dict(MyContainer.mapping.resolve_sync())
mapping["extra"] = "value"

The same applies to async resolution:

mapping = dict(await MyContainer.mapping.resolve())
mapping["extra"] = "value"

Further Help

If you continue to experience issues during migration, consider creating a discussion or opening an issue.