""" Pydantic models for scientific-surfing data structures. """ from datetime import datetime from typing import Dict, List, Optional from pydantic import BaseModel, Field, validator class Subscription(BaseModel): """Model for a single subscription.""" name: str = Field(..., description="Name of the subscription") url: str = Field(..., description="Clash RSS subscription URL") status: str = Field(default="inactive", description="Status: active or inactive") last_refresh: Optional[datetime] = Field(default=None, description="Last refresh timestamp") file_path: Optional[str] = Field(default=None, description="Path to downloaded file") file_size: Optional[int] = Field(default=None, description="Size of downloaded file in bytes") status_code: Optional[int] = Field(default=None, description="HTTP status code of last refresh") content_hash: Optional[int] = Field(default=None, description="Hash of downloaded content") last_error: Optional[str] = Field(default=None, description="Last error message if any") @validator('status') def validate_status(cls, v): if v not in ['active', 'inactive']: raise ValueError('Status must be either "active" or "inactive"') return v class Config: json_encoders = { datetime: lambda v: v.isoformat() if v else None } class Config(BaseModel): """Model for application configuration.""" auto_refresh: bool = Field(default=False, description="Auto-refresh subscriptions") refresh_interval_hours: int = Field(default=24, description="Refresh interval in hours") default_user_agent: str = Field( default="scientific-surfing/0.1.0", description="Default User-Agent for HTTP requests" ) timeout_seconds: int = Field(default=30, description="HTTP request timeout in seconds") @validator('refresh_interval_hours') def validate_refresh_interval(cls, v): if v < 1: raise ValueError('Refresh interval must be at least 1 hour') return v @validator('timeout_seconds') def validate_timeout(cls, v): if v < 1: raise ValueError('Timeout must be at least 1 second') return v class SubscriptionsData(BaseModel): """Model for the entire subscriptions collection.""" subscriptions: Dict[str, Subscription] = Field(default_factory=dict) def get_active_subscription(self) -> Optional[Subscription]: """Get the currently active subscription.""" for subscription in self.subscriptions.values(): if subscription.status == 'active': return subscription return None def set_active(self, name: str) -> bool: """Set a subscription as active and deactivate others.""" if name not in self.subscriptions: return False for sub_name, subscription in self.subscriptions.items(): subscription.status = 'active' if sub_name == name else 'inactive' return True def add_subscription(self, name: str, url: str) -> Subscription: """Add a new subscription.""" subscription = Subscription(name=name, url=url) # If this is the first subscription, set it as active if not self.subscriptions: subscription.status = 'active' self.subscriptions[name] = subscription return subscription def remove_subscription(self, name: str) -> bool: """Remove a subscription.""" if name not in self.subscriptions: return False del self.subscriptions[name] return True def rename_subscription(self, old_name: str, new_name: str) -> bool: """Rename a subscription.""" if old_name not in self.subscriptions or new_name in self.subscriptions: return False subscription = self.subscriptions.pop(old_name) subscription.name = new_name self.subscriptions[new_name] = subscription return True