Skip to content
Job sheet 003 · Worked example Verified

A deployment helper with scoped policy

Working directory, environment overlays, a timeout, and cleanup, all under one narrowed scope.

Verified 2026-10-19 · Cuprum 0.2.0b2 · Python 3.12 · Linux

PolicyTimeouts

01 — Problem

The job

A deployment step is several commands that belong together: render a configuration file for the target environment, pack a release, check what went into it. Each runs in the same working directory, with the same environment, under the same deadline, and the scratch directory has to go away afterwards whether the step succeeded or not.

In Cuprum the shared policy is one ScopeConfig: the programs allowed, a default deadline, an environment overlay, and a hook that records every command's outcome. The working directory is a per-run ExecutionContext, and cleanup is an ordinary with block.

02 — Before you start

Before you start

Level
Intermediate
Needs
Python 3.12 or later, and tar on PATH (any POSIX system)
Install
Cuprum 0.2.0b2 from PyPI; see Getting started
Tested
2026-10-19 against Cuprum 0.2.0b2 on Linux, Python 3.12; both runs repeated eight times with identical output
Uses
ScopeConfig (allowlist, timeout, env_overlay, after_hooks), ExecutionContext(cwd=...), TimeoutExpired

03 — Code

The code

deploy.py Complete
import sys
import tempfile
from pathlib import Path

from cuprum import (
    TAR,
    ExecutionContext,
    Program,
    ProgramCatalogue,
    ScopeConfig,
    scoped,
    sh,
)

PYTHON = Program(sys.executable)
CATALOGUE = ProgramCatalogue.from_programs(PYTHON, TAR, name="deploy")

python = sh.make(PYTHON, catalogue=CATALOGUE)
tar = sh.make(TAR, catalogue=CATALOGUE)

audit: list[str] = []


def record(cmd, result):
    tool = "python" if cmd.program == PYTHON else cmd.program
    audit.append(f"{tool} {cmd.argv[0]} exited {result.exit_code}")


RENDER = (
    "import os, pathlib\n"
    "pathlib.Path('app.conf').write_text(f\"env={os.environ['DEPLOY_ENV']}\\n\")"
)

policy = ScopeConfig(
    allowlist=CATALOGUE.allowlist,
    timeout=10.0,
    env_overlay={"DEPLOY_ENV": "staging"},
    after_hooks=(record,),
)

with tempfile.TemporaryDirectory(prefix="deploy-") as workdir:
    staging = Path(workdir)
    here = ExecutionContext(cwd=staging)
    with scoped(policy):
        python("-c", RENDER).run_sync(context=here)
        packed = tar("-czf", "release.tgz", "app.conf").run_sync(context=here)
        listed = tar("-tzf", "release.tgz").run_sync(context=here)
    rendered = (staging / "app.conf").read_text()

print(f"rendered {rendered.strip()!r}")
print(f"packed ok={packed.ok}")
print(f"archive holds {listed.stdout.split()}")
print(f"cleaned up: {not staging.exists()}")
for line in audit:
    print(line)
Verified 2026-10-19 · Cuprum 0.2.0b2 · Python 3.12 · Linux

04 — Output

Expected output

Expected outputterminal
rendered 'env=staging'
packed ok=True
archive holds ['app.conf']
cleaned up: True
python -c exited 0
tar -czf exited 0
tar -tzf exited 0

The first line comes from the file the child wrote: the child saw DEPLOY_ENV=staging from the scope's overlay without the parent's environment changing. The archive holds the rendered file, packed in the scratch directory because every command ran with that cwd.

The directory is gone once the with block closes, and the after hook saw each of the three commands in the order they finished.

05 — Failure path

When it fails

Now a migration step that hangs. The scope's one-second default applies to every command inside it, so the step is torn down: asked to terminate, given cancel_grace to do so, then killed.

deploy_timeout.py Failure path
import sys
import tempfile
from pathlib import Path

from cuprum import (
    ExecutionContext,
    Program,
    ProgramCatalogue,
    ScopeConfig,
    TimeoutExpired,
    scoped,
    sh,
)

PYTHON = Program(sys.executable)
CATALOGUE = ProgramCatalogue.from_programs(PYTHON, name="deploy")
python = sh.make(PYTHON, catalogue=CATALOGUE)

MIGRATE = "import time; print('migrating', flush=True); time.sleep(30)"

policy = ScopeConfig(allowlist=CATALOGUE.allowlist, timeout=1.0)

staging = None
try:
    with tempfile.TemporaryDirectory(prefix="deploy-") as workdir:
        staging = Path(workdir)
        with scoped(policy):
            python("-c", MIGRATE).run_sync(
                context=ExecutionContext(cwd=staging, cancel_grace=0.5)
            )
except TimeoutExpired as exc:
    print(f"timed out after {exc.timeout}s")
    print(f"partial output: {exc.output!r}")
print(f"cleaned up: {staging is not None and not staging.exists()}")
Verified 2026-10-19 · Cuprum 0.2.0b2 · Python 3.12 · Linux
Observed outputstdout
timed out after 1.0s
partial output: 'migrating\n'
cleaned up: True

TimeoutExpired carries what the child had printed before its deadline, and the scratch directory was still removed: the exception unwound through the with block like any other. Every expiry also writes a subprocess_timeout_expired warning to the cuprum.timeout logger on stderr, with the process ID and the deadline.

06 — Why it is allowed

Why the policy permits it

The catalogue lists this interpreter and tar, and the scope's allowlist is that catalogue's, so nothing else can run inside the block. The overlay, the deadline, and the audit hook apply to every command in the scope and disappear when it closes; none of them changes the process's own environment.