#!/usr/bin/env python3
"""Measure whether a workload scales with threads, processes or sub-interpreters.
WARNING: this script EXECUTES CODE. With --target it imports your module and calls
your function many times (serially, in N threads, in N worker processes and, with
--interpreters on Python 3.14+, in N sub-interpreters). Only point it at code you
trust and that is safe to run repeatedly. --builtin runs synthetic workloads that
ship with this script.
Everything runs in a child process placed in its own process group/session; the
parent enforces --timeout and hard-kills the whole group (child, pool workers,
forkserver) when it expires or on Ctrl-C, so a hung workload cannot outlive the probe.
For each mode it runs N tasks (N = --workers), keeps the best of --repeat runs, and
reports wall time, process CPU time, CPU/wall ratio (how many cores were busy),
per-worker CPU, and speedup over serial. It then classifies the workload:
I/O-bound mostly waiting; threads/asyncio scale
releases the GIL CPU-heavy and threads scale
GIL-bound: scales with processes, not threads
likely GIL-bound threads never overlapped, processes did not scale
either (busy machine: low confidence, re-run)
mixed I/O + CPU waits overlap, the CPU part serialises
CPU-bound, does not scale threads keep cores busy but no speedup
(memory bandwidth, lock contention, false sharing)
inconclusive too few workers or ambiguous numbers; re-run
and reports the interpreter: GIL build or free-threaded, and sys._is_gil_enabled().
Built-in workloads (--builtin): pure-python (Python loop), native-release (hashlib on
1 MiB buffers; releases the GIL), native-hold (re.findall; C code holding the GIL),
io (time.sleep), mixed (half Python loop, half sleep).
Exit codes: 0 measured, 1 --fail-if-gil-bound and the workload is GIL-bound,
2 usage error, target import/call error, or --timeout expired, 130 Ctrl-C.
Examples:
python gil_probe.py --builtin pure-python
python gil_probe.py --builtin io --workers 8 --repeat 2 --json
python gil_probe.py --target mypkg.text:clean_doc --arg '"some text"' --arg 3 --timeout 120
python gil_probe.py --target ./bench.py:score_batch --arg 2000 --interpreters
"""
import argparse
import ast
import json
import os
import signal
import subprocess
import sys
import tempfile
import time
EXIT_OK, EXIT_GATE, EXIT_ERROR = 0, 1, 2
BUILTINS = ("pure-python", "native-release", "native-hold", "io", "mixed")
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# ----------------------------------------------------------------------------- workloads
def _py_loop(n):
acc = 0
for i in range(n):
acc += (i * i) % 7
return acc
def builtin_workload(name, scale):
"""Run one unit of a built-in workload (about 0.1-0.2 s on a modern core at scale 1)."""
if name == "pure-python":
return _py_loop(int(3_000_000 * scale))
if name == "native-release":
import hashlib
buf = bytes(range(256)) * 4096 # 1 MiB: hashlib releases the GIL for inputs > 2 KiB
digest = b""
for _ in range(int(250 * scale)):
digest = hashlib.sha256(buf).digest()
return digest.hex()
if name == "native-hold":
import re
text = "alpha beta gamma, delta! 123 epsilon zeta\n" * 40_000
pattern = re.compile(r"(\w+)\s+(\w+)")
found = 0
for _ in range(max(1, int(3 * scale))):
found += len(pattern.findall(text))
return found
if name == "io":
time.sleep(0.15 * scale)
return None
if name == "mixed":
_py_loop(int(1_500_000 * scale))
time.sleep(0.08 * scale)
return None
raise ValueError(f"unknown builtin workload {name!r}")
_TARGET_CACHE = {}
def resolve_target(spec, cwd):
"""Import 'module:function' or 'path/to/file.py:function' and return the callable."""
if spec in _TARGET_CACHE:
return _TARGET_CACHE[spec]
import importlib
import importlib.util
if ":" not in spec:
raise ValueError(f"--target must look like module:function or file.py:function, got {spec!r}")
mod_part, func_name = spec.rsplit(":", 1)
if cwd and cwd not in sys.path:
sys.path.insert(0, cwd)
if mod_part.endswith(".py") or os.sep in mod_part or "/" in mod_part:
path = mod_part if os.path.isabs(mod_part) else os.path.join(cwd or os.getcwd(), mod_part)
if not os.path.isfile(path):
raise FileNotFoundError(f"target file not found: {path}")
mod_name = "_gil_probe_target_" + os.path.splitext(os.path.basename(path))[0].replace("-", "_")
module = sys.modules.get(mod_name)
if module is None:
spec_obj = importlib.util.spec_from_file_location(mod_name, path)
module = importlib.util.module_from_spec(spec_obj)
sys.modules[mod_name] = module
spec_obj.loader.exec_module(module)
else:
module = importlib.import_module(mod_part)
func = module
for attr in func_name.split("."):
func = getattr(func, attr)
if not callable(func):
raise TypeError(f"{spec} is not callable")
_TARGET_CACHE[spec] = func
return func
def run_task(job):
"""Run one unit of work; return (wall_s, thread_cpu_s). Module-level so it pickles by reference."""
kind, name, args, scale, cwd = job
fn = None
if kind == "target":
fn = resolve_target(name, cwd)
wall0, cpu0 = time.perf_counter(), time.thread_time()
if kind == "builtin":
builtin_workload(name, scale)
else:
fn(*args)
return time.perf_counter() - wall0, time.thread_time() - cpu0
def _warm(_):
time.sleep(0.05)
return os.getpid()
# ----------------------------------------------------------------------------- measurement (child)
def measure_serial(job, n):
wall0, cpu0 = time.perf_counter(), time.process_time()
per = [run_task(job) for _ in range(n)]
return wall0, cpu0, per
def measure_threads(job, n):
import threading
results = [None] * n
errors = []
barrier = threading.Barrier(n + 1)
def worker(i):
try:
barrier.wait()
results[i] = run_task(job)
except BaseException as err: # reported to the parent, never swallowed
errors.append(err)
threads = [threading.Thread(target=worker, args=(i,), daemon=True, name=f"probe-{i}") for i in range(n)]
for t in threads:
t.start()
wall0, cpu0 = time.perf_counter(), time.process_time()
barrier.wait()
for t in threads:
t.join()
if errors:
raise errors[0]
return wall0, cpu0, results
def measure_pool(executor, job, n):
list(executor.map(_warm, range(n))) # start every worker before timing
wall0, cpu0 = time.perf_counter(), time.process_time()
futures = [executor.submit(run_task, job) for _ in range(n)]
per = [f.result() for f in futures]
return wall0, cpu0, per
def run_mode(mode, job, n, repeat):
import concurrent.futures as cf
samples = []
executor = None
try:
if mode == "processes":
import multiprocessing as mp
method = "forkserver" if "forkserver" in mp.get_all_start_methods() else "spawn"
executor = cf.ProcessPoolExecutor(max_workers=n, mp_context=mp.get_context(method))
elif mode == "interpreters":
executor = cf.InterpreterPoolExecutor(max_workers=n)
for _ in range(repeat):
if mode == "serial":
wall0, cpu0, per = measure_serial(job, n)
elif mode == "threads":
wall0, cpu0, per = measure_threads(job, n)
else:
wall0, cpu0, per = measure_pool(executor, job, n)
wall = time.perf_counter() - wall0
proc_cpu = time.process_time() - cpu0
worker_cpu = [round(c, 4) for _w, c in per]
cpu = sum(worker_cpu) if mode in ("processes", "interpreters") else proc_cpu
samples.append({"wall": wall, "cpu": cpu, "worker_cpu": worker_cpu,
"task_wall": [round(w, 4) for w, _c in per]})
finally:
if executor is not None:
executor.shutdown(wait=True, cancel_futures=True)
best = min(samples, key=lambda s: s["wall"])
walls = sorted(s["wall"] for s in samples)
return {"mode": mode, "wall_s": round(best["wall"], 4), "median_wall_s": round(walls[len(walls) // 2], 4),
"cpu_s": round(best["cpu"], 4), "cpu_per_wall": round(best["cpu"] / best["wall"], 2) if best["wall"]
else 0.0, "worker_cpu_s": best["worker_cpu"], "task_wall_s": best["task_wall"], "runs": len(samples)}
def interpreter_info():
import platform
import sysconfig
gil_fn = getattr(sys, "_is_gil_enabled", None)
return {"python": platform.python_version(), "implementation": platform.python_implementation(),
"free_threaded_build": bool(sysconfig.get_config_var("Py_GIL_DISABLED")),
"gil_enabled": gil_fn() if gil_fn else True, "switch_interval_s": sys.getswitchinterval(),
"cpu_count": os.cpu_count(), "usable_cpus": usable_cpus()}
def usable_cpus():
try:
return len(os.sched_getaffinity(0))
except (AttributeError, OSError):
return os.cpu_count() or 1
def cpu_availability():
"""Share of one core this process actually gets right now (1.0 idle, lower when the machine is busy)."""
ratios = []
for _ in range(7):
wall0, cpu0 = time.perf_counter(), time.process_time()
_py_loop(1_000_000)
wall = time.perf_counter() - wall0
if wall > 0:
ratios.append((time.process_time() - cpu0) / wall)
ratios.sort()
return round(min(1.0, max(ratios[len(ratios) // 2] if ratios else 1.0, 0.05)), 2)
def classify(modes, n, avail=1.0):
"""Label the workload from speedups and CPU/wall ratios (ratios are robust to a busy machine)."""
serial, threads = modes["serial"], modes["threads"]
procs = modes.get("processes") if "wall_s" in modes.get("processes", {}) else None
s_t = serial["wall_s"] / threads["wall_s"] if threads["wall_s"] else 0.0
s_p = serial["wall_s"] / procs["wall_s"] if procs and procs["wall_s"] else None
ser_ratio = serial["cpu_per_wall"]
cpu_norm = ser_ratio / avail if avail else ser_ratio
scale_bar = max(1.5, 0.4 * n)
if n < 2:
return "inconclusive: need --workers >= 2", "low"
if cpu_norm < 0.25:
if s_t >= max(1.5, 0.5 * n):
return "I/O-bound: threads and asyncio scale (waiting, not computing)", "high"
return "I/O-bound but threads did not overlap the waits (shared lock or serial resource?)", "medium"
if cpu_norm <= 0.7:
return ("mixed I/O + CPU: threads overlap the waits, the CPU part still serialises on the GIL"
if s_t > 1.2 else "mixed I/O + CPU: no thread speedup"), "medium"
# CPU-bound: how many more cores did threads / processes keep busy than the serial run?
p_t = threads["cpu_per_wall"] / ser_ratio
p_p = procs["cpu_per_wall"] / ser_ratio if procs else None
if p_t >= 1.4:
if s_t >= scale_bar:
return "releases the GIL: CPU-bound and threads scale", "high"
if s_t >= 1.2:
return "releases the GIL: threads scale (limited by free cores)", "medium"
return ("CPU-bound, does not scale: threads keep cores busy without speedup (memory bandwidth, lock "
"contention or false sharing)"), "medium"
if p_t < 1.2:
if p_p is None:
return "GIL-bound: threads run one at a time (processes not measured)", "medium"
if p_p >= 1.25:
return ("GIL-bound: scales with processes but not threads",
"high" if s_p is not None and s_p >= scale_bar else "medium")
return ("likely GIL-bound: threads never ran in parallel, but processes did not scale either, so the "
"machine may be too busy to tell; re-run on an idle machine"), "low"
return "inconclusive: threads ran partly in parallel; re-run with --repeat 5 on an idle machine", "low"
def child_main(cfg_path):
with open(cfg_path) as fh:
cfg = json.load(fh)
out_path = cfg["out"]
result = {"interpreter": interpreter_info()}
try:
sys.path.insert(0, SCRIPT_DIR)
import gil_probe as me # import by name so workers and sub-interpreters can unpickle run_task
job = tuple(cfg["job"])
if job[0] == "target":
try:
me.resolve_target(job[1], job[4]) # fail fast on import errors
except Exception as err:
raise ProbeError(f"cannot load target {job[1]}: {type(err).__name__}: {err}") from err
n, repeat = cfg["workers"], cfg["repeat"]
avail = me.cpu_availability()
result["interpreter"]["cpu_availability"] = avail
modes = {}
for mode in ["serial", "threads", "processes"] + (["interpreters"] if cfg["interpreters"] else []):
if mode == "interpreters" and sys.version_info < (3, 14):
modes[mode] = {"mode": mode, "error": "InterpreterPoolExecutor needs Python 3.14+"}
continue
try:
modes[mode] = me.run_mode(mode, job, n, repeat)
except Exception as err:
if mode in ("serial", "threads"):
raise ProbeError(f"workload raised during the {mode} run: {type(err).__name__}: {err}") from err
modes[mode] = {"mode": mode, "error": f"{type(err).__name__}: {err}"[:400]}
for m in modes.values():
if "wall_s" in m and m["mode"] != "serial":
m["speedup"] = round(modes["serial"]["wall_s"] / m["wall_s"], 2) if m["wall_s"] else None
label, confidence = me.classify(modes, n, avail)
result.update({"modes": modes, "classification": label, "confidence": confidence})
except ProbeError as err:
result["error"] = str(err)
except BaseException as err:
result["error"] = f"{type(err).__name__}: {err}"
with open(out_path, "w") as fh:
json.dump(result, fh)
return 0
# ----------------------------------------------------------------------------- parent
class ProbeError(Exception):
"""A failure reported to the user as a one-line message."""
def kill_group(proc):
"""Kill the child and everything in its process group (pool workers, forkserver)."""
try:
if os.name == "posix":
os.killpg(proc.pid, signal.SIGKILL)
else:
subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], capture_output=True, check=False)
except (ProcessLookupError, PermissionError, OSError):
pass
if proc.poll() is None:
try:
proc.kill()
except OSError:
pass
proc.wait()
def parse_arg_value(text):
try:
return ast.literal_eval(text)
except (ValueError, SyntaxError):
return text
def advice(label, info):
if label.startswith("I/O-bound"):
return ["Use asyncio with a Semaphore (or a ThreadPoolExecutor) to overlap the waits; processes add "
"nothing (python-asyncio-pattern-advisor)."]
if label.startswith("releases the GIL"):
return ["Threads are fine: ThreadPoolExecutor(max_workers≈cores); cap library thread pools "
"(OMP_NUM_THREADS, torch.set_num_threads) so they do not oversubscribe."]
if label.startswith("likely GIL-bound"):
return ["Re-run with --repeat 5 when the machine is idle (check the CPU availability line); if threads "
"still show no speedup while processes do, treat it as GIL-bound."]
if label.startswith("GIL-bound"):
tips = ["Use processes for this function (python-multiprocessing-pool-designer), or move the loop into "
"native code that releases the GIL (numpy, numba nogil=True, Cython nogil, Rust allow_threads)."]
if not info.get("free_threaded_build"):
tips.append("Or try a free-threaded build (python3.14t) where pure-Python threads run in parallel, "
"if every extension you import supports it.")
return tips
if label.startswith("mixed"):
return ["Split the stages: asyncio/threads for the waits, a process pool for the CPU part "
"(loop.run_in_executor(process_pool, fn))."]
if label.startswith("CPU-bound, does not scale"):
return ["Look for a shared lock, memory-bandwidth limits (large arrays) or oversubscribed native thread "
"pools; measure with perf stat and fewer workers."]
return ["Re-run with --workers >= 2 and --repeat 3 on an idle machine."]
def render(res, args):
info = res["interpreter"]
lines = [f"gil_probe: {args.target or args.builtin} (workers={args.workers}, repeat={args.repeat})",
f"Interpreter: Python {info['python']} ({info['implementation']}), "
f"{'free-threaded build' if info['free_threaded_build'] else 'GIL build'}, "
f"GIL {'enabled' if info['gil_enabled'] else 'DISABLED'} at runtime, "
f"switch interval {info['switch_interval_s'] * 1000:.1f} ms, usable CPUs {info['usable_cpus']}",
f"CPU availability: {info.get('cpu_availability', 1.0) * 100:.0f}% of one core"
+ (" (machine is busy: speedups will be understated)" if info.get("cpu_availability", 1.0) < 0.9
else ""),
"",
f" {'mode':<13}{'wall s':>9}{'median s':>10}{'cpu s':>9}{'cpu/wall':>10}{'speedup':>9} per-worker cpu s"]
for m in res["modes"].values():
if "error" in m:
lines.append(f" {m['mode']:<13} unavailable: {m['error']}")
continue
sp = f"{m['speedup']:.2f}x" if m.get("speedup") else "1.00x"
lines.append(f" {m['mode']:<13}{m['wall_s']:>9.3f}{m['median_wall_s']:>10.3f}{m['cpu_s']:>9.3f}"
f"{m['cpu_per_wall']:>10.2f}{sp:>9} {', '.join(f'{c:.3f}' for c in m['worker_cpu_s'])}")
lines += ["", f"Classification: {res['classification']} (confidence: {res['confidence']})"]
lines += [f"Next step: {tip}" for tip in advice(res["classification"], info)]
lines.append("Note: timings are noisy on shared machines; trust ratios and repeat on an idle box before "
"deciding.")
return "\n".join(lines)
def build_parser():
p = argparse.ArgumentParser(
prog="gil_probe.py",
description="EXECUTES CODE: runs a workload serially, in N threads, in N processes and optionally in N "
"sub-interpreters (3.14+), measures wall/CPU time and speedup, and classifies it as GIL-bound, "
"GIL-releasing, I/O-bound or mixed. Only use --target with code you trust. The run happens in "
"a child process group that is hard-killed after --timeout seconds.",
epilog="Exit codes: 0 measured, 1 --fail-if-gil-bound gate failed, 2 usage/target error or timeout.")
src = p.add_mutually_exclusive_group()
src.add_argument("--target", metavar="MODULE:FUNC",
help="function to run: package.module:func or path/to/file.py:func (imported and called)")
src.add_argument("--builtin", choices=BUILTINS, help="built-in synthetic workload")
p.add_argument("--arg", action="append", default=[], metavar="VALUE",
help="positional argument for --target (Python literal, else a string); repeatable")
p.add_argument("--workers", type=int, default=None,
help="threads/processes/interpreters and tasks per run (default max(2, min(4, usable CPUs)))")
p.add_argument("--repeat", type=int, default=3, help="runs per mode; the fastest is reported (default 3)")
p.add_argument("--scale", type=float, default=1.0, help="work multiplier for --builtin (default 1.0)")
p.add_argument("--interpreters", action="store_true",
help="also measure concurrent.futures.InterpreterPoolExecutor (Python 3.14+)")
p.add_argument("--timeout", type=float, default=300.0,
help="hard limit in seconds for the whole probe; everything is killed after it (default 300)")
p.add_argument("--fail-if-gil-bound", action="store_true",
help="CI gate: exit 1 when the classification is GIL-bound")
p.add_argument("--json", action="store_true", help="emit JSON instead of text")
p.add_argument("--_child", help=argparse.SUPPRESS)
return p
def main(argv=None):
parser = build_parser()
args = parser.parse_args(argv)
if args._child:
return child_main(args._child)
if not (args.target or args.builtin):
parser.error("one of the arguments --target --builtin is required")
if args.workers is None:
args.workers = max(2, min(4, usable_cpus()))
if not 1 <= args.workers <= 256:
parser.error("--workers must be between 1 and 256")
if not 1 <= args.repeat <= 50:
parser.error("--repeat must be between 1 and 50")
if args.timeout <= 0:
parser.error("--timeout must be > 0")
if args.scale <= 0:
parser.error("--scale must be > 0")
if args.arg and not args.target:
parser.error("--arg only applies to --target")
if args.target and ":" not in args.target:
parser.error("--target must look like module:function or path/to/file.py:function")
job = (["target", args.target, [parse_arg_value(a) for a in args.arg], 1.0, os.getcwd()] if args.target
else ["builtin", args.builtin, [], args.scale, os.getcwd()])
tmpdir = tempfile.mkdtemp(prefix="gil_probe_")
cfg_path, out_path = os.path.join(tmpdir, "cfg.json"), os.path.join(tmpdir, "out.json")
with open(cfg_path, "w") as fh:
json.dump({"job": job, "workers": args.workers, "repeat": args.repeat,
"interpreters": args.interpreters, "out": out_path}, fh)
env = dict(os.environ)
env["PYTHONDONTWRITEBYTECODE"] = "1" # do not leave __pycache__ next to this script or the target
env["PYTHONPATH"] = os.pathsep.join([SCRIPT_DIR, os.getcwd()] + ([env["PYTHONPATH"]] if env.get("PYTHONPATH")
else []))
popen_kw = {"start_new_session": True} if os.name == "posix" else {
"creationflags": getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)}
# the workload's own prints go to stderr so --json output stays clean
proc = subprocess.Popen([sys.executable, os.path.abspath(__file__), "--_child", cfg_path],
stdout=sys.stderr.fileno(), env=env, **popen_kw)
try:
proc.wait(timeout=args.timeout)
except subprocess.TimeoutExpired:
kill_group(proc)
print(f"gil_probe.py: error: timed out after {args.timeout:g}s; killed the probe and all its workers",
file=sys.stderr)
return EXIT_ERROR
except KeyboardInterrupt:
kill_group(proc)
print("gil_probe.py: interrupted; killed the probe and all its workers", file=sys.stderr)
return 130
finally:
kill_group(proc) # also reaps stragglers left in the group (e.g. a forkserver)
try:
with open(out_path) as fh:
res = json.load(fh)
except (OSError, ValueError):
print(f"gil_probe.py: error: probe process exited with code {proc.returncode} without a result",
file=sys.stderr)
return EXIT_ERROR
finally:
for path in (cfg_path, out_path):
try:
os.unlink(path)
except OSError:
pass
try:
os.rmdir(tmpdir)
except OSError:
pass
if "error" in res:
print(f"gil_probe.py: error: {res['error']}", file=sys.stderr)
return EXIT_ERROR
res["advice"] = advice(res["classification"], res["interpreter"])
if args.json:
print(json.dumps(res, indent=2))
else:
print(render(res, args))
if args.fail_if_gil_bound and "GIL-bound" in res["classification"]:
return EXIT_GATE
return EXIT_OK
if __name__ == "__main__":
sys.exit(main())
python-async-deadlock-debugger
Diagnose and fix hangs, deadlocks, livelocks and starvation in asyncio, threaded and multiprocessing Python: classify CPU spin vs blocked IO vs deadlock vs lost wakeup vs event loop blocked by sync code, collect evidence with faulthandler, py-spy dump, gdb py-bt, asyncio.all_tasks, python -m asyncio ps/pstree, debug mode and sys._current_frames, then fix lock-order cycles, non-reentrant asyncio.Lock re-acquisition, run_coroutine_threadsafe().result() on the loop thread, Queue.join without task_done, Condition lost wakeups, semaphore leaks, futures nobody resolves, nested event loops, thread pool exhaustion, multiprocessing Queue/fork deadlocks and shutdown hangs. Ships a static hang scanner and a watchdog that dumps every thread and asyncio task.
python-asyncio-pattern-advisor
Recommend, implement and review Python asyncio patterns: async vs threads vs processes (IO vs CPU-bound), coroutines vs tasks vs futures, TaskGroup, gather and return_exceptions, wait and as_completed, Semaphore and Queue worker pools with backpressure, asyncio.timeout vs wait_for, cancellation, shield and cleanup, token-bucket RPM/TPM rate limits for LLM APIs, retries with backoff and jitter, async generators and SSE token streaming, to_thread/run_in_executor, background task references, eager tasks, asyncio.run/Runner, uvloop, Jupyter, FastAPI async vs def routes, httpx.AsyncClient lifespan, async DB/vector-store clients, debug mode and pytest-asyncio.
python-beginner-concept-tutor
Teach Python concepts to beginners heading into AI engineering (data scientists, analysts, students, career switchers): plain-language definitions, analogies, minimal examples, line-by-line walkthroughs, common mistakes, check-your-understanding questions and mini exercises; Socratic hints before solutions; explaining tracebacks; mutable vs immutable, references and aliasing, is vs ==, integer division, floating point, slicing and off-by-one, indentation errors; a staged learning path from variables to calling an LLM API, embeddings and a tiny RAG. Ships a script that lists the concepts a snippet uses in learning order and traces execution step by step like Python Tutor.
python-comprehension-refactorer
Refactor Python loops into list, dict and set comprehensions and generator expressions, and unroll comprehensions that are too clever: filtering, mapping, nested-loop flattening, conditional expressions, walrus in comprehensions, dict inversion, grouping with defaultdict/Counter, lazy generators for sum/any/all/max, streaming large files, itertools (chain, islice, groupby, batched, pairwise, accumulate, product), scoping rules, side-effect anti-patterns and performance (when map/filter or numpy/pandas vectorization win). AI-engineering focus: building prompt lists, batching texts for embeddings, filtering retrieved chunks by score, id->embedding dicts, flattening tool-call results, lazy dataset streaming and token counting.
python-context-manager-writer
Write correct Python context managers and find resource-handling bugs — __enter__/__exit__ semantics and exception suppression, contextlib (contextmanager, asynccontextmanager, ExitStack, AsyncExitStack, closing, aclosing, suppress, redirect_stdout, nullcontext, chdir), async with, re-entrancy, leaked files/connections/clients/pools/locks, and AI-engineering recipes: torch.no_grad/inference_mode/autocast, eval()/train() switches, GPU cache cleanup, token and latency meters, tracing spans, temp env vars, rate-limit semaphores, httpx/aiohttp client lifecycle, FastAPI lifespan, DB/vector-store transactions, atomic checkpoint writes, seeds and agent sandboxes.
python-dataclass-converter
Convert boilerplate Python classes, dicts, tuples and JSON payloads into dataclasses, or into pydantic v2 BaseModel, TypedDict, NamedTuple, attrs or msgspec when that fits better. Covers every @dataclass option (frozen, slots, kw_only, order, unsafe_hash, match_args, weakref_slot), field() (default_factory, init=False, repr, compare, hash, metadata), __post_init__, InitVar, KW_ONLY, inheritance and default ordering, replace(), asdict deep-copy pitfalls, hashing/equality, serialization and validation. AI focus: LLM request/response and chat Message types, RAG chunks with embeddings (numpy eq gotcha), eval records, configs, typed OpenAI/Anthropic JSON.