""" Code Generator Generates Python code files (main.py, data layer, system main files). """ from pathlib import Path from .config_loader import ConfigLoader class CodeGenerator: """Generates Python code from configuration""" def __init__(self, config: ConfigLoader, output_dir: Path): self.config = config self.output_dir = Path(output_dir) def generate(self): """Generate all code files""" # Generate hub main.py self._generate_hub_main() # Generate data layer self._generate_data_layer() # Generate system main files for system in self.config.systems: self._generate_system_main(system) print(f"Generated code in {self.output_dir}") def _generate_hub_main(self): """Generate hub main.py file""" fw = self.config.framework systems = self.config.systems # Build system URL mappings system_urls = "\n".join([ f'{s.name.upper()}_URL = os.getenv("{s.name.upper()}_URL", "http://localhost:{s.port}")' for s in systems ]) system_external_urls = "\n".join([ f'{s.name.upper()}_EXTERNAL_URL = os.getenv("{s.name.upper()}_EXTERNAL_URL", {s.name.upper()}_URL)' for s in systems ]) system_health = ",\n ".join([ f'"{s.name}": {s.name.upper()}_URL' for s in systems ]) system_routes = "\n".join([ f' "{s.name}": {s.name.upper()}_EXTERNAL_URL,' for s in systems ]) system_redirects = "\n\n".join([ f'''@app.get("/{s.name}") @app.get("/{s.name}/{{path:path}}") def {s.name}_redirect(path: str = ""): """Redirect to {s.name} service.""" target = os.getenv("{s.name.upper()}_URL") if target: return RedirectResponse(url=f"{{target}}/{{path}}") return {{"error": "{s.name.upper()}_URL not configured"}}''' for s in systems ]) content = f'''""" {fw.name.capitalize()} - Overview and routing hub. {fw.description} {fw.icon} {fw.tagline} Systems: ''' # Add system documentation for s in systems: content += f' {s.icon} {s.title} ({s.name}) - {s.tagline}\n' content += f''' Routes: / → index /health → health check ''' # Add data routes for s in systems: content += f' /api/data/{s.name} → {s.name} data\n' # Add system redirects for s in systems: content += f' /{s.name}/* → proxy to {s.name} service\n' content += f'''""" import os from pathlib import Path from fastapi import FastAPI, Request from fastapi.responses import RedirectResponse from fastapi.templating import Jinja2Templates # Import data functions from data import get_{systems[0].name}_data, get_{systems[1].name}_data, get_{systems[2].name}_data app = FastAPI(title="{fw.name.capitalize()}", version="{fw.version}") templates = Jinja2Templates(directory=Path(__file__).parent) # Service URLs (internal for API calls) {system_urls} # External URLs (for frontend links, falls back to internal) {system_external_urls} @app.get("/health") def health(): return {{ "status": "ok", "service": "{fw.name}", "subsystems": {{ {system_health}, }} }} # === Data API === @app.get("/api/data/{systems[0].name}") def api_{systems[0].name}_data(): """Data for {systems[0].name} service.""" return get_{systems[0].name}_data() @app.get("/api/data/{systems[1].name}") def api_{systems[1].name}_data(): """Data for {systems[1].name} service.""" return get_{systems[1].name}_data() @app.get("/api/data/{systems[2].name}") def api_{systems[2].name}_data(): """Data for {systems[2].name} service.""" return get_{systems[2].name}_data() @app.get("/") def index(request: Request): return templates.TemplateResponse("index.html", {{ "request": request, {system_routes} }}) # === Cross-system redirects === # These allow {fw.name} to act as a hub, redirecting to subsystem routes {system_redirects} if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host="0.0.0.0", port=int(os.getenv("PORT", "{fw.hub_port}")), reload=os.getenv("DEV", "").lower() in ("1", "true"), ) ''' (self.output_dir / "main.py").write_text(content) def _generate_data_layer(self): """Generate data/__init__.py file""" # Get all component names for imports connector = self.config.get_component('data_flow', 'connector') pattern = self.config.get_component('documentation', 'pattern') tool = self.config.get_component('execution', 'utility') monitor = self.config.get_component('execution', 'watcher') cabinet = self.config.get_component('execution', 'container') config_comp = self.config.get_shared_component('config') data_comp = self.config.get_shared_component('data') pulse = self.config.get_component('data_flow', 'composed') doc_composed = self.config.get_component('documentation', 'composed') exec_composed = self.config.get_component('execution', 'composed') systems = self.config.systems # Build imports imports = f'''from models.pydantic import ( {connector.title}, {config_comp.title}, {data_comp.title}, {pattern.title}, {tool.title}, {pulse.title}, {doc_composed.title}, {exec_composed.title}, {connector.title}Collection, {config_comp.title}Collection, {data_comp.title}Collection, {pattern.title}Collection, {tool.title}Collection, {pulse.title}Collection, {doc_composed.title}Collection, {exec_composed.title}Collection, Status )''' # Build loader functions loaders = f''' def get_{connector.plural}() -> List[{connector.title}]: data = _load_json("{connector.plural}.json") return {connector.title}Collection(**data).items def get_{config_comp.plural}() -> List[{config_comp.title}]: data = _load_json("{config_comp.plural}.json") return {config_comp.title}Collection(**data).items def get_{data_comp.plural}() -> List[{data_comp.title}]: data = _load_json("{data_comp.plural}.json") return {data_comp.title}Collection(**data).items def get_{pattern.plural}() -> List[{pattern.title}]: data = _load_json("{pattern.plural}.json") return {pattern.title}Collection(**data).items def get_{tool.plural}() -> List[{tool.title}]: data = _load_json("{tool.plural}.json") return {tool.title}Collection(**data).items def get_{cabinet.plural}() -> list: """Load {cabinet.plural} (simple dict, no pydantic yet).""" data = _load_json("{cabinet.plural}.json") return data.get("items", []) def get_{monitor.plural}() -> list: """Load {monitor.plural} (simple dict, no pydantic yet).""" data = _load_json("{monitor.plural}.json") return data.get("items", []) def get_{pulse.plural}() -> List[{pulse.title}]: data = _load_json("{pulse.plural}.json") return {pulse.title}Collection(**data).items def get_{doc_composed.plural}() -> List[{doc_composed.title}]: data = _load_json("{doc_composed.plural}.json") return {doc_composed.title}Collection(**data).items def get_{exec_composed.plural}() -> List[{exec_composed.title}]: data = _load_json("{exec_composed.plural}.json") return {exec_composed.title}Collection(**data).items ''' # Build system data functions data_flow_sys = systems[0] doc_sys = systems[1] exec_sys = systems[2] system_data = f''' def get_{data_flow_sys.name}_data() -> dict: """Data for {data_flow_sys.name} frontend.""" return {{ "{connector.plural}": [v.model_dump() for v in get_{connector.plural}()], "{config_comp.plural}": [n.model_dump() for n in get_{config_comp.plural}()], "{data_comp.plural}": [l.model_dump() for l in get_{data_comp.plural}()], "{pulse.plural}": [p.model_dump() for p in get_{pulse.plural}()], }} def get_{doc_sys.name}_data() -> dict: """Data for {doc_sys.name} frontend.""" return {{ "{pattern.plural}": [t.model_dump() for t in get_{pattern.plural}()], "{data_comp.plural}": [l.model_dump() for l in get_{data_comp.plural}()], "{doc_composed.plural}": [b.model_dump() for b in get_{doc_composed.plural}()], }} def get_{exec_sys.name}_data() -> dict: """Data for {exec_sys.name} frontend.""" return {{ "{tool.plural}": [t.model_dump() for t in get_{tool.plural}()], "{monitor.plural}": get_{monitor.plural}(), "{cabinet.plural}": get_{cabinet.plural}(), "{config_comp.plural}": [n.model_dump() for n in get_{config_comp.plural}()], "{data_comp.plural}": [l.model_dump() for l in get_{data_comp.plural}()], "{exec_composed.plural}": [t.model_dump() for t in get_{exec_composed.plural}()], }} ''' content = f'''""" {self.config.framework.name.capitalize()} Data Layer JSON file storage (future: MongoDB) """ import json from pathlib import Path from typing import List, Optional # Add parent to path for models import import sys sys.path.insert(0, str(Path(__file__).parent.parent)) {imports} DATA_DIR = Path(__file__).parent.resolve() def _load_json(filename: str) -> dict: filepath = DATA_DIR / filename if filepath.exists(): with open(filepath) as f: return json.load(f) return {{"items": []}} def _save_json(filename: str, data: dict): filepath = DATA_DIR / filename with open(filepath, 'w') as f: json.dump(data, f, indent=2) # === Loaders === {loaders} # === For frontend rendering === {system_data} ''' (self.output_dir / "data" / "__init__.py").write_text(content) def _generate_system_main(self, system): """Generate main.py for a system""" fw = self.config.framework content = f'''""" {system.title} - {system.tagline} """ import os import httpx from pathlib import Path from fastapi import FastAPI, Request from fastapi.templating import Jinja2Templates app = FastAPI(title="{system.title}", version="{fw.version}") templates = Jinja2Templates(directory=Path(__file__).parent) # {fw.name.capitalize()} URL for data fetching {fw.name.upper()}_URL = os.getenv("{fw.name.upper()}_URL", "http://localhost:{fw.hub_port}") def get_data(): """Fetch data from {fw.name} hub.""" try: resp = httpx.get(f"{{{fw.name.upper()}_URL}}/api/data/{system.name}", timeout=5.0) if resp.status_code == 200: return resp.json() except Exception as e: print(f"Failed to fetch data from {fw.name}: {{e}}") return {{"items": []}} @app.get("/health") def health(): return {{"status": "ok", "service": "{system.name}"}} @app.get("/") def index(request: Request): data = get_data() return templates.TemplateResponse("index.html", {{ "request": request, "{fw.name}_url": os.getenv("{fw.name.upper()}_EXTERNAL_URL", {fw.name.upper()}_URL), **data, }}) @app.get("/api/data") def api_data(): """API endpoint for frontend data (proxied from {fw.name}).""" return get_data() if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host="0.0.0.0", port=int(os.getenv("PORT", "{system.port}")), reload=os.getenv("DEV", "").lower() in ("1", "true"), ) ''' (self.output_dir / system.name / "main.py").write_text(content) if __name__ == "__main__": from .config_loader import load_config # Test with soleprint config config_path = Path(__file__).parent.parent / "soleprint.config.json" config = load_config(config_path) output_dir = Path(__file__).parent.parent generator = CodeGenerator(config, output_dir) generator.generate() print("Code generated successfully!")