from pydantic import ( BaseModel, ConfigDict, DirectoryPath, HttpUrl, Field, model_validator, ) from typing import List, Optional, Any, Dict from datetime import datetime from enum import Enum class FolderType(Enum): DEFAULT = "DEFAULT" DUMMY = "DUMMY" class MetadataSource(Enum): USER_GENERATED = "user_generated" SYSTEM_GENERATED = "system_generated" PLUGIN_GENERATED = "plugin_generated" class MetadataType(Enum): JSON_DATA = "json" TEXT_DATA = "text" NUMBER_DATA = "number" class NewFolderParam(BaseModel): path: DirectoryPath last_modified_at: datetime type: str = FolderType.DEFAULT class NewLibraryParam(BaseModel): name: str folders: List[NewFolderParam] = [] class NewFoldersParam(BaseModel): folders: List[NewFolderParam] = [] class EntityMetadataParam(BaseModel): key: str value: str source: str data_type: MetadataType class NewEntityParam(BaseModel): filename: str filepath: str size: int file_created_at: datetime file_last_modified_at: datetime file_type: str file_type_group: str folder_id: int tags: List[str] | None = None metadata_entries: List[EntityMetadataParam] | None = None class UpdateEntityParam(BaseModel): size: int | None = None file_created_at: datetime | None = None file_last_modified_at: datetime | None = None file_type: str | None = None file_type_group: str | None = None tags: List[str] | None = None metadata_entries: List[EntityMetadataParam] | None = None class UpdateTagParam(BaseModel): description: str | None color: str | None class UpdateEntityTagsParam(BaseModel): tags: List[str] = [] class UpdateEntityMetadataParam(BaseModel): metadata_entries: List[EntityMetadataParam] class NewPluginParam(BaseModel): name: str description: str | None webhook_url: HttpUrl class NewLibraryPluginParam(BaseModel): plugin_id: Optional[int] = None plugin_name: Optional[str] = None @model_validator(mode="after") def check_either_id_or_name(self): plugin_id = self.plugin_id plugin_name = self.plugin_name if not (plugin_id or plugin_name): raise ValueError("Either plugin_id or plugin_name must be provided") if plugin_id is not None and plugin_name is not None: raise ValueError("Only one of plugin_id or plugin_name should be provided") return self class Folder(BaseModel): id: int path: str last_modified_at: datetime type: FolderType model_config = ConfigDict(from_attributes=True) class Plugin(BaseModel): id: int name: str description: str | None webhook_url: str model_config = ConfigDict(from_attributes=True) class Library(BaseModel): id: int name: str folders: List[Folder] = [] plugins: List[Plugin] = [] model_config = ConfigDict(from_attributes=True) class Tag(BaseModel): id: int name: str description: str | None color: str | None created_at: datetime # source: str model_config = ConfigDict(from_attributes=True) class EntityMetadata(BaseModel): id: int entity_id: int key: str value: str source: str data_type: MetadataType model_config = ConfigDict(from_attributes=True) class Entity(BaseModel): id: int filepath: str filename: str size: int file_created_at: datetime file_last_modified_at: datetime file_type: str file_type_group: str last_scan_at: datetime | None folder_id: int library_id: int tags: List[Tag] = [] metadata_entries: List[EntityMetadata] = [] model_config = ConfigDict(from_attributes=True) def get_metadata_by_key(self, key: str) -> Optional[EntityMetadata]: """ Get EntityMetadata by key. Args: key (str): The key to search for in metadata entries. Returns: Optional[EntityMetadata]: The EntityMetadata if found, None otherwise. """ for metadata in self.metadata_entries: if metadata.key == key: return metadata return None class MetadataIndexItem(BaseModel): key: str value: Any source: str class EntitySearchResult(BaseModel): id: str filepath: str filename: str size: int file_created_at: int = Field(..., description="Unix timestamp") created_date: Optional[str] = None created_month: Optional[str] = None created_year: Optional[str] = None file_last_modified_at: int = Field(..., description="Unix timestamp") file_type: str file_type_group: str last_scan_at: Optional[int] = Field(None, description="Unix timestamp") library_id: int folder_id: int tags: List[str] metadata_entries: List[MetadataIndexItem] facets: Optional[Dict[str, Any]] = None class FacetCount(BaseModel): count: int highlighted: str value: str class FacetStats(BaseModel): total_values: int class Facet(BaseModel): counts: List[FacetCount] field_name: str sampled: bool stats: FacetStats class TextMatchInfo(BaseModel): best_field_score: str best_field_weight: int fields_matched: int num_tokens_dropped: int score: str tokens_matched: int typo_prefix_score: int class HybridSearchInfo(BaseModel): rank_fusion_score: float class SearchHit(BaseModel): document: EntitySearchResult highlight: Dict[str, Any] = {} highlights: List[Any] = [] hybrid_search_info: Optional[HybridSearchInfo] = None text_match: Optional[int] = None text_match_info: Optional[TextMatchInfo] = None class RequestParams(BaseModel): collection_name: str first_q: str per_page: int q: str class SearchResult(BaseModel): facet_counts: List[Facet] found: int hits: List[SearchHit] out_of: int page: int request_params: RequestParams search_cutoff: bool search_time_ms: int class EntityContext(BaseModel): prev: List[Entity] next: List[Entity] class BatchIndexRequest(BaseModel): entity_ids: List[int]