Bulk Operations API¶
The Bulk Operations API provides a REST-friendly foundation for performing bulk operations on Gira tickets. It's designed to enable TUI and web interface development with consistent patterns, structured error handling, and async operation support.
Overview¶
The API provides: - Standardized request/response formats across all bulk operations - Pre-flight validation with detailed error messages - Operation status tracking with unique operation IDs - Structured error responses with field-level details - JSON schema support for request/response validation - Async operation support for long-running operations - Transaction support for atomic operations
Core Components¶
BulkOperationManager¶
Base class for all bulk operation managers. Provides: - Request validation - Operation tracking - Error handling - Response formatting - Async/sync execution modes
ValidationEngine¶
Validates requests before execution: - Field validation (enums, required fields) - Ticket existence checks - Circular dependency detection - Custom validation rules
OperationTracker¶
Tracks operation status and progress: - Real-time progress updates - Operation history - Cancellation support - TTL-based cleanup
ResponseFormatter¶
Ensures consistent JSON responses: - Standard response structure - Error formatting - CLI-friendly output - Metadata inclusion
ErrorHandler¶
Provides structured error handling: - Error categorization - Field-level error details - Error collection and limiting - Custom error mappings
Request Format¶
All bulk operations follow this standard request format:
{
"operation_id": "uuid", // Optional, generated if not provided
"operation_type": "bulk_update",
"items": [
{
"id": "GCM-1",
// Operation-specific fields
}
],
"options": {
"dry_run": false,
"validate_only": false,
"all_or_nothing": false,
"use_transaction": false,
"skip_invalid": false,
"validation_level": "basic" // none, basic, strict
}
}
Response Format¶
Standard response format for all operations:
{
"operation_id": "uuid",
"operation_type": "bulk_update",
"status": "completed", // pending, validating, in_progress, completed, failed, partially_completed
"progress": {
"completed": 5,
"total": 10,
"percentage": 50.0,
"current_item": "GCM-3",
"estimated_time_remaining": 30 // seconds
},
"results": {
"successful": [
{
"item_id": "GCM-1",
"status": "success",
"changes": {
"status": {
"old": "todo",
"new": "in_progress"
}
}
}
],
"failed": [
{
"item_id": "GCM-2",
"status": "failed",
"error": {
"code": "TICKET_NOT_FOUND",
"message": "Ticket GCM-2 not found",
"details": {"item_id": "GCM-2"}
}
}
],
"skipped": []
},
"errors": [], // Global errors
"metadata": {
"created_at": "2025-01-29T10:00:00Z",
"updated_at": "2025-01-29T10:00:30Z",
"duration_ms": 30000
}
}
Operation Types¶
Bulk Update¶
Update multiple tickets with the same or different changes.
from gira.api.bulk.update import create_bulk_update_api
from gira.api.bulk.schemas import BulkOperationRequest, BulkUpdateItem
# Create API manager
api = create_bulk_update_api()
# Create request
request = BulkOperationRequest(
operation_type="bulk_update",
items=[
BulkUpdateItem(id="GCM-1", status="in_progress"),
BulkUpdateItem(id="GCM-2", status="done", assignee="john")
],
options={"use_transaction": True}
)
# Execute synchronously
response = api.execute(request)
# Or execute asynchronously
operation_id = api.execute(request, async_mode=True)
Bulk Add Dependencies¶
Add dependencies to multiple tickets.
from gira.api.bulk.dependencies import create_bulk_add_deps_api
from gira.api.bulk.schemas import BulkOperationRequest, BulkDependencyItem
api = create_bulk_add_deps_api()
request = BulkOperationRequest(
operation_type="bulk_add_deps",
items=[
BulkDependencyItem(id="GCM-1", dependency_id="GCM-3"),
BulkDependencyItem(id="GCM-2", dependencies=["GCM-3", "GCM-4"])
]
)
response = api.execute(request)
Bulk Remove Dependencies¶
Remove dependencies from multiple tickets.
from gira.api.bulk.dependencies import create_bulk_remove_deps_api
api = create_bulk_remove_deps_api()
request = BulkOperationRequest(
operation_type="bulk_remove_deps",
items=[
{"id": "GCM-1", "dependency_id": "GCM-3"},
{"id": "GCM-2", "remove_all": true}
]
)
response = api.execute(request)
Bulk Clear Dependencies¶
Clear all dependencies from tickets.
from gira.api.bulk.dependencies import create_bulk_clear_deps_api
api = create_bulk_clear_deps_api()
request = BulkOperationRequest(
operation_type="bulk_clear_deps",
items=[
{"id": "GCM-1"},
{"id": "GCM-2"}
]
)
response = api.execute(request)
Validation¶
Validation Levels¶
- none: No validation, execute immediately
- basic: Validate required fields and basic constraints
- strict: Full validation including field values and relationships
Pre-flight Validation¶
# Validate without executing
request.options.validate_only = True
response = api.execute(request)
# Check validation result
if response.status == "failed":
for error in response.errors:
print(f"{error.code}: {error.message}")
Custom Validation Rules¶
from gira.api.bulk.validation import ValidationRule, ValidationEngine
class CustomRule(ValidationRule):
def validate(self, value, context=None):
if not self.is_valid(value):
return OperationError(
code="CUSTOM_ERROR",
message="Custom validation failed"
)
return None
# Add to validation engine
validator = ValidationEngine()
validator.custom_rules.append(CustomRule())
Error Handling¶
Error Codes¶
VALIDATION_ERROR
: Request validation failedTICKET_NOT_FOUND
: Referenced ticket doesn't existCIRCULAR_DEPENDENCY
: Would create circular dependencyOPERATION_NOT_ALLOWED
: Operation not permittedTRANSACTION_ERROR
: Transaction failedINTERNAL_ERROR
: Unexpected error
Error Response¶
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid status value",
"field": "status",
"details": {
"allowed_values": ["todo", "in_progress", "done"],
"provided_value": "invalid"
},
"error_id": "uuid"
}
}
Async Operations¶
Starting Async Operation¶
# Start async operation
operation_id = api.execute(request, async_mode=True)
print(f"Operation started: {operation_id}")
# Check status
status_response = api.get_status(operation_id)
print(f"Progress: {status_response.progress.percentage}%")
# Cancel if needed
cancelled = api.cancel_operation(operation_id)
Progress Callbacks¶
# Configure webhook for progress updates
request.options.progress_callback = "https://example.com/webhook"
# The API will POST progress updates to the webhook
Transaction Support¶
Atomic Operations¶
# Enable transactions for all-or-nothing execution
request.options.use_transaction = True
request.options.all_or_nothing = True
response = api.execute(request)
# If any item fails, all changes are rolled back
Transaction Metadata¶
Integration Examples¶
Web API Integration¶
from flask import Flask, request, jsonify
from gira.api.bulk import BulkOperationManager
from gira.api.bulk.update import create_bulk_update_api
app = Flask(__name__)
bulk_update_api = create_bulk_update_api()
@app.route('/api/bulk/update', methods=['POST'])
def bulk_update():
try:
# Parse request
bulk_request = BulkOperationRequest(**request.json)
# Execute operation
response = bulk_update_api.execute(bulk_request)
# Return response
return jsonify(response.model_dump()), 200
except ValidationError as e:
return jsonify(e.to_dict()), 400
except Exception as e:
return jsonify({"error": str(e)}), 500
TUI Integration¶
from textual.app import App
from gira.api.bulk import BulkOperationManager
class BulkOperationTUI(App):
def __init__(self):
super().__init__()
self.bulk_api = create_bulk_update_api()
self.current_operation = None
async def perform_bulk_update(self, items):
# Start async operation
request = BulkOperationRequest(
operation_type="bulk_update",
items=items
)
self.current_operation = self.bulk_api.execute(
request,
async_mode=True
)
# Poll for updates
while True:
status = self.bulk_api.get_status(self.current_operation)
self.update_progress_bar(status.progress)
if status.is_complete:
self.show_results(status)
break
await asyncio.sleep(0.5)
CLI Integration¶
import typer
from gira.api.bulk.schemas import BulkOperationRequest
from gira.api.bulk.update import create_bulk_update_api
app = typer.Typer()
@app.command()
def bulk_update(
items_file: Path,
dry_run: bool = False,
transaction: bool = False
):
"""Execute bulk update from JSON file."""
# Load items
with open(items_file) as f:
items = json.load(f)
# Create API and request
api = create_bulk_update_api()
request = BulkOperationRequest(
operation_type="bulk_update",
items=items,
options={
"dry_run": dry_run,
"use_transaction": transaction
}
)
# Execute and display results
response = api.execute(request)
# Use CLI formatter
output = api.response_formatter.format_cli_output(response)
typer.echo(output)
Best Practices¶
-
Use transactions for critical operations
-
Implement progress tracking for large operations
-
Handle partial failures gracefully
-
Validate before executing
-
Use appropriate validation levels
Performance Considerations¶
- Batch Size: Optimal batch size is 100-500 items
- Async Threshold: Use async mode for >50 items
- Transaction Overhead: ~10% performance impact
- Validation Cost: Strict validation adds ~20% overhead
Future Enhancements¶
- WebSocket support for real-time progress
- Batch scheduling and queuing
- Distributed execution across workers
- Result caching and deduplication
- Audit logging and history