obsidian.pyโข2.87 kB
"""Pydantic models for Obsidian data structures."""
from typing import Optional, List, Dict, Any
from datetime import datetime
from pydantic import BaseModel, Field, field_validator
class NoteMetadata(BaseModel):
"""Metadata for an Obsidian note."""
created: Optional[datetime] = Field(None, description="Creation timestamp")
modified: Optional[datetime] = Field(None, description="Last modification timestamp")
tags: List[str] = Field(default_factory=list, description="List of tags")
aliases: List[str] = Field(default_factory=list, description="Alternative names for the note")
frontmatter: Dict[str, Any] = Field(default_factory=dict, description="Raw frontmatter data")
class Note(BaseModel):
"""Represents an Obsidian note."""
path: str = Field(..., description="Path to the note relative to vault root")
content: str = Field(..., description="Markdown content of the note")
metadata: NoteMetadata = Field(default_factory=NoteMetadata, description="Note metadata")
@field_validator("path")
def validate_path(cls, v):
"""Ensure path doesn't contain invalid characters."""
if not v or ".." in v:
raise ValueError("Invalid path")
return v.strip("/")
class VaultItem(BaseModel):
"""Represents an item in the vault (file or folder)."""
path: str = Field(..., description="Path relative to vault root")
name: str = Field(..., description="Name of the file or folder")
is_folder: bool = Field(..., description="Whether this is a folder")
children: Optional[List["VaultItem"]] = Field(None, description="Child items if this is a folder")
class SearchResult(BaseModel):
"""Result from a search operation."""
path: str = Field(..., description="Path to the matching note")
score: float = Field(..., description="Relevance score")
matches: List[str] = Field(default_factory=list, description="Matching text excerpts")
context: Optional[str] = Field(None, description="Context around the match")
class Tag(BaseModel):
"""Represents a tag in the vault."""
name: str = Field(..., description="Tag name without the # prefix")
count: int = Field(default=0, description="Number of notes with this tag")
notes: List[str] = Field(default_factory=list, description="Paths to notes with this tag")
@field_validator("name")
def clean_tag_name(cls, v):
"""Remove # prefix if present."""
return v.lstrip("#")
class Backlink(BaseModel):
"""Represents a backlink to a note."""
source_path: str = Field(..., description="Path of the note containing the link")
link_text: str = Field(..., description="Text of the link")
context: Optional[str] = Field(None, description="Text around the link for context")
# Enable forward references
VaultItem.model_rebuild()