Skip to main content

Fast Windows process execution library.

Project description

fastrun

A Windows-only, quote-safe, string-based, feature-rich alternative to Python's subprocess module, implemented as a native C extension around CreateProcessW.

import fastrun

r = fastrun.run("python --version")
print(r.stdout)        # 'Python 3.12.3\n'
print(r.returncode)    # 0

Feature overview

Feature How
String-based commands, quote-safe run("echo \"hello world\"")
No shell by default (safe) direct CreateProcessW, no cmd.exe
Optional real shell mode run(cmd, shell=True) -> cmd.exe /c ...
Timeouts run(cmd, timeout=5.0) -> raises TimeoutExpired
Kill entire process tree on timeout kill_tree=True (default), via Job Objects
Send data to child's stdin run(cmd, input="some text")
Full environment replacement run(cmd, env={...})
Environment overlay (merge, don't replace) run(cmd, extra_env={"FOO": "bar"})
Custom working directory run(cmd, cwd="C:\\path")
Redirect output straight to a file (no memory buffering) run(cmd, stdout_file="out.log")
Merge stderr into stdout (2>&1) run(cmd, merge_stderr=True)
Raise on non-zero exit run(cmd, check=True) -> CalledProcessError
Text or raw bytes output run(cmd, text=False)
Custom output encoding run(cmd, encoding="cp1252")
Process priority run(cmd, priority="below_normal")
Hide/show console window run(cmd, hide_window=False)
PID of launched process r.pid
Standalone quote-aware tokenizer fastrun.split(cmdline)
Standalone Windows argv quoter fastrun.quote(arg)
PATH resolution fastrun.which("python")

How it works

  1. Tokenizer (fastrun.split) parses the command string into argv-like tokens: 'single quotes', "double quotes with \" and \\ escapes", and bare backslashes treated as literal (so C:\Users\me\tool.exe needs no escaping).
  2. Re-quoter (fastrun.quote) re-serializes tokens using the official Microsoft argv-quoting algorithm (the one CommandLineToArgvW expects -- the same one CPython's own subprocess.list2cmdline implements).
  3. Launcher calls CreateProcessW directly with redirected stdout/stderr pipes (or files), an optional stdin pipe, an optional merged/replaced environment block, an optional Job Object so timeouts kill the whole descendant tree (not just the immediate child), and optional priority-class / window-visibility control.

Full API

fastrun.run(cmdline, **options) -> CompletedProcess

fastrun.run(
    cmdline,                # str: the command line, e.g. "python --version"
    cwd=None,                # str | None: working directory
    env=None,                # dict[str,str] | None: full replacement environment
    extra_env=None,          # dict[str,str] | None: overlay on top of inherited env
    timeout=None,             # float | None: seconds before kill + TimeoutExpired
    text=True,                # bool: decode output to str (vs bytes)
    encoding="utf-8",         # str: decode/encode encoding
    check=False,              # bool: raise CalledProcessError on non-zero exit
    shell=False,               # bool: run via cmd.exe /c instead of our own parser
    input=None,                 # str | bytes | None: data piped to child's stdin
    kill_tree=True,             # bool: timeout kills whole process tree via Job Object
    stdout_file=None,           # str | None: write stdout directly to this file path
    stderr_file=None,           # str | None: write stderr directly to this file path
    merge_stderr=False,          # bool: combine stderr into stdout (like 2>&1)
    hide_window=True,             # bool: suppress the child's console window
    priority=None,                 # str | None: "idle" | "below_normal" | "normal"
                                    #             | "above_normal" | "high" | "realtime"
)

Returns a fastrun.CompletedProcess with:

  • .args -- the original command-line string
  • .argv -- the parsed argument list
  • .returncode -- process exit code
  • .stdout / .stderr -- captured output (None if redirected to a file)
  • .pid -- the process ID that was assigned
  • .timed_out -- True if the timeout fired
  • .check_returncode() -- raise CalledProcessError if non-zero

fastrun.split(cmdline) -> list[str]

Standalone tokenizer access (like shlex.split, but Windows path-aware).

fastrun.quote(arg) -> str

Standalone Windows argv quoting for one argument.

fastrun.which(name) -> str | None

Resolve an executable name to a full path using Windows's own search order (app dir -> cwd -> System32 -> Windows dir -> PATH), trying .exe/.cmd/.bat/.com if no extension was given.

Exceptions

  • fastrun.CalledProcessError(returncode, cmd, output=None, stderr=None)
  • fastrun.TimeoutExpired(cmd, output=None)

How to compile

You need Windows (this cannot be built or run on Linux/macOS -- it's a hard dependency on the Win32 API) plus:

  1. Python (3.8+), matching architecture (x64 recommended) -- the regular python.org installer or the Microsoft Store one both work, as long as they include development headers (they do by default).
  2. Microsoft Visual C++ Build Tools -- the compiler CPython extensions require on Windows. Two ways to get it:
    • Install Visual Studio Community (free) and select the "Desktop development with C++" workload during setup, or
    • Install just the smaller "Build Tools for Visual Studio" package from Microsoft and select the C++ build tools workload.
  3. setuptools for your Python: pip install setuptools wheel

Build steps

Open a terminal where the MSVC compiler is on PATH. The easiest way is to use the Start Menu shortcut Visual Studio installs, called something like "x64 Native Tools Command Prompt for VS 2022" (this pre-loads all the right environment variables -- cl.exe, INCLUDE, LIB, etc.). Using a plain cmd.exe/PowerShell without that shortcut usually fails with error: Microsoft Visual C++ 14.0 or greater is required.

cd path\to\fastrun
python -m pip install --upgrade setuptools wheel
python setup.py build_ext --inplace

If it succeeds, you'll see a new file appear in the project root:

fastrun.cp312-win_amd64.pyd      (exact name depends on your Python version)

That .pyd file is the compiled module -- Python can import it directly like any .py file, as long as it's on sys.path (the project root, or wherever you copy it, or your site-packages folder).

Verifying the build

python test_fastrun.py

This runs a small smoke-test suite covering the tokenizer, quoting, return codes, check=True, and timeouts. All lines should print [PASS].

Building a redistributable wheel (optional)

If you want to pip install this on other Windows machines without them needing MSVC installed:

python setup.py bdist_wheel

This produces a .whl file under dist\ that bundles the compiled .pyd, installable via pip install dist\fastrun-1.0.0-*.whl on any matching Windows/Python-version/architecture combination -- no compiler needed on the target machine.

Common build errors

Error Fix
Microsoft Visual C++ 14.0 or greater is required Install VS Build Tools with the C++ workload, and build from the "x64 Native Tools Command Prompt"
fatal error C1083: Cannot open include file: 'Python.h' Your Python install is missing dev headers, or you're mixing a 32-bit Python with a 64-bit compiler prompt (or vice versa) -- match architectures
LINK : fatal error LNK1181: cannot open input file 'python3xx.lib' Same architecture mismatch as above; reinstall Python for the matching architecture
Import succeeds but crashes / ImportError: DLL load failed You built with a different Python minor version than you're importing into -- rebuild with the interpreter you intend to use

How to use it

Basic run

import fastrun

r = fastrun.run("python --version")
print(r.returncode)   # 0
print(r.stdout)        # "Python 3.12.3\n"

Quotes just work

r = fastrun.run('echo "hello world"')
r = fastrun.run("tool.exe --path 'C:\\Program Files\\thing' --flag")

Timeouts that actually kill the whole tree

try:
    fastrun.run("build.bat", timeout=30)
except fastrun.TimeoutExpired:
    print("build took too long, and everything it spawned was killed too")

Piping input to a process

r = fastrun.run('python -c "import sys; print(sys.stdin.read().upper())"',
                 input="hello from python\n")
print(r.stdout)   # "HELLO FROM PYTHON\n"

Environment control

# Full replacement -- child sees ONLY these variables
r = fastrun.run("set", shell=True, env={"PATH": r"C:\Windows\System32"})

# Overlay -- child inherits everything, plus these overrides
r = fastrun.run("python script.py", extra_env={"PYTHONUNBUFFERED": "1"})

Checking for failure

try:
    fastrun.run('python -c "import sys; sys.exit(1)"', check=True)
except fastrun.CalledProcessError as e:
    print(f"failed with code {e.args[0]}")

(CalledProcessError mirrors subprocess.CalledProcessError's constructor order: returncode, cmd, output, stderr.)

Large output straight to a file

# Avoids buffering gigabytes of build output in memory
r = fastrun.run("dotnet build MySolution.sln", stdout_file="build.log",
                 stderr_file="build.err.log")
print(r.returncode)
# r.stdout / r.stderr are None here -- check the files instead

Merging stderr into stdout

r = fastrun.run('python -c "import sys; sys.stderr.write(\'oops\\n\')"',
                 merge_stderr=True)
print(r.stdout)   # "oops\n"

Real shell semantics (wildcards, &&, env expansion)

# Only do this with trusted input -- shell=True reintroduces the
# injection risk that the default mode avoids.
r = fastrun.run("dir *.txt && echo done", shell=True)

Priority and window visibility

# Run a background batch job at low priority, no visible window
r = fastrun.run("cleanup.bat", priority="below_normal", hide_window=True)

# Launch something the user should actually see
r = fastrun.run("notepad.exe", hide_window=False)

PATH resolution

path = fastrun.which("python")
print(path)   # "C:\\...\\Python312\\python.exe"

Using the tokenizer/quoter standalone

tokens = fastrun.split('tool --flag="a b" \'c d\' plain')
# ['tool', '--flag=a b', 'c d', 'plain']

quoted = fastrun.quote("has space")
# '"has space"'

Design notes / limitations

  • No shell semantics by default. &&, |, >, wildcards, and %VAR% expansion are not performed unless you pass shell=True.
  • argv[0] resolution (non-shell mode) goes through normal Windows executable search behavior (app dir, current dir, System32, Windows dir, PATH).
  • Output capture uses a polling read loop (PeekNamedPipe + Sleep(5) when idle) rather than overlapped I/O or dedicated reader threads -- small, auditable, at the cost of a few ms latency on very chatty long-running processes. Use stdout_file/stderr_file for very large output instead of in-memory capture.
  • kill_tree depends on Job Objects; if CreateJobObjectW or AssignProcessToJobObject fails (e.g. restrictive sandbox, or the process is already in a job on some older systems), it silently falls back to killing just the direct child.
  • Max 512 arguments per command by default (FASTRUN_MAX_ARGS in fastrun.c) -- bump the constant and rebuild if you need more.
  • env and extra_env are mutually exclusive; if both are given, env wins and extra_env is ignored.

License

Do whatever you want with it. No warranty.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastrun_win-1.0.0.tar.gz (29.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastrun_win-1.0.0-cp314-cp314-win_amd64.whl (26.4 kB view details)

Uploaded CPython 3.14Windows x86-64

File details

Details for the file fastrun_win-1.0.0.tar.gz.

File metadata

  • Download URL: fastrun_win-1.0.0.tar.gz
  • Upload date:
  • Size: 29.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for fastrun_win-1.0.0.tar.gz
Algorithm Hash digest
SHA256 29aaba76c924d45b996bd4dc3c3b0104e17c7f0e0cfcb21253855a273b961ceb
MD5 3698e5dd0d4eea6e05e7ee03c71cb092
BLAKE2b-256 90a7edbea87263bbc357dc039877f326d9213ec7aeaf9435bf5780717194478e

See more details on using hashes here.

File details

Details for the file fastrun_win-1.0.0-cp314-cp314-win_amd64.whl.

File metadata

File hashes

Hashes for fastrun_win-1.0.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 0c91564abb71e92ef12ec1b3c7ab14e543ea607ed8ee4504494805c90c73ef5b
MD5 0c15051c0024dba9f8deb25fb2427b0a
BLAKE2b-256 10201f9114467ee6cba110595f9bc194416fdcfeb8c51e810556b4584abae70b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page