""" Model Generator Generic model generation from configuration files. Supports multiple output formats and is extensible for bidirectional conversion. Output formats: - pydantic: Pydantic BaseModel classes - django: Django ORM models (planned) - prisma: Prisma schema (planned) - sqlalchemy: SQLAlchemy models (planned) Future: Extract models FROM existing codebases (reverse direction) """ from abc import ABC, abstractmethod from pathlib import Path from typing import Dict, Type from .config_loader import ConfigLoader class BaseModelWriter(ABC): """Abstract base for model output writers.""" @abstractmethod def write(self, config: ConfigLoader, output_path: Path) -> None: """Write models to the specified path.""" pass @abstractmethod def file_extension(self) -> str: """Return the file extension for this format.""" pass class PydanticWriter(BaseModelWriter): """Generates Pydantic model files.""" def file_extension(self) -> str: return ".py" def write(self, config: ConfigLoader, output_path: Path) -> None: """Write Pydantic models to output_path.""" output_path.parent.mkdir(parents=True, exist_ok=True) content = self._generate_content(config) output_path.write_text(content) def _generate_content(self, config: ConfigLoader) -> str: """Generate the Pydantic models file content.""" # Get component names from config config_comp = config.get_shared_component("config") data_comp = config.get_shared_component("data") data_flow_sys = config.get_system("data_flow") doc_sys = config.get_system("documentation") exec_sys = config.get_system("execution") connector_comp = config.get_component("data_flow", "connector") pulse_comp = config.get_component("data_flow", "composed") pattern_comp = config.get_component("documentation", "pattern") doc_composed = config.get_component("documentation", "composed") tool_comp = config.get_component("execution", "utility") monitor_comp = config.get_component("execution", "watcher") cabinet_comp = config.get_component("execution", "container") exec_composed = config.get_component("execution", "composed") return f'''""" Pydantic models - Generated from {config.framework.name}.config.json DO NOT EDIT MANUALLY - Regenerate from config """ from enum import Enum from typing import List, Literal, Optional from pydantic import BaseModel, Field class Status(str, Enum): PENDING = "pending" PLANNED = "planned" BUILDING = "building" DEV = "dev" LIVE = "live" READY = "ready" class System(str, Enum): {data_flow_sys.name.upper()} = "{data_flow_sys.name}" {doc_sys.name.upper()} = "{doc_sys.name}" {exec_sys.name.upper()} = "{exec_sys.name}" class ToolType(str, Enum): APP = "app" CLI = "cli" # === Shared Components === class {config_comp.title}(BaseModel): """{config_comp.description}. Shared across {data_flow_sys.name}, {exec_sys.name}.""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None config_path: Optional[str] = None class {data_comp.title}(BaseModel): """{data_comp.description}. Shared across all systems.""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None source_template: Optional[str] = None data_path: Optional[str] = None # === System-Specific Components === class {connector_comp.title}(BaseModel): """{connector_comp.description} ({data_flow_sys.name}).""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None system: Literal["{data_flow_sys.name}"] = "{data_flow_sys.name}" mock: Optional[bool] = None description: Optional[str] = None class {pattern_comp.title}(BaseModel): """{pattern_comp.description} ({doc_sys.name}).""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None template_path: Optional[str] = None system: Literal["{doc_sys.name}"] = "{doc_sys.name}" class {tool_comp.title}(BaseModel): """{tool_comp.description} ({exec_sys.name}).""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None system: Literal["{exec_sys.name}"] = "{exec_sys.name}" type: Optional[ToolType] = None description: Optional[str] = None path: Optional[str] = None url: Optional[str] = None cli: Optional[str] = None class {monitor_comp.title}(BaseModel): """{monitor_comp.description} ({exec_sys.name}).""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None system: Literal["{exec_sys.name}"] = "{exec_sys.name}" class {cabinet_comp.title}(BaseModel): """{cabinet_comp.description} ({exec_sys.name}).""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None tools: List[{tool_comp.title}] = Field(default_factory=list) system: Literal["{exec_sys.name}"] = "{exec_sys.name}" # === Composed Types === class {pulse_comp.title}(BaseModel): """{pulse_comp.description} ({data_flow_sys.name}). Formula: {pulse_comp.formula}.""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None {connector_comp.name}: Optional[{connector_comp.title}] = None {config_comp.name}: Optional[{config_comp.title}] = None {data_comp.name}: Optional[{data_comp.title}] = None system: Literal["{data_flow_sys.name}"] = "{data_flow_sys.name}" class {doc_composed.title}(BaseModel): """{doc_composed.description} ({doc_sys.name}). Formula: {doc_composed.formula}.""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None template: Optional[{pattern_comp.title}] = None {data_comp.name}: Optional[{data_comp.title}] = None output_{data_comp.name}: Optional[{data_comp.title}] = None system: Literal["{doc_sys.name}"] = "{doc_sys.name}" class {exec_composed.title}(BaseModel): """{exec_composed.description} ({exec_sys.name}). Formula: {exec_composed.formula}.""" name: str # Unique identifier slug: str # URL-friendly identifier title: str # Display title for UI status: Optional[Status] = None cabinet: Optional[{cabinet_comp.title}] = None {config_comp.name}: Optional[{config_comp.title}] = None {data_comp.plural}: List[{data_comp.title}] = Field(default_factory=list) system: Literal["{exec_sys.name}"] = "{exec_sys.name}" # === Collection wrappers for JSON files === class {config_comp.title}Collection(BaseModel): items: List[{config_comp.title}] = Field(default_factory=list) class {data_comp.title}Collection(BaseModel): items: List[{data_comp.title}] = Field(default_factory=list) class {connector_comp.title}Collection(BaseModel): items: List[{connector_comp.title}] = Field(default_factory=list) class {pattern_comp.title}Collection(BaseModel): items: List[{pattern_comp.title}] = Field(default_factory=list) class {tool_comp.title}Collection(BaseModel): items: List[{tool_comp.title}] = Field(default_factory=list) class {monitor_comp.title}Collection(BaseModel): items: List[{monitor_comp.title}] = Field(default_factory=list) class {cabinet_comp.title}Collection(BaseModel): items: List[{cabinet_comp.title}] = Field(default_factory=list) class {pulse_comp.title}Collection(BaseModel): items: List[{pulse_comp.title}] = Field(default_factory=list) class {doc_composed.title}Collection(BaseModel): items: List[{doc_composed.title}] = Field(default_factory=list) class {exec_composed.title}Collection(BaseModel): items: List[{exec_composed.title}] = Field(default_factory=list) ''' class DjangoWriter(BaseModelWriter): """Generates Django model files (placeholder).""" def file_extension(self) -> str: return ".py" def write(self, config: ConfigLoader, output_path: Path) -> None: raise NotImplementedError("Django model generation not yet implemented") class PrismaWriter(BaseModelWriter): """Generates Prisma schema files (placeholder).""" def file_extension(self) -> str: return ".prisma" def write(self, config: ConfigLoader, output_path: Path) -> None: raise NotImplementedError("Prisma schema generation not yet implemented") class SQLAlchemyWriter(BaseModelWriter): """Generates SQLAlchemy model files (placeholder).""" def file_extension(self) -> str: return ".py" def write(self, config: ConfigLoader, output_path: Path) -> None: raise NotImplementedError("SQLAlchemy model generation not yet implemented") # Registry of available writers WRITERS: Dict[str, Type[BaseModelWriter]] = { "pydantic": PydanticWriter, "django": DjangoWriter, "prisma": PrismaWriter, "sqlalchemy": SQLAlchemyWriter, } class ModelGenerator: """ Generates typed models from configuration. This is the main entry point for model generation. Delegates to format-specific writers. """ def __init__( self, config: ConfigLoader, output_path: Path, output_format: str = "pydantic", ): """ Initialize the generator. Args: config: Loaded configuration output_path: Exact path where to write (file or directory depending on format) output_format: Output format (pydantic, django, prisma, sqlalchemy) """ self.config = config self.output_path = Path(output_path) self.output_format = output_format if output_format not in WRITERS: raise ValueError( f"Unknown output format: {output_format}. " f"Available: {list(WRITERS.keys())}" ) self.writer = WRITERS[output_format]() def generate(self) -> Path: """ Generate models to the specified output path. Returns: Path to the generated file/directory """ # Determine output file path if self.output_path.suffix: # User specified a file path output_file = self.output_path else: # User specified a directory, add default filename output_file = self.output_path / f"__init__{self.writer.file_extension()}" self.writer.write(self.config, output_file) print(f"Generated {self.output_format} models: {output_file}") return output_file @classmethod def available_formats(cls) -> list: """Return list of available output formats.""" return list(WRITERS.keys())