""" Artifact service for high-level artifact management operations. This service provides business logic for creating, querying, and updating artifacts with automatic content digest calculation and change tracking. """ from typing import List, Optional from datetime import datetime from markitect.prompts.models import ( Artifact, ArtifactType, ArtifactMetadata, ArtifactReference, calculate_content_digest, ) from markitect.prompts.repositories.interfaces import ( IArtifactRepository, ArtifactNotFoundError, ) class ArtifactService: """ Service for artifact management operations. Provides high-level business logic on top of artifact repository, handling content digest calculation, change detection, and cross-space artifact resolution. """ def __init__(self, repository: IArtifactRepository): """ Initialize service with repository. Args: repository: Artifact repository implementation """ self.repository = repository def create_artifact( self, space_id: str, name: str, content: str, artifact_type: ArtifactType = ArtifactType.CONTENT, metadata: Optional[ArtifactMetadata] = None, ) -> Artifact: """ Create a new artifact with automatic digest calculation. Args: space_id: ID of containing space name: Artifact name content: Artifact content artifact_type: Type classification metadata: Optional metadata Returns: Created artifact Raises: DuplicateArtifactError: If artifact already exists """ artifact = Artifact.create( space_id=space_id, name=name, content=content, artifact_type=artifact_type, metadata=metadata, ) return self.repository.create(artifact) def get_artifact(self, artifact_id: str) -> Artifact: """ Retrieve artifact by ID. Args: artifact_id: Artifact identifier Returns: Artifact instance Raises: ArtifactNotFoundError: If artifact doesn't exist """ artifact = self.repository.get_by_id(artifact_id) if not artifact: raise ArtifactNotFoundError(f"Artifact with ID '{artifact_id}' not found") return artifact def get_artifact_by_name(self, space_id: str, name: str) -> Artifact: """ Retrieve artifact by space and name. Args: space_id: Space identifier name: Artifact name Returns: Artifact instance Raises: ArtifactNotFoundError: If artifact doesn't exist """ artifact = self.repository.get_by_name(space_id, name) if not artifact: raise ArtifactNotFoundError( f"Artifact '{name}' not found in space '{space_id}'" ) return artifact def resolve_reference( self, reference: ArtifactReference, search_spaces: List[str], ) -> Optional[Artifact]: """ Resolve an artifact reference across multiple spaces. Implements FR-1.3: Cross-space artifact references Searches spaces in order until artifact is found. Args: reference: Artifact reference to resolve search_spaces: List of space IDs to search in order Returns: Resolved artifact, or None if not found """ # If reference specifies space, search only that space if reference.space_id: return self.repository.get_by_name(reference.space_id, reference.name) # Search spaces in order for space_id in search_spaces: artifact = self.repository.get_by_name(space_id, reference.name) if artifact: # TODO: Handle version constraint when versioning is implemented return artifact return None def update_artifact_content( self, artifact_id: str, new_content: str, ) -> Artifact: """ Update artifact content with automatic digest recalculation. Args: artifact_id: Artifact to update new_content: New content Returns: Updated artifact Raises: ArtifactNotFoundError: If artifact doesn't exist """ artifact = self.get_artifact(artifact_id) artifact.update_content(new_content) return self.repository.update(artifact) def detect_change(self, artifact_id: str, current_content: str) -> bool: """ Detect if artifact content has changed. Args: artifact_id: Artifact to check current_content: Current content to compare Returns: True if content has changed Raises: ArtifactNotFoundError: If artifact doesn't exist """ artifact = self.get_artifact(artifact_id) current_digest = calculate_content_digest(current_content) return artifact.has_changed(current_digest) def list_artifacts( self, space_id: str, artifact_type: Optional[ArtifactType] = None, ) -> List[Artifact]: """ List artifacts in a space. Args: space_id: Space identifier artifact_type: Optional type filter Returns: List of artifacts """ return self.repository.list_by_space(space_id, artifact_type) def find_by_digest(self, content_digest: str) -> List[Artifact]: """ Find all artifacts with matching content digest. Useful for finding duplicate content across spaces. Args: content_digest: SHA-256 digest to match Returns: List of artifacts with matching digest """ return self.repository.get_by_digest(content_digest) def delete_artifact(self, artifact_id: str) -> bool: """ Delete an artifact. Args: artifact_id: Artifact to delete Returns: True if deleted, False if not found """ return self.repository.delete(artifact_id) def artifact_exists(self, space_id: str, name: str) -> bool: """ Check if artifact exists. Args: space_id: Space identifier name: Artifact name Returns: True if artifact exists """ return self.repository.exists(space_id, name)