"""App factory comune per i servizi mcp-{exchange}. Centralizza il boilerplate dei `__main__.py`: - configure_root_logging (JSON) - fail_fast_if_missing su env mandatory - summarize env - load creds JSON - resolve_environment con default URLs - load token store - delega creazione client + app a callback per-servizio - uvicorn.run Ogni servizio invoca `run_exchange_main(spec)` con uno spec dichiarativo. """ from __future__ import annotations import json import os from collections.abc import Callable from dataclasses import dataclass from typing import Any import uvicorn from mcp_common.auth import load_token_store_from_files from mcp_common.env_validation import fail_fast_if_missing, require_env, summarize from mcp_common.environment import EnvironmentInfo, resolve_environment from mcp_common.logging import configure_root_logging @dataclass(frozen=True) class ExchangeAppSpec: exchange: str creds_env_var: str env_var: str # es. "BYBIT_TESTNET", "ALPACA_PAPER" flag_key: str # campo nel secret JSON ("testnet" o "paper") default_base_url_live: str default_base_url_testnet: str default_port: int build_client: Callable[[dict, EnvironmentInfo], Any] build_app: Callable[..., Any] extra_summarize_envs: tuple[str, ...] = () def run_exchange_main(spec: ExchangeAppSpec) -> None: configure_root_logging() fail_fast_if_missing([spec.creds_env_var]) summarize([ spec.creds_env_var, "CORE_TOKEN_FILE", "OBSERVER_TOKEN_FILE", "PORT", "HOST", spec.env_var, *spec.extra_summarize_envs, ]) creds_file = require_env(spec.creds_env_var, f"{spec.exchange} credentials JSON path") with open(creds_file) as f: creds = json.load(f) env_info = resolve_environment( creds, env_var=spec.env_var, flag_key=spec.flag_key, exchange=spec.exchange, default_base_url_live=spec.default_base_url_live, default_base_url_testnet=spec.default_base_url_testnet, ) client = spec.build_client(creds, env_info) token_store = load_token_store_from_files( core_token_file=os.environ.get("CORE_TOKEN_FILE"), observer_token_file=os.environ.get("OBSERVER_TOKEN_FILE"), ) app = spec.build_app(client=client, token_store=token_store, creds=creds, env_info=env_info) uvicorn.run( app, log_config=None, host=os.environ.get("HOST", "0.0.0.0"), port=int(os.environ.get("PORT", str(spec.default_port))), )