Files
prompts/src/personal_mcp/registry/models/common.py
T
2026-06-21 16:15:37 -05:00

50 lines
1.6 KiB
Python

import re
from collections.abc import Mapping
from pathlib import PurePosixPath
from types import MappingProxyType
from typing import ClassVar
from typing import Final
from pydantic import BaseModel
from pydantic import ConfigDict
from pydantic import field_validator
SKILL_ID_RE: Final[re.Pattern[str]] = re.compile(r"^[a-z][a-z0-9-]*$")
SEMVER_RE: Final[re.Pattern[str]] = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+][0-9A-Za-z.-]+)?$")
class StrictFrozenModel(BaseModel):
"""Immutable base model with strict field validation rules."""
model_config: ClassVar[ConfigDict] = ConfigDict(
extra="forbid",
frozen=True,
validate_by_alias=True,
validate_by_name=True,
str_strip_whitespace=True,
)
def frozen_mapping[K, V](value: Mapping[K, V] | None = None) -> Mapping[K, V]:
return MappingProxyType(dict(value) if value is not None else {})
class ReferenceEntry(StrictFrozenModel):
"""Reference metadata for a markdown file within a skill."""
path: str
mime_type: str = "text/markdown"
title: str | None = None
@field_validator("path")
@classmethod
def validate_reference_path(cls, value: str) -> str:
path = PurePosixPath(value)
if path.is_absolute() or ".." in path.parts:
raise ValueError("reference path must be a relative in-skill path")
if not str(path).startswith("references/"):
raise ValueError("reference path must stay under references/")
if path.suffix.lower() != ".md":
raise ValueError("reference path must target a markdown file")
return path.as_posix()