Python
Classes
PythonScanner
class PythonScanner implements Scanner
Use PythonScanner to extract API signatures and metadata from .py files so skrypt can generate documentation for your Python codebase.
Reach for this when you're building a custom skrypt pipeline and need to explicitly scan Python source files — for example, when composing multiple scanners together or when you want to process Python files independently from other languages in a mixed codebase.
PythonScanner delegates the actual AST parsing to a bundled python_parser.py script, spawned as a subprocess via python3. This means python3 must be available in the environment where skrypt runs. The scanner identifies itself as handling the 'python' language and will only process files ending in .py.
Properties
| Name | Type | Description |
|---|---|---|
languages | string[] | Always ['python'] — used by the skrypt scanner registry to route files to the correct handler. |
Methods
canHandle(filePath)
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the file being evaluated. Returns true only for paths ending in .py. |
Returns boolean. Use this before calling scanFile to confirm the scanner supports a given file — passing a non-Python file to scanFile will produce an empty or error result.
scanFile(filePath)
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Path to the .py file to parse. Must be readable by the process and accessible to the python3 subprocess. |
Returns a Promise<ScanResult> containing the extracted classes, functions, and their signatures from the file. Pass the result to skrypt's doc generation pipeline to produce MDX output.
Heads up
python3must be on your systemPATH. If it isn't,scanFilewill fail silently or return an empty result — check your environment if you're getting blank output for Python files.- Large files with complex decorators or dynamic class definitions may not be fully captured, since parsing relies on static AST analysis.
Example:
import { spawn } from 'child_process'
import { join } from 'path'
import { tmpdir } from 'os'
import { writeFileSync } from 'fs'
// Inline types (do not import from autodocs)
interface ScanResult {
elements: Array<{
name: string
kind: 'function' | 'class'
signature: string
docstring?: string
}>
}
interface Scanner {
languages: string[]
canHandle(filePath: string): boolean
scanFile(filePath: string): Promise<ScanResult>
}
// Minimal self-contained PythonScanner implementation for demonstration
class PythonScanner implements Scanner {
languages = ['python']
canHandle(filePath: string): boolean {
return filePath.endsWith('.py')
}
async scanFile(filePath: string): Promise<ScanResult> {
return new Promise((resolve) => {
// In the real implementation, this calls the bundled python_parser.py script.
// Here we simulate the subprocess output for demonstration.
const mockOutput = JSON.stringify({
elements: [
{
name: 'PaymentClient',
kind: 'class',
signature: 'class PaymentClient',
docstring: 'Handles payment processing via the Stripe API.',
},
{
name: 'charge',
kind: 'function',
signature: 'def charge(self, amount: int, currency: str) -> dict',
docstring: 'Creates a charge for the given amount.',
},
],
})
resolve(JSON.parse(mockOutput))
})
}
}
// --- Usage ---
const scanner = new PythonScanner()
// Write a sample Python file to scan
const samplePyPath = join(tmpdir(), 'payment_client.py')
writeFileSync(
samplePyPath,
`
class PaymentClient:
"""Handles payment processing via the Stripe API."""
def charge(self, amount: int, currency: str) -> dict:
"""Creates a charge for the given amount."""
pass
`.trim()
)
async function main() {
if (!scanner.canHandle(samplePyPath)) {
console.error('Scanner cannot handle this file type.')
process.exit(1)
}
try {
const result = await scanner.scanFile(samplePyPath)
console.log(`Scanned ${result.elements.length} elements:\n`)
for (const el of result.elements) {
console.log(`[${el.kind.toUpperCase()}] ${el.name}`)
console.log(` Signature : ${el.signature}`)
console.log(` Docstring : ${el.docstring ?? '(none)'}`)
console.log()
}
// Expected output:
// Scanned 2 elements:
//
// [CLASS] PaymentClient
// Signature : class PaymentClient
// Docstring : Handles payment processing via the Stripe API.
//
// [FUNCTION] charge
// Signature : def charge(self, amount: int, currency: str) -> dict
// Docstring : Creates a charge for the given amount.
} catch (err) {
console.error('Failed to scan file:', err)
}
}
main()
Methods
canHandle
canHandle(filePath: string): boolean
Use canHandle to determine whether a PythonScanner instance is capable of processing a given file before attempting to scan it.
Call this before invoking scanFile when you're routing files across multiple scanners — it lets you dispatch each file to the right scanner without hardcoding extension logic in your orchestration layer.
Returns true if filePath ends with .py, false for everything else. The check is extension-only; the file doesn't need to exist on disk.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the file you want to check — only the .py extension matters, not whether the file exists |
Returns
A boolean indicating whether this scanner can handle the file. Use the result to gate calls to scanFile — passing a non-Python file to scanFile directly will produce unexpected results.
Heads up
- The check is case-sensitive:
.PYor.Pywill returnfalse. Normalize file paths to lowercase first if your environment might produce mixed-case extensions.
Example:
// Simulated scanner interface and implementations — no imports from autodocs
interface Scanner {
languages: string[];
canHandle(filePath: string): boolean;
}
class PythonScanner implements Scanner {
languages = ['python'];
canHandle(filePath: string): boolean {
return filePath.endsWith('.py');
}
}
class TypeScriptScanner implements Scanner {
languages = ['typescript'];
canHandle(filePath: string): boolean {
return filePath.endsWith('.ts') || filePath.endsWith('.tsx');
}
}
const scanners: Scanner[] = [new PythonScanner(), new TypeScriptScanner()];
const filesToProcess = [
'/workspace/myproject/auth/login.py',
'/workspace/myproject/api/routes.ts',
'/workspace/myproject/README.md',
'/workspace/myproject/utils/helpers.py',
];
for (const filePath of filesToProcess) {
const scanner = scanners.find((s) => s.canHandle(filePath));
if (scanner) {
console.log(`${filePath} → handled by ${scanner.languages[0]} scanner`);
} else {
console.log(`${filePath} → no scanner available, skipping`);
}
}
// Expected output:
// /workspace/myproject/auth/login.py → handled by python scanner
// /workspace/myproject/api/routes.ts → handled by typescript scanner
// /workspace/myproject/README.md → no scanner available, skipping
// /workspace/myproject/utils/helpers.py → handled by python scanner
scanFile
async scanFile(filePath: string): Promise<ScanResult>
Use scanFile to extract all API signatures, classes, and functions from a Python source file into a structured result ready for documentation generation.
Reach for this when you're building a custom documentation pipeline and need to programmatically scan individual .py files — for example, processing only files that changed in a git diff, or scanning files one-by-one with custom filtering logic. It's the per-file counterpart to skrypt's CLI generate command.
Under the hood, scanFile spawns a Python 3 subprocess that runs skrypt's AST parser against the target file, so Python 3 must be available in your environment's PATH. The parser extracts signatures, docstrings, and type annotations, then returns them as structured data you can pass directly to the doc generator.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the .py file to scan. Must end in .py — pass this through canHandle() first if the extension isn't guaranteed. |
Returns
Returns a Promise<ScanResult> containing the extracted API elements (functions, classes, methods) along with their signatures, parameters, and docstrings. Pass the resolved ScanResult to your doc generator or accumulate multiple results across files before generating output.
Heads up
PythonScanneronly handles.pyfiles — callcanHandle(filePath)beforescanFile()if you're iterating a mixed-language directory, or the subprocess will fail silently.- The subprocess requires
python3onPATH. If your environment usespythoninstead, the scan will hang or reject.
Example:
import { spawn } from "child_process";
import * as path from "path";
// Inline types (do not import from autodocs)
interface ScannedParam {
name: string;
type: string | null;
default: string | null;
}
interface ScannedElement {
name: string;
kind: "function" | "class" | "method";
params: ScannedParam[];
returnType: string | null;
docstring: string | null;
lineNumber: number;
}
interface ScanResult {
filePath: string;
elements: ScannedElement[];
language: string;
}
// Minimal self-contained PythonScanner implementation
class PythonScanner {
private parserScript: string;
constructor(parserScript: string) {
this.parserScript = parserScript;
}
canHandle(filePath: string): boolean {
return filePath.endsWith(".py");
}
async scanFile(filePath: string): Promise<ScanResult> {
return new Promise((resolve, reject) => {
const proc = spawn("python3", [this.parserScript, filePath], {
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
proc.stdout.on("data", (chunk: Buffer) => {
stdout += chunk.toString();
});
proc.stderr.on("data", (chunk: Buffer) => {
stderr += chunk.toString();
});
proc.on("close", (code: number) => {
if (code !== 0) {
reject(new Error(`Parser exited with code ${code}: ${stderr}`));
return;
}
try {
const elements: ScannedElement[] = JSON.parse(stdout);
resolve({ filePath, elements, language: "python" });
} catch {
reject(new Error(`Failed to parse scanner output: ${stdout}`));
}
});
});
}
}
// --- Usage ---
const targetFile = path.resolve("./src/payments/stripe_client.py");
const scanner = new PythonScanner("/usr/local/lib/skrypt/parser.py");
async function main() {
if (!scanner.canHandle(targetFile)) {
console.error("Not a Python file — skipping:", targetFile);
process.exit(1);
}
try {
const result = await scanner.scanFile(targetFile);
console.log(`Scanned: ${result.filePath}`);
console.log(`Language: ${result.language}`);
console.log(`Elements found: ${result.elements.length}`);
for (const el of result.elements) {
console.log(` [${el.kind}] ${el.name} — line ${el.lineNumber}`);
if (el.docstring) {
console.log(` "${el.docstring.slice(0, 60)}..."`);
}
}
// Expected output:
// Scanned: /project/src/payments/stripe_client.py
// Language: python
// Elements found: 4
// [class] StripeClient — line 12
// [method] charge — line 28
// "Charge a customer by amount in cents. Returns the charge..."
// [method] refund — line 47
// [function] build_webhook_event — line 81
} catch (err) {
console.error("Scan failed:", (err as Error).message);
}
}
main();