Skip to content

Multi-step upgrades

When a project is multiple versions behind, the upgrade engine chains all intermediate upgrades and collapses redundant operations.

Defining a chain

Each Upgrade specifies from_version and to_version. The planner walks the chain automatically.

upgrades = [
    Upgrade(from_version="0.1.0", to_version="0.2.0", description="v0.2.0", operations=[
        ReplaceFile(path="Dockerfile", template="dockerfile_v2.jinja", description="v2"),
        BumpDependency(name="fastapi", old_spec=">=0.115", new_spec=">=0.118"),
        AddDependency(name="httpx", spec=">=0.25"),
    ]),
    Upgrade(from_version="0.2.0", to_version="0.3.0", description="v0.3.0", operations=[
        ReplaceFile(path="Dockerfile", template="dockerfile_v3.jinja", description="v3"),
        BumpDependency(name="fastapi", old_spec=">=0.118", new_spec=">=0.120"),
        BumpDependency(name="httpx", old_spec=">=0.25", new_spec=">=0.27"),
    ]),
    Upgrade(from_version="0.3.0", to_version="0.4.0", description="v0.4.0", operations=[
        ReplaceFile(path="Dockerfile", template="dockerfile_v4.jinja", description="v4"),
        BumpDependency(name="fastapi", old_spec=">=0.120", new_spec=">=0.125"),
    ]),
]

# Project is at 0.1.0, target is 0.4.0
plan = plan_upgrade(dotfile, upgrades, target="0.4.0")

What gets collapsed

The planner collects all operations from the chain and collapses them:

  • 3 ReplaceFile("Dockerfile") → 1 (keeps the last template: dockerfile_v4.jinja)
  • 3 BumpDependency("fastapi") → 1 (first old_spec to last new_spec: >=0.115>=0.125)
  • AddDependency("httpx") + BumpDependency("httpx") → 1 AddDependency with final spec (>=0.27)

Result: 3 operations instead of 8.

Error handling

The planner raises UpgradeError if:

  • No path exists from the current version to the target
  • The version chain contains a cycle
  • The current version matches the target (nothing to do)
from bluefox_upgrade import UpgradeError

try:
    plan = plan_upgrade(dotfile, upgrades, target="0.5.0")
except UpgradeError as e:
    print(f"Cannot upgrade: {e}")