Python Scripting (spec: script-py)

Napper runs Python scripts (.py) via Python 3, found as python3 on your PATH. Scripts run on the real Python runtime with full access to PyPI — no sandbox, no limits.

A script can be a step in a .naplist, or a pre/post hook on a .nap request. It passes when it exits 0 and fails on any non-zero exit; everything it prints to stdout is captured into the run output.

The injected ctx object (spec: script-context)

Napper injects a global named ctx into every Python script and hook. There is no import and nothing to installctx is already in scope.

Member Available Description
ctx.env step + hook Current environment name (--env), or ""
ctx.vars step + hook Dict of all resolved variables (read)
ctx.request hook The request: method, url, headers, body
ctx.response post hook The response: status, headers, body, json, duration_ms
ctx.set(key, value) step + hook Set a variable visible to every later step
ctx.fail(message) step + hook Fail the step — even if the process exits 0
ctx.log(message) step + hook Write a line to the run output

Pre/post request hooks (spec: script-pre, script-post)

Reference hooks from a .nap file's [script] block:

[script]
pre = ./scripts/setup_auth.py
post = ./scripts/validate_response.py

A pre hook runs before the request is sent — set up variables or log:

# setup_auth.py
token = "demo-" + ctx.vars["userId"]
ctx.set("token", token)               # {{token}} is now available to this request and later steps
ctx.log("token ready for env " + ctx.env)

A post hook runs after the response — validate, extract, or chain. If it calls ctx.fail (or exits non-zero), the request step fails even when the HTTP assertions passed:

# validate_response.py
if ctx.response.status != 200:
    ctx.fail("expected 200, got " + str(ctx.response.status))
body = ctx.response.json              # parsed JSON, or None if the body isn't JSON
if body["id"] <= 0:
    ctx.fail("id must be positive")
ctx.set("postId", str(body["id"]))    # hand the id to the next step
ctx.log("created post " + str(body["id"]) + " in " + str(ctx.response.duration_ms) + "ms")

Scripts as playlist steps (spec: naplist-script-step)

A .py step shares the playlist's variable scope through ctx, so it can seed data for the steps that follow:

# seed.py
ctx.set("postId", "7")
ctx.log("seeded postId=7")
[steps]
./seed.py
./get-post.nap        # this request can use {{postId}}

Runnable end-to-end: examples/scripting-ctx/ctx-demo-python.naplist.

Mixing with other languages (spec: script-dispatch)

Dispatch is by extension, so a .naplist can interleave Python with JavaScript, F#, and C# steps — see examples/jsonplaceholder/mixed-scripts.naplist and the Scripting Overview.

Requirements (spec: script-runtime)

Python scripts require Python 3 available as python3 on your PATH. The Napper CLI binary itself is self-contained; plain .nap and .naplist files need no runtime at all.

Prefer JavaScript? See JavaScript Scripting — it has the same ctx surface. F# and C# scripts also run (as exit-code steps and hooks); see F# and C#.