"""公共工具 — 消除各模块间的重复代码。""" from __future__ import annotations from datetime import datetime, timezone from pathlib import Path from zoneinfo import ZoneInfo import httpx from fastapi.templating import Jinja2Templates from app.config import settings # ── 路径常量 ────────────────────────────────────────────────────────── DATA_DIR = Path("data") PAPERS_DIR = DATA_DIR / "papers" TMP_DIR = DATA_DIR / "tmp" # ── 模板单例 ────────────────────────────────────────────────────────── templates = Jinja2Templates(directory="app/templates") # ── 时区工具 ────────────────────────────────────────────────────────── def today_str() -> str: """当前日期字符串(按 APP_TIMEZONE)。""" tz = ZoneInfo(settings.APP_TIMEZONE) return datetime.now(tz).strftime("%Y-%m-%d") # ── 锁释放 ──────────────────────────────────────────────────────────── def release_lock(db, lock) -> None: """释放 TaskLock。""" try: lock.status = "finished" lock.released_at = datetime.now(timezone.utc) db.commit() except Exception: db.rollback() # ── HTTP 客户端工厂 ─────────────────────────────────────────────────── def make_http_client(*, sync: bool = False, follow_redirects: bool = False, **kwargs) -> httpx.AsyncClient | httpx.Client: """创建带 proxy 和默认配置的 httpx 客户端。 Args: sync: True 返回同步 Client,False 返回 AsyncClient follow_redirects: 是否跟随重定向 **kwargs: 覆盖默认参数 """ defaults: dict = { "timeout": settings.HTTP_TIMEOUT_SECONDS, "headers": {"User-Agent": settings.HTTP_USER_AGENT}, "follow_redirects": follow_redirects, } if settings.http_proxy: defaults["transport"] = ( httpx.HTTPTransport(proxy=settings.http_proxy) if sync else httpx.AsyncHTTPTransport(proxy=settings.http_proxy) ) defaults.update(kwargs) if sync: return httpx.Client(**defaults) return httpx.AsyncClient(**defaults)