Python’s py_compile module compiles a single .py source file to a .pyc bytecode file. import py_compile. Compile: py_compile.compile(file, cfile=None, dfile=None, doraise=False, optimize=-1, invalidation_mode=PycInvalidationMode.TIMESTAMP, quiet=0) — file is the source path; cfile overrides the output .pyc path (defaults to __pycache__/file.cpython-XY.pyc); dfile sets the filename embedded in tracebacks; doraise=True raises PyCompileError on syntax errors instead of returning None; optimize is 0 (no optimization), 1 (strip asserts), or 2 (strip docstrings); invalidation_mode controls stale-bytecode detection. Invalidation modes: PycInvalidationMode.TIMESTAMP (default) — mtime+size check; PycInvalidationMode.CHECKED_HASH — SHA-256 hash of source checked at import; PycInvalidationMode.UNCHECKED_HASH — hash embedded but not checked (fastest runtime). Error: py_compile.PyCompileError(exc_type, exc_value, file, msg) raised on doraise=True; .msg contains the formatted error. py_compile.main() — command-line interface (python -m py_compile file.py). Claude Code generates pre-compilation pipelines, deploy validators, syntax checkers, and bytecode cache warmers.
CLAUDE.md for py_compile
## py_compile Stack
- Stdlib: import py_compile
- Compile: py_compile.compile("app.py", doraise=True)
- Optimize: py_compile.compile("app.py", optimize=2) # strip docstrings
- Error: try: py_compile.compile(f, doraise=True)
- except py_compile.PyCompileError as e: print(e.msg)
- CLI: python -m py_compile file1.py file2.py
- Note: compileall for batch compilation of packages
py_compile Compilation Pipeline
# app/py_compileutil.py — single compile, syntax check, batch, validate
from __future__ import annotations
import io
import os
import py_compile
import sys
from dataclasses import dataclass, field
from pathlib import Path
# ─────────────────────────────────────────────────────────────────────────────
# 1. Single-file helpers
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class CompileResult:
source: Path
success: bool
output: "Path | None" = None
error: str = ""
def __str__(self) -> str:
if self.success:
return f"OK {self.source} → {self.output}"
return f"ERR {self.source}: {self.error}"
def compile_file(
source: "str | Path",
output: "str | Path | None" = None,
optimize: int = -1,
invalidation_mode: py_compile.PycInvalidationMode = py_compile.PycInvalidationMode.TIMESTAMP,
) -> CompileResult:
"""
Compile a single .py file to .pyc. Returns a CompileResult.
Example:
result = compile_file("app/main.py")
if not result.success:
print(result.error)
"""
src = Path(source)
cfile = str(output) if output else None
try:
out = py_compile.compile(
str(src),
cfile=cfile,
doraise=True,
optimize=optimize,
invalidation_mode=invalidation_mode,
)
return CompileResult(source=src, success=True, output=Path(out))
except py_compile.PyCompileError as e:
return CompileResult(source=src, success=False, error=e.msg)
def is_valid_python(source: "str | Path") -> bool:
"""
Return True if source is a syntactically valid Python file.
Example:
if not is_valid_python("script.py"):
print("syntax error")
"""
return compile_file(source).success
def check_syntax(source: "str | Path") -> "str | None":
"""
Return a syntax error message string if present, else None.
Example:
err = check_syntax("broken.py")
if err:
print(f"Syntax error: {err}")
"""
result = compile_file(source)
return None if result.success else result.error
# ─────────────────────────────────────────────────────────────────────────────
# 2. Batch compilation
# ─────────────────────────────────────────────────────────────────────────────
def compile_directory(
root: "str | Path",
optimize: int = -1,
recursive: bool = True,
skip_errors: bool = True,
) -> list[CompileResult]:
"""
Compile all .py files under root.
Returns a list of CompileResult (one per file).
Example:
results = compile_directory("src/myapp")
errors = [r for r in results if not r.success]
if errors:
for r in errors:
print(r)
"""
root = Path(root)
pattern = "**/*.py" if recursive else "*.py"
results = []
for py_file in sorted(root.glob(pattern)):
result = compile_file(py_file, optimize=optimize)
results.append(result)
if not result.success and not skip_errors:
raise py_compile.PyCompileError(
SyntaxError, result.error, str(py_file), result.error
)
return results
def syntax_check_files(paths: list["str | Path"]) -> dict[str, "str | None"]:
"""
Check multiple files for syntax errors.
Returns {filename: error_message_or_None}.
Example:
report = syntax_check_files(["a.py", "b.py", "c.py"])
for name, err in report.items():
if err:
print(f"FAIL {name}: {err}")
else:
print(f"OK {name}")
"""
return {
str(p): check_syntax(p)
for p in paths
}
# ─────────────────────────────────────────────────────────────────────────────
# 3. Compile string source (check without a file)
# ─────────────────────────────────────────────────────────────────────────────
def compile_source_string(
source: str,
filename: str = "<string>",
optimize: int = 0,
) -> "tuple[bool, str]":
"""
Compile a Python source string to bytecode (using builtins.compile).
Returns (ok, error_message). Does not write a .pyc file.
Example:
ok, err = compile_source_string("x = 1 +")
print(ok, err) # False, "invalid syntax"
"""
try:
compile(source, filename, "exec", optimize=optimize)
return True, ""
except SyntaxError as e:
return False, f"{e.__class__.__name__}: {e.msg} (line {e.lineno})"
except Exception as e:
return False, str(e)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Deployment pre-compilation validator
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class DeployValidation:
"""
Run syntax checks on all Python files in a project before deployment.
Example:
v = DeployValidation("src/myapp")
v.run()
if v.has_errors:
v.print_report()
sys.exit(1)
"""
root: Path
results: list[CompileResult] = field(default_factory=list)
def __post_init__(self) -> None:
self.root = Path(self.root)
def run(self, optimize: int = 0) -> "DeployValidation":
"""Run compilation on all .py files under root."""
self.results = compile_directory(self.root, optimize=optimize, skip_errors=True)
return self
@property
def has_errors(self) -> bool:
return any(not r.success for r in self.results)
@property
def error_count(self) -> int:
return sum(1 for r in self.results if not r.success)
@property
def ok_count(self) -> int:
return sum(1 for r in self.results if r.success)
def print_report(self, file=None) -> None:
"""Print a summary report to file (default: stderr)."""
f = file or sys.stderr
total = len(self.results)
print(f"Syntax validation: {self.ok_count}/{total} OK, "
f"{self.error_count} errors", file=f)
for r in self.results:
if not r.success:
print(f" FAIL {r.source.relative_to(self.root)}: {r.error}", file=f)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== py_compile demo ===")
# ── compile_file ──────────────────────────────────────────────────────────
print("\n--- compile_file ---")
with tempfile.TemporaryDirectory() as td:
# Write a valid file
good = Path(td) / "good.py"
good.write_text("def hello():\n return 'hello'\n")
result = compile_file(good)
print(f" {result}")
# Write a bad file
bad = Path(td) / "bad.py"
bad.write_text("def broken(\n")
result_bad = compile_file(bad)
print(f" {result_bad}")
# ── compile_directory ─────────────────────────────────────────────────────
print("\n--- compile_directory ---")
with tempfile.TemporaryDirectory() as td:
(Path(td) / "a.py").write_text("x = 1\n")
(Path(td) / "b.py").write_text("y = 2\n")
(Path(td) / "broken.py").write_text("class X\n")
results = compile_directory(td, recursive=False)
for r in results:
rel = Path(r.source).name
print(f" {rel}: {'OK' if r.success else 'ERR'}")
# ── compile_source_string ─────────────────────────────────────────────────
print("\n--- compile_source_string ---")
cases = [
"x = 1 + 2",
"def f(): pass",
"x = 1 +",
"for",
]
for src in cases:
ok, err = compile_source_string(src)
status = "OK " if ok else f"ERR: {err}"
print(f" {src!r[:25]:27} → {status}")
# ── DeployValidation ──────────────────────────────────────────────────────
print("\n--- DeployValidation ---")
with tempfile.TemporaryDirectory() as td:
(Path(td) / "app.py").write_text("import os\nprint(os.getcwd())\n")
(Path(td) / "util.py").write_text("def helper(): return 42\n")
(Path(td) / "config.py").write_text("settings = {\n") # syntax error
v = DeployValidation(td).run()
v.print_report(file=sys.stdout)
# ── py_compile.PycInvalidationMode ───────────────────────────────────────
print("\n--- PycInvalidationMode values ---")
for mode in py_compile.PycInvalidationMode:
print(f" {mode.name} = {mode.value}")
print("\n=== done ===")
For the compileall alternative — python -m compileall mypackage/ and compileall.compile_dir(path) recursively compile all .py files in a package tree using multiprocessing — use compileall for batch production pre-compilation; use py_compile.compile() when you need single-file control, custom output paths, detailed error objects, or integration into a CI build pipeline. For the ast.parse / compile() built-in alternative — ast.parse(source) parses source into an AST without writing bytecode; compile(source, filename, "exec") compiles to a code object in memory — use ast.parse when you want to inspect or transform the syntax tree; use py_compile.compile when you want the standard __pycache__ artifact; use the built-in compile() when you need a code object for exec() or eval(). The Claude Skills 360 bundle includes py_compile skill sets covering CompileResult dataclass and compile_file() single-file compiler, is_valid_python()/check_syntax() boolean and message helpers, compile_directory()/syntax_check_files() batch checkers, compile_source_string() in-memory syntax validation, and DeployValidation with run()/has_errors/print_report() pre-deploy pipeline. Start with the free tier to try compilation patterns and py_compile pipeline code generation.