apcore-toolkit provides several ways to export the ScannedModule metadata into formats used by the apcore ecosystem.
Generates individual .binding.yaml files for each scanned module. These files are typically placed in an extensions/ or bindings/ directory and loaded by apcore.BindingLoader.
- File-per-Module: Each module is written to its own file for easy management.
- Sanitized Filenames: Replaces periods and special characters in module IDs to ensure valid file names.
- Header Injection: Optionally adds a "Generated by" header to all files.
=== "Python"
```python
from apcore_toolkit import YAMLWriter
writer = YAMLWriter()
writer.write(modules, output_dir="./bindings", dry_run=False)
```
=== "TypeScript"
```typescript
import { YAMLWriter } from "apcore-toolkit";
const writer = new YAMLWriter();
writer.write(modules, "./bindings", { dryRun: false });
```
Generates source files containing decorator-based wrapper functions. This is useful for migrating legacy code to the apcore decorator pattern without manual re-writing.
- Auto-Decorators: Generates the decorator with all extracted metadata.
- Target Integration: Points to the original view function as the
targetof the module. - Standard Formatting: Produces clean, idiomatic code for the target language.
=== "Python"
```python
from apcore_toolkit import PythonWriter
writer = PythonWriter()
writer.write(modules, output_dir="./generated_apcore", dry_run=False)
```
=== "TypeScript"
```typescript
import { TypeScriptWriter } from "apcore-toolkit";
const writer = new TypeScriptWriter();
writer.write(modules, "./generated_apcore", { dryRun: false });
```
Directly registers the scanned modules into an active apcore.Registry instance. This is ideal for "live" scanning where you want to expose existing endpoints without generating intermediate files.
- No Disk Usage: Modules are held only in memory.
- Hot-Reload Ready: Can be re-run to refresh the Registry as endpoints change.
=== "Python"
```python
from apcore import Registry
from apcore_toolkit import RegistryWriter
registry = Registry()
writer = RegistryWriter()
writer.write(modules, registry, dry_run=False)
```
=== "TypeScript"
```typescript
import { Registry } from "apcore-js";
import { RegistryWriter } from "apcore-toolkit";
const registry = new Registry();
const writer = new RegistryWriter();
writer.write(modules, registry, { dryRun: false });
```
Registers scanned modules as HTTP proxy classes that forward requests to a running web API. This enables CLI execution without invoking route handlers directly (which depend on framework DI systems).
- No Direct Handler Invocation: Proxies requests over HTTP instead of calling view functions.
- Path Parameter Substitution: Automatically maps path parameters (e.g.,
/items/{item_id}) from input values. - Auth Support: Pluggable
auth_header_factoryfor Bearer tokens or custom headers. - Success Range: Accepts any
2xxresponse as success;204 No Contentreturns{}.
Requires the httpx optional dependency:
pip install apcore-toolkit[http-proxy]from apcore import Registry
from apcore_toolkit import HTTPProxyRegistryWriter
registry = Registry()
writer = HTTPProxyRegistryWriter(
base_url="http://localhost:8000",
auth_header_factory=lambda: {"Authorization": "Bearer xxx"},
)
writer.write(modules, registry)The get_writer(format) factory function returns the appropriate writer instance for a given output format, avoiding the need to import each writer class individually.
| Format | Returns | Language |
|---|---|---|
"yaml" |
YAMLWriter instance |
Both |
"python" |
PythonWriter instance |
Python |
"typescript" |
TypeScriptWriter instance |
TypeScript |
"registry" |
RegistryWriter instance |
Both |
"http-proxy" |
HTTPProxyRegistryWriter instance |
Python |
=== "Python"
```python
from apcore_toolkit.output import get_writer
writer = get_writer("yaml") # YAMLWriter
writer = get_writer("python") # PythonWriter
writer = get_writer("registry") # RegistryWriter
writer = get_writer("http-proxy", base_url="http://localhost:8000") # HTTPProxyRegistryWriter
```
=== "TypeScript"
```typescript
import { getWriter } from "apcore-toolkit";
const writer = getWriter("yaml"); // YAMLWriter
const writer = getWriter("typescript"); // TypeScriptWriter
const writer = getWriter("registry"); // RegistryWriter
```
Writers can optionally verify that their output artifacts are well-formed after writing. This prevents silent failures where a writer produces a file that apcore cannot load.
| Writer | Verification Checks |
|---|---|
YAMLWriter |
File exists, YAML parses without error, contains required module_id and target fields |
PythonWriter / TypeScriptWriter |
File exists, source code parses without syntax errors (AST/TS compiler check) |
RegistryWriter |
Module ID is registered, registry.get(module_id) returns a valid module |
Verification is enabled via the verify parameter. All writers support verify and verifiers:
=== "Python"
```python
from apcore_toolkit import YAMLWriter, PythonWriter, RegistryWriter
# YAMLWriter
writer = YAMLWriter()
results = writer.write(modules, output_dir="./bindings", verify=True, verifiers=[])
# PythonWriter
writer = PythonWriter()
results = writer.write(modules, output_dir="./generated", verify=True, verifiers=[])
# RegistryWriter
writer = RegistryWriter()
results = writer.write(modules, registry, verify=True, verifiers=[])
# results contains verification status per module
for r in results:
if not r.verified:
print(f"WARNING: {r.module_id} — {r.verification_error}")
```
=== "TypeScript"
```typescript
import { YAMLWriter, TypeScriptWriter, RegistryWriter } from "apcore-toolkit";
// YAMLWriter
const yamlWriter = new YAMLWriter();
const results1 = yamlWriter.write(modules, "./bindings", { verify: true, verifiers: [] });
// TypeScriptWriter
const tsWriter = new TypeScriptWriter();
const results2 = tsWriter.write(modules, "./generated", { verify: true, verifiers: [] });
// RegistryWriter
const regWriter = new RegistryWriter();
const results3 = regWriter.write(modules, registry, { verify: true, verifiers: [] });
for (const r of results1) {
if (!r.verified) {
console.warn(`WARNING: ${r.moduleId} — ${r.verificationError}`);
}
}
```
Each write operation returns a list of WriteResult objects:
| Field (Python / TypeScript) | Python Type | TypeScript Type | Description |
|---|---|---|---|
module_id / moduleId |
str |
string |
The module that was written |
path |
str | None |
string | null |
Output file path (None/null for RegistryWriter) |
verified |
bool |
boolean |
Whether verification passed (always True if verify=False) |
verification_error / verificationError |
str | None |
string | null |
Error message if verification failed |
!!! tip "Use in CI" Enable verification in CI pipelines to catch binding generation issues before deployment. A scan → write → verify cycle ensures that generated artifacts are always loadable by apcore.
The built-in verification checks (YAML parsability, AST syntax, Registry lookup) cover common cases. For domain-specific needs, writers accept a pluggable Verifier interface.
=== "Python"
```python
from typing import Protocol
class Verifier(Protocol):
def verify(self, path: str, module_id: str) -> VerifyResult: ...
@dataclass
class VerifyResult:
ok: bool
error: str | None = None
```
=== "TypeScript"
```typescript
interface Verifier {
verify(path: string, moduleId: string): VerifyResult;
}
interface VerifyResult {
ok: boolean;
error?: string;
}
```
| Verifier | Used By | Checks |
|---|---|---|
YAMLVerifier |
YAMLWriter |
YAML parses, required fields present |
SyntaxVerifier |
PythonWriter / TypeScriptWriter |
Source code parses without errors |
RegistryVerifier |
RegistryWriter |
Module registered and retrievable |
MagicBytesVerifier |
(available for custom use) | File header matches expected format |
JSONVerifier |
(available for custom use) | JSON parses, optional schema validation |
=== "Python"
```python
from apcore_toolkit.output import YAMLWriter, Verifier, VerifyResult
class StrictYAMLVerifier:
"""Verify YAML bindings have descriptions for all parameters."""
def verify(self, path: str, module_id: str) -> VerifyResult:
with open(path) as f:
data = yaml.safe_load(f)
schema = data.get("input_schema", {})
for prop, spec in schema.get("properties", {}).items():
if "description" not in spec:
return VerifyResult(ok=False, error=f"Missing description for {prop}")
return VerifyResult(ok=True)
writer = YAMLWriter()
results = writer.write(modules, output_dir="./bindings", verify=True, verifiers=[StrictYAMLVerifier()])
```
When multiple verifiers are provided, they run in order. The first failure stops the chain and sets WriteResult.verified = False.
results = writer.write(
modules,
output_dir="./bindings",
verify=True,
verifiers=[YAMLVerifier(), StrictYAMLVerifier(), CustomSchemaVerifier()],
)!!! tip "Lesson from CLI-Anything"
The CLI-Anything project validates output with magic bytes, pixel analysis, and RMS audio levels — never trusting exit code alone. The same principle applies: always verify the artifact, not the process exit status. The MagicBytesVerifier is directly inspired by this approach.
Writers and verifiers follow a consistent error handling pattern:
| Scenario | Behavior |
|---|---|
| Write succeeds, verify succeeds | WriteResult(verified=True) |
| Write succeeds, verify fails | WriteResult(verified=False, verification_error="...") — artifact exists but is malformed |
| Write fails (I/O error) | Raises WriteError with path and cause |
| Verifier itself throws | Caught and wrapped as WriteResult(verified=False, verification_error="Verifier crashed: ...") |
Writers never silently swallow errors. If verify=False, verification is skipped entirely (not suppressed).
=== "Python"
```python
from apcore_toolkit.output import WriteError
try:
results = writer.write(modules, output_dir="./bindings", verify=True)
except WriteError as e:
print(f"Failed to write {e.path}: {e.cause}")
```
| Use Case | Recommended Writer |
|---|---|
| Configuration-first approach | YAMLWriter |
| Migrating legacy views to decorators | PythonWriter / TypeScriptWriter |
| Live, dynamic module exposure | RegistryWriter |
| Fast, zero-config integration | RegistryWriter |
| CLI execution against a running API (Python) | HTTPProxyRegistryWriter |