| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
parent directory.. | ||||
A comprehensive example demonstrating how to use both the Client SDK and Task SDK together for file analysis workflows.
This example shows the complete workflow of building a file analysis system with two separate services:
This architecture demonstrates how to separate concerns:
Build a file analysis API that:
Common applications:
┌─────────────────┐
│ User/Client │
└────────┬────────┘
│ HTTP POST /analyze
│ (CSV file upload)
▼
┌─────────────────────────────┐
│ API Service │
│ (Client SDK - FastAPI) │
│ │
│ - Receives file uploads │
│ - Calls workflow tasks │
│ - Returns results │
└────────┬────────────────────┘
│ Client SDK:
│ client.workflows.run_task(
│ "file-analyzer-workflows/analyze_file",
│ [file_content]
│ )
▼
┌─────────────────────────────┐
│ Workflow Service │
│ (Task SDK - Workflows) │
│ │
│ - Defines analysis tasks │
│ - Processes data │
│ - Returns results │
│ │
│ Tasks: │
│ - parse_csv_data │
│ - calculate_statistics │
│ - identify_trends │
│ - generate_insights │
│ - analyze_file │
└─────────────────────────────┘
A workflow slug is the unique identifier for your workflow service on Render. It's used to route task calls to the correct service.
Task calls use the format: {service-slug}/{task-name}
Example:
Option 1: From Service URL
Option 2: From Service Name
Option 3: From Dashboard
Set the WORKFLOW_SERVICE_SLUG environment variable in your API service:
WORKFLOW_SERVICE_SLUG=file-analyzer-workflowsThe API service uses this to construct full task identifiers:
def get_task_identifier(task_name: str) -> str:
service_slug = os.getenv("WORKFLOW_SERVICE_SLUG")
return f"{service_slug}/{task_name}"
# Example usage:
task_id = get_task_identifier("analyze_file")
# Result: "file-analyzer-workflows/analyze_file"file-analyzer/
├── README.md # This file
├── workflow-service/ # Task SDK - Defines tasks
│ ├── requirements.txt # Python dependencies
│ ├── main.py # Task definitions
│ └── sample_files/
│ ├── sales_data.csv # Sample sales data
│ └── customer_data.csv # Sample customer data
└── api-service/ # Client SDK - Calls tasks
├── requirements.txt # Python dependencies
└── main.py # FastAPI endpoints
parse_csv_data(ctx, file_content: str) -> dict
calculate_statistics(ctx, data: dict) -> dict (Subtask)
identify_trends(ctx, data: dict) -> dict (Subtask)
generate_insights(ctx, stats: dict, trends: dict, metadata: dict) -> dict (Subtask)
analyze_file(ctx, file_content: str) -> dict (Main orchestrator)
The main analyze_file task demonstrates subtask orchestration:
@app.task
async def analyze_file(ctx: TaskContext, file_content: str) -> dict:
# SUBTASK CALL: Parse CSV data
parsed_data = await ctx.run(parse_csv_data, file_content)
# SUBTASK CALL: Calculate statistics
stats = await ctx.run(calculate_statistics, parsed_data)
# SUBTASK CALL: Identify trends
trends = await ctx.run(identify_trends, parsed_data)
# SUBTASK CALL: Generate insights
insights = await ctx.run(generate_insights, stats, trends, parsed_data)
return {"statistics": stats, "trends": trends, "insights": insights}GET / - API information and available endpoints
GET /health - Health check with configuration status
POST /analyze - Upload and analyze a CSV file
POST /analyze-task/{task_name} - Call specific workflow task
The API service demonstrates the complete Client SDK workflow:
from render import Render
# 1. Get client instance (uses RENDER_API_KEY env var automatically)
render = Render()
# 2. Construct task identifier: {service-slug}/{task-name}
service_slug = os.getenv("WORKFLOW_SERVICE_SLUG")
task_identifier = f"{service_slug}/analyze_file"
# 3. Call the workflow task with arguments as a dict
task_run = await render.workflows.run_task(
task_identifier,
{"file_content": file_content}
)
# 4. Await the task completion
result = await task_run
# 5. Access the results
print(result.id) # Task run ID
print(result.status) # Task status (e.g., "SUCCEEDED")
print(result.results) # Task return value# Navigate to workflow service
cd file-analyzer/workflow-service
# Install dependencies
pip install -r requirements.txt
# Run the service
python main.pyThe service will start and register all tasks. Keep this running.
In a separate terminal:
# Navigate to API service
cd file-analyzer/api-service
# Install dependencies
pip install -r requirements.txt
# Set environment variables
export RENDER_API_KEY="your_render_api_key"
export WORKFLOW_SERVICE_SLUG="local" # For local development
# Run the service
uvicorn main:app --host 0.0.0.0 --port 8000Using curl:
curl -X POST "http://localhost:8000/analyze" \
-F "file=@workflow-service/sample_files/sales_data.csv"Using Python:
import requests
with open('workflow-service/sample_files/sales_data.csv', 'rb') as f:
response = requests.post(
'http://localhost:8000/analyze',
files={'file': f}
)
print(response.json())Check health:
curl http://localhost:8000/healthService Type: Workflow
Configuration:
cd file-analyzer/workflow-service && pip install -r requirements.txtcd file-analyzer/workflow-service && python main.pyEnvironment Variables:
Deployment Steps:
Important: Note the service slug (usually the service name in lowercase with hyphens). You'll need this for the API service.
Once the workflow service is deployed, you can test tasks directly in the Render Dashboard:
Recommended Starting Point: Start with analyze_file - this is the main orchestrator task that runs the complete analysis pipeline (parse → statistics → trends → insights).
Test the complete analysis pipeline:
Task: analyze_file
Input:
{
"file_content": "date,product,quantity,price\n2024-01-15,Laptop,5,1200.00\n2024-01-16,Mouse,25,25.99\n2024-01-17,Monitor,8,350.00"
}This will parse the CSV, calculate statistics, identify trends, and generate insights.
Test individual tasks:
Task: parse_csv_data
Input:
{
"file_content": "name,age,country\nAlice,28,USA\nBob,35,Canada"
}Returns parsed CSV structure with rows and columns.
Task: calculate_statistics
Input (requires parsed data structure):
{
"data": {
"success": true,
"rows": [
{"age": "28", "score": "85"},
{"age": "35", "score": "92"}
],
"columns": ["age", "score"],
"row_count": 2
}
}Returns statistical metrics for numeric columns.
Note: The workflow service doesn't handle file uploads - it processes raw CSV content. For file uploads, use the API service (tested via HTTP endpoints, not the Dashboard).
Service Type: Web Service
Configuration:
cd file-analyzer/api-service && pip install -r requirements.txtcd file-analyzer/api-service && uvicorn main:app --host 0.0.0.0 --port $PORTEnvironment Variables:
Deployment Steps:
Once both services are deployed and healthy:
# Get your API service URL from Render Dashboard
# Example: https://file-analyzer-api.onrender.com
# Test health endpoint
curl https://file-analyzer-api.onrender.com/health
# Upload a file for analysis
curl -X POST "https://file-analyzer-api.onrender.com/analyze" \
-F "file=@path/to/your/file.csv"| Variable | Required | Description | Where to Get |
|---|---|---|---|
| RENDER_API_KEY | Yes | Your Render API key | Render Dashboard → Account Settings → API Keys |
| Variable | Required | Description | Example |
|---|---|---|---|
| RENDER_API_KEY | Yes | Your Render API key | Get from Account Settings |
| WORKFLOW_SERVICE_SLUG | Yes | Your workflow service slug | file-analyzer-workflows |
Contains sales transaction data with columns:
Analysis Output:
Contains customer information with columns:
Analysis Output:
Creating the Client:
from render import Render
# Uses RENDER_API_KEY environment variable automatically
render = Render()Calling Tasks:
# Format: render.workflows.run_task(task_identifier, {args})
task_run = await render.workflows.run_task(
"service-slug/task-name",
{"arg1": value1, "arg2": value2}
)
# Await completion
result = await task_run
# Access results
print(result.id) # Task run ID
print(result.status) # "SUCCEEDED", "FAILED", etc.
print(result.results) # Return value from taskDefining Tasks:
from render import TaskContext, Workflows
app = Workflows()
# Every task takes a TaskContext as its first parameter, followed by its inputs
@app.task
def my_task(ctx: TaskContext, param: str) -> dict:
return {"result": param}
app.start()Why separate services?
Service slug determines routing:
# Service slug: "file-analyzer-workflows"
# Task name: "analyze_file"
# Full identifier: "file-analyzer-workflows/analyze_file"
# This routes the call to:
# - Service: file-analyzer-workflows
# - Task: analyze_filefrom fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
@app.post("/analyze")
async def analyze_file(
file: UploadFile,
token: str = Depends(security)
):
# Verify token
if not verify_token(token.credentials):
raise HTTPException(status_code=401)
# ... rest of logic@app.post("/analyze")
async def analyze_file(file: UploadFile):
# Trigger analysis
result = await task_run
# Store in database
db.insert({
"filename": file.filename,
"task_run_id": result.id,
"results": result.results,
"created_at": datetime.now()
})@app.task
async def analyze_file(
ctx: TaskContext, file_content: str, webhook_url: str = None
) -> dict:
# ... perform analysis ...
if webhook_url:
# Notify completion
await ctx.run(send_webhook, webhook_url, results)
return results@app.task
def parse_json_data(ctx: TaskContext, file_content: str) -> dict:
# Parse JSON files
pass
@app.task
def parse_excel_data(ctx: TaskContext, file_content: bytes) -> dict:
# Parse Excel files
passSolution: Set the RENDER_API_KEY environment variable in both services:
Solution: Set the WORKFLOW_SERVICE_SLUG in the API service:
Solution:
Solution:
File Size Limits: Current implementation loads entire file into memory
Task Timeout: Long-running analysis may timeout
Concurrent Requests: FastAPI handles concurrent requests well
Result Caching: Cache analysis results for identical files
Built with Render Workflows | Render.com
| Back | FazBrowse Home | New Git URL |