better data models

This commit is contained in:
John Lancaster
2026-06-21 16:53:43 -05:00
parent 34923b51d7
commit 37fa9b6c6f
8 changed files with 261 additions and 525 deletions
@@ -1,56 +0,0 @@
from pathlib import PurePosixPath
from typing import Self
from .common import StrictFrozenModel
class MarkdownDocumentModel(StrictFrozenModel):
"""Normalized markdown document content with path and frontmatter."""
relpath: str
content: str
frontmatter: str | None = None
skill_slug: str | None = None
class SkillFilesBundleModel(StrictFrozenModel):
"""Grouped skill documents partitioned into skill, references, and other files."""
slug: str
skill: MarkdownDocumentModel
references: tuple[MarkdownDocumentModel, ...] = ()
other: tuple[MarkdownDocumentModel, ...] = ()
@classmethod
def from_parts(
cls,
*,
slug: str,
skill: MarkdownDocumentModel,
references: tuple[MarkdownDocumentModel, ...] = (),
other: tuple[MarkdownDocumentModel, ...] = (),
) -> Self:
# Ensure deterministic ordering even when the source collection is unordered.
references_sorted = tuple(sorted(references, key=lambda doc: doc.relpath))
other_sorted = tuple(sorted(other, key=lambda doc: doc.relpath))
return cls(
slug=slug,
skill=skill,
references=references_sorted,
other=other_sorted,
)
def to_markdown_document_model(
*,
relpath: PurePosixPath,
content: str,
frontmatter: str | None,
skill_slug: str | None,
) -> MarkdownDocumentModel:
return MarkdownDocumentModel(
relpath=relpath.as_posix(),
content=content,
frontmatter=frontmatter,
skill_slug=skill_slug,
)
+17 -4
View File
@@ -1,8 +1,10 @@
import re
from collections.abc import Mapping
from pathlib import PurePosixPath
from typing import Any
from typing import Literal
import yaml
from pydantic import Field
from pydantic import field_validator
@@ -100,10 +102,21 @@ class PromptFrontmatter(StrictFrozenModel):
return value
class PromptDocumentModel(StrictFrozenModel):
"""Structured representation of a prompt markdown document."""
class StoredPrompt(StrictFrozenModel):
"""Normalized prompt document content with path and frontmatter for storage in the registry."""
prompt_id: str
relpath: str
relpath: PurePosixPath
content: str
frontmatter: PromptFrontmatter
frontmatter: PromptFrontmatter | None = None
@field_validator("frontmatter", mode="before")
@classmethod
def parse_frontmatter_yaml(cls, value: str | None):
if value is None:
return None
try:
data = yaml.safe_load(value)
except yaml.YAMLError as e:
raise ValueError(f"invalid YAML in frontmatter: {e}") from e
return PromptFrontmatter.model_validate(data)
+23 -10
View File
@@ -1,6 +1,7 @@
from collections.abc import Mapping
from pathlib import PurePosixPath
import yaml
from pydantic import Field
from pydantic import field_validator
@@ -107,19 +108,31 @@ class SkillFrontmatter(StrictFrozenModel):
return value
class SkillDocumentModel(StrictFrozenModel):
"""Structured representation of a skill markdown document."""
skill_id: str
relpath: PurePosixPath
content: str
frontmatter: SkillFrontmatter
class SkillReferenceDocumentModel(StrictFrozenModel):
class StoredSkillReference(StrictFrozenModel):
"""Structured representation of a skill reference markdown document."""
ref_id: str
relpath: PurePosixPath
content: str
entry: ReferenceEntry
class StoredSkill(StrictFrozenModel):
"""Structured representation of a skill markdown document."""
skill_id: str
relpath: PurePosixPath
content: str
frontmatter: SkillFrontmatter
references: dict[str, StoredSkillReference] = Field(default_factory=dict)
@field_validator("frontmatter", mode="before")
@classmethod
def parse_frontmatter_yaml(cls, value: str | None):
if value is None:
return None
try:
data = yaml.safe_load(value)
except yaml.YAMLError as e:
raise ValueError(f"invalid YAML in frontmatter: {e}") from e
return SkillFrontmatter.model_validate(data)