mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
feat: add FastAPI manager API compatibility baseline
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import re
|
||||
from collections.abc import Mapping, Sequence
|
||||
from datetime import date, datetime, time
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.core.config import get_settings
|
||||
|
||||
_SNAKE_PART = re.compile(r"_([a-zA-Z0-9])")
|
||||
_LONG_FIELD_NAMES = {
|
||||
"id",
|
||||
"userId",
|
||||
"creator",
|
||||
"updater",
|
||||
"createUserId",
|
||||
"updateUserId",
|
||||
"createDateTimestamp",
|
||||
"createTime",
|
||||
"createTimeFrom",
|
||||
"createTimeTo",
|
||||
"fileSize",
|
||||
"lastConnectedAtTimestamp",
|
||||
"pid",
|
||||
"reportTime",
|
||||
"size",
|
||||
"timestamp",
|
||||
"tokenCount",
|
||||
"tokenNum",
|
||||
"totalDocCount",
|
||||
"totalTokenCount",
|
||||
"updateTime",
|
||||
}
|
||||
|
||||
|
||||
class JavaMap(dict[str, Any]):
|
||||
"""Marker for Java ``Map`` payloads whose keys Jackson leaves untouched."""
|
||||
|
||||
|
||||
def preserve_java_map_keys(value: Any) -> Any:
|
||||
"""Recursively mark a dynamic Java Map/List graph as key-preserving."""
|
||||
|
||||
if isinstance(value, Mapping):
|
||||
return JavaMap({str(key): preserve_java_map_keys(item) for key, item in value.items()})
|
||||
if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
|
||||
return [preserve_java_map_keys(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def snake_to_camel(value: str) -> str:
|
||||
return _SNAKE_PART.sub(lambda match: match.group(1).upper(), value)
|
||||
|
||||
|
||||
def _is_long_field(name: str | None) -> bool:
|
||||
if not name:
|
||||
return False
|
||||
return name in _LONG_FIELD_NAMES or name.endswith("Id") or name.endswith("Ids")
|
||||
|
||||
|
||||
def java_compatible(value: Any, *, field_name: str | None = None) -> Any:
|
||||
if value is None or isinstance(value, str | bool | float):
|
||||
return value
|
||||
if isinstance(value, BaseModel):
|
||||
return java_compatible(value.model_dump(by_alias=True, exclude_unset=False), field_name=field_name)
|
||||
if dataclasses.is_dataclass(value) and not isinstance(value, type):
|
||||
return java_compatible(dataclasses.asdict(value), field_name=field_name)
|
||||
if isinstance(value, Enum):
|
||||
return java_compatible(value.value, field_name=field_name)
|
||||
if isinstance(value, datetime):
|
||||
timezone = ZoneInfo(get_settings().timezone)
|
||||
localized = value.astimezone(timezone) if value.tzinfo else value
|
||||
return localized.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if isinstance(value, date):
|
||||
return value.strftime("%Y-%m-%d")
|
||||
if isinstance(value, time):
|
||||
return value.strftime("%H:%M:%S")
|
||||
if isinstance(value, Decimal):
|
||||
return float(value)
|
||||
if isinstance(value, int):
|
||||
return str(value) if _is_long_field(field_name) or not -(2**31) <= value < 2**31 else value
|
||||
if isinstance(value, bytes):
|
||||
return value
|
||||
if isinstance(value, Path):
|
||||
return str(value)
|
||||
if isinstance(value, JavaMap):
|
||||
return {
|
||||
str(raw_key): java_compatible(item, field_name=snake_to_camel(str(raw_key)))
|
||||
for raw_key, item in value.items()
|
||||
}
|
||||
if isinstance(value, Mapping):
|
||||
result: dict[str, Any] = {}
|
||||
for raw_key, item in value.items():
|
||||
key = snake_to_camel(str(raw_key))
|
||||
result[key] = java_compatible(item, field_name=key)
|
||||
return result
|
||||
if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
|
||||
return [java_compatible(item, field_name=field_name) for item in value]
|
||||
return value
|
||||
Reference in New Issue
Block a user