almosther isolating soleprint
This commit is contained in:
418
station/tools/modelgen/code_generator.py
Normal file
418
station/tools/modelgen/code_generator.py
Normal file
@@ -0,0 +1,418 @@
|
||||
"""
|
||||
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!")
|
||||
Reference in New Issue
Block a user