A streamlined, type-safe application framework for Python ML/HPC.
Project description
Liquifai
Liquifai is a modern, type-safe application framework for Python, designed to bind Loggair and Confluid into high-performance CLI applications.
Key Features
- Zero-Boilerplate Startup: Automatically handles logging and hierarchical config initialization.
- Type-Safe CLI: Streamlined argument parsing and validation.
- Dependency Injection: Seamlessly injects configured Confluid objects into your commands.
- Rich Integration: Beautiful terminal output and progress reporting via Rich.
- Modular Commands: Register and compose multiple tools into a single entry point.
- Operations → CLI + MCP: register a pure operation once (
@app.operation) and surface it as an auto-generated CLI command and an MCP tool (make_mcp_tools). - SDK Bridge (provisional): mirror an existing Python SDK as a full CLI/MCP app by decorating its client classes (
liquifai.bridge). - Shell Completion: bash/zsh/fish tab completion for commands, options, overrides — and live positional values.
Documentation
Each topic has its own guide, and every guide has a runnable companion script in examples/:
| Guide | What it covers | Example |
|---|---|---|
| Commands & Dependency Injection | @command / @script_command, config promotion (with ./config/ + XDG search paths and its DEBUG provenance notice), DI block lookup, positional arguments, flow modes |
positionals_app.py, promotion_app.py et al. |
| CLI Overrides | The override grammar (--key value, dotted keys, polarity, add/delete) and the dropped-token warning |
overrides_app.py |
| Global Flags | Log control (--level, --log-dir, …), --scope / dimension flags, --debug, --docs |
global_flags_app.py |
| Error Handling | The typed LiquifaiError hierarchy and the CLI failure contract |
failure_contract.py |
| Shell Completion | Install, aliases, workspace-local setup, dynamic & dependent positional values | completion_providers.py |
| Architecture Decisions | Why liquifai is shaped this way: the hand-rolled parser, out-of-process completion, the shared argv walk, who owns settability | — (records carry inline examples) |
For everything at once, examples/pypeek/
is a complete, installable showcase app — a small PyPI query CLI whose
<package> completes from your installed distributions (offline) and whose
<version> completes from the live PyPI API (dependent completion with the
background self-heal cache), plus the failure contract, override broadcast,
and dry-run in action.
Its counterpart examples/linefit/
is a training-style installable CLI (think Lightning CLI): fit /
validate / test / predict script commands with config promotion, a YAML
that instantiates the model/data/optimizer via confluid tags (the optimizer
!lazy: until the run supplies the live model), dotted CLI overrides reaching
any knob, and a print-config verb that dumps the fully-merged configuration
as a reloadable recipe.
Design Goals & Requirements
CLI Framework
- Zero-Boilerplate Startup: Automate the bootstrapping of Loggair and Confluid.
- Contextual Scripting: Support
@app.script_command()which promotes the first positional argument to a configuration file path. - Type-Safe DI: Inject fully-configured objects directly into command signatures based on type hints.
- Default Command Redirection: Support running a default command if no subcommand is provided.
User Experience
- Abbreviation Support: Allow brief aliases for the main executable (e.g.
maformy-app). - Dynamic Overrides: Support
--KEY VALCLI overrides with broadcast injection into nested configurations. - Observability Overrides: Provide CLI flags for log control (
--level,--console-level,--file-level,--log-dir).
Architecture
- Config Promotion: Automatically look for
<arg>.yamlif the first argument is not a registered command. - Smart DI Lookup: Search configuration blocks by both argument name and class name to ensure hydration.
Quick Start
from liquifai import LiquifyApp, LiquifyContext
from confluid import configurable
@configurable
class MyTrainer:
def __init__(self, lr: float = 0.01):
self.lr = lr
app = LiquifyApp(name="my-app")
@app.command()
def train(trainer: MyTrainer) -> None:
# 'trainer' is automatically loaded via Confluid and injected
print(f"Training with lr={trainer.lr}")
if __name__ == "__main__":
app.run()
CLI Overrides
Any token left after the command, positionals, and global flags is parsed as a config override. All forms interoperate in one invocation:
| Form | Example | Effect |
|---|---|---|
--key value |
--max_epochs 10 |
set max_epochs (broadcast into matching nested configs) |
--key=value |
--trainer.lr=0.001 |
equals form; dotted keys target nested blocks |
key=value |
model.dropout=0.2 |
bare form, no dashes |
--key+ / --key- |
--debug+ |
polarity: explicit True / False |
--key |
--verbose |
implicit True |
+key=value |
+new_feature=true |
add a new key |
~key |
~trainer.stale |
delete the dotted key from the config |
A token that matches none of these forms is not applied and liquifai logs a
warning naming it (Ignoring unrecognized CLI token 'lr' — expected one of: …). Previously such tokens were dropped silently — a typo'd lr 0.1 instead
of --lr 0.1 would run the whole job on defaults without a trace.
The single source of truth for the global-flag vocabulary and token
classification is liquifai/grammar.py (stdlib-only); the parser, --help,
and shell completion all derive from it, so they cannot drift apart. Override
parsing/application lives in liquifai/overrides.py, and annotation-driven
dependency injection in liquifai/di.py.
Error Handling
Liquifai raises typed exceptions rooted at LiquifaiError; each also inherits the builtin it replaces, so pre-existing except ValueError: / except KeyError: code keeps working unchanged:
| Exception | Also a | Raised when |
|---|---|---|
CommandDefinitionError |
ValueError |
a @script_command / @operation / bridge declaration is invalid (bad flow_mode / presentation, or an SdkBridge group naming an unregistered policy / adapter) |
UnknownOperationError |
KeyError |
set_completions() names an operation that is not registered |
UnsupportedShellError |
ValueError |
a completion shell is not one of bash / zsh / fish |
Configuration-loading failures propagate Confluid's own hierarchy (confluid.ConfluidError and subclasses) — LiquifaiError covers CLI-definition errors only.
CLI failure contract
When a command runs via app.run():
| Failure | Behavior | Exit code |
|---|---|---|
LiquifaiError or confluid.ConfluidError (bad config, unresolvable class, invalid declaration) |
One clean Error: … line on the console; full traceback written to the log file at DEBUG |
1 |
Same, with --debug on the line |
The exception propagates — full traceback on the console | (Python default) |
Missing --config file |
Dedicated Configuration file not found message |
1 |
| Unknown command/group | Unknown command or group (or help when no default command exists) |
1 |
| Any other exception | A bug — always propagates with its traceback, never converted to a clean exit | (Python default) |
Operations, MCP Tools, and the SDK Bridge
Beyond plain @app.command handlers, liquifai has a pure operations model: a
function returning a JSON-serializable dict is registered once and surfaced on
every front-end.
app = LiquifyApp(name="dataset")
@app.operation(presentation="list", columns=(("name", "Name"),))
def dataset_list(conn: MyConn) -> dict: # `conn` injected, never CLI-visible
return {"items": [...], "count": 3}
app.set_context_factory(build_conn) # how CLI calls get their `conn`
app.set_presenter(render_result) # how CLI renders the returned dict
app.build_commands() # -> CLI command `dataset list`
from liquifai import make_mcp_tools # -> MCP tools from the same ops
for tool in make_mcp_tools(app):
mcp_server.tool()(tool)
@app.operation is the ONE ops-registration path (the pre-release
@app.command(presentation=...) dual-mode was removed). CLI
positionals are derived from the operation signature: every keyword-only
parameter without a default becomes a positional slot.
SDK bridge (liquifai.bridge, provisional)
For CLIs that mirror an existing Python SDK, liquifai.bridge generates the
operations themselves: decorate a subclass of the real SDK client and declare
how each method maps to a CLI/MCP operation.
from liquifai.bridge import P, SdkBridge, custom, expose
bridge = SdkBridge(conn_cls=MyConn) # your @configurable connection
@bridge.group(name="widget", sub="widget", aliases=["w"])
class WidgetClient(sdk.WidgetClient): # inherits the real SDK class
@expose(verb="info", presentation="fields", params=[P("name")])
def get_widget(self) -> None: ... # method name = the SDK method
@expose(verb="delete", presentation="status", params=[P("name")],
status_word="deleted", status_echo=("name",))
def delete_widget(self) -> None: ...
@custom(verb="export", presentation="status")
def widget_export(conn, *, name: str) -> dict: # escape hatch: conn-first body
"""Export one widget somewhere the declarative engine can't express."""
...
bridge.mount(root_app) # CLI sub-app `widget` / `w`
The knobs you can tune, and where they live:
| Knob | Where | Purpose |
|---|---|---|
conn_cls= |
SdkBridge(...) |
Concrete connection class baked into op signatures (contract: dry_run attr + get_client() — see BridgeConnection) |
configure= |
SdkBridge(...) |
Hook run per group after all ops register (wire context factory / presenter, then build_commands()); default just builds commands |
adapters= |
SdkBridge(...) |
Extra P(adapt=...) string→value parsers merged over DEFAULT_ADAPTERS (list/csv/kv/props/str) |
policies= |
SdkBridge(...) |
Spec→op builders merged over the built-ins (call, items); register SDK dialects (e.g. a paginated list) here |
target= |
SdkBridge(...) |
(conn, sub) -> call target override for non-attribute sub-client access |
shape_status= |
SdkBridge(...) |
Override the default status-presentation result shaper |
P(cli, sdk=, default=, adapt=, ...) |
@expose(params=[...]) |
One CLI param mapped onto an SDK kwarg; no default = required = CLI positional |
extra **options |
@expose(...) |
Policy-specific knobs (e.g. a list policy's client_filter= / extras=) — opaque to the generic bridge |
Every generated op honors conn.dry_run before touching the SDK client,
returning a {"dry_run": True, "command", "action", "call"} descriptor — so
--help, MCP schemas, and tests run without credentials.
Provisional — outside the version contract.
liquifai.bridgehas a single production consumer so far; its API may change in 0.x minor releases without a deprecation cycle, and it may move to its own distribution. It is not re-exported from the top-levelliquifaipackage, soimport liquifainever reaches it. Depend on it asliquifai[bridge]so the dependency on an unstable surface is recorded in your own metadata. Rationale: Architecture Decisions §6.
Installation
pip install liquifai # from PyPI
Or straight from GitHub:
pip install git+https://github.com/Gearlux/liquifai.git@main
Optional extras
| Extra | What it opts into |
|---|---|
liquifai[bridge] |
The provisional liquifai.bridge subpackage (see above). It adds no requirements today — it is a contract marker that records, in your metadata, a dependency on a surface excluded from the version contract. |
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file liquifai-0.1.0.tar.gz.
File metadata
- Download URL: liquifai-0.1.0.tar.gz
- Upload date:
- Size: 153.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a9c27418a728fa3134abed813efb3dccd84e0cab187f2be33b57b861d79b159
|
|
| MD5 |
7c6b1ca820d65b08e944e6aacd1d926b
|
|
| BLAKE2b-256 |
805103a71bf8584ebf42b476079a576fd064340f041ea0d569d828210f1cc8e6
|
Provenance
The following attestation bundles were made for liquifai-0.1.0.tar.gz:
Publisher:
release.yml on Gearlux/liquifai
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
liquifai-0.1.0.tar.gz -
Subject digest:
1a9c27418a728fa3134abed813efb3dccd84e0cab187f2be33b57b861d79b159 - Sigstore transparency entry: 2276013572
- Sigstore integration time:
-
Permalink:
Gearlux/liquifai@c55c86edf717fbd99ee8b44796dda09dbffd137e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Gearlux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c55c86edf717fbd99ee8b44796dda09dbffd137e -
Trigger Event:
push
-
Statement type:
File details
Details for the file liquifai-0.1.0-py3-none-any.whl.
File metadata
- Download URL: liquifai-0.1.0-py3-none-any.whl
- Upload date:
- Size: 101.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7635a07dc20bc49486637a42e03ec4be6d542fa040b0635f35d54d317f616c06
|
|
| MD5 |
644e851aa273dc25d2ef7f84f0580bf6
|
|
| BLAKE2b-256 |
633de050159ec468a2f91d5abe555118c4a7d4d0969866fe0f6752e761aeebb4
|
Provenance
The following attestation bundles were made for liquifai-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Gearlux/liquifai
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
liquifai-0.1.0-py3-none-any.whl -
Subject digest:
7635a07dc20bc49486637a42e03ec4be6d542fa040b0635f35d54d317f616c06 - Sigstore transparency entry: 2276013612
- Sigstore integration time:
-
Permalink:
Gearlux/liquifai@c55c86edf717fbd99ee8b44796dda09dbffd137e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Gearlux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c55c86edf717fbd99ee8b44796dda09dbffd137e -
Trigger Event:
push
-
Statement type: