Files
Cerbero-mcp/services/common/src/mcp_common/app_factory.py
T
AdrianoDev 4d9db750be chore: ruff py313, conftest unification, audit log, app factory comune
- pyproject.toml: ruff target-version py311 → py313 (auto-fix 42 lint
  warnings via UP rules); aggiunto consider_namespace_packages = true
  che risolve la collisione conftest tra servizi e permette di lanciare
  pytest sull'intera suite cross-servizio.

- mcp_common.audit: nuovo helper audit_write_op() con logger dedicato
  mcp.audit. Wirato su tutti i write endpoint di deribit, bybit, alpaca
  e hyperliquid (place_order, place_combo_order, cancel_*, set_*,
  close_*, transfer_*, switch_*, amend_*) con principal + target +
  payload non-sensibile + result summarizzato.

- mcp_common.app_factory: ExchangeAppSpec + run_exchange_main()
  centralizza il boilerplate dei __main__.py (configure_root_logging,
  fail_fast_if_missing, summarize, load creds, resolve_environment,
  load token store, uvicorn). I 4 __main__.py exchange ridotti da ~60
  LOC ognuno a ~25 LOC dichiarativi. mcp_common.env_validation
  promosso da mcp_deribit (mantenuto re-export shim per back-compat
  test_env_validation).

- 8 test nuovi (4 audit + 4 app_factory). Suite full: 450/450 verdi.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:27:02 +02:00

87 lines
2.5 KiB
Python

"""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))),
)