""" Template models for Prompt Dependency Resolution. Defines PromptTemplate and ContentMacro models for template-based prompt generation with dependency resolution. """ from dataclasses import dataclass, field from typing import Dict, Any, List, Optional from enum import Enum from markitect.prompts.models import Artifact, ArtifactType, ArtifactMetadata class MacroKind(Enum): """Types of content macros supported in templates.""" REQUIRED = "required" # Must be resolved, fail if missing OPTIONAL = "optional" # Resolve if available, empty if missing GENERATE = "generate" # Trigger generator template execution @dataclass class ContentMacro: """ Content macro extracted from a template. Implements FR-2.3: ContentMacro kinds (Required, Optional, Generate) Syntax: {{:[|=|=...]}} Examples: {{require:glossary}} {{optional:technical-constraints}} {{generate:code-examples|language=python|framework=fastapi}} Attributes: kind: Type of macro (required, optional, generate) target: Name of artifact or template to resolve parameters: Optional parameters for macro resolution raw_text: Original macro text from template line_number: Line number where macro appears (for error reporting) """ kind: MacroKind target: str parameters: Dict[str, str] = field(default_factory=dict) raw_text: str = "" line_number: int = 0 def __str__(self) -> str: """String representation of macro.""" params = ''.join(f"|{k}={v}" for k, v in self.parameters.items()) return f"{{{{{self.kind.value}:{self.target}{params}}}}}" def to_dict(self) -> Dict[str, Any]: """Convert macro to dictionary for serialization.""" return { "kind": self.kind.value, "target": self.target, "parameters": self.parameters, "raw_text": self.raw_text, "line_number": self.line_number, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "ContentMacro": """Create macro from dictionary.""" return cls( kind=MacroKind(data["kind"]), target=data["target"], parameters=data.get("parameters", {}), raw_text=data.get("raw_text", ""), line_number=data.get("line_number", 0), ) @dataclass class TemplateMetadata: """ Extended metadata specific to prompt templates. Attributes: purpose: Description of what the template generates model_hints: Suggested model parameters expected_inputs: Documentation of expected required/optional inputs output_type: Expected type of generated content """ purpose: Optional[str] = None model_hints: Dict[str, Any] = field(default_factory=dict) expected_inputs: List[str] = field(default_factory=list) output_type: Optional[str] = None def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization.""" return { "purpose": self.purpose, "model_hints": self.model_hints, "expected_inputs": self.expected_inputs, "output_type": self.output_type, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "TemplateMetadata": """Create from dictionary.""" return cls( purpose=data.get("purpose"), model_hints=data.get("model_hints", {}), expected_inputs=data.get("expected_inputs", []), output_type=data.get("output_type"), ) @dataclass class PromptTemplate: """ Prompt template with content macros. Extends Artifact with template-specific functionality. Implements FR-2: PromptTemplate Definition A PromptTemplate is an Artifact of type TEMPLATE containing ContentMacros that reference other artifacts or trigger nested generation. Attributes: artifact: Underlying artifact template_metadata: Template-specific metadata macros: Extracted content macros analyzed: Whether template has been analyzed """ artifact: Artifact template_metadata: TemplateMetadata = field(default_factory=TemplateMetadata) macros: List[ContentMacro] = field(default_factory=list) analyzed: bool = False @classmethod def create( cls, space_id: str, name: str, content: str, artifact_metadata: Optional[ArtifactMetadata] = None, template_metadata: Optional[TemplateMetadata] = None, ) -> "PromptTemplate": """ Create a new template. Args: space_id: ID of containing space name: Template name content: Template content with macros artifact_metadata: General artifact metadata template_metadata: Template-specific metadata Returns: New PromptTemplate instance """ artifact = Artifact.create( space_id=space_id, name=name, content=content, artifact_type=ArtifactType.TEMPLATE, metadata=artifact_metadata, ) return cls( artifact=artifact, template_metadata=template_metadata or TemplateMetadata(), macros=[], analyzed=False, ) @classmethod def from_artifact(cls, artifact: Artifact) -> "PromptTemplate": """ Create template from existing artifact. Args: artifact: Artifact to wrap Returns: PromptTemplate instance Raises: ValueError: If artifact is not of type TEMPLATE """ if artifact.artifact_type != ArtifactType.TEMPLATE: raise ValueError( f"Artifact must be of type TEMPLATE, got {artifact.artifact_type}" ) return cls( artifact=artifact, template_metadata=TemplateMetadata(), macros=[], analyzed=False, ) @property def id(self) -> str: """Get template ID.""" return self.artifact.id @property def space_id(self) -> str: """Get space ID.""" return self.artifact.space_id @property def name(self) -> str: """Get template name.""" return self.artifact.name @property def content_digest(self) -> str: """Get content digest.""" return self.artifact.content_digest def get_required_dependencies(self) -> List[str]: """ Get list of required artifact names. Returns: List of artifact names from required macros """ return [ macro.target for macro in self.macros if macro.kind == MacroKind.REQUIRED ] def get_optional_dependencies(self) -> List[str]: """ Get list of optional artifact names. Returns: List of artifact names from optional macros """ return [ macro.target for macro in self.macros if macro.kind == MacroKind.OPTIONAL ] def get_generators(self) -> List[str]: """ Get list of generator template names. Returns: List of template names from generate macros """ return [ macro.target for macro in self.macros if macro.kind == MacroKind.GENERATE ] def to_dict(self) -> Dict[str, Any]: """Convert template to dictionary for serialization.""" return { "artifact": self.artifact.to_dict(), "template_metadata": self.template_metadata.to_dict(), "macros": [macro.to_dict() for macro in self.macros], "analyzed": self.analyzed, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "PromptTemplate": """Create template from dictionary.""" return cls( artifact=Artifact.from_dict(data["artifact"]), template_metadata=TemplateMetadata.from_dict( data.get("template_metadata", {}) ), macros=[ContentMacro.from_dict(m) for m in data.get("macros", [])], analyzed=data.get("analyzed", False), )