faceplant
Build a faceplant platform service in ~30 lines. Identity, the 30-second broker heartbeat, dashboards, the runtime plugin host, the daily garbage collector, and MCP tool serving are all under the hood — you write domain code.
pip install faceplant
Quickstart — a whole service
from faceplant import Service, Table, Column
svc = Service(
"billing",
description="Track usage bills.",
icon="receipt",
category="tools",
)
@svc.get("/data")
async def bills(user): # user.email, user.slug, user.dir
rows = load_bills(user.dir) # per-user private storage, created for you
return Table(
title="Bills",
columns=[Column(key="month", header="Month"),
Column(key="amount", header="Amount")],
rows=rows,
)
@svc.responder("billing.total") # a broker API other services can call
async def total(data):
return {"total": sum_bills()}
app = svc.app # uvicorn src.main:app
That's the entire service. Service(...) already gave you:
- a gateway card heartbeated every 30s (stop the pod → the card disappears);
/health,/manifest,/_pluginsand CORS;- identity:
useris injected from the platform JWT (401 when absent), withuser.dirfor per-user storage anduser.tags/user.services; - the runtime plugin host: other services can extend this one with signed
wheels, with zero code here (
svc.registryif you want to add hook points); - the daily GC that reclaims a user's data ~10 days after they're gone.
More surfaces (each one decorator)
@svc.consumer("CHAT", filter_keys=["chat.>"]) # subscribe to broker events
async def on_chat(data): ...
@svc.mcp_tool() # a tool for coding agents (pip install faceplant[mcp])
async def get_bill(month: str) -> str: ...
svc.announce_wheel(target="faceplant-users", # ship an extension wheel AT another service
wheel="plugin", entrypoint="billing_gate.plugin:register")
@svc.manifest # compute the dashboard per request
def dash(user): return Stack(children=[...])
Escape hatches, always available: svc.app is a real FastAPI app (add
WebSockets, SSE, middleware); svc.broker.request(key, data) / .publish(...);
await svc.provision(fn) for blocking work off the DNS-critical event loop;
@svc.background for startup loops; @svc.on_purge / @svc.gc_verdict to
customize GC.
Testing is safe by default
Service only starts its heartbeat when a broker is actually configured
(BROKER_URL set). A bare pytest or with TestClient(app) runs offline —
no accidental registration against a live gateway. To test online behavior, use
the in-process stub:
from fastapi.testclient import TestClient
from faceplant.testing import StubBroker, dev_jwt
def test_bills():
with TestClient(svc.app) as client: # offline: no heartbeat
r = client.get("/data", headers={"authorization": f"Bearer {dev_jwt('a@b.co')}"})
assert r.status_code == 200
async def test_registration():
stub = StubBroker(); stub.install(svc)
await stub.beat(svc)
assert "billing.total" in stub.responders
Deployment is boilerplate-free too
Your repo needs no Dockerfile and no k8s/values-prod.yaml — the SDK
generates them. A deployable service is just src/main.py, pyproject.toml,
and a ~10-line deploy workflow that calls the SDK's reusable one:
jobs:
deploy:
uses: faceplant-ai/faceplant-sdk/.github/workflows/deploy-service.yml@v1
with: { service: billing, kind: stateless }
secrets: inherit
Or generate the files to check in: faceplant init --name billing. See
docs/deployment.md.
Docs
One page per concept in docs/: identity,
widgets, plugin-host,
providers,
consumers & responders, mcp,
gc, escape hatches,
testing, and
migrating from the template.
License
MIT.
Metadata
Release files for faceplant 0.7.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| faceplant-0.7.3.tar.gz | 167.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| faceplant-0.7.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 232.1 kB
Release files / faceplant-0.7.3.tar.gz
| Download URL | faceplant-0.7.3.tar.gz |
|---|---|
| Size | 167.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3f456c757411ec7d44b435b80c3e77ac859f44d4b83d61972336423493d4942a
|
|
BLAKE2b-256 checksum How to use checksums |
dc60d00b12f8a52c7399e60d9a8267f62a9d6faaa83f0d491508b648ff8cfd90
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / faceplant-0.7.3-py3-none-any.whl
| Download URL | faceplant-0.7.3-py3-none-any.whl |
|---|---|
| Size | 64.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
805e2a0211fcfe2de436b9048319c563167ec2732b59bde2d9e7fe20afc97fc3
|
|
BLAKE2b-256 checksum How to use checksums |
12b91491a3057329397bb1766ec339fc4a60f7822e0a03404a27bb567a352d90
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|