|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Import the production WSGI app with an inert IBKR configuration. |
| 3 | +
|
| 4 | +This check performs no network or broker operations. It catches dependency API |
| 5 | +drift, module import failures, and Flask route registration conflicts before a |
| 6 | +container can be deployed. |
| 7 | +""" |
| 8 | + |
| 9 | +from __future__ import annotations |
| 10 | + |
| 11 | +import json |
| 12 | +import os |
| 13 | + |
| 14 | + |
| 15 | +def _install_smoke_environment() -> None: |
| 16 | + account_group = "startup-smoke" |
| 17 | + os.environ["ACCOUNT_GROUP"] = account_group |
| 18 | + os.environ["IB_ACCOUNT_GROUP_CONFIG_JSON"] = json.dumps( |
| 19 | + { |
| 20 | + "groups": { |
| 21 | + account_group: { |
| 22 | + "ib_gateway_instance_name": "127.0.0.1", |
| 23 | + "ib_gateway_mode": "paper", |
| 24 | + "ib_client_id": 1, |
| 25 | + "account_ids": ["DU000000"], |
| 26 | + } |
| 27 | + } |
| 28 | + }, |
| 29 | + separators=(",", ":"), |
| 30 | + ) |
| 31 | + os.environ["IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME"] = "" |
| 32 | + os.environ["RUNTIME_TARGET_JSON"] = json.dumps( |
| 33 | + { |
| 34 | + "platform_id": "ibkr", |
| 35 | + "strategy_profile": "global_etf_rotation", |
| 36 | + "dry_run_only": True, |
| 37 | + "execution_mode": "paper", |
| 38 | + "account_scope": account_group, |
| 39 | + }, |
| 40 | + separators=(",", ":"), |
| 41 | + ) |
| 42 | + |
| 43 | + |
| 44 | +def validate_startup() -> None: |
| 45 | + _install_smoke_environment() |
| 46 | + |
| 47 | + import main |
| 48 | + |
| 49 | + routes = { |
| 50 | + rule.rule: set(rule.methods) - {"HEAD", "OPTIONS"} |
| 51 | + for rule in main.app.url_map.iter_rules() |
| 52 | + } |
| 53 | + required_routes = { |
| 54 | + "/health": {"GET"}, |
| 55 | + "/run": {"GET", "POST"}, |
| 56 | + "/dry-run": {"GET", "POST"}, |
| 57 | + "/probe": {"GET", "POST"}, |
| 58 | + "/monitor-dispatch": {"GET", "POST"}, |
| 59 | + } |
| 60 | + missing_or_invalid = { |
| 61 | + path: {"expected": sorted(methods), "actual": sorted(routes.get(path, set()))} |
| 62 | + for path, methods in required_routes.items() |
| 63 | + if routes.get(path) != methods |
| 64 | + } |
| 65 | + if missing_or_invalid: |
| 66 | + raise RuntimeError(f"Cloud Run route contract is invalid: {missing_or_invalid}") |
| 67 | + |
| 68 | + print(f"Cloud Run startup validation passed: routes={len(routes)}") |
| 69 | + |
| 70 | + |
| 71 | +if __name__ == "__main__": |
| 72 | + validate_startup() |
0 commit comments