Spaces:
Running
Running
File size: 5,470 Bytes
c10af81 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | #!/usr/bin/env python3
"""
patch_websocket_hub.py
ββββββββββββββββββββββ
Injects TradeLogParser (from hub_dashboard_service.py) and four API routes
into /app/websocket_hub.py so that port 7860 serves:
GET /api/trades β full open + closed state + stats
GET /api/trades/open β open trades only
GET /api/trades/closed β recent closed trades + stats (?limit=N, default 50)
GET /api/health β service health including trade counts
Usage:
python3 patch_websocket_hub.py [--target /app/websocket_hub.py] [--dry-run]
"""
import argparse
import shutil
import sys
from datetime import datetime
from pathlib import Path
# ββ Snippet 1: Parser instantiation block βββββββββββββββββββββββββββββββββββββ
# Inserted BEFORE `_START_TIME = time.time()`
PARSER_BLOCK = '''
# ββ Trade log parser β injected so /api/trades is served on port 7860 ββββββββ
import sys as _sys, os as _os
_sys.path.insert(0, '/app')
from hub_dashboard_service import TradeLogParser as _TradeLogParser
_trade_parser = _TradeLogParser(log_dir=_os.environ.get("RANKER_LOG_DIR", "/app/ranker_logs"))
_trade_parser.start_background()
'''
# ββ Snippet 2: FastAPI route definitions ββββββββββββββββββββββββββββββββββββββ
# Appended AFTER `_START_TIME = time.time()`
TRADE_ROUTES = '''
# ββ /api/trades routes β injected by patch_websocket_hub.py ββββββββββββββββββ
@app.get("/api/trades")
async def api_trades():
"""Full trade state: open trades, recent closed trades, summary stats."""
return JSONResponse(_trade_parser.get_state())
@app.get("/api/trades/open")
async def api_trades_open():
"""Open trades only."""
state = _trade_parser.get_state()
return JSONResponse({"open": state["open"]})
@app.get("/api/trades/closed")
async def api_trades_closed(limit: int = 50):
"""Recent closed trades (newest first) + cumulative stats."""
state = _trade_parser.get_state()
return JSONResponse({
"closed": state["closed"][:limit],
"stats": state["stats"],
})
@app.get("/api/health")
async def api_health():
"""Service health check β includes live trade counts and log-file inventory."""
import glob as _glob
return JSONResponse({
"service": "websocket_hub",
"version": "v2.0",
"status": "running",
"log_files": len(_glob.glob("/app/ranker_logs/*.log")),
"trade_open": len(_trade_parser.get_state()["open"]),
"trade_closed": len(_trade_parser.get_state()["closed"]),
})
'''
ANCHOR = "_START_TIME = time.time()"
def patch(source: str) -> str:
"""Apply both substitutions and return the patched source."""
if ANCHOR not in source:
raise ValueError(
f"Anchor '{ANCHOR}' not found in source β "
"is this the right file?"
)
# Count occurrences so we can give a clear warning if there are multiples.
count = source.count(ANCHOR)
if count > 1:
print(
f"[WARNING] Anchor appears {count} times; "
"only the first occurrence will be modified.",
file=sys.stderr,
)
# ββ Step 1: prepend the parser block before the anchor ββββββββββββββββββββ
src = source.replace(ANCHOR, PARSER_BLOCK + ANCHOR, 1)
# ββ Step 2: append trade routes after the (now-unique) anchor ββββββββββββ
# After step 1, ANCHOR still appears exactly once (at the end of the
# inserted block), so a second replace(β¦, 1) is safe.
src = src.replace(ANCHOR, ANCHOR + TRADE_ROUTES, 1)
return src
def main() -> None:
ap = argparse.ArgumentParser(description="Patch websocket_hub.py with trade routes.")
ap.add_argument(
"--target",
default="/app/websocket_hub.py",
help="Path to websocket_hub.py (default: /app/websocket_hub.py)",
)
ap.add_argument(
"--dry-run",
action="store_true",
help="Print the patched source to stdout instead of writing to disk.",
)
args = ap.parse_args()
target = Path(args.target)
if not target.exists():
print(f"[ERROR] Target file not found: {target}", file=sys.stderr)
sys.exit(1)
original = target.read_text(encoding="utf-8")
# Guard: don't double-patch
if "_trade_parser" in original:
print(
"[SKIP] Patch already applied (_trade_parser already present in file).",
file=sys.stderr,
)
sys.exit(0)
patched = patch(original)
if args.dry_run:
print(patched)
return
# ββ Backup the original βββββββββββββββββββββββββββββββββββββββββββββββββββ
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
backup = target.with_suffix(f".bak_{ts}.py")
shutil.copy2(target, backup)
print(f"[INFO] Backup written β {backup}")
target.write_text(patched, encoding="utf-8")
print(f"[OK] Patch applied β {target}")
print(f" Routes added: /api/trades /api/trades/open /api/trades/closed /api/health")
if __name__ == "__main__":
main()
|