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.1.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.1-py3-none-any.whl (31.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pipely_core-0.1.1.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.1.tar.gz
Algorithm Hash digest
SHA256 aa145b30733562a0e3aed5337fa446f5cda017707138a80cd03b52f9240eb9af
MD5 69f7bfbfe156cd74a2f29367e865e7d0
BLAKE2b-256 f09bda36191aa2e7df95c2fc4107c90280455315154a325656bbf89d10b124bd

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pipely_core-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 31.3 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7637d558f9760d4b047a4a99b97b05c5239d3d1f514ebd0fdad68275a0c43284
MD5 daaf2d395d38595b2a3688d1b206cb48
BLAKE2b-256 b181795e060db2cd495a6623b5b87b911751d5fda0139727aea6a05de2d1428a

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