Files
HCFS/hcfs-python/hcfs/api/models.py
2025-07-30 09:34:16 +10:00

347 lines
11 KiB
Python

"""
Enhanced API Models for HCFS Production API.
Comprehensive Pydantic models for request/response validation,
API versioning, and enterprise-grade data validation.
"""
from typing import List, Optional, Dict, Any, Union
from datetime import datetime
from enum import Enum
from pydantic import BaseModel, Field, validator, ConfigDict
import uuid
class APIVersion(str, Enum):
"""API version enumeration."""
V1 = "v1"
V2 = "v2"
class SearchType(str, Enum):
"""Search type enumeration."""
SEMANTIC = "semantic"
HYBRID = "hybrid"
KEYWORD = "keyword"
SIMILARITY = "similarity"
class SortOrder(str, Enum):
"""Sort order enumeration."""
ASC = "asc"
DESC = "desc"
class ContextStatus(str, Enum):
"""Context status enumeration."""
ACTIVE = "active"
ARCHIVED = "archived"
DRAFT = "draft"
DELETED = "deleted"
# Base Models
class BaseResponse(BaseModel):
"""Base response model with metadata."""
model_config = ConfigDict(from_attributes=True)
success: bool = True
message: Optional[str] = None
timestamp: datetime = Field(default_factory=datetime.utcnow)
request_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
api_version: APIVersion = APIVersion.V1
class PaginationParams(BaseModel):
"""Pagination parameters."""
page: int = Field(default=1, ge=1, description="Page number (1-based)")
page_size: int = Field(default=20, ge=1, le=100, description="Items per page")
@property
def offset(self) -> int:
"""Calculate offset from page and page_size."""
return (self.page - 1) * self.page_size
class PaginationMeta(BaseModel):
"""Pagination metadata."""
page: int
page_size: int
total_items: int
total_pages: int
has_next: bool
has_previous: bool
# Context Models
class ContextBase(BaseModel):
"""Base context model with common fields."""
path: str = Field(..., description="Hierarchical path for the context")
content: str = Field(..., description="Main content of the context")
summary: Optional[str] = Field(None, description="Brief summary of the content")
author: Optional[str] = Field(None, description="Author or creator of the context")
tags: Optional[List[str]] = Field(default_factory=list, description="Tags associated with the context")
metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional metadata")
status: ContextStatus = Field(default=ContextStatus.ACTIVE, description="Context status")
@validator('path')
def validate_path(cls, v):
"""Validate path format."""
if not v.startswith('/'):
raise ValueError('Path must start with /')
if '//' in v:
raise ValueError('Path cannot contain double slashes')
return v
@validator('content')
def validate_content(cls, v):
"""Validate content is not empty."""
if not v.strip():
raise ValueError('Content cannot be empty')
return v.strip()
class ContextCreate(ContextBase):
"""Model for creating a new context."""
pass
class ContextUpdate(BaseModel):
"""Model for updating an existing context."""
content: Optional[str] = None
summary: Optional[str] = None
author: Optional[str] = None
tags: Optional[List[str]] = None
metadata: Optional[Dict[str, Any]] = None
status: Optional[ContextStatus] = None
@validator('content')
def validate_content(cls, v):
"""Validate content if provided."""
if v is not None and not v.strip():
raise ValueError('Content cannot be empty')
return v.strip() if v else v
class ContextResponse(ContextBase):
"""Model for context responses."""
id: int = Field(..., description="Unique context identifier")
created_at: datetime = Field(..., description="Creation timestamp")
updated_at: datetime = Field(..., description="Last update timestamp")
version: int = Field(..., description="Context version number")
embedding_model: Optional[str] = Field(None, description="Embedding model used")
similarity_score: Optional[float] = Field(None, description="Similarity score (for search results)")
class ContextListResponse(BaseResponse):
"""Response model for context list operations."""
data: List[ContextResponse]
pagination: PaginationMeta
class ContextDetailResponse(BaseResponse):
"""Response model for single context operations."""
data: ContextResponse
# Search Models
class SearchRequest(BaseModel):
"""Model for search requests."""
query: str = Field(..., description="Search query text")
search_type: SearchType = Field(default=SearchType.SEMANTIC, description="Type of search to perform")
path_prefix: Optional[str] = Field(None, description="Limit search to paths with this prefix")
top_k: int = Field(default=10, ge=1, le=100, description="Maximum number of results to return")
min_similarity: float = Field(default=0.0, ge=0.0, le=1.0, description="Minimum similarity threshold")
semantic_weight: float = Field(default=0.7, ge=0.0, le=1.0, description="Weight for semantic vs keyword search")
include_content: bool = Field(default=True, description="Whether to include full content in results")
filters: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional search filters")
@validator('query')
def validate_query(cls, v):
"""Validate query is not empty."""
if not v.strip():
raise ValueError('Query cannot be empty')
return v.strip()
class SearchResult(BaseModel):
"""Individual search result."""
context: ContextResponse
score: float = Field(..., description="Relevance score")
highlight: Optional[Dict[str, List[str]]] = Field(None, description="Highlighted matching text")
explanation: Optional[str] = Field(None, description="Explanation of why this result was returned")
class SearchResponse(BaseResponse):
"""Response model for search operations."""
data: List[SearchResult]
query: str
search_type: SearchType
total_results: int
search_time_ms: float
filters_applied: Dict[str, Any]
# Version Models
class VersionResponse(BaseModel):
"""Model for context version information."""
version_id: int
version_number: int
context_id: int
author: str
message: Optional[str]
created_at: datetime
content_hash: str
metadata: Optional[Dict[str, Any]] = None
class VersionListResponse(BaseResponse):
"""Response model for version history."""
data: List[VersionResponse]
context_id: int
total_versions: int
class VersionCreateRequest(BaseModel):
"""Request model for creating a new version."""
message: Optional[str] = Field(None, description="Version commit message")
metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Version metadata")
class RollbackRequest(BaseModel):
"""Request model for version rollback."""
target_version: int = Field(..., description="Target version number to rollback to")
message: Optional[str] = Field(None, description="Rollback commit message")
# Analytics Models
class ContextStats(BaseModel):
"""Context statistics model."""
total_contexts: int
contexts_by_status: Dict[ContextStatus, int]
contexts_by_author: Dict[str, int]
average_content_length: float
most_active_paths: List[Dict[str, Union[str, int]]]
recent_activity: List[Dict[str, Any]]
class SearchStats(BaseModel):
"""Search statistics model."""
total_searches: int
searches_by_type: Dict[SearchType, int]
average_response_time_ms: float
popular_queries: List[Dict[str, Union[str, int]]]
search_success_rate: float
class SystemStats(BaseModel):
"""System statistics model."""
uptime_seconds: float
memory_usage_mb: float
active_connections: int
cache_hit_rate: float
embedding_model_info: Dict[str, Any]
database_size_mb: float
class StatsResponse(BaseResponse):
"""Response model for statistics."""
context_stats: ContextStats
search_stats: SearchStats
system_stats: SystemStats
# Batch Operations Models
class BatchContextCreate(BaseModel):
"""Model for batch context creation."""
contexts: List[ContextCreate] = Field(..., max_items=100, description="List of contexts to create")
@validator('contexts')
def validate_contexts_not_empty(cls, v):
"""Validate contexts list is not empty."""
if not v:
raise ValueError('Contexts list cannot be empty')
return v
class BatchOperationResult(BaseModel):
"""Result of batch operation."""
success_count: int
error_count: int
total_items: int
errors: List[Dict[str, Any]] = Field(default_factory=list)
created_ids: List[int] = Field(default_factory=list)
class BatchResponse(BaseResponse):
"""Response model for batch operations."""
data: BatchOperationResult
# WebSocket Models
class WebSocketMessage(BaseModel):
"""WebSocket message model."""
type: str = Field(..., description="Message type")
data: Dict[str, Any] = Field(..., description="Message data")
timestamp: datetime = Field(default_factory=datetime.utcnow)
message_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
class SubscriptionRequest(BaseModel):
"""WebSocket subscription request."""
path_prefix: str = Field(..., description="Path prefix to subscribe to")
event_types: List[str] = Field(default_factory=lambda: ["created", "updated", "deleted"])
filters: Optional[Dict[str, Any]] = Field(default_factory=dict)
# Health Check Models
class HealthStatus(str, Enum):
"""Health status enumeration."""
HEALTHY = "healthy"
DEGRADED = "degraded"
UNHEALTHY = "unhealthy"
class ComponentHealth(BaseModel):
"""Individual component health."""
name: str
status: HealthStatus
response_time_ms: Optional[float] = None
error_message: Optional[str] = None
last_check: datetime
class HealthResponse(BaseModel):
"""System health response."""
status: HealthStatus
timestamp: datetime = Field(default_factory=datetime.utcnow)
version: str
uptime_seconds: float
components: List[ComponentHealth]
# Error Models
class ErrorDetail(BaseModel):
"""Detailed error information."""
field: Optional[str] = None
message: str
error_code: Optional[str] = None
class ErrorResponse(BaseModel):
"""Error response model."""
success: bool = False
error: str
error_details: Optional[List[ErrorDetail]] = None
timestamp: datetime = Field(default_factory=datetime.utcnow)
request_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
api_version: APIVersion = APIVersion.V1