Manifest Compiler¶
from_routes() discovers commands by walking your routes package with
pkgutil.walk_packages on every invocation. For most CLIs this is imperceptible, but it
adds ~13–15 ms of cold-start overhead on Apple Silicon — enough to matter if your tool is
invoked hundreds of times per second (e.g. shell completion hooks or CI scripts).
The manifest compiler eliminates this overhead. It walks the package once at build time
and writes a static _xclif_manifest.py file. At runtime, loading the manifest is a plain
Python import — no filesystem walk, no inspect.getmembers.
When to use it¶
Consider compiling a manifest when:
Shell completion feels sluggish, because the completion hook spawns your CLI on every keypress.
Your tool is called in tight loops (CI scripts,
githooks, makefiles).You ship a compiled package (wheel) and want the fastest possible startup.
For interactive development, from_routes is easier — no extra build step, and route changes
are picked up automatically.
Compiling a manifest¶
From the command line (recommended):
python -m xclif compile myapp.routes
This imports myapp.routes and writes _xclif_manifest.py next to the routes package
(i.e. inside the myapp/ directory). Pass --output <dir> to write elsewhere:
python -m xclif compile myapp.routes --output src/myapp
Programmatically (e.g. in a build script):
from xclif.compiler import compile_routes
import myapp.routes as routes
path = compile_routes(routes)
print(f"Manifest written to {path}")
Either way, commit the generated file to version control so it is available in installed distributions without a separate compilation step.
Loading the manifest at runtime¶
Replace from_routes() with from_manifest() in your entry
point:
# myapp/__main__.py
from xclif import Cli
from myapp import _xclif_manifest
cli = Cli.from_manifest(_xclif_manifest)
if __name__ == "__main__":
cli()
from_manifest() calls the _build_cli() function inside the manifest
module, which lazily imports each route and assembles the command tree — exactly the same tree
that from_routes would produce.
What the manifest looks like¶
The generated _xclif_manifest.py is readable Python. Given a routes package with this
layout:
myapp/routes/
├── __init__.py → myapp (root)
├── greet.py → myapp greet
└── server/
├── __init__.py → myapp server (group)
├── start.py → myapp server start
└── stop.py → myapp server stop
The compiler writes:
# Generated by `xclif compile` — do not edit by hand.
# Re-run `python -m xclif compile <routes_module>` after adding/removing routes.
from __future__ import annotations
from xclif import Cli
def _build_cli(version: str | None = None, env_prefix: str | None = None, config_name: str | None = None, local_config: str | None = None) -> Cli:
from myapp.routes import _ as _root
from myapp.routes.greet import _ as _myapp_routes_greet
from myapp.routes.server import _ as _myapp_routes_server
from myapp.routes.server.start import _ as _myapp_routes_server_start
from myapp.routes.server.stop import _ as _myapp_routes_server_stop
root = _root
cli = Cli(root_command=root, version=version, env_prefix=env_prefix, config_name=config_name, local_config=local_config)
cli.add_command(['server'], _myapp_routes_server)
cli.add_command(['greet'], _myapp_routes_greet)
cli.add_command(['server', 'start'], _myapp_routes_server_start)
cli.add_command(['server', 'stop'], _myapp_routes_server_stop)
cli._finalize()
return cli
The imports are inside _build_cli() so loading the manifest module itself is free —
route modules are only imported when you call _build_cli(), matching the laziness of
from_routes.
Keeping the manifest up to date¶
Re-run xclif compile after adding, removing, or renaming any route file. The manifest
does not self-update.
A convenient place to hook this in is your build system. For example, with pyproject.toml
and hatch:
[tool.hatch.build.hooks.custom]
# runs `python -m xclif compile myapp.routes` before building the wheel
Or add a Makefile target:
manifest:
python -m xclif compile myapp.routes
If you use a CI pipeline, run the compile step before running tests so the manifest under test matches the current routes.
Tip
Run python -m xclif compile --help to see all available options.
API Reference¶
Manifest compiler for xclif routes.
Walks a routes package once (at build/install time) and emits a
_xclif_manifest.py file next to the routes package. At runtime the
manifest is loaded by Cli.from_manifest() instead of re-walking the
filesystem, skipping pkgutil.walk_packages + inspect.getmembers
overhead (~13-15 ms on Apple Silicon).
Usage¶
From the command line:
python -m xclif compile myapp.routes
Or programmatically:
from xclif.compiler import compile_routes
import myapp.routes as routes
compile_routes(routes)
- xclif.compiler.compile_routes(routes, output_dir=None)[source]¶
Walk routes and write a manifest file.
- Parameters:
routes (ModuleType) – The routes package module (e.g.
import myapp.routes as routes).output_dir (Path | None) – Directory to write
_xclif_manifest.pyinto. Defaults to the directory containing the routes package itself (i.e. sits next to it).
- Returns:
The path of the written manifest file.
- Return type: