Skip to content

Commit

Permalink
Replace datetime.datetime with DateTime across codebase (#1285)
Browse files Browse the repository at this point in the history
* #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.
  • Loading branch information
JrooTJunior committed Aug 27, 2023
1 parent 397f30b commit 6eb5ef2
Show file tree
Hide file tree
Showing 25 changed files with 65 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .butcher/types/Chat/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
emoji_status_expiration_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
2 changes: 1 addition & 1 deletion .butcher/types/ChatInviteLink/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
expire_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
2 changes: 1 addition & 1 deletion .butcher/types/ChatJoinRequest/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
2 changes: 1 addition & 1 deletion .butcher/types/ChatMember/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
until_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
2 changes: 1 addition & 1 deletion .butcher/types/ChatMemberBanned/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
until_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
2 changes: 1 addition & 1 deletion .butcher/types/ChatMemberRestricted/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
until_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
2 changes: 1 addition & 1 deletion .butcher/types/ChatMemberUpdated/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
4 changes: 2 additions & 2 deletions .butcher/types/Message/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion .butcher/types/Poll/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
close_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
2 changes: 1 addition & 1 deletion .butcher/types/VideoChatScheduled/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ annotations:
start_date:
parsed_type:
type: std
name: datetime.datetime
name: DateTime
2 changes: 1 addition & 1 deletion .butcher/types/WebhookInfo/replace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 2 additions & 0 deletions CHANGES/1277.bugfix.rst
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions aiogram/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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
Expand Down Expand Up @@ -197,6 +198,7 @@
"ChosenInlineResult",
"Contact",
"ContentType",
"DateTime",
"Dice",
"Document",
"Downloadable",
Expand Down
5 changes: 3 additions & 2 deletions aiogram/types/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import TYPE_CHECKING, Any, List, Optional, Union

from .base import TelegramObject
from .custom import DateTime

if TYPE_CHECKING:
from ..methods import (
Expand Down Expand Up @@ -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`."""
Expand Down Expand Up @@ -126,7 +127,7 @@ def __init__(
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,
Expand Down
6 changes: 3 additions & 3 deletions aiogram/types/chat_invite_link.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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"""
Expand All @@ -48,7 +48,7 @@ def __init__(
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,
Expand Down
5 changes: 3 additions & 2 deletions aiogram/types/chat_join_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
UNSET_PROTECT_CONTENT,
TelegramObject,
)
from .custom import DateTime

if TYPE_CHECKING:
from ..methods import (
Expand Down Expand Up @@ -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."""
Expand All @@ -80,7 +81,7 @@ def __init__(
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,
Expand Down
6 changes: 3 additions & 3 deletions aiogram/types/chat_member_banned.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand All @@ -33,7 +33,7 @@ def __init__(
*,
status: Literal[ChatMemberStatus.KICKED] = ChatMemberStatus.KICKED,
user: User,
until_date: datetime.datetime,
until_date: DateTime,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
Expand Down
6 changes: 3 additions & 3 deletions aiogram/types/chat_member_restricted.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -78,7 +78,7 @@ def __init__(
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!!!
Expand Down
5 changes: 3 additions & 2 deletions aiogram/types/chat_member_updated.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
UNSET_PROTECT_CONTENT,
TelegramObject,
)
from .custom import DateTime

if TYPE_CHECKING:
from ..methods import (
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -99,7 +100,7 @@ def __init__(
*,
chat: Chat,
from_user: User,
date: datetime.datetime,
date: DateTime,
old_chat_member: Union[
ChatMemberOwner,
ChatMemberAdministrator,
Expand Down
14 changes: 14 additions & 0 deletions aiogram/types/custom.py
Original file line number Diff line number Diff line change
@@ -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",
),
]
9 changes: 5 additions & 4 deletions aiogram/types/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
UNSET_PROTECT_CONTENT,
TelegramObject,
)
from .custom import DateTime

if TYPE_CHECKING:
from ..methods import (
Expand Down Expand Up @@ -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"""
Expand All @@ -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"""
Expand Down Expand Up @@ -260,7 +261,7 @@ def __init__(
__pydantic__self__,
*,
message_id: int,
date: datetime.datetime,
date: DateTime,
chat: Chat,
message_thread_id: Optional[int] = None,
from_user: Optional[User] = None,
Expand All @@ -270,7 +271,7 @@ def __init__(
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,
Expand Down
6 changes: 3 additions & 3 deletions aiogram/types/poll.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -63,7 +63,7 @@ def __init__(
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!!!
Expand Down
6 changes: 3 additions & 3 deletions aiogram/types/video_chat_scheduled.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -13,15 +13,15 @@ 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:
# DO NOT EDIT MANUALLY!!!
# 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`
Expand Down

0 comments on commit 6eb5ef2

Please sign in to comment.