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.
50 lines
1.6 KiB
Python
Executable File
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)
|