Phase 2 build initial
This commit is contained in:
347
hcfs-python/hcfs/api/models.py
Normal file
347
hcfs-python/hcfs/api/models.py
Normal file
@@ -0,0 +1,347 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user