Replace datetime.datetime with DateTime across codebase (#1285)

* #1277  Replace datetime.datetime with DateTime across codebase

Replaced all instances of standard library 'datetime.datetime' with a new 'DateTime' type from `.custom` module. This change is necessary to make all date-time values compatible with the Telegram Bot API (it uses Unix time). This will simplify the conversion process and eliminate potential errors related to date-time format mismatches. Changed codebase, butcher files, and modified 'pyproject.toml' to shift the typing-extensions dependency. The 'aiogram/custom_types.py' file was renamed to 'aiogram/types/custom.py' to better reflect its nature as a location for custom types used in the aiogram library.
This commit is contained in:
Alex Root Junior 2023-08-27 17:09:56 +03:00 committed by GitHub
parent 397f30b58b
commit 6eb5ef2606
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 65 additions and 43 deletions

View file

@ -2,4 +2,4 @@ annotations:
emoji_status_expiration_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,4 +2,4 @@ annotations:
expire_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,4 +2,4 @@ annotations:
date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,4 +2,4 @@ annotations:
until_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,4 +2,4 @@ annotations:
until_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,4 +2,4 @@ annotations:
until_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,4 +2,4 @@ annotations:
date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,8 +2,8 @@ annotations:
date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
forward_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,4 +2,4 @@ annotations:
close_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,4 +2,4 @@ annotations:
start_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime

View file

@ -2,5 +2,5 @@ annotations:
last_error_date: &date
parsed_type:
type: std
name: datetime.datetime
name: DateTime
last_synchronization_error_date: *date

2
CHANGES/1277.bugfix.rst Normal file
View file

@ -0,0 +1,2 @@
Replaced :code:`datetime.datetime` with `DateTime` type wrapper across types to make dumped JSONs object
more compatible with data that is sent by Telegram.

View file

@ -37,6 +37,7 @@ from .chat_photo import ChatPhoto
from .chat_shared import ChatShared
from .chosen_inline_result import ChosenInlineResult
from .contact import Contact
from .custom import DateTime
from .dice import Dice
from .document import Document
from .downloadable import Downloadable
@ -197,6 +198,7 @@ __all__ = (
"ChosenInlineResult",
"Contact",
"ContentType",
"DateTime",
"Dice",
"Document",
"Downloadable",

View file

@ -4,6 +4,7 @@ import datetime
from typing import TYPE_CHECKING, Any, List, Optional, Union
from .base import TelegramObject
from .custom import DateTime
if TYPE_CHECKING:
from ..methods import (
@ -70,7 +71,7 @@ class Chat(TelegramObject):
"""*Optional*. If non-empty, the list of all `active chat usernames <https://telegram.org/blog/topics-in-groups-collectible-usernames#collectible-usernames>`_; for private chats, supergroups and channels. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
emoji_status_custom_emoji_id: Optional[str] = None
"""*Optional*. Custom emoji identifier of emoji status of the other party in a private chat. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
emoji_status_expiration_date: Optional[datetime.datetime] = None
emoji_status_expiration_date: Optional[DateTime] = None
"""*Optional*. Expiration date of the emoji status of the other party in a private chat, if any. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
bio: Optional[str] = None
"""*Optional*. Bio of the other party in a private chat. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
@ -126,7 +127,7 @@ class Chat(TelegramObject):
photo: Optional[ChatPhoto] = None,
active_usernames: Optional[List[str]] = None,
emoji_status_custom_emoji_id: Optional[str] = None,
emoji_status_expiration_date: Optional[datetime.datetime] = None,
emoji_status_expiration_date: Optional[DateTime] = None,
bio: Optional[str] = None,
has_private_forwards: Optional[bool] = None,
has_restricted_voice_and_video_messages: Optional[bool] = None,

View file

@ -1,9 +1,9 @@
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, Optional
from .base import TelegramObject
from .custom import DateTime
if TYPE_CHECKING:
from .user import User
@ -28,7 +28,7 @@ class ChatInviteLink(TelegramObject):
""":code:`True`, if the link is revoked"""
name: Optional[str] = None
"""*Optional*. Invite link name"""
expire_date: Optional[datetime.datetime] = None
expire_date: Optional[DateTime] = None
"""*Optional*. Point in time (Unix timestamp) when the link will expire or has been expired"""
member_limit: Optional[int] = None
"""*Optional*. The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999"""
@ -48,7 +48,7 @@ class ChatInviteLink(TelegramObject):
is_primary: bool,
is_revoked: bool,
name: Optional[str] = None,
expire_date: Optional[datetime.datetime] = None,
expire_date: Optional[DateTime] = None,
member_limit: Optional[int] = None,
pending_join_request_count: Optional[int] = None,
**__pydantic_kwargs: Any,

View file

@ -11,6 +11,7 @@ from .base import (
UNSET_PROTECT_CONTENT,
TelegramObject,
)
from .custom import DateTime
if TYPE_CHECKING:
from ..methods import (
@ -63,7 +64,7 @@ class ChatJoinRequest(TelegramObject):
"""User that sent the join request"""
user_chat_id: int
"""Identifier of a private chat with the user who sent the join request. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a 64-bit integer or double-precision float type are safe for storing this identifier. The bot can use this identifier for 24 hours to send messages until the join request is processed, assuming no other administrator contacted the user."""
date: datetime.datetime
date: DateTime
"""Date the request was sent in Unix time"""
bio: Optional[str] = None
"""*Optional*. Bio of the user."""
@ -80,7 +81,7 @@ class ChatJoinRequest(TelegramObject):
chat: Chat,
from_user: User,
user_chat_id: int,
date: datetime.datetime,
date: DateTime,
bio: Optional[str] = None,
invite_link: Optional[ChatInviteLink] = None,
**__pydantic_kwargs: Any,

View file

@ -1,10 +1,10 @@
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, Literal
from ..enums import ChatMemberStatus
from .chat_member import ChatMember
from .custom import DateTime
if TYPE_CHECKING:
from .user import User
@ -21,7 +21,7 @@ class ChatMemberBanned(ChatMember):
"""The member's status in the chat, always 'kicked'"""
user: User
"""Information about the user"""
until_date: datetime.datetime
until_date: DateTime
"""Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever"""
if TYPE_CHECKING:
@ -33,7 +33,7 @@ class ChatMemberBanned(ChatMember):
*,
status: Literal[ChatMemberStatus.KICKED] = ChatMemberStatus.KICKED,
user: User,
until_date: datetime.datetime,
until_date: DateTime,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!

View file

@ -1,10 +1,10 @@
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, Literal
from ..enums import ChatMemberStatus
from .chat_member import ChatMember
from .custom import DateTime
if TYPE_CHECKING:
from .user import User
@ -51,7 +51,7 @@ class ChatMemberRestricted(ChatMember):
""":code:`True`, if the user is allowed to pin messages"""
can_manage_topics: bool
""":code:`True`, if the user is allowed to create forum topics"""
until_date: datetime.datetime
until_date: DateTime
"""Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever"""
if TYPE_CHECKING:
@ -78,7 +78,7 @@ class ChatMemberRestricted(ChatMember):
can_invite_users: bool,
can_pin_messages: bool,
can_manage_topics: bool,
until_date: datetime.datetime,
until_date: DateTime,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!

View file

@ -11,6 +11,7 @@ from .base import (
UNSET_PROTECT_CONTENT,
TelegramObject,
)
from .custom import DateTime
if TYPE_CHECKING:
from ..methods import (
@ -65,7 +66,7 @@ class ChatMemberUpdated(TelegramObject):
"""Chat the user belongs to"""
from_user: User = Field(..., alias="from")
"""Performer of the action, which resulted in the change"""
date: datetime.datetime
date: DateTime
"""Date the change was done in Unix time"""
old_chat_member: Union[
ChatMemberOwner,
@ -99,7 +100,7 @@ class ChatMemberUpdated(TelegramObject):
*,
chat: Chat,
from_user: User,
date: datetime.datetime,
date: DateTime,
old_chat_member: Union[
ChatMemberOwner,
ChatMemberAdministrator,

14
aiogram/types/custom.py Normal file
View file

@ -0,0 +1,14 @@
from datetime import datetime
from pydantic import PlainSerializer
from typing_extensions import Annotated
# Make datetime compatible with Telegram Bot API (unixtime)
DateTime = Annotated[
datetime,
PlainSerializer(
func=lambda dt: int(dt.timestamp()),
return_type=int,
when_used="json-unless-none",
),
]

View file

@ -18,6 +18,7 @@ from .base import (
UNSET_PROTECT_CONTENT,
TelegramObject,
)
from .custom import DateTime
if TYPE_CHECKING:
from ..methods import (
@ -109,7 +110,7 @@ class Message(TelegramObject):
message_id: int
"""Unique message identifier inside this chat"""
date: datetime.datetime
date: DateTime
"""Date the message was sent in Unix time"""
chat: Chat
"""Conversation the message belongs to"""
@ -129,7 +130,7 @@ class Message(TelegramObject):
"""*Optional*. For forwarded messages that were originally sent in channels or by an anonymous chat administrator, signature of the message sender if present"""
forward_sender_name: Optional[str] = None
"""*Optional*. Sender's name for messages forwarded from users who disallow adding a link to their account in forwarded messages"""
forward_date: Optional[datetime.datetime] = None
forward_date: Optional[DateTime] = None
"""*Optional*. For forwarded messages, date the original message was sent in Unix time"""
is_topic_message: Optional[bool] = None
"""*Optional*. :code:`True`, if the message is sent to a forum topic"""
@ -260,7 +261,7 @@ class Message(TelegramObject):
__pydantic__self__,
*,
message_id: int,
date: datetime.datetime,
date: DateTime,
chat: Chat,
message_thread_id: Optional[int] = None,
from_user: Optional[User] = None,
@ -270,7 +271,7 @@ class Message(TelegramObject):
forward_from_message_id: Optional[int] = None,
forward_signature: Optional[str] = None,
forward_sender_name: Optional[str] = None,
forward_date: Optional[datetime.datetime] = None,
forward_date: Optional[DateTime] = None,
is_topic_message: Optional[bool] = None,
is_automatic_forward: Optional[bool] = None,
reply_to_message: Optional[Message] = None,

View file

@ -1,9 +1,9 @@
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, List, Optional
from .base import TelegramObject
from .custom import DateTime
if TYPE_CHECKING:
from .message_entity import MessageEntity
@ -41,7 +41,7 @@ class Poll(TelegramObject):
"""*Optional*. Special entities like usernames, URLs, bot commands, etc. that appear in the *explanation*"""
open_period: Optional[int] = None
"""*Optional*. Amount of time in seconds the poll will be active after creation"""
close_date: Optional[datetime.datetime] = None
close_date: Optional[DateTime] = None
"""*Optional*. Point in time (Unix timestamp) when the poll will be automatically closed"""
if TYPE_CHECKING:
@ -63,7 +63,7 @@ class Poll(TelegramObject):
explanation: Optional[str] = None,
explanation_entities: Optional[List[MessageEntity]] = None,
open_period: Optional[int] = None,
close_date: Optional[datetime.datetime] = None,
close_date: Optional[DateTime] = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!

View file

@ -1,9 +1,9 @@
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any
from .base import TelegramObject
from .custom import DateTime
class VideoChatScheduled(TelegramObject):
@ -13,7 +13,7 @@ class VideoChatScheduled(TelegramObject):
Source: https://core.telegram.org/bots/api#videochatscheduled
"""
start_date: datetime.datetime
start_date: DateTime
"""Point in time (Unix timestamp) when the video chat is supposed to be started by a chat administrator"""
if TYPE_CHECKING:
@ -21,7 +21,7 @@ class VideoChatScheduled(TelegramObject):
# This section was auto-generated via `butcher`
def __init__(
__pydantic__self__, *, start_date: datetime.datetime, **__pydantic_kwargs: Any
__pydantic__self__, *, start_date: DateTime, **__pydantic_kwargs: Any
) -> None:
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`

View file

@ -1,9 +1,9 @@
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, List, Optional
from .base import TelegramObject
from .custom import DateTime
class WebhookInfo(TelegramObject):
@ -21,11 +21,11 @@ class WebhookInfo(TelegramObject):
"""Number of updates awaiting delivery"""
ip_address: Optional[str] = None
"""*Optional*. Currently used webhook IP address"""
last_error_date: Optional[datetime.datetime] = None
last_error_date: Optional[DateTime] = None
"""*Optional*. Unix time for the most recent error that happened when trying to deliver an update via webhook"""
last_error_message: Optional[str] = None
"""*Optional*. Error message in human-readable format for the most recent error that happened when trying to deliver an update via webhook"""
last_synchronization_error_date: Optional[datetime.datetime] = None
last_synchronization_error_date: Optional[DateTime] = None
"""*Optional*. Unix time of the most recent error that happened when trying to synchronize available updates with Telegram datacenters"""
max_connections: Optional[int] = None
"""*Optional*. The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery"""
@ -43,9 +43,9 @@ class WebhookInfo(TelegramObject):
has_custom_certificate: bool,
pending_update_count: int,
ip_address: Optional[str] = None,
last_error_date: Optional[datetime.datetime] = None,
last_error_date: Optional[DateTime] = None,
last_error_message: Optional[str] = None,
last_synchronization_error_date: Optional[datetime.datetime] = None,
last_synchronization_error_date: Optional[DateTime] = None,
max_connections: Optional[int] = None,
allowed_updates: Optional[List[str]] = None,
**__pydantic_kwargs: Any,

View file

@ -45,6 +45,7 @@ dependencies = [
"pydantic>=2.1.1,<3",
"aiofiles~=23.1.0",
"certifi>=2023.7.22",
"typing-extensions~=4.7.1",
]
dynamic = ["version"]
@ -102,7 +103,6 @@ dev = [
"pre-commit~=3.3.3",
"towncrier~=23.6.0",
"packaging~=23.0",
"typing-extensions~=4.7.1",
]
[project.urls]