Local command-line operations¶
lcl-fastapi uses lclang.cli.CliEntrance. The service file is an ordinary
config parameter, selected with -o config service.lclcfg. It is loaded
independently from the short-lived CLI logger, so worker-dependent logger
expressions are resolved only in an actual server worker.
| Command | Operation | Optional parameters |
|---|---|---|
serve |
Run the platform process manager in the foreground | -o hot_reload |
status |
Read verified local service and worker state as JSON | None |
logs |
Read live workers' observed log paths as JSON | None |
stop |
Request authenticated loopback graceful shutdown | None |
nginx render |
Generate an HTTPS reverse-proxy configuration | -o output example.conf |
systemd render |
Generate a Linux service unit | -o output example.service |
Select a service file with -c service.lclcfg or --config service.lclcfg.
The existing -o config service.lclcfg spelling remains supported and takes
precedence if both are supplied. A downstream entrance can provide the default.
Use --help on the root,
command, or group to inspect the upstream command help; --version reports the
installed distribution version. The help label ends in .py because lclang's
argv adapter expects a Python script label; the installed command remains
lcl-fastapi.
lcl-fastapi serve -o config service.lclcfg
lcl-fastapi serve -o config service.lclcfg -o hot_reload
lcl-fastapi status -o config service.lclcfg
lcl-fastapi logs -o config service.lclcfg
lcl-fastapi stop -o config service.lclcfg
lcl-fastapi nginx render -o config service.lclcfg -o output example.conf
lcl-fastapi systemd render -o config service.lclcfg -o output example.service
Place a valueless -o hot_reload last, or use an explicit Boolean:
-o hot_reload "LCL[True]" enables watching and
-o hot_reload "LCL[False]" keeps ordinary service behavior. Plain Boolean text
is rejected. Hot reload defaults to false and is used only by serve.
status and logs always return JSON; there is no JSON output option or
configuration setting, leaving LCL's builtin json namespace available.
Paths containing spaces must be quoted using the calling shell's
normal rules. Relative config and output CLI paths use the calling working
directory; relative paths inside the service file use that file's directory.
All native lclang common options are accepted, including --override,
--as-of, --verbose, and --dryrun. Any application or framework configuration
key can be overridden with -o key value; repeated keys use the last value.
Values retain lclang semantics: plain text is a string, a valueless override is
True, and LCL[...] is evaluated lazily against the effective configuration.
For example, -o server.port "LCL[9000]" overrides the file's listener port;
-o logger.level DEBUG overrides logging in both controller and workers.
serve --verbose carries the native Boolean __verbose__ binding into service
configuration and enables LCL diagnostics in API lifespan/handlers and Workers.
Downstream expressions can read that binding; it cannot be overridden.
Validation still applies. worker_pid and lcl_fastapi_defaults are reserved.
Invalid logger settings, including non-text logger.timezone values, produce a
nonzero status and a source-aware diagnostic on stderr before service sinks open.
The native parser still rejects invented flags such as --port or --json.
hot_reload and output may also be defined in the service file;
command-line values win. Commands use only the operational options relevant to
them. Dry-run serve and stop validate settings without starting or stopping
processes; dry-run render validates and prints text without writing the output
file. Status and logs remain read-only. This does not import the application or
prove that a listener can bind. The CLI logger remains a separate stderr-only
scope so worker-dependent file expressions are never evaluated in that scope.
There is no restart or configuration-watching command.
Downstream console entrances¶
The simplest branded command points directly at the public entrance in your
application's pyproject.toml:
[project.scripts]
catalog = "lcl_fastapi.cli:run_cli"
For a default configuration and branded help, point it at your own wrapper:
[project.scripts]
catalog = "catalog.cli:main"
from lcl_fastapi.cli import run_cli
def main() -> int:
return run_cli(config_path="service.lclcfg", prog="catalog", version_text="1.0.0")
Install the downstream package with python -m pip install -e ., then run
catalog serve, catalog status, or catalog serve -o server.port
"LCL[9000]". Explicit -c/-o config still overrides the wrapper's file default.
Relative wrapper paths use the caller's working directory; use an absolute
Path when the wrapper owns a fixed configuration location. run_cli(arguments=None,
*, config_path=None, prog="lcl-fastapi", version_text=None) returns the native
integer exit status; None arguments reads process argv, and None version uses
installed framework metadata. It is a synchronous process entrance: call it
outside an event loop and serialize invocations within a process. Its event loop
and CLI logger close before native process management begins.
Output and errors¶
Successful status and logs output consists of exactly one JSON object on
stdout. Diagnostics go to stderr. The logs object always contains paths,
observed_at (Unix seconds, or null for an empty observation), and stale.
These paths are eventually consistent snapshots
from live workers, not a filesystem-mtime guess. An exited worker's paths are
excluded. stale means that a participating observation is older than the
configured interval, not that its worker is dead. It can temporarily be true
while a healthy worker samples or publishes its next observation. See the
runtime documentation for sampling and failure semantics.
Status JSON contract¶
status -o config service.lclcfg returns these three top-level keys:
| Key | Type | Meaning |
|---|---|---|
status |
string | RUNNING, STOPPED, or STALE. |
service |
object or null | Verified master-start identity and launch-time settings, or null when no usable live service exists. |
workers |
array of objects | Currently live, identity-verified workers belonging to this service start. |
Example running response (identifiers and times are illustrative):
{
"status": "RUNNING",
"service": {
"pid": 28140,
"process_create_time": 1789021230.0,
"service_id": "6e4ca00c60d347f3aef86b96b3412145",
"name": "example-service",
"version": "1.0.0",
"runtime": "gunicorn",
"host": "127.0.0.1",
"port": 8080,
"configured_workers": 1,
"worker_state_dir": "/opt/example/run/workers",
"sample_interval_seconds": 1.0,
"graceful_timeout_seconds": 30,
"started_at": 1789021230.1
},
"workers": [
{
"pid": 28146,
"process_create_time": 1789021231.0,
"service_id": "6e4ca00c60d347f3aef86b96b3412145",
"snowflake_worker_id": 0,
"log_files": ["/opt/example/logs/example-service.28146.000001.log"],
"observed_at": 1789021232.0
}
]
}
pid and worker counts are integers. Process-creation, observation, and start
times are Unix seconds; intervals and deadlines are seconds. service_id is a
non-secret opaque identifier for this complete service start. runtime is
uvicorn on Windows and gunicorn on Linux. The service pid identifies the
runtime's master/parent, and each worker has its own PID and creation time.
Paths use the host platform's absolute path syntax. Control tokens never appear
in this response. To count currently running workers, use the length of
workers; compare it with service.configured_workers rather than assuming
startup/recovery has already reached the configured count.
An absent or unusable runtime record returns
{"status": "STOPPED", "service": null, "workers": []}. A present record that
cannot be verified as the current live service returns
{"status": "STALE", "service": null, "workers": []}. Neither result performs
network requests or sends termination signals. Keep health API fields distinct:
the health response uses service_pid and gunicorn_pid, while this local state
schema uses the service identity's pid.
Renderer output goes to stdout unless output is supplied. Writing a file
produces no success banner on stdout and does not create parent directories.
An existing explicitly selected output file is replaced. The renderers never
install configuration or invoke Nginx, systemd, or a subprocess.
The serve handler returns before the process manager starts. The CLI's Frame,
logger writer, and event loop are closed first; each actual worker owns its own
configuration Frame and logger runtime. Other CLI commands never evaluate a
worker's logger.file.service.filename expression themselves.
See Nginx rendering and systemd rendering.