python-pipelines — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited python-pipelines (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Prefect adds scheduling, retry logic, observability, and failure notifications to Python scripts without rewriting them. It wins over Celery (no broker needed) and cron (no visibility, no retries) for API-dependent pipelines.
| Need | Prefect | Celery | cron |
|---|---|---|---|
| Retry with backoff on API failures | Yes | Yes | No |
| UI to inspect run history | Yes | Flower (limited) | No |
| No message broker required | Yes | No (Redis/RabbitMQ) | Yes |
| Dependency between tasks | Yes | Limited | No |
| Slack alert on failure | Yes (automations) | Manual | Manual |
| Setup complexity | Low | High | Zero |
Default to Prefect when pipelines call LLM/external APIs and need failure visibility. Use cron only for fire-and-forget shell scripts with no retry requirement.
from prefect import flow, task
from prefect.logging import get_run_logger
@task(
retries=3,
retry_delay_seconds=60,
log_prints=True,
)
def fetch_articles(source_url: str) -> list[dict]:
logger = get_run_logger()
logger.info(f"Fetching from {source_url}")
# ... fetch logic
return articles
@task(log_prints=True)
def process_with_llm(article: dict, api_key: str) -> str:
# ... LLM call
return summary
@flow(name="UPSC Daily Processing", log_prints=True)
def upsc_pipeline(source_url: str):
articles = fetch_articles(source_url)
summaries = process_with_llm.map(articles) # concurrent map
return summariesRules:
@task wraps atomic units of work (one API call, one DB write)@flow orchestrates tasks — it is the entry point# Option 1: serve() — simplest, runs in a local process
if __name__ == "__main__":
upsc_pipeline.serve(
name="upsc-daily",
cron="0 6 * * *", # 6 AM daily
parameters={"source_url": "https://example.com/upsc"},
)
# Option 2: interval schedule
from datetime import timedelta
upsc_pipeline.serve(
name="upsc-hourly",
interval=timedelta(hours=1),
)
# Option 3: RRule for complex schedules (every weekday)
from prefect.schedules import RRuleSchedule
import rrule
schedule = RRuleSchedule(rrule="FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR")Run prefect server start locally and open http://localhost:4200 to see all runs.
from prefect import task
from prefect.tasks import exponential_backoff
@task(
retries=4,
retry_delay_seconds=exponential_backoff(backoff_factor=2),
# delays: 2s, 4s, 8s, 16s
retry_jitter_factor=0.5, # add randomness to avoid thundering herd
)
def call_llm_api(prompt: str, api_key: str) -> str:
import anthropic
client = anthropic.Anthropic(api_key=api_key)
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].textRate-limited APIs (Anthropic, OpenAI) should use retries=4 + exponential backoff as the default. Do not set retries on tasks that write to a DB without idempotency — duplicate writes are worse than failures.
Prefect captures all print() and logger output in the UI per task run. Add structured context:
from prefect import task
from prefect.logging import get_run_logger
@task
def process_article(article_id: str):
logger = get_run_logger()
logger.info("processing started", extra={"article_id": article_id})
# ...
logger.info("processing complete", extra={"article_id": article_id, "tokens": 412})Slack failure notification via Prefect automations (requires Prefect Cloud) or via a hook:
from prefect import flow
from prefect.blocks.notifications import SlackWebhook
@flow(name="ask-ai-daily")
def ask_ai_daily():
try:
run_pipeline()
except Exception as e:
# Fallback: call Slack MCP directly if not on Prefect Cloud
# or use SlackWebhook block
slack = SlackWebhook.load("slack-alerts")
slack.notify(f"ask-ai-daily FAILED: {e}")
raiseFor Slack MCP integration (preferred — no secrets in code): After flow failure, use mcp__c6399901__slack_send_message to post to your alerts channel. Wire this in a flow.on_failure hook.
import asyncio
from prefect import flow, task
@task
async def fetch_one(url: str) -> dict:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.json()
@flow
async def fetch_all(urls: list[str]):
# Run all fetches concurrently
results = await asyncio.gather(*[fetch_one(url) for url in urls])
return results
# Or use Prefect's built-in .map() for parallel task submission:
@flow
def parallel_pipeline(article_ids: list[str]):
# .map() submits all tasks concurrently, collects results
summaries = process_article.map(article_ids)
return summariesUse .map() for CPU/IO-bound work over a list. Use asyncio.gather() inside a single async task for fine-grained concurrency within one step.
Never put API keys in flow parameters or code. Use Prefect Blocks:
from prefect.blocks.system import Secret
# Store once (CLI or UI):
# prefect block create secret --name anthropic-api-key --value sk-...
@task
def call_api():
api_key = Secret.load("anthropic-api-key").get()
# use api_keyFor local dev, .env + python-dotenv is fine. In production, Prefect Blocks store secrets encrypted in the Prefect server DB.
| Mode | When | Command |
|---|---|---|
flow.serve() | Dev + simple prod (single machine) | python pipeline.py |
| Docker worker | Multi-pipeline, isolated envs | prefect worker start --pool docker-pool |
| Prefect Cloud | Team visibility, managed infra | Sign up at app.prefect.cloud |
For ask-ai-daily-automation and UPSC pipelines: start with flow.serve() on the same machine running the scripts today. Migrate to Docker only when you need environment isolation between pipelines.
# Install
pip install prefect
# Start local UI
prefect server start
# In another terminal, run your pipeline (registers it as a deployment)
python upsc_pipeline.pyOpen http://localhost:4200 — all runs, logs, and retries are visible there.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.