- 新增 SQLite:measure/health 快照、delivery_cursor 单消费者 pop;clear/start_fresh 可清空库 - biomass GET 仅返回约定 data 字段,X-Fish-Biomass-New 表示是否有新快照;poller 读响应头 - loguru 桥接 uvicorn,子进程 stdout 流式输出;format_json_pretty 与算法摘要日志 - measure/action watch 无新任务时限流 WARNING;watch_idle 共用逻辑 - 依赖 loguru;新增 db、logging_config、subprocess_run、watch_idle、启动脚本 FishMeasure: 更新 fish_video_weight_evaluation 与 predict_weigth_from_svo2;移除未用 refbox/segmentation 脚本 Made-with: Cursor
100 lines
3.1 KiB
Python
Executable File
100 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
独立进程:轮询 Fish API 的两个结果接口,用 loguru 输出响应。
|
||
|
||
接口每次 GET 会「消费」一条未投递快照;仅当响应头 X-Fish-Biomass-New: 1(或 JSON code!=200)时打日志。
|
||
|
||
BIOMASS_API_BASE=http://127.0.0.1:8000 POLL_INTERVAL=5 \\
|
||
python scripts/biomass_poller.py
|
||
|
||
依赖(与 fish_api dev 组一致):httpx、loguru
|
||
cd fish_api && pip install -e ".[dev]"
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import asyncio
|
||
import json
|
||
import os
|
||
import sys
|
||
from typing import Any
|
||
|
||
import httpx
|
||
from loguru import logger
|
||
|
||
|
||
def _fmt_body(resp: httpx.Response) -> Any:
|
||
try:
|
||
return resp.json()
|
||
except Exception:
|
||
return resp.text
|
||
|
||
|
||
def _should_log(resp: httpx.Response, body: Any) -> bool:
|
||
"""有新投递的快照(X-Fish-Biomass-New: 1)或业务错误(JSON code!=200)时打日志。"""
|
||
if not isinstance(body, dict):
|
||
return bool(body)
|
||
if body.get("code") != 200:
|
||
return True
|
||
return resp.headers.get("X-Fish-Biomass-New", "").strip() == "1"
|
||
|
||
|
||
async def poll_once(client: httpx.AsyncClient, base: str) -> None:
|
||
base = base.rstrip("/")
|
||
camera_url = f"{base}/api/v1/biomass/real/camera/"
|
||
health_url = f"{base}/api/v1/biomass/health/result/"
|
||
r1 = await client.get(camera_url)
|
||
r2 = await client.get(health_url)
|
||
b1 = _fmt_body(r1)
|
||
b2 = _fmt_body(r2)
|
||
if _should_log(r1, b1):
|
||
logger.info("[real/camera/] HTTP {} | {}", r1.status_code, json.dumps(b1, ensure_ascii=False))
|
||
if _should_log(r2, b2):
|
||
logger.info("[health/result/] HTTP {} | {}", r2.status_code, json.dumps(b2, ensure_ascii=False))
|
||
|
||
|
||
async def poll_loop(base: str, interval: float) -> None:
|
||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||
while True:
|
||
try:
|
||
await poll_once(client, base)
|
||
except Exception:
|
||
logger.exception("poll round failed")
|
||
await asyncio.sleep(max(interval, 0.5))
|
||
|
||
|
||
def main() -> None:
|
||
parser = argparse.ArgumentParser(description="Poll Fish API biomass GET endpoints.")
|
||
parser.add_argument(
|
||
"--base-url",
|
||
default=os.environ.get("BIOMASS_API_BASE", "http://127.0.0.1:8000"),
|
||
help="Fish API 根 URL(默认 BIOMASS_API_BASE 或 http://127.0.0.1:8000)",
|
||
)
|
||
parser.add_argument(
|
||
"--interval",
|
||
type=float,
|
||
default=float(os.environ.get("POLL_INTERVAL", "5")),
|
||
help="轮询间隔秒数(默认 POLL_INTERVAL 或 5)",
|
||
)
|
||
args = parser.parse_args()
|
||
|
||
logger.remove()
|
||
logger.add(
|
||
sys.stderr,
|
||
level=os.environ.get("LOG_LEVEL", "INFO"),
|
||
format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
||
"<level>{level: <8}</level> | "
|
||
"<level>{message}</level>",
|
||
)
|
||
logger.info(
|
||
"biomass_poller 启动 | base={} | interval={}s | GET /api/v1/biomass/real/camera/ 与 /health/result/",
|
||
args.base_url.rstrip("/"),
|
||
args.interval,
|
||
)
|
||
asyncio.run(poll_loop(args.base_url, args.interval))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|