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, git hooks, 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.py into. 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:

Path