mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Add serialization utilities and update documentation (#1515)
* Add serialization utilities and update documentation Introduced utilities to deserialize Telegram objects to JSON-compliant Python objects and vice versa. These utilities manage both cases with and without files. The documentation has been updated to reflect these changes, including updates in migration recommendations and tutorials. A new unit test is added to verify the new functionality. * Fixed Must-die implementation of the datetime serialization * Fixed `TypeError: can't subtract offset-naive and offset-aware datetimes`
This commit is contained in:
parent
1f7bbeb355
commit
1888039cee
7 changed files with 213 additions and 10 deletions
|
|
@ -1,14 +1,31 @@
|
|||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from datetime import timezone
|
||||
from pydantic import PlainSerializer
|
||||
from typing_extensions import Annotated
|
||||
|
||||
if sys.platform == "win32": # pragma: no cover
|
||||
|
||||
def _datetime_serializer(value: datetime) -> int:
|
||||
tz = timezone.utc if value.tzinfo else None
|
||||
|
||||
# https://github.com/aiogram/aiogram/issues/349
|
||||
# https://github.com/aiogram/aiogram/pull/880
|
||||
return round((value - datetime(1970, 1, 1, tzinfo=tz)).total_seconds())
|
||||
|
||||
else: # pragma: no cover
|
||||
|
||||
def _datetime_serializer(value: datetime) -> int:
|
||||
return round(value.timestamp())
|
||||
|
||||
|
||||
# Make datetime compatible with Telegram Bot API (unixtime)
|
||||
DateTime = Annotated[
|
||||
datetime,
|
||||
PlainSerializer(
|
||||
func=lambda dt: int(dt.timestamp()),
|
||||
func=_datetime_serializer,
|
||||
return_type=int,
|
||||
when_used="json-unless-none",
|
||||
when_used="unless-none",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
89
aiogram/utils/serialization.py
Normal file
89
aiogram/utils/serialization.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.client.default import DefaultBotProperties
|
||||
from aiogram.methods import TelegramMethod
|
||||
from aiogram.types import InputFile
|
||||
|
||||
|
||||
def _get_fake_bot(default: Optional[DefaultBotProperties] = None) -> Bot:
|
||||
if default is None:
|
||||
default = DefaultBotProperties()
|
||||
return Bot(token="42:Fake", default=default)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeserializedTelegramObject:
|
||||
"""
|
||||
Represents a dumped Telegram object.
|
||||
|
||||
:param data: The dumped data of the Telegram object.
|
||||
:type data: Any
|
||||
:param files: The dictionary containing the file names as keys
|
||||
and the corresponding `InputFile` objects as values.
|
||||
:type files: Dict[str, InputFile]
|
||||
"""
|
||||
|
||||
data: Any
|
||||
files: Dict[str, InputFile]
|
||||
|
||||
|
||||
def deserialize_telegram_object(
|
||||
obj: Any,
|
||||
default: Optional[DefaultBotProperties] = None,
|
||||
include_api_method_name: bool = True,
|
||||
) -> DeserializedTelegramObject:
|
||||
"""
|
||||
Deserialize Telegram Object to JSON compatible Python object.
|
||||
|
||||
:param obj: The object to be deserialized.
|
||||
:param default: Default bot properties
|
||||
should be passed only if you want to use custom defaults.
|
||||
:param include_api_method_name: Whether to include the API method name in the result.
|
||||
:return: The deserialized Telegram object.
|
||||
"""
|
||||
extends = {}
|
||||
if include_api_method_name and isinstance(obj, TelegramMethod):
|
||||
extends["method"] = obj.__api_method__
|
||||
|
||||
if isinstance(obj, BaseModel):
|
||||
obj = obj.model_dump(mode="python", warnings=False)
|
||||
|
||||
# Fake bot is needed to exclude global defaults from the object.
|
||||
fake_bot = _get_fake_bot(default=default)
|
||||
|
||||
files: Dict[str, InputFile] = {}
|
||||
prepared = fake_bot.session.prepare_value(
|
||||
obj,
|
||||
bot=fake_bot,
|
||||
files=files,
|
||||
_dumps_json=False,
|
||||
)
|
||||
|
||||
if isinstance(prepared, dict):
|
||||
prepared.update(extends)
|
||||
return DeserializedTelegramObject(data=prepared, files=files)
|
||||
|
||||
|
||||
def deserialize_telegram_object_to_python(
|
||||
obj: Any,
|
||||
default: Optional[DefaultBotProperties] = None,
|
||||
include_api_method_name: bool = True,
|
||||
) -> Any:
|
||||
"""
|
||||
Deserialize telegram object to JSON compatible Python object excluding files.
|
||||
|
||||
:param obj: The telegram object to be deserialized.
|
||||
:param default: Default bot properties
|
||||
should be passed only if you want to use custom defaults.
|
||||
:param include_api_method_name: Whether to include the API method name in the result.
|
||||
:return: The deserialized telegram object.
|
||||
"""
|
||||
return deserialize_telegram_object(
|
||||
obj,
|
||||
default=default,
|
||||
include_api_method_name=include_api_method_name,
|
||||
).data
|
||||
Loading…
Add table
Add a link
Reference in a new issue