Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 270 additions & 0 deletions docs/guide/bulk-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,274 @@ total_successful = sum(len(r.successful) for r in all_results)
total_failed = sum(len(r.failed) for r in all_results)
```

## Async Bulk Operations

The Bloomy SDK provides async versions of all bulk operations, enabling concurrent execution for significantly better performance when processing multiple items.

### Benefits of Async Bulk Operations

- **Concurrent Execution**: Process multiple items simultaneously instead of sequentially
- **Better Performance**: Reduce total execution time by up to 80% for large batches
- **Rate Limit Control**: Use `max_concurrent` parameter to control request parallelism
- **Same Error Handling**: Uses the same `BulkCreateResult` structure as sync operations

### Async Todos Example

```python
import asyncio
from bloomy import AsyncClient
from datetime import datetime, timedelta

async def create_todos_async():
async with AsyncClient(api_key="your-api-key") as client:
# Prepare todos
todos = [
{
"meeting_id": 123,
"title": f"Task {i}",
"due_date": (datetime.now() + timedelta(days=i)).date().isoformat()
}
for i in range(1, 21) # Create 20 todos
]

# Create with controlled concurrency
result = await client.todo.create_many(todos, max_concurrent=10)

print(f"Created {len(result.successful)} todos concurrently")
if result.failed:
print(f"Failed: {len(result.failed)}")

# Run the async function
asyncio.run(create_todos_async())
```

### Async Issues Example

```python
async def bulk_create_issues():
async with AsyncClient(api_key="your-api-key") as client:
issues = [
{
"meeting_id": 123,
"title": f"Issue {i}: Performance concern",
"notes": f"Details about issue {i}"
}
for i in range(1, 11)
]

# Higher concurrency for smaller payloads
result = await client.issue.create_many(issues, max_concurrent=15)

# Process results
for issue in result.successful:
print(f"Created issue: {issue.title} (ID: {issue.id})")
```

### Async Goals Example

```python
async def create_quarterly_goals():
async with AsyncClient(api_key="your-api-key") as client:
# Different goals for team members
team_goals = []
for user_id in [456, 789, 101, 112]:
team_goals.extend([
{
"meeting_id": 123,
"title": f"Q1 Goal for User {user_id}",
"user_id": user_id
}
])

# Conservative concurrency for complex operations
result = await client.goal.create_many(team_goals, max_concurrent=5)

return result
```

### Async Meetings Example

```python
async def setup_recurring_meetings():
async with AsyncClient(api_key="your-api-key") as client:
# Create a month of weekly meetings
meetings = [
{
"title": f"Week {week} Team Sync",
"attendees": [456, 789, 321]
}
for week in range(1, 5)
]

# Create meetings concurrently
create_result = await client.meeting.create_many(meetings, max_concurrent=5)

# Then fetch all details concurrently
meeting_ids = [m['id'] for m in create_result.successful]
details_result = await client.meeting.get_many(meeting_ids, max_concurrent=10)

return details_result.successful
```

### Controlling Concurrency with max_concurrent

The `max_concurrent` parameter controls how many requests can be in flight simultaneously:

```python
async def demonstrate_concurrency_control():
async with AsyncClient(api_key="your-api-key") as client:
todos = [{"title": f"Todo {i}", "meeting_id": 123} for i in range(100)]

# Conservative: 3 concurrent requests (slower but safer)
result_conservative = await client.todo.create_many(todos, max_concurrent=3)

# Moderate: 10 concurrent requests (balanced)
result_moderate = await client.todo.create_many(todos, max_concurrent=10)

# Aggressive: 20 concurrent requests (faster but may hit rate limits)
result_aggressive = await client.todo.create_many(todos, max_concurrent=20)
```

!!! tip "Choosing max_concurrent"
- **Small payloads** (todos, issues): 10-20 concurrent requests
- **Complex operations** (meetings with attendees): 5-10 concurrent requests
- **Rate-limited environments**: 3-5 concurrent requests
- **Default value**: 5 (conservative and safe)

### Error Handling with Async Bulk Operations

Error handling works the same as sync operations but with async/await syntax:

```python
async def robust_bulk_create():
async with AsyncClient(api_key="your-api-key") as client:
todos = [
{"title": "Valid todo", "meeting_id": 123},
{"title": "Missing meeting_id"}, # Will fail
{"title": "Another valid todo", "meeting_id": 123}
]

result = await client.todo.create_many(todos)

# Handle successes
successful_ids = [todo.id for todo in result.successful]
print(f"Successfully created todos: {successful_ids}")

# Handle failures
for failure in result.failed:
print(f"Failed at index {failure.index}: {failure.error}")
print(f"Failed data: {failure.input_data}")

# Optionally retry with corrected data
if "meeting_id" in failure.error:
corrected = {**failure.input_data, "meeting_id": 123}
retry_result = await client.todo.create(**corrected)
```

### Performance Comparison: Async vs Sync

Here's a practical example comparing async and sync performance:

```python
import asyncio
import time
from bloomy import Client, AsyncClient

def sync_bulk_create(todos_data):
"""Synchronous bulk creation."""
start = time.time()

with Client(api_key="your-api-key") as client:
result = client.todo.create_many(todos_data)

duration = time.time() - start
return result, duration

async def async_bulk_create(todos_data, max_concurrent=10):
"""Asynchronous bulk creation."""
start = time.time()

async with AsyncClient(api_key="your-api-key") as client:
result = await client.todo.create_many(todos_data, max_concurrent=max_concurrent)

duration = time.time() - start
return result, duration

# Compare performance
async def performance_comparison():
# Create test data
todos_data = [
{"title": f"Todo {i}", "meeting_id": 123}
for i in range(50)
]

# Run sync version
sync_result, sync_time = sync_bulk_create(todos_data)

# Run async version
async_result, async_time = await async_bulk_create(todos_data)

print(f"Sync version: {sync_time:.2f} seconds")
print(f"Async version: {async_time:.2f} seconds")
print(f"Speed improvement: {(sync_time / async_time - 1) * 100:.0f}%")

# Run comparison
asyncio.run(performance_comparison())
```

!!! note "Typical Performance Gains"
- 10 items: 50-60% faster with async
- 50 items: 70-80% faster with async
- 100+ items: 80-85% faster with async (diminishing returns due to rate limiting)

### Combining Multiple Async Bulk Operations

```python
async def complete_meeting_setup():
"""Set up a complete meeting with all components."""
async with AsyncClient(api_key="your-api-key") as client:
# Create meeting first
meeting_result = await client.meeting.create_many([
{"title": "Q1 Planning Session", "attendees": [456, 789]}
])

if not meeting_result.successful:
return None

meeting_id = meeting_result.successful[0]['id']

# Create all meeting components concurrently
todos_task = client.todo.create_many([
{"title": "Prepare Q1 roadmap", "meeting_id": meeting_id},
{"title": "Review budget allocation", "meeting_id": meeting_id}
])

issues_task = client.issue.create_many([
{"title": "Resource constraints", "meeting_id": meeting_id},
{"title": "Timeline concerns", "meeting_id": meeting_id}
])

goals_task = client.goal.create_many([
{"title": "Complete product launch", "meeting_id": meeting_id},
{"title": "Achieve 20% growth", "meeting_id": meeting_id}
])

# Wait for all operations to complete
todos_result, issues_result, goals_result = await asyncio.gather(
todos_task, issues_task, goals_task
)

print(f"Meeting {meeting_id} setup complete:")
print(f" - Todos: {len(todos_result.successful)}")
print(f" - Issues: {len(issues_result.successful)}")
print(f" - Goals: {len(goals_result.successful)}")

return meeting_id

# Run the setup
asyncio.run(complete_meeting_setup())
```

## Best Practices

1. **Validate Input Data**: Check required fields before bulk operations
Expand All @@ -397,6 +665,8 @@ total_failed = sum(len(r.failed) for r in all_results)
4. **Use Appropriate Chunk Sizes**: Balance between efficiency and rate limits
5. **Implement Retry Logic**: For transient failures, consider retrying
6. **Monitor Rate Limits**: Watch for 429 errors and adjust accordingly
7. **Choose Async When Appropriate**: Use async for better performance with multiple items
8. **Control Concurrency**: Adjust `max_concurrent` based on your use case and API limits

## Next Steps

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "bloomy-python"
version = "0.17.0"
version = "0.18.0"
description = "Python SDK for Bloom Growth API"
readme = "README.md"
authors = [{ name = "Franccesco Orozco", email = "franccesco@codingdose.info" }]
Expand Down
Loading