""" Pydantic Configuration Models for OWILIX This module provides type-safe configuration management using Pydantic. Configurations can be loaded from YAML files or environment variables. """ from typing import Dict, List, Literal, Optional, Any from pydantic import BaseModel, Field, field_validator from pydantic_settings import BaseSettings import os class RepositoryConfig(BaseModel): """Configuration for a single repository. Attributes: repository: Type of repository backend. options: Backend-specific configuration options. """ repository: Literal["lexis", "lexis+http", "s3a", "file"] options: Dict[str, Any] = Field(default_factory=dict) model_config = {"extra": "allow"} class ProfileConfig(BaseModel): """Display profile configuration. Attributes: showfields: Comma-separated list of fields to display. nodisplay: Regex pattern for fields to hide. sort: Sort specification (e.g., "+startDate"). colorder: Column ordering. """ showfields: str = "" nodisplay: str = ".*" sort: str = "+startDate" colorder: str = "" model_config = {"extra": "allow"} class ThemeConfig(BaseModel): """CLI theme colors.""" info: str = "dim cyan" warning: str = "magenta" danger: str = "bold red" error: str = "bold red" model_config = {"extra": "allow"} class UserConfig(BaseModel): """User metadata.""" name: str = "" email: str = "" organisation: str = "" model_config = {"extra": "allow"} class LoginConfig(BaseModel): """Authentication configuration.""" username: Optional[str] = None password: Optional[str] = None model_config = {"extra": "allow"} class GeneralConfig(BaseModel): """General settings.""" version: str = "" server_update_url: str = Field( default="https://dashboard.ows.eu/api/owilix/default-cfg-0-17", alias="server-update-url" ) license: Optional[str] = None model_config = {"extra": "allow", "populate_by_name": True} class RepositoriesConfig(BaseModel): """Repositories configuration section.""" config: Dict[str, RepositoryConfig] = Field(default_factory=dict) selected_remote: List[str] = Field(default_factory=list) selected_local: List[str] = Field(default_factory=list) model_config = {"extra": "allow"} class OWILIXSettings(BaseModel): """ Complete OWILIX configuration model. This model represents the full owilix.cfg YAML structure. Example YAML: general: version: "0.17.0" server-update-url: "https://..." repositories: config: it4i: repository: lexis+http options: zone: IT4ILexisV2 selected_remote: [it4i] selected_local: [local] profiles: default: showfields: "..." theme: info: "dim cyan" """ general: GeneralConfig = Field(default_factory=GeneralConfig) repositories: RepositoriesConfig = Field(default_factory=RepositoriesConfig) profiles: Dict[str, ProfileConfig] = Field(default_factory=dict) theme: ThemeConfig = Field(default_factory=ThemeConfig) user: UserConfig = Field(default_factory=UserConfig) dataset: Dict[str, Any] = Field(default_factory=dict) login: LoginConfig = Field(default_factory=LoginConfig) # Server-provided config (merged at runtime) server_config: Dict[str, Any] = Field(default_factory=dict, alias="server-config") model_config = {"extra": "allow", "populate_by_name": True} def get_repository(self, name: str) -> Optional[RepositoryConfig]: """Get a specific repository configuration.""" return self.repositories.config.get(name) def get_remote_repositories(self) -> Dict[str, RepositoryConfig]: """Get all selected remote repositories.""" return { k: v for k, v in self.repositories.config.items() if k in self.repositories.selected_remote } def get_local_repositories(self) -> Dict[str, RepositoryConfig]: """Get all selected local repositories.""" return { k: v for k, v in self.repositories.config.items() if k in self.repositories.selected_local } def get_profile(self, name: str = "default") -> ProfileConfig: """Get a profile, merging with default.""" default = self.profiles.get("default", ProfileConfig()) if name == "default" or name not in self.profiles: return default # Merge specific profile over default profile_data = default.model_dump() profile_data.update(self.profiles[name].model_dump(exclude_unset=True)) return ProfileConfig(**profile_data) class OWILIXEnvSettings(BaseSettings): """ Environment-based settings for OWILIX. These override or supplement file-based configuration. Environment Variables: OWILIX_LEXIS_PROJECT_NAME: Project name OWILIX_LEXIS_PROJECT_ID: Project ID OWS_OWI_PATH: Base path for OWILIX data OWILIX_IRODS_CONNECTION_TIMEOUT: iRODS timeout in seconds PY4LEXIS_TOKEN: Refresh token for authentication """ project_name: str = Field(default="openwebsearch", alias="OWILIX_LEXIS_PROJECT_NAME") project_id: Optional[str] = Field(default=None, alias="OWILIX_LEXIS_PROJECT_ID") owi_path: str = Field(default="~/.owi", alias="OWS_OWI_PATH") irods_connection_timeout: int = Field(default=120, alias="OWILIX_IRODS_CONNECTION_TIMEOUT") py4lexis_token: Optional[str] = Field(default=None, alias="PY4LEXIS_TOKEN") model_config = { "env_prefix": "", # Use explicit aliases "extra": "ignore", "populate_by_name": True, } @field_validator("owi_path") @classmethod def expand_path(cls, v: str) -> str: return os.path.expanduser(v)