347 lines
11 KiB
Python
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 |