Files
meida_backend/packages/shared/response.py
T
xsl 12583e9739 feat(shared): cross-cutting plumbing and pure domain layer
Typed pydantic-settings config, structlog JSON logging with request/trace id
propagation and redaction of phone numbers, tokens and face keys, the unified
response envelope and error-code table from 架构文档 §9.2, async SQLAlchemy
session management, ORM models for the eight foundation tables, and the
OpenTelemetry/Prometheus wiring.

packages/domain stays pure logic -- snowflake ids, the quota three-phase
protocol and the tk_task state machine -- so it can be tested without a
database and is held to mypy strict.
2026-08-14 00:26:39 +08:00

50 lines
1.6 KiB
Python
Executable File

"""Unified response envelope (架构文档 §9.2).
{"code": 0, "message": "ok", "data": {...}, "request_id": "..."}
{"code": 40301, "message": "今日额度已用完", "data": {...},
"request_id": "...", "hint": "明天恢复,或完成任务多换 3 次"}
Every endpoint returns this shape. Handlers in :mod:`packages.shared.handlers`
guarantee it even for unhandled exceptions.
"""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, Field
from packages.shared.context import get_request_id
from packages.shared.errors import ERROR_META, ErrorCode
class Envelope(BaseModel):
code: int = Field(default=int(ErrorCode.OK), description="0 表示成功;4/5/6 开头为错误段")
message: str = Field(default="ok", description="用户可读文案")
data: Any = Field(default=None)
request_id: str | None = Field(default=None)
hint: str | None = Field(default=None, description="告诉用户下一步怎么办")
def ok(data: Any = None) -> dict[str, Any]:
return Envelope(
code=int(ErrorCode.OK), message="ok", data=data, request_id=get_request_id()
).model_dump(exclude_none=True)
def fail(
code: ErrorCode,
*,
message: str | None = None,
hint: str | None = None,
data: Any = None,
) -> dict[str, Any]:
meta = ERROR_META.get(code, ERROR_META[ErrorCode.INTERNAL_ERROR])
return Envelope(
code=int(code),
message=message or meta.message,
data=data,
request_id=get_request_id(),
hint=hint if hint is not None else meta.hint,
).model_dump(exclude_none=True)