xml-pipeline/bloxserver/api/schemas.py
dullfig a5c00c1e90 Add BloxServer API scaffold + architecture docs
BloxServer API (FastAPI + SQLAlchemy async):
- Database models: users, flows, triggers, executions, usage tracking
- Clerk JWT auth with dev mode bypass for local testing
- SQLite support for local dev, PostgreSQL for production
- CRUD routes for flows, triggers, executions
- Public webhook endpoint with token auth
- Health/readiness endpoints
- Pydantic schemas with camelCase aliases for frontend
- Docker + docker-compose setup

Architecture documentation:
- Librarian architecture with RLM-powered query engine
- Stripe billing integration (usage-based, trials, webhooks)
- LLM abstraction layer (rate limiting, semantic cache, failover)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 22:04:25 -08:00

322 lines
7.7 KiB
Python

"""
Pydantic schemas for API request/response validation.
These match the TypeScript types in types.ts for frontend compatibility.
Uses camelCase aliases for JSON serialization.
"""
from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import Any, Generic, Literal, TypeVar
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field
# =============================================================================
# Config for camelCase serialization
# =============================================================================
def to_camel(string: str) -> str:
"""Convert snake_case to camelCase."""
components = string.split("_")
return components[0] + "".join(x.title() for x in components[1:])
class CamelModel(BaseModel):
"""Base model with camelCase JSON serialization."""
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True,
from_attributes=True,
)
# =============================================================================
# Common Types
# =============================================================================
T = TypeVar("T")
class PaginatedResponse(CamelModel, Generic[T]):
"""Paginated list response."""
items: list[T]
total: int
page: int
page_size: int
has_more: bool
class ApiError(CamelModel):
"""API error response."""
code: str
message: str
details: dict[str, Any] | None = None
# =============================================================================
# Enums
# =============================================================================
class Tier(str, Enum):
FREE = "free"
PRO = "pro"
ENTERPRISE = "enterprise"
HIGH_FREQUENCY = "high_frequency"
class FlowStatus(str, Enum):
STOPPED = "stopped"
STARTING = "starting"
RUNNING = "running"
STOPPING = "stopping"
ERROR = "error"
class TriggerType(str, Enum):
WEBHOOK = "webhook"
SCHEDULE = "schedule"
MANUAL = "manual"
class ExecutionStatus(str, Enum):
RUNNING = "running"
SUCCESS = "success"
ERROR = "error"
TIMEOUT = "timeout"
# =============================================================================
# User
# =============================================================================
class User(CamelModel):
"""User account (synced from Clerk)."""
id: UUID
clerk_id: str
email: str
name: str | None = None
avatar_url: str | None = None
tier: Tier = Tier.FREE
created_at: datetime
# =============================================================================
# Canvas State (React Flow)
# =============================================================================
class CanvasNode(CamelModel):
"""A node in the React Flow canvas."""
id: str
type: str
position: dict[str, float]
data: dict[str, Any]
class CanvasEdge(CamelModel):
"""An edge connecting nodes in the canvas."""
id: str
source: str
target: str
source_handle: str | None = None
target_handle: str | None = None
class CanvasState(CamelModel):
"""React Flow canvas state."""
nodes: list[CanvasNode]
edges: list[CanvasEdge]
viewport: dict[str, float]
# =============================================================================
# Flows
# =============================================================================
class Flow(CamelModel):
"""A user's workflow/flow."""
id: UUID
user_id: UUID
name: str
description: str | None = None
organism_yaml: str
canvas_state: CanvasState | None = None
status: FlowStatus = FlowStatus.STOPPED
container_id: str | None = None
error_message: str | None = None
created_at: datetime
updated_at: datetime
class FlowSummary(CamelModel):
"""Abbreviated flow for list views."""
id: UUID
name: str
description: str | None = None
status: FlowStatus
updated_at: datetime
class CreateFlowRequest(CamelModel):
"""Request to create a new flow."""
name: str = Field(min_length=1, max_length=100)
description: str | None = Field(default=None, max_length=500)
organism_yaml: str | None = None
class UpdateFlowRequest(CamelModel):
"""Request to update a flow."""
name: str | None = Field(default=None, min_length=1, max_length=100)
description: str | None = Field(default=None, max_length=500)
organism_yaml: str | None = None
canvas_state: CanvasState | None = None
# =============================================================================
# Triggers
# =============================================================================
class WebhookTriggerConfig(CamelModel):
"""Config for webhook triggers."""
type: Literal["webhook"] = "webhook"
class ScheduleTriggerConfig(CamelModel):
"""Config for scheduled triggers."""
type: Literal["schedule"] = "schedule"
cron: str = Field(description="Cron expression")
timezone: str = "UTC"
class ManualTriggerConfig(CamelModel):
"""Config for manual triggers."""
type: Literal["manual"] = "manual"
TriggerConfig = WebhookTriggerConfig | ScheduleTriggerConfig | ManualTriggerConfig
class Trigger(CamelModel):
"""A trigger that can start a flow."""
id: UUID
flow_id: UUID
type: TriggerType
name: str
config: dict[str, Any]
webhook_token: str | None = None
webhook_url: str | None = None
created_at: datetime
class CreateTriggerRequest(CamelModel):
"""Request to create a trigger."""
type: TriggerType
name: str = Field(min_length=1, max_length=100)
config: dict[str, Any]
# =============================================================================
# Executions
# =============================================================================
class Execution(CamelModel):
"""A single execution/run of a flow."""
id: UUID
flow_id: UUID
trigger_id: UUID | None = None
trigger_type: TriggerType
status: ExecutionStatus
started_at: datetime
completed_at: datetime | None = None
duration_ms: int | None = None
error_message: str | None = None
input_payload: str | None = None
output_payload: str | None = None
class ExecutionSummary(CamelModel):
"""Abbreviated execution for list views."""
id: UUID
status: ExecutionStatus
trigger_type: TriggerType
started_at: datetime
duration_ms: int | None = None
# =============================================================================
# Usage & Stats
# =============================================================================
class UsageDashboard(CamelModel):
"""Current usage for user dashboard."""
period_start: datetime
period_end: datetime | None
runs_used: int
runs_limit: int
runs_percentage: float
tokens_used: int
estimated_overage: float
days_remaining: int
class FlowStats(CamelModel):
"""Statistics for a single flow."""
flow_id: UUID
executions_total: int
executions_success: int
executions_error: int
avg_duration_ms: float
last_executed_at: datetime | None = None
# =============================================================================
# API Keys (BYOK)
# =============================================================================
class ApiKeyInfo(CamelModel):
"""Info about a stored API key (never exposes the key itself)."""
provider: str
key_hint: str | None # Last few chars: "...abc123"
is_valid: bool
last_used_at: datetime | None
created_at: datetime
class AddApiKeyRequest(CamelModel):
"""Request to add a user's API key."""
provider: str = Field(description="Provider name: openai, anthropic, xai")
api_key: str = Field(min_length=10, description="The API key")