Skip to main content

Embedded async pipeline engine for FastAPI

Project description

pipely-core

PyPI Python License: MIT

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

pipely_core-0.1.2.tar.gz (6.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pipely_core-0.1.2-py3-none-any.whl (32.8 kB view details)

Uploaded Python 3

File details

Details for the file pipely_core-0.1.2.tar.gz.

File metadata

  • Download URL: pipely_core-0.1.2.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

Hashes for pipely_core-0.1.2.tar.gz
Algorithm Hash digest
SHA256 e6d80dbe2615500e510176ae1e51417c4bdc52bd35f652970e8acfdfbfe4104b
MD5 3d35be03e2136bb406e9288dce05a579
BLAKE2b-256 40cd89b791f8d651688ba6ef165a794b225ca3e827b3fb87d4d72f374d15ff96

See more details on using hashes here.

File details

Details for the file pipely_core-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: pipely_core-0.1.2-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

Hashes for pipely_core-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 96ce6e84aa16370dd3d8df33e4da2983a7074af29b3cc44d3a74d635eb2323e0
MD5 abac32191ed74a5018bc136b1dbc4abe
BLAKE2b-256 7bae7b3c6fef119af38ad12b8b833fee6e6de307b0c6f484c2db7d97fe8f4b8c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page