Embedded async pipeline engine for FastAPI
Project description
pipely-core
Embedded async pipeline engine for FastAPI. For long in-process workflows — OCR, document processing, LLM agents, report generation — where Airflow or Prefect would be too heavy.
- Linear async pipelines with retries and timeouts
- Postgres persistence via SQLAlchemy 2 async (SQLite for tests)
- Automatic table creation — no migrations needed to start
- Resume, retry failed, restart from step, cancel
- Built-in REST API and developer UI
Install
pip install pipely-core
Quickstart
from fastapi import FastAPI
from pipely import Pipely, PipelineContext, step
app = FastAPI()
pipely = Pipely(database_url="postgresql+asyncpg://user:password@host:5432/db")
pipely.mount(app)
@pipely.pipeline("meeting_protocol")
class MeetingProtocolPipeline:
@step(retries=3)
async def transcribe(self, ctx: PipelineContext) -> None:
transcript = await transcribe_audio(ctx.input["audio_url"])
ctx.set("transcript", transcript)
@step(retries=2)
async def clean_transcript(self, ctx: PipelineContext) -> None:
clean = await clean_text(ctx.get("transcript"))
ctx.set("clean_transcript", clean)
@step(retries=2)
async def extract_tasks(self, ctx: PipelineContext) -> None:
tasks = await extract_tasks(ctx.get("clean_transcript"))
ctx.set("tasks", tasks)
Running pipelines
Blocking — waits until the run finishes:
run = await pipely.start("meeting_protocol", input={"audio_url": "s3://bucket/audio.mp3"})
print(run.status) # "completed"
print(run.state) # {"transcript": ..., "tasks": [...]}
Background — returns immediately, runs in the app's task group:
run = await pipely.submit("meeting_protocol", input={"audio_url": "..."})
print(run.status) # "pending"
Controlling runs
await pipely.retry_failed(run.id) # retry from first failed step
await pipely.resume(run.id) # resume from first unfinished step
await pipely.restart_from_step(run.id, "extract_tasks") # restart from a specific step
await pipely.cancel(run.id) # cancel immediately
PipelineContext
Each step receives a ctx object:
| Member | Description |
|---|---|
ctx.input |
Original input dict passed to start() |
ctx.state |
Mutable dict shared across all steps, persisted to DB |
ctx.get(key) |
Read from state |
ctx.set(key, value) |
Write to state (auto-flushed after step) |
await ctx.flush() |
Persist state mid-step without waiting for step to finish |
await ctx.log(msg, level, payload) |
Write a structured log entry |
@step(retries=2, timeout=30.0)
async def process(self, ctx: PipelineContext) -> None:
await ctx.log("starting", payload={"input": ctx.input})
result = await do_work(ctx.get("data"))
ctx.set("result", result)
await ctx.flush() # persist now, in case the next part fails
await do_more_work()
Composing with an existing lifespan
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app):
await startup()
async with pipely.lifespan(app):
yield
await shutdown()
app = FastAPI(lifespan=lifespan)
pipely.mount(app)
Using an existing SQLAlchemy engine
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
engine = create_async_engine(DATABASE_URL)
pipely = Pipely(engine=engine)
# or pass your own sessionmaker:
pipely = Pipely(sessionmaker=async_sessionmaker(engine, expire_on_commit=False))
REST API & UI
pipely.mount(app) adds these routes (default prefix /_pipely):
| Method | Path | Description |
|---|---|---|
GET |
/_pipely |
Developer UI |
GET |
/_pipely/api/runs |
List runs |
GET |
/_pipely/api/runs/{run_id} |
Get run with steps and logs |
POST |
/_pipely/api/runs |
Start a run |
POST |
/_pipely/api/runs/{run_id}/retry |
Retry from failed step |
POST |
/_pipely/api/runs/{run_id}/resume |
Resume from first unfinished step |
POST |
/_pipely/api/runs/{run_id}/restart-from-step |
Restart from a named step |
POST |
/_pipely/api/runs/{run_id}/cancel |
Cancel a run |
Change the prefix:
pipely = Pipely(database_url=..., ui_path="/admin/pipelines")
Development
git clone https://github.com/pipely/pipely
cd pipely
pip install -e ".[test]"
pytest
Project details
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 pipely_core-0.1.3.tar.gz.
File metadata
- Download URL: pipely_core-0.1.3.tar.gz
- Upload date:
- Size: 6.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
107c1b24b86dd8a62d85a14737b7cf60d24024ef62bfd5569b01528287c12a7a
|
|
| MD5 |
a2eb741a5fac65711224f7912da052d6
|
|
| BLAKE2b-256 |
32b1327d6d20cc921fb4c569e97408abb3d82233f13472f5a19ef7ace99365f8
|
File details
Details for the file pipely_core-0.1.3-py3-none-any.whl.
File metadata
- Download URL: pipely_core-0.1.3-py3-none-any.whl
- Upload date:
- Size: 32.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
856964da4bb340fcfed97dac9c2dec0df7aef40c7a65dd9532eda2aa8e50bbfe
|
|
| MD5 |
ddce3eb84dcc13a24cb00c792ee0659a
|
|
| BLAKE2b-256 |
48daaf750070fa807cd3dbc59f6b61bca261188cc359fb2e6e45139cd26aa6b7
|