Skip to main content

Python SDK for Pingback — reliable cron jobs and background tasks

Project description

pingback-py

Python SDK for Pingback — reliable cron jobs and background tasks.

Installation

pip install pingback-py

Quick Start

import os
from pingback import Pingback

pb = Pingback(
    api_key=os.environ["PINGBACK_API_KEY"],
    cron_secret=os.environ["PINGBACK_CRON_SECRET"],
)

@pb.cron("cleanup", "0 3 * * *", retries=2, timeout="60s")
def cleanup(ctx):
    removed = remove_expired_sessions()
    ctx.log("Removed sessions", count=removed)
    return {"removed": removed}

@pb.task("send-email", retries=3, timeout="15s")
def send_email(ctx):
    to = ctx.payload["to"]
    deliver_email(to)
    ctx.log("Sent email", to=to)
    return {"sent": to}

Framework Integration

Flask

from flask import Flask

app = Flask(__name__)
app.route("/api/pingback", methods=["POST"])(pb.flask_handler())

FastAPI

from fastapi import FastAPI

app = FastAPI()
app.post("/api/pingback")(pb.fastapi_handler())

Django

# settings.py
from pingback import Pingback

pb = Pingback(
    api_key="pb_live_...",
    cron_secret="...",
    platform_url="https://api.pingback.lol",  # default
    base_url="https://myapp.com",              # your app's public URL
)
# views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from myproject.settings import pb

@csrf_exempt
def pingback_handler(request):
    result = pb.handle(request.body, dict(request.headers))
    status = result.pop("_status", 200)
    return JsonResponse(result, status=status)

Register your url:

# urls.py
from django.urls import path
from myapp.views import pingback_handler

urlpatterns = [
    path("api/pingback", pingback_handler),
]

Register on startup in your AppConfig:

# apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        from myprojct.settings import pb 
        pb.register()

Any Framework

result = pb.handle(body=request_body_bytes, headers=request_headers_dict)

Registration: flask_handler() and fastapi_handler() automatically register your functions with the platform on startup. For Django or other frameworks, call pb.register() after all functions are defined. Registration only runs once.

Defining Functions

Cron Jobs

@pb.cron("daily-report", "0 9 * * *", retries=3, timeout="60s")
def daily_report(ctx):
    report = generate_report()
    ctx.log("Report generated", rows=report.row_count)
    return report

Background Tasks

@pb.task("process-upload", retries=2, timeout="5m")
def process_upload(ctx):
    file_id = ctx.payload["file_id"]
    result = process_file(file_id)
    ctx.log("Processed file", file_id=file_id)
    return result

Typed Payloads

Task handlers can accept a typed second parameter for autocomplete, validation, and self-documenting code. Works with dataclasses and Pydantic models:

from dataclasses import dataclass

@dataclass
class EmailPayload:
    to: str
    subject: str
    priority: int = 1

@pb.task("send-email", retries=3)
def send_email(ctx, payload: EmailPayload):
    # payload.to, payload.subject — full autocomplete
    send_mail(payload.to, payload.subject)
    ctx.log("Sent", to=payload.to, priority=payload.priority)

With Pydantic (pip install pydantic):

from pydantic import BaseModel

class OrderPayload(BaseModel):
    order_id: str
    amount: float
    email: str

@pb.task("process-order")
def process_order(ctx, payload: OrderPayload):
    # validated, with defaults and type coercion
    ctx.log("Processing", order_id=payload.order_id)

Unpacked Kwargs

Task handlers can receive payload fields directly as keyword arguments — no need to extract from a payload object:

@pb.task("send-password-reset", retries=3)
def send_password_reset(ctx, otp_code: str, user_email: list[str]):
    message = f"Your OTP is {otp_code}."
    send_mail(message=message, recipient_list=user_email)
    ctx.log("Sent reset email", to=user_email)

Triggered with:

pb.trigger("send-password-reset", {"otp_code": "482910", "user_email": ["user@example.com"]})

This activates automatically when unpack_payload=True (the default) and the handler has more than one parameter beyond ctx, or a single extra parameter not named payload. The SDK unpacks ctx.payload as keyword arguments into the function. Set unpack_payload=False to disable this and use the raw dict or typed payload styles instead.

All four styles are supported:

Style Signature Payload access
No param def job(ctx) ctx.payload["key"]
Raw dict def job(ctx, payload) payload["key"]
Typed def job(ctx, payload: MyType) payload.key
Unpacked kwargs def job(ctx, field1, field2) field1, field2 directly

Fan-Out

@pb.cron("send-emails", "*/15 * * * *")
def send_emails(ctx):
    pending = get_pending_emails()
    for email in pending:
        ctx.task("send-email", {"to": email.recipient, "subject": email.subject})
    ctx.log("Dispatched emails", count=len(pending))
    return {"dispatched": len(pending)}

Workflows (Task Chaining)

Tasks can call ctx.task() to chain into multi-step workflows with branching:

@dataclass
class Order:
    order_id: str
    amount: float
    email: str

@pb.task("validate-order", retries=2)
def validate_order(ctx, order: Order):
    ctx.log("Validating", order_id=order.order_id)

    if order.amount <= 0:
        ctx.task("notify-failure", {"order_id": order.order_id, "reason": "Invalid amount"})
        return {"valid": False}

    ctx.task("charge-payment", {"order_id": order.order_id, "amount": order.amount, "email": order.email})
    return {"valid": True}

@pb.task("charge-payment", retries=3)
def charge_payment(ctx, payload: Order):
    charge = stripe.Charge.create(amount=int(payload.amount * 100))
    ctx.log("Charged", charge_id=charge.id)
    ctx.task("send-confirmation", {"email": payload.email, "order_id": payload.order_id})

@pb.task("send-confirmation", retries=2)
def send_confirmation(ctx, payload):
    send_email(payload["email"], "Order confirmed")
    ctx.log("Confirmation sent")

Each step runs as its own execution with independent retries and logging. The workflow graph in your dashboard visualizes the full chain.

Programmatic Triggering

exec_id = pb.trigger("send-email", {"to": "user@example.com"})

# With a delay — run 15 minutes from now
exec_id = pb.trigger("send-email", {"to": "user@example.com"}, delay="15m")

# Delay as seconds
exec_id = pb.trigger("send-email", {"to": "user@example.com"}, delay=900)

Supported delay formats: integer (seconds), or a string like "30s", "15m", "2h", "1d", "1d2h30m". Maximum delay: 30 days.

Structured Logging

ctx.log("message")                         # info
ctx.log("message", key="value")            # info with metadata
ctx.warn("slow query", ms=2500)            # warning
ctx.error("failed", code="E001")           # error
ctx.debug("cache stats", hits=847)         # debug

Configuration

pb = Pingback(
    api_key="pb_live_...",
    cron_secret="...",
    platform_url="https://api.pingback.lol",  # default
    base_url="https://myapp.com",              # your app's public URL
)

Function Options

@pb.cron("job", "* * * * *", retries=3, timeout="30s", concurrency=5)
@pb.task("job", retries=3, timeout="30s", concurrency=5, unpack_payload=True)

unpack_payload (default True) — when the handler has multiple parameters beyond ctx, or a single extra parameter not named payload, the SDK unpacks ctx.payload as keyword arguments. Set to False to always use the raw dict / typed payload styles.

Environment Variables

PINGBACK_API_KEY=pb_live_...        # From your Pingback project settings
PINGBACK_CRON_SECRET=...            # From your Pingback project settings

How It Works

  1. Define cron jobs and tasks with @pb.cron() and @pb.task() decorators
  2. Mount the handler using your framework's routing
  3. Functions are registered with the platform on startup (flask_handler() and fastapi_handler() do this automatically; for Django or other frameworks, call pb.register())
  4. The platform sends signed HTTP requests to your handler when jobs are due
  5. The handler verifies the HMAC signature, executes the function, and returns results
  6. Fan-out tasks and workflow chains are dispatched independently by the platform

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

pingback_py-0.3.0.tar.gz (14.0 kB view details)

Uploaded Source

Built Distribution

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

pingback_py-0.3.0-py3-none-any.whl (10.2 kB view details)

Uploaded Python 3

File details

Details for the file pingback_py-0.3.0.tar.gz.

File metadata

  • Download URL: pingback_py-0.3.0.tar.gz
  • Upload date:
  • Size: 14.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for pingback_py-0.3.0.tar.gz
Algorithm Hash digest
SHA256 9e57eecfd26efaf5824819f3b25634151508adb7bdd2ca26d1fd917caf2448df
MD5 7e38e100bad13173b8e55f70447dce84
BLAKE2b-256 206ce7a17060954cef56f817f074c71c45b799bdb71a74212066d3381a255a34

See more details on using hashes here.

File details

Details for the file pingback_py-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: pingback_py-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 10.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for pingback_py-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c1fc13502546fef97a4440e1821fc76931547bbbecd29dc126238fbc48a78dd8
MD5 23c891a85882a4b02393c812f0b43d49
BLAKE2b-256 4adbdb6bf976f53e20bf0990defff5140028742de516bd4b8d31e9e5a2277907

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