Skip to main content

astblock

Switch off individual Python statements without editing or redeploying the code.

You write a small JSON blocklist naming statements by an AST fingerprint. When the program starts with that blocklist, each blocked statement is rewritten at import time so that it either skips (does nothing) or raises BlockedStatementError. Everything not on the list is compiled exactly as normal.

The intended use is emergency mitigation: a third-party call that hangs, a side effect that fires twice, a code path that corrupts data. It lets you turn that one statement off with a config change and a restart, while the proper fix goes through your normal release process.

Workflow

# 1. Find the statement's fingerprint
$ python -m astblock list shop.checkout --line 15
   15  ebfa39018db86a24  checkout   notify_partner_api(order)

# 2. Generate a rule (then add a reason)
$ python -m astblock list shop.checkout --line 15 --json --action skip > blocklist.json

# 3. Verify every rule matches the code you're about to run
#    (reads the source; does not import it)
$ python -m astblock check blocklist.json
OK       shop.checkout ebfa39018db86a24 [skip] line 15: notify_partner_api(order)

# 4. Run with it
$ python -m astblock run --blocklist blocklist.json -m shop.checkout

examples/ contains this exact scenario.

Activating it in an application

Pick one:

  • CLI wrapper: python -m astblock run --blocklist FILE -m yourapp or ... run --blocklist FILE script.py.
  • One line at the top of your entry point, before your own modules are imported: import astblock; astblock.install_from_env(). It does nothing unless ASTBLOCK_FILE is set.
  • No code change: a .pth file in site-packages containing the single line import astblock; astblock.install_from_env() runs at interpreter startup. This is powerful, so only do it in environments you control.

If ASTBLOCK_FILE is set but the file is missing or invalid, startup fails rather than running unpatched.

Fingerprints

A fingerprint is a hash of the module name, the enclosing function/class path, the statement's AST (without positions), and an occurrence index for identical statements in the same scope. So it:

  • survives reformatting, comment changes and code added above it;
  • changes if the statement itself changes or moves to another function, so an old rule stops matching instead of hitting the wrong code. Stale rules are logged at import time and reported by astblock check.

Generate fingerprints with the same Python minor version you run in production: AST shapes occasionally change between versions.

Blocklist format

{
  "version": 1,
  "strict": true,
  "rules": [
    {
      "module": "shop.checkout",
      "fingerprint": "ebfa39018db86a24",
      "action": "skip",
      "reason": "Partner API outage, INC-2231"
    }
  ]
}

action is "raise" (the default) or "skip". Scripts run directly use the module name __main__; code run with -m pkg.mod uses pkg.mod.

strict (default false) decides what happens to a rule that does not fire. By default that is a logged warning and the statement runs. Under strict the process refuses to start. Prefer strict during an incident: a rule that silently stopped working is the one failure this tool cannot afford, and a process that will not start is easier to notice than one quietly running the statement you meant to block.

strict is enforced at two moments, because they catch different mistakes:

  • At install(), every rule is checked against the source it names, without importing it. This catches a rule whose module is misspelled or no longer exists, which the import-time check never would: a module that is never imported never reaches it.
  • At import, a rule that matches no statement in a module being compiled raises StaleRuleError. This catches code that changed after the rule was written.

Rules for __main__ are skipped by the first check, since a script's module name says nothing about where the file is, and caught by the second.

A module must be a dotted module name and a reason is at most 200 characters with no control characters, because both are echoed by astblock check and written to log records. A blocklist file is limited to 1 MiB and 10,000 rules.

Semantics and limits: read before using in an incident

  • Skipping is not free. A skipped assignment leaves the name undefined, a skipped return falls through to the following code, and a skipped def or import removes the name entirely. Block the narrowest statement that does the job, and prefer raise where the caller already handles errors.
  • Blocking a compound statement (if, for, with, def) blocks all of it.
  • If you block a function's only yield, it stays a generator (it just yields nothing).
  • Import time only. Rules apply when a module is imported, so the process must restart. Modules imported before install() are not patched, and a warning names them.
  • Only modules loaded from .py source are patchable, not extension modules or pyc-only distributions. Targeted modules are always compiled from source and never cached, so a stale .pyc can't bypass a rule.
  • Hits are logged to the astblock logger (first hit at WARNING, later hits at DEBUG) and counted in astblock.hits().

Security

Whoever can write the blocklist decides which statements in your program run, so the file and the ASTBLOCK_FILE variable deserve the same protection as your deploy credentials.

  • A world-writable blocklist is refused outright. A group-writable one loads with a warning naming the group. A blocklist in a world-writable directory without the sticky bit also warns, because anyone can replace a file in such a directory whatever the file's own mode says.
  • ASTBLOCK_FILE is read from the ambient environment. If you use the .pth activation, every Python process in that environment honours it, so anyone who can set that variable for a more privileged process can disable that process's checks. Prefer the CLI wrapper or an explicit install() call where you can.
  • astblock list and astblock check do not import the modules they name, so checking a blocklist someone handed you does not run their code. Resolution falls back to importing only if you pass --allow-import.
  • Blocking is not a safe default for security-relevant code. Do not block an authorization check, a lock acquisition, or a statement whose result is consumed inside a try. A skipped assignment leaves the name undefined, and the resulting NameError swallowed by a broad except can turn a denial into an approval.

Python API

import astblock

astblock.install("blocklist.json")        # or a Blocklist object
astblock.fingerprint_source(src, "mod")   # -> list[Statement]
astblock.hits()                           # {fingerprint: count}
astblock.uninstall()

Development

pip install -e ".[test]"
pytest

Release files for astblock 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for astblock 0.2.0
File Size Uploaded
astblock-0.2.0.tar.gz 22.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for astblock 0.2.0
File Interpreter ABI Platform
astblock-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 43.9 kB

Release files / astblock-0.2.0.tar.gz

Download URL astblock-0.2.0.tar.gz
Size 22.6 kB
Tags Source
SHA-256 checksum
How to use checksums
74ef3ffc95a1f4bf4d3afd798259de552f5a2189f3aa4b01d16d28cb94e1278d
BLAKE2b-256 checksum
How to use checksums
364fad3d2c2ef24529846b878d64a82dc24e420ebc02207e0ee4c59b5c1f9cb3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / astblock-0.2.0-py3-none-any.whl

Download URL astblock-0.2.0-py3-none-any.whl
Size 21.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
51dd8c48e67986778b28c85cd9e5e93e1ae3fcbc4c4633f8093d237d01e2db8d
BLAKE2b-256 checksum
How to use checksums
60e9403bf62cb93c0faf3c61ffd6e1792e4758f23ba9626d4ca740b13adc9384
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page