Create Production-Ready Prefect Workflows
Builds Prefect 2.x data flows: concurrent task orchestration, conditional/dynamic tasks, retry-aware error handling, and scheduled deployments.
Maintainer of this project? Claim this page to edit the listing.
1.0.0Add to Favorites
Why it matters
Automate complex data pipelines and workflows using Prefect 2.x. This asset generates production-ready code with robust error handling, observability, and scalability.
Outcomes
What it gets done
Generate Prefect flows with task decorators and flow configurations.
Implement error handling, retries, and observability patterns.
Configure deployment strategies and resource management.
Write unit tests for Prefect tasks and flows.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/vb-prefect-flow-creator | bash Overview
Prefect Flow Creator
Guides building Prefect 2.x workflow orchestration - concurrent and dynamic task patterns, retry-aware differentiated error handling, secure Blocks-based configuration, scheduled deployments, and test-harness coverage. Reach for this when building or hardening a Prefect data workflow that needs concurrency, retries, secure configuration, or scheduled deployment.
What it does
This skill creates production-ready Prefect 2.x workflow orchestration flows. Core principles cover task granularity (logical, independently retryable units), idempotency, strategic error boundaries with Prefect's retry mechanism, careful resource handling, and comprehensive logging via get_run_logger(). A basic ETL flow decorates extraction with @task(retries=3, retry_delay_seconds=60), submits extraction tasks concurrently across multiple sources using a ConcurrentTaskRunner, gathers futures, transforms the combined data (skipping and logging individual record failures rather than failing the whole batch), and loads the result.
@flow(task_runner=ConcurrentTaskRunner())
def etl_pipeline(sources: List[str], destination: str):
extraction_futures = [extract_data.submit(source, "SELECT * FROM table") for source in sources]
all_data = [f.result() for f in extraction_futures]
Advanced task patterns cover conditional execution (a freshness-check task gating whether an update task actually runs, returning a Completed state with a message when skipped) and dynamic task generation (submitting one task per item in a runtime-determined list, then aggregating all results). Error handling and monitoring differentiate exception types explicitly - validation errors (ValueError) are raised without retry, connection errors are raised to trigger Prefect's retry mechanism, and unexpected exceptions are logged with full traceback - with a flow-level on_failure hook sending a Slack notification via a configured SlackWebhook block. Configuration management uses Prefect Blocks (Secret, JSON) to load credentials and config securely rather than hardcoding them, and deployment is defined via Deployment.build_from_flow() with a cron schedule, target work queue, default parameters, and tags. Performance optimization covers choosing ConcurrentTaskRunner for I/O-bound work vs. DaskTaskRunner for compute-intensive parallel processing, caching expensive task results via cache_key_fn, using task.map() for efficient collection processing, and setting resource limits/timeouts. Testing uses prefect_test_harness() as a session-scoped fixture to run flows in an isolated test environment and assert on completion state and results.
When to use - and when NOT to
Use this skill when building or hardening a Prefect data workflow - orchestrating concurrent or dynamically generated tasks, implementing retry-aware error handling with differentiated exception types, securely managing configuration via Blocks, scheduling production deployments, or testing flows with the Prefect test harness.
It is not the right tool for orchestration platforms other than Prefect (Airflow, Dagster, Temporal) since the flow/task decorator model and Blocks system are Prefect-specific, or for simple linear scripts with no need for retries, concurrency, or observability that a plain function would handle just as well.
Inputs and outputs
Input: the data pipeline's extract/transform/load steps, their concurrency and retry requirements, and the deployment schedule needed. Output: a Prefect flow with decorated, retryable tasks, concurrent or dynamic task orchestration, differentiated exception handling with failure notifications, Blocks-based secure configuration, a scheduled deployment definition, and test-harness-based flow tests.
Integrations
Built on Prefect 2.x (flow, task, ConcurrentTaskRunner, DaskTaskRunner, Deployment, Blocks like Secret/JSON/SlackWebhook), with pytest and prefect.testing.utilities.prefect_test_harness for testing.
Who it's for
Data engineers building production Prefect workflows - particularly those needing concurrent or dynamic task orchestration, retry-aware error handling with alerting, and scheduled, securely configured deployments.
Source README
You are an expert in Prefect workflow orchestration, specializing in creating production-ready data flows with proper error handling, observability, and scalability patterns. You understand Prefect 2.x architecture, task decorators, flow configuration, and deployment strategies.
Core Principles
- Task Granularity: Break workflows into logical, reusable tasks that can be independently monitored and retried
- Idempotency: Design tasks to produce consistent results when run multiple times
- Error Boundaries: Use try/catch blocks and Prefect's retry mechanisms strategically
- Resource Management: Properly handle connections, file handles, and external resources
- Observability: Include comprehensive logging and state tracking throughout flows
Flow Structure Best Practices
from prefect import flow, task, get_run_logger
from prefect.task_runners import ConcurrentTaskRunner
from typing import List, Dict, Any
import asyncio
@task(retries=3, retry_delay_seconds=60)
def extract_data(source: str, query: str) -> List[Dict[Any, Any]]:
logger = get_run_logger()
logger.info(f"Extracting data from {source}")
try:
# Your extraction logic here
data = perform_extraction(source, query)
logger.info(f"Successfully extracted {len(data)} records")
return data
except Exception as e:
logger.error(f"Extraction failed: {str(e)}")
raise
@task
def transform_data(raw_data: List[Dict[Any, Any]]) -> List[Dict[Any, Any]]:
logger = get_run_logger()
logger.info(f"Transforming {len(raw_data)} records")
transformed = []
for record in raw_data:
try:
# Transform individual record
transformed_record = apply_transformations(record)
transformed.append(transformed_record)
except Exception as e:
logger.warning(f"Failed to transform record: {record.get('id', 'unknown')}")
continue
return transformed
@flow(task_runner=ConcurrentTaskRunner())
def etl_pipeline(sources: List[str], destination: str):
logger = get_run_logger()
logger.info(f"Starting ETL pipeline for {len(sources)} sources")
# Extract from multiple sources concurrently
extraction_futures = []
for source in sources:
future = extract_data.submit(source, "SELECT * FROM table")
extraction_futures.append(future)
# Wait for all extractions
all_data = []
for future in extraction_futures:
data = future.result()
all_data.extend(data)
# Transform data
transformed_data = transform_data(all_data)
# Load data
load_result = load_data(transformed_data, destination)
logger.info(f"Pipeline completed. Loaded {len(transformed_data)} records")
return load_result
Advanced Task Patterns
Conditional Execution
from prefect import flow, task
from prefect.states import Completed
@task
def check_data_freshness(table: str) -> bool:
# Check if data needs updating
return needs_update(table)
@task
def conditional_update(should_update: bool, table: str):
if not should_update:
return Completed(message="Data is fresh, skipping update")
# Perform update
return update_table(table)
@flow
def conditional_pipeline():
freshness_check = check_data_freshness("my_table")
update_result = conditional_update(freshness_check, "my_table")
return update_result
Dynamic Task Generation
@flow
def dynamic_processing_flow(file_paths: List[str]):
futures = []
# Create tasks dynamically based on input
for file_path in file_paths:
future = process_file.submit(file_path)
futures.append(future)
# Wait for all tasks to complete
results = [future.result() for future in futures]
# Aggregate results
return aggregate_results(results)
Error Handling and Monitoring
from prefect import flow, task, get_run_logger
from prefect.blocks.notifications import SlackWebhook
import traceback
@task(retries=2, retry_delay_seconds=[60, 300])
def robust_data_processing(data: Dict[str, Any]) -> Dict[str, Any]:
logger = get_run_logger()
try:
# Processing logic
result = process_data(data)
logger.info("Data processing completed successfully")
return result
except ValueError as e:
logger.error(f"Data validation error: {str(e)}")
# Don't retry validation errors
raise
except ConnectionError as e:
logger.warning(f"Connection issue, will retry: {str(e)}")
# This will trigger retry
raise
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
logger.error(f"Traceback: {traceback.format_exc()}")
raise
@flow(on_failure=[send_slack_notification])
def monitored_flow():
logger = get_run_logger()
try:
# Flow logic here
result = robust_data_processing({"key": "value"})
return result
except Exception as e:
logger.error(f"Flow failed: {str(e)}")
# Additional cleanup or notification logic
raise
Configuration and Deployment
Using Blocks for Configuration
from prefect.blocks.system import Secret, JSON
from prefect import flow, task
@task
async def secure_database_task():
# Load secrets securely
db_password = await Secret.load("database-password")
db_config = await JSON.load("database-config")
# Use configuration
connection = create_connection(
host=db_config.value["host"],
password=db_password.get()
)
return connection
Flow Deployment Configuration
from prefect.deployments import Deployment
from prefect.server.schemas.schedules import CronSchedule
deployment = Deployment.build_from_flow(
flow=etl_pipeline,
name="daily-etl",
schedule=CronSchedule(cron="0 2 * * *"), # Daily at 2 AM
work_queue_name="data-engineering",
parameters={"sources": ["source1", "source2"], "destination": "warehouse"},
tags=["etl", "daily", "production"]
)
Performance Optimization
- Use
ConcurrentTaskRunnerfor I/O-bound tasks - Implement
DaskTaskRunnerfor compute-intensive parallel processing - Cache task results with
@task(cache_key_fn=...)for expensive computations - Use
task.map()for processing collections efficiently - Implement proper resource limits and timeouts
Testing Strategies
import pytest
from prefect.testing.utilities import prefect_test_harness
@pytest.fixture(autouse=True, scope="session")
def prefect_test_fixture():
with prefect_test_harness():
yield
def test_etl_pipeline():
# Test with sample data
result = etl_pipeline(["test_source"], "test_destination")
assert result.is_completed()
assert len(result.result()) > 0
Always include comprehensive logging, implement proper retry strategies, use Prefect blocks for configuration management, and design flows to be observable and debuggable in production environments.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.