Initial commit: Quantum Terminal — Free & Open Source Trading Platform
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# version: v1
|
||||
"""Chart-template REST endpoints.
|
||||
|
||||
A "chart template" is a named full-chart-config snapshot the operator can
|
||||
save and re-apply to any chart ("XAUUSD swing", "XAUUSD scalp", ...).
|
||||
Distinct from /api/chart-presets, which holds chart-STYLE-only fragments.
|
||||
|
||||
Wire into data_server.py:
|
||||
from chart_templates_routes import create_chart_templates_router
|
||||
app.include_router(create_chart_templates_router())
|
||||
|
||||
Endpoints:
|
||||
GET /api/chart-templates — returns dict of all named templates {name: {sourceTicker, config, savedAt}}
|
||||
PUT /api/chart-templates/{name} — body is {sourceTicker, config, savedAt}; creates or replaces
|
||||
DELETE /api/chart-templates/{name} — removes the named template (idempotent)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from urllib.parse import unquote
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
import config_manager
|
||||
|
||||
|
||||
log = logging.getLogger("mk.chart_templates_routes")
|
||||
|
||||
|
||||
# Permissive body model: a template carries a sourceTicker, a config dict
|
||||
# (the full chart-config bundle), and a savedAt timestamp. `extra = "allow"`
|
||||
# keeps the API forward-compatible if the shape grows.
|
||||
class ChartTemplateIn(BaseModel):
|
||||
sourceTicker: str | None = None
|
||||
config: dict | None = None
|
||||
savedAt: str | None = None
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
|
||||
|
||||
_RESERVED_NAMES = {"defaults", "default"}
|
||||
_MAX_NAME_LEN = 60
|
||||
|
||||
|
||||
def _validate_name(name: str) -> None:
|
||||
if not name or not name.strip():
|
||||
raise HTTPException(400, detail="Template name cannot be empty")
|
||||
if len(name) > _MAX_NAME_LEN:
|
||||
raise HTTPException(400, detail=f"Template name exceeds {_MAX_NAME_LEN} characters")
|
||||
if name.strip().lower() in _RESERVED_NAMES:
|
||||
raise HTTPException(400, detail=f"'{name}' is a reserved name")
|
||||
|
||||
|
||||
def create_chart_templates_router() -> APIRouter:
|
||||
router = APIRouter(prefix="/api/chart-templates", tags=["chart-templates"])
|
||||
|
||||
@router.get("")
|
||||
async def list_templates() -> dict[str, Any]:
|
||||
return config_manager.get_chart_templates()
|
||||
|
||||
@router.put("/{name}")
|
||||
async def upsert_template(name: str, body: ChartTemplateIn) -> dict[str, Any]:
|
||||
decoded = unquote(name)
|
||||
_validate_name(decoded)
|
||||
try:
|
||||
config_manager.save_chart_template(decoded, body.model_dump(exclude_none=False))
|
||||
except Exception as e:
|
||||
log.exception("save_chart_template failed")
|
||||
raise HTTPException(500, detail=f"Save failed: {e}") from e
|
||||
return config_manager.get_chart_templates()
|
||||
|
||||
@router.delete("/{name}")
|
||||
async def delete_template(name: str) -> dict[str, Any]:
|
||||
decoded = unquote(name)
|
||||
try:
|
||||
config_manager.delete_chart_template(decoded)
|
||||
except Exception as e:
|
||||
log.exception("delete_chart_template failed")
|
||||
raise HTTPException(500, detail=f"Delete failed: {e}") from e
|
||||
return config_manager.get_chart_templates()
|
||||
|
||||
return router
|
||||
Reference in New Issue
Block a user