Source code for cursus.step_catalog.models
"""
Simple data models for the unified step catalog system.
Following the Code Redundancy Evaluation Guide principles, these models are
simple and focused, avoiding over-engineering while supporting all US1-US5 requirements.
"""
from pydantic import BaseModel, Field
from pathlib import Path
from datetime import datetime
from typing import Optional, Dict, Any, List, Callable
# A "builder provider" is anything the assembler can call with the fixed 5-kwarg signature
# (config, sagemaker_session, role, registry_manager, dependency_resolver) to get a StepBuilderBase
# instance. A per-step builder CLASS is already such a callable, so today the catalog returns classes
# and this alias is satisfied trivially (dual-mode). It exists so the catalog's resolution methods can
# later return non-class factories (the classless Design-B end-state, FZ 31e1d3g1) WITHOUT changing
# the caller contract: callers must treat the result as a provider callable, never assume a class.
# (Loose Callable[..., Any] rather than a Protocol — the assembler invokes it as a constructor and
# duck-types the result; there is intentionally no isinstance gate, see pipeline_assembler.py:190.)
BuilderProvider = Callable[..., Any]
[docs]
class StepInfo(BaseModel):
"""Essential step information combining registry data with file metadata."""
step_name: str = Field(..., description="Name of the step")
workspace_id: str = Field(..., description="Workspace where the step is located")
registry_data: Dict[str, Any] = Field(
default_factory=dict, description="Data from cursus.registry.step_names"
)
file_components: Dict[str, Optional[FileMetadata]] = Field(
default_factory=dict,
description="Discovered file components keyed by component type",
)
model_config = {"arbitrary_types_allowed": True}
@property
def config_class(self) -> str:
"""Get config class name from registry data."""
value = self.registry_data.get("config_class", "")
return str(value) if value is not None else ""
@property
def sagemaker_step_type(self) -> str:
"""Get SageMaker step type from registry data."""
value = self.registry_data.get("sagemaker_step_type", "")
return str(value) if value is not None else ""
@property
def builder_step_name(self) -> str:
"""Get builder step name from registry data."""
value = self.registry_data.get("builder_step_name", "")
return str(value) if value is not None else ""
@property
def description(self) -> str:
"""Get step description from registry data."""
value = self.registry_data.get("description", "")
return str(value) if value is not None else ""
[docs]
class StepSearchResult(BaseModel):
"""Simple search result for step queries."""
step_name: str = Field(..., description="Name of the matching step")
workspace_id: str = Field(..., description="Workspace where the step is located")
match_score: float = Field(..., description="Relevance score (0.0 to 1.0)")
match_reason: str = Field(
..., description="Reason for the match (e.g., 'name_match', 'fuzzy_match')"
)
components_available: List[str] = Field(
default_factory=list,
description="List of available component types for this step",
)
model_config = {"frozen": True}