xml-pipeline/docs/bloxserver-api-contract/models.py
dullfig d682c677db Rename Nextra → BloxServer (OpenBlox.ai)
- Rename all nextra-* files to bloxserver-*
- Replace all "Nextra" references with "BloxServer"
- Update copyright year to 2026
- Add domain: OpenBlox.ai

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

475 lines
12 KiB
Python

"""
BloxServer API Contract - Pydantic Models
These models define the API contract between frontend and backend.
They match the TypeScript types in types.ts.
Usage:
from bloxserver.api.models import Flow, CreateFlowRequest
"""
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, Field
# =============================================================================
# Common Types
# =============================================================================
T = TypeVar("T")
class PaginatedResponse(BaseModel, Generic[T]):
"""Paginated list response."""
items: list[T]
total: int
page: int
page_size: int = Field(alias="pageSize")
has_more: bool = Field(alias="hasMore")
class Config:
populate_by_name = True
class ApiError(BaseModel):
"""API error response."""
code: str
message: str
details: dict[str, Any] | None = None
# =============================================================================
# User (synced from Clerk)
# =============================================================================
class Tier(str, Enum):
FREE = "free"
PAID = "paid"
PRO = "pro"
ENTERPRISE = "enterprise"
class User(BaseModel):
"""User account (synced from Clerk)."""
id: UUID
clerk_id: str = Field(alias="clerkId")
email: str
name: str | None = None
avatar_url: str | None = Field(default=None, alias="avatarUrl")
tier: Tier = Tier.FREE
created_at: datetime = Field(alias="createdAt")
class Config:
populate_by_name = True
# =============================================================================
# Flows
# =============================================================================
class FlowStatus(str, Enum):
STOPPED = "stopped"
STARTING = "starting"
RUNNING = "running"
STOPPING = "stopping"
ERROR = "error"
class CanvasNode(BaseModel):
"""A node in the React Flow canvas."""
id: str
type: str
position: dict[str, float]
data: dict[str, Any]
class CanvasEdge(BaseModel):
"""An edge connecting nodes in the canvas."""
id: str
source: str
target: str
source_handle: str | None = Field(default=None, alias="sourceHandle")
target_handle: str | None = Field(default=None, alias="targetHandle")
class Config:
populate_by_name = True
class CanvasState(BaseModel):
"""React Flow canvas state."""
nodes: list[CanvasNode]
edges: list[CanvasEdge]
viewport: dict[str, float]
class Flow(BaseModel):
"""A user's workflow/flow."""
id: UUID
user_id: UUID = Field(alias="userId")
name: str
description: str | None = None
organism_yaml: str = Field(alias="organismYaml")
canvas_state: CanvasState | None = Field(default=None, alias="canvasState")
status: FlowStatus = FlowStatus.STOPPED
container_id: str | None = Field(default=None, alias="containerId")
error_message: str | None = Field(default=None, alias="errorMessage")
created_at: datetime = Field(alias="createdAt")
updated_at: datetime = Field(alias="updatedAt")
class Config:
populate_by_name = True
class FlowSummary(BaseModel):
"""Abbreviated flow for list views."""
id: UUID
name: str
description: str | None = None
status: FlowStatus
updated_at: datetime = Field(alias="updatedAt")
class Config:
populate_by_name = True
class CreateFlowRequest(BaseModel):
"""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 = Field(default=None, alias="organismYaml")
class Config:
populate_by_name = True
class UpdateFlowRequest(BaseModel):
"""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 = Field(default=None, alias="organismYaml")
canvas_state: CanvasState | None = Field(default=None, alias="canvasState")
class Config:
populate_by_name = True
# =============================================================================
# Triggers
# =============================================================================
class TriggerType(str, Enum):
WEBHOOK = "webhook"
SCHEDULE = "schedule"
MANUAL = "manual"
class WebhookTriggerConfig(BaseModel):
"""Config for webhook triggers."""
type: Literal["webhook"] = "webhook"
class ScheduleTriggerConfig(BaseModel):
"""Config for scheduled triggers."""
type: Literal["schedule"] = "schedule"
cron: str = Field(description="Cron expression")
timezone: str = "UTC"
class ManualTriggerConfig(BaseModel):
"""Config for manual triggers."""
type: Literal["manual"] = "manual"
TriggerConfig = WebhookTriggerConfig | ScheduleTriggerConfig | ManualTriggerConfig
class Trigger(BaseModel):
"""A trigger that can start a flow."""
id: UUID
flow_id: UUID = Field(alias="flowId")
type: TriggerType
name: str
config: TriggerConfig
webhook_token: str | None = Field(default=None, alias="webhookToken")
webhook_url: str | None = Field(default=None, alias="webhookUrl")
created_at: datetime = Field(alias="createdAt")
class Config:
populate_by_name = True
class CreateTriggerRequest(BaseModel):
"""Request to create a trigger."""
type: TriggerType
name: str = Field(min_length=1, max_length=100)
config: TriggerConfig
# =============================================================================
# Executions
# =============================================================================
class ExecutionStatus(str, Enum):
RUNNING = "running"
SUCCESS = "success"
ERROR = "error"
TIMEOUT = "timeout"
class Execution(BaseModel):
"""A single execution/run of a flow."""
id: UUID
flow_id: UUID = Field(alias="flowId")
trigger_id: UUID | None = Field(default=None, alias="triggerId")
trigger_type: TriggerType = Field(alias="triggerType")
status: ExecutionStatus
started_at: datetime = Field(alias="startedAt")
completed_at: datetime | None = Field(default=None, alias="completedAt")
duration_ms: int | None = Field(default=None, alias="durationMs")
error_message: str | None = Field(default=None, alias="errorMessage")
input_payload: str | None = Field(default=None, alias="inputPayload")
output_payload: str | None = Field(default=None, alias="outputPayload")
class Config:
populate_by_name = True
class ExecutionSummary(BaseModel):
"""Abbreviated execution for list views."""
id: UUID
status: ExecutionStatus
trigger_type: TriggerType = Field(alias="triggerType")
started_at: datetime = Field(alias="startedAt")
duration_ms: int | None = Field(default=None, alias="durationMs")
class Config:
populate_by_name = True
# =============================================================================
# WASM Modules (Pro+)
# =============================================================================
class WasmModule(BaseModel):
"""A custom WASM module."""
id: UUID
user_id: UUID = Field(alias="userId")
name: str
description: str | None = None
wit_interface: str | None = Field(default=None, alias="witInterface")
size_bytes: int = Field(alias="sizeBytes")
created_at: datetime = Field(alias="createdAt")
updated_at: datetime = Field(alias="updatedAt")
class Config:
populate_by_name = True
class WasmModuleSummary(BaseModel):
"""Abbreviated module for list views."""
id: UUID
name: str
description: str | None = None
created_at: datetime = Field(alias="createdAt")
class Config:
populate_by_name = True
class CreateWasmModuleRequest(BaseModel):
"""Request to create a WASM module (file uploaded separately)."""
name: str = Field(min_length=1, max_length=100)
description: str | None = Field(default=None, max_length=500)
wit_interface: str | None = Field(default=None, alias="witInterface")
class Config:
populate_by_name = True
# =============================================================================
# Marketplace
# =============================================================================
class MarketplaceListingType(str, Enum):
TOOL = "tool"
FLOW = "flow"
class ToolContent(BaseModel):
"""Content for a tool listing."""
type: Literal["tool"] = "tool"
wasm_module_id: UUID = Field(alias="wasmModuleId")
wit_interface: str = Field(alias="witInterface")
example_usage: str = Field(alias="exampleUsage")
class Config:
populate_by_name = True
class FlowTemplateContent(BaseModel):
"""Content for a flow template listing."""
type: Literal["flow"] = "flow"
organism_yaml: str = Field(alias="organismYaml")
canvas_state: CanvasState = Field(alias="canvasState")
class Config:
populate_by_name = True
MarketplaceContent = ToolContent | FlowTemplateContent
class MarketplaceListing(BaseModel):
"""A marketplace listing (tool or flow template)."""
id: UUID
author_id: UUID = Field(alias="authorId")
author_name: str = Field(alias="authorName")
type: MarketplaceListingType
name: str
description: str
category: str
tags: list[str]
downloads: int = 0
rating: float | None = None
content: MarketplaceContent
created_at: datetime = Field(alias="createdAt")
updated_at: datetime = Field(alias="updatedAt")
class Config:
populate_by_name = True
class MarketplaceListingSummary(BaseModel):
"""Abbreviated listing for browse views."""
id: UUID
author_name: str = Field(alias="authorName")
type: MarketplaceListingType
name: str
description: str
category: str
downloads: int
rating: float | None = None
class Config:
populate_by_name = True
class PublishToMarketplaceRequest(BaseModel):
"""Request to publish to marketplace."""
type: MarketplaceListingType
name: str = Field(min_length=1, max_length=100)
description: str = Field(min_length=10, max_length=2000)
category: str
tags: list[str] = Field(default_factory=list, max_length=10)
content: MarketplaceContent
# =============================================================================
# Project Memory (Pro+)
# =============================================================================
class ProjectMemory(BaseModel):
"""Project memory status for a flow."""
flow_id: UUID = Field(alias="flowId")
enabled: bool = False
used_bytes: int = Field(default=0, alias="usedBytes")
max_bytes: int = Field(alias="maxBytes")
keys: list[str] = Field(default_factory=list)
class Config:
populate_by_name = True
class MemoryEntry(BaseModel):
"""A single memory entry."""
key: str
value: str # JSON string
size_bytes: int = Field(alias="sizeBytes")
updated_at: datetime = Field(alias="updatedAt")
class Config:
populate_by_name = True
# =============================================================================
# Logs
# =============================================================================
class LogLevel(str, Enum):
DEBUG = "debug"
INFO = "info"
WARNING = "warning"
ERROR = "error"
class LogEntry(BaseModel):
"""A log entry from a running flow."""
timestamp: datetime
level: LogLevel
message: str
metadata: dict[str, Any] | None = None
# =============================================================================
# Stats
# =============================================================================
class FlowStats(BaseModel):
"""Statistics for a single flow."""
flow_id: UUID = Field(alias="flowId")
executions_total: int = Field(alias="executionsTotal")
executions_success: int = Field(alias="executionsSuccess")
executions_error: int = Field(alias="executionsError")
avg_duration_ms: float = Field(alias="avgDurationMs")
last_executed_at: datetime | None = Field(default=None, alias="lastExecutedAt")
class Config:
populate_by_name = True
class UsageStats(BaseModel):
"""Usage statistics for billing/limits."""
user_id: UUID = Field(alias="userId")
period: Literal["day", "month"]
flow_count: int = Field(alias="flowCount")
execution_count: int = Field(alias="executionCount")
execution_limit: int = Field(alias="executionLimit")
class Config:
populate_by_name = True