mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 06:25:12 +01:00
Localize Received datetime
Objects According to Defaults.tzinfo
(#3632)
This commit is contained in:
parent
934e4c9bd4
commit
7b116be344
21 changed files with 376 additions and 40 deletions
|
@ -72,6 +72,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `Li-aung Yip <https://github.com/LiaungYip>`_
|
||||
- `Loo Zheng Yuan <https://github.com/loozhengyuan>`_
|
||||
- `LRezende <https://github.com/lrezende>`_
|
||||
- `Luca Bellanti <https://github.com/Trifase>`_
|
||||
- `macrojames <https://github.com/macrojames>`_
|
||||
- `Matheus Lemos <https://github.com/mlemosf>`_
|
||||
- `Michael Dix <https://github.com/Eisberge>`_
|
||||
|
|
|
@ -55,3 +55,5 @@
|
|||
.. |sequenceargs| replace:: Accepts any :class:`collections.abc.Sequence` as input instead of just a list.
|
||||
|
||||
.. |captionentitiesattr| replace:: Tuple of special entities that appear in the caption, which can be specified instead of ``parse_mode``.
|
||||
|
||||
.. |datetime_localization| replace:: The default timezone of the bot is used for localization, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.
|
||||
|
|
|
@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Optional
|
|||
|
||||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._user import User
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -54,6 +54,9 @@ class ChatInviteLink(TelegramObject):
|
|||
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
|
||||
expire_date (:class:`datetime.datetime`, optional): Date when the link will expire or
|
||||
has been expired.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
member_limit (:obj:`int`, optional): Maximum number of users that can be members of the
|
||||
chat simultaneously after joining the chat via this invite link;
|
||||
:tg-const:`telegram.constants.ChatInviteLinkLimit.MIN_MEMBER_LIMIT`-
|
||||
|
@ -78,6 +81,9 @@ class ChatInviteLink(TelegramObject):
|
|||
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
|
||||
expire_date (:class:`datetime.datetime`): Optional. Date when the link will expire or
|
||||
has been expired.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
member_limit (:obj:`int`): Optional. Maximum number of users that can be members
|
||||
of the chat simultaneously after joining the chat via this invite link;
|
||||
:tg-const:`telegram.constants.ChatInviteLinkLimit.MIN_MEMBER_LIMIT`-
|
||||
|
@ -152,7 +158,10 @@ class ChatInviteLink(TelegramObject):
|
|||
if not data:
|
||||
return None
|
||||
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
|
||||
data["creator"] = User.de_json(data.get("creator"), bot)
|
||||
data["expire_date"] = from_timestamp(data.get("expire_date", None))
|
||||
data["expire_date"] = from_timestamp(data.get("expire_date", None), tzinfo=loc_tzinfo)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
|
|
@ -24,7 +24,7 @@ from telegram._chat import Chat
|
|||
from telegram._chatinvitelink import ChatInviteLink
|
||||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._user import User
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.defaultvalue import DEFAULT_NONE
|
||||
from telegram._utils.types import JSONDict, ODVInput
|
||||
|
||||
|
@ -56,6 +56,9 @@ class ChatJoinRequest(TelegramObject):
|
|||
chat (:class:`telegram.Chat`): Chat to which the request was sent.
|
||||
from_user (:class:`telegram.User`): User that sent the join request.
|
||||
date (:class:`datetime.datetime`): Date the request was sent.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
user_chat_id (:obj:`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
|
||||
|
@ -73,6 +76,9 @@ class ChatJoinRequest(TelegramObject):
|
|||
chat (:class:`telegram.Chat`): Chat to which the request was sent.
|
||||
from_user (:class:`telegram.User`): User that sent the join request.
|
||||
date (:class:`datetime.datetime`): Date the request was sent.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
user_chat_id (:obj:`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
|
||||
|
@ -124,9 +130,12 @@ class ChatJoinRequest(TelegramObject):
|
|||
if not data:
|
||||
return None
|
||||
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
|
||||
data["chat"] = Chat.de_json(data.get("chat"), bot)
|
||||
data["from_user"] = User.de_json(data.pop("from", None), bot)
|
||||
data["date"] = from_timestamp(data.get("date", None))
|
||||
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
|
||||
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
|
|
@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type
|
|||
from telegram import constants
|
||||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._user import User
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -125,7 +125,10 @@ class ChatMember(TelegramObject):
|
|||
|
||||
data["user"] = User.de_json(data.get("user"), bot)
|
||||
if "until_date" in data:
|
||||
data["until_date"] = from_timestamp(data["until_date"])
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
|
||||
data["until_date"] = from_timestamp(data["until_date"], tzinfo=loc_tzinfo)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
||||
|
@ -386,6 +389,9 @@ class ChatMemberRestricted(ChatMember):
|
|||
.. versionadded:: 20.0
|
||||
until_date (:class:`datetime.datetime`): Date when restrictions
|
||||
will be lifted for this user.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
can_send_audios (:obj:`bool`): :obj:`True`, if the user is allowed to send audios.
|
||||
|
||||
.. versionadded:: 20.1
|
||||
|
@ -438,6 +444,9 @@ class ChatMemberRestricted(ChatMember):
|
|||
.. versionadded:: 20.0
|
||||
until_date (:class:`datetime.datetime`): Date when restrictions
|
||||
will be lifted for this user.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
can_send_audios (:obj:`bool`): :obj:`True`, if the user is allowed to send audios.
|
||||
|
||||
.. versionadded:: 20.1
|
||||
|
@ -565,6 +574,9 @@ class ChatMemberBanned(ChatMember):
|
|||
until_date (:class:`datetime.datetime`): Date when restrictions
|
||||
will be lifted for this user.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
always :tg-const:`telegram.ChatMember.BANNED`.
|
||||
|
@ -572,6 +584,9 @@ class ChatMemberBanned(ChatMember):
|
|||
until_date (:class:`datetime.datetime`): Date when restrictions
|
||||
will be lifted for this user.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("until_date",)
|
||||
|
|
|
@ -25,7 +25,7 @@ from telegram._chatinvitelink import ChatInviteLink
|
|||
from telegram._chatmember import ChatMember
|
||||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._user import User
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -52,6 +52,9 @@ class ChatMemberUpdated(TelegramObject):
|
|||
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
|
||||
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
|
||||
:class:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
|
||||
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
|
||||
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used
|
||||
|
@ -62,6 +65,9 @@ class ChatMemberUpdated(TelegramObject):
|
|||
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
|
||||
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
|
||||
:class:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
|
||||
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
|
||||
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used
|
||||
|
@ -118,9 +124,12 @@ class ChatMemberUpdated(TelegramObject):
|
|||
if not data:
|
||||
return None
|
||||
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
|
||||
data["chat"] = Chat.de_json(data.get("chat"), bot)
|
||||
data["from_user"] = User.de_json(data.pop("from", None), bot)
|
||||
data["date"] = from_timestamp(data.get("date"))
|
||||
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
|
||||
data["old_chat_member"] = ChatMember.de_json(data.get("old_chat_member"), bot)
|
||||
data["new_chat_member"] = ChatMember.de_json(data.get("new_chat_member"), bot)
|
||||
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
|
||||
|
|
|
@ -56,7 +56,7 @@ from telegram._shared import ChatShared, UserShared
|
|||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._user import User
|
||||
from telegram._utils.argumentparsing import parse_sequence_arg
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
|
||||
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
|
||||
from telegram._videochat import (
|
||||
|
@ -121,6 +121,9 @@ class Message(TelegramObject):
|
|||
sent on behalf of a chat.
|
||||
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
|
||||
:class:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
chat (:class:`telegram.Chat`): Conversation the message belongs to.
|
||||
forward_from (:class:`telegram.User`, optional): For forwarded messages, sender of
|
||||
the original message.
|
||||
|
@ -132,6 +135,9 @@ class Message(TelegramObject):
|
|||
users who disallow adding a link to their account in forwarded messages.
|
||||
forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the
|
||||
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
is_automatic_forward (:obj:`bool`, optional): :obj:`True`, if the message is a channel
|
||||
post that was automatically forwarded to the connected discussion group.
|
||||
|
||||
|
@ -141,6 +147,9 @@ class Message(TelegramObject):
|
|||
``reply_to_message`` fields even if it itself is a reply.
|
||||
edit_date (:class:`datetime.datetime`, optional): Date the message was last edited in Unix
|
||||
time. Converted to :class:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
has_protected_content (:obj:`bool`, optional): :obj:`True`, if the message can't be
|
||||
forwarded.
|
||||
|
||||
|
@ -338,6 +347,9 @@ class Message(TelegramObject):
|
|||
sent on behalf of a chat.
|
||||
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
|
||||
:class:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
chat (:class:`telegram.Chat`): Conversation the message belongs to.
|
||||
forward_from (:class:`telegram.User`): Optional. For forwarded messages, sender of the
|
||||
original message.
|
||||
|
@ -347,6 +359,9 @@ class Message(TelegramObject):
|
|||
the original message in the channel.
|
||||
forward_date (:class:`datetime.datetime`): Optional. For forwarded messages, date the
|
||||
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
is_automatic_forward (:obj:`bool`): Optional. :obj:`True`, if the message is a channel
|
||||
post that was automatically forwarded to the connected discussion group.
|
||||
|
||||
|
@ -356,6 +371,9 @@ class Message(TelegramObject):
|
|||
``reply_to_message`` fields even if it itself is a reply.
|
||||
edit_date (:class:`datetime.datetime`): Optional. Date the message was last edited in Unix
|
||||
time. Converted to :class:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if the message can't be
|
||||
forwarded.
|
||||
|
||||
|
@ -850,17 +868,20 @@ class Message(TelegramObject):
|
|||
if not data:
|
||||
return None
|
||||
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
|
||||
data["from_user"] = User.de_json(data.pop("from", None), bot)
|
||||
data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)
|
||||
data["date"] = from_timestamp(data["date"])
|
||||
data["date"] = from_timestamp(data["date"], tzinfo=loc_tzinfo)
|
||||
data["chat"] = Chat.de_json(data.get("chat"), bot)
|
||||
data["entities"] = MessageEntity.de_list(data.get("entities"), bot)
|
||||
data["caption_entities"] = MessageEntity.de_list(data.get("caption_entities"), bot)
|
||||
data["forward_from"] = User.de_json(data.get("forward_from"), bot)
|
||||
data["forward_from_chat"] = Chat.de_json(data.get("forward_from_chat"), bot)
|
||||
data["forward_date"] = from_timestamp(data.get("forward_date"))
|
||||
data["forward_date"] = from_timestamp(data.get("forward_date"), tzinfo=loc_tzinfo)
|
||||
data["reply_to_message"] = Message.de_json(data.get("reply_to_message"), bot)
|
||||
data["edit_date"] = from_timestamp(data.get("edit_date"))
|
||||
data["edit_date"] = from_timestamp(data.get("edit_date"), tzinfo=loc_tzinfo)
|
||||
data["audio"] = Audio.de_json(data.get("audio"), bot)
|
||||
data["document"] = Document.de_json(data.get("document"), bot)
|
||||
data["animation"] = Animation.de_json(data.get("animation"), bot)
|
||||
|
|
|
@ -26,7 +26,7 @@ from telegram._telegramobject import TelegramObject
|
|||
from telegram._user import User
|
||||
from telegram._utils import enum
|
||||
from telegram._utils.argumentparsing import parse_sequence_arg
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -173,6 +173,9 @@ class Poll(TelegramObject):
|
|||
close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the
|
||||
poll will be automatically closed. Converted to :obj:`datetime.datetime`.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
|
||||
Attributes:
|
||||
id (:obj:`str`): Unique poll identifier.
|
||||
question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
|
||||
|
@ -206,6 +209,9 @@ class Poll(TelegramObject):
|
|||
close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be
|
||||
automatically closed.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
|
@ -271,9 +277,12 @@ class Poll(TelegramObject):
|
|||
if not data:
|
||||
return None
|
||||
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
|
||||
data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
|
||||
data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
|
||||
data["close_date"] = from_timestamp(data.get("close_date"))
|
||||
data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
||||
|
|
|
@ -29,7 +29,10 @@ Warning:
|
|||
"""
|
||||
import datetime as dtm # skipcq: PYL-W0406
|
||||
import time
|
||||
from typing import Optional, Union
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
# pytz is only available if it was installed as dependency of APScheduler, so we make a little
|
||||
# workaround here
|
||||
|
@ -162,7 +165,10 @@ def to_timestamp(
|
|||
)
|
||||
|
||||
|
||||
def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optional[dtm.datetime]:
|
||||
def from_timestamp(
|
||||
unixtime: Optional[int],
|
||||
tzinfo: Optional[dtm.tzinfo] = None,
|
||||
) -> Optional[dtm.datetime]:
|
||||
"""
|
||||
Converts an (integer) unix timestamp to a timezone aware datetime object.
|
||||
:obj:`None` s are left alone (i.e. ``from_timestamp(None)`` is :obj:`None`).
|
||||
|
@ -170,7 +176,8 @@ def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optiona
|
|||
Args:
|
||||
unixtime (:obj:`int`): Integer POSIX timestamp.
|
||||
tzinfo (:obj:`datetime.tzinfo`, optional): The timezone to which the timestamp is to be
|
||||
converted to. Defaults to UTC.
|
||||
converted to. Defaults to :obj:`None`, in which case the returned datetime object will
|
||||
be timezone aware and in UTC.
|
||||
|
||||
Returns:
|
||||
Timezone aware equivalent :obj:`datetime.datetime` value if :paramref:`unixtime` is not
|
||||
|
@ -179,9 +186,19 @@ def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optiona
|
|||
if unixtime is None:
|
||||
return None
|
||||
|
||||
if tzinfo is not None:
|
||||
return dtm.datetime.fromtimestamp(unixtime, tz=tzinfo)
|
||||
return dtm.datetime.utcfromtimestamp(unixtime)
|
||||
return dtm.datetime.fromtimestamp(unixtime, tz=UTC if tzinfo is None else tzinfo)
|
||||
|
||||
|
||||
def extract_tzinfo_from_defaults(bot: "Bot") -> Union[dtm.tzinfo, None]:
|
||||
"""
|
||||
Extracts the timezone info from the default values of the bot.
|
||||
If the bot has no default values, :obj:`None` is returned.
|
||||
"""
|
||||
# We don't use `ininstance(bot, ExtBot)` here so that this works
|
||||
# in `python-telegram-bot-raw` as well
|
||||
if hasattr(bot, "defaults") and bot.defaults:
|
||||
return bot.defaults.tzinfo
|
||||
return None
|
||||
|
||||
|
||||
def _datetime_to_float_timestamp(dt_obj: dtm.datetime) -> float:
|
||||
|
|
|
@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Optional, Sequence, Tuple
|
|||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._user import User
|
||||
from telegram._utils.argumentparsing import parse_sequence_arg
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -149,10 +149,16 @@ class VideoChatScheduled(TelegramObject):
|
|||
Args:
|
||||
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
|
||||
chat is supposed to be started by a chat administrator
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
Attributes:
|
||||
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
|
||||
chat is supposed to be started by a chat administrator
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("start_date",)
|
||||
|
@ -178,6 +184,9 @@ class VideoChatScheduled(TelegramObject):
|
|||
if not data:
|
||||
return None
|
||||
|
||||
data["start_date"] = from_timestamp(data["start_date"])
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
|
||||
data["start_date"] = from_timestamp(data["start_date"], tzinfo=loc_tzinfo)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
|
|
@ -21,7 +21,7 @@ from typing import TYPE_CHECKING, Optional, Sequence, Tuple
|
|||
|
||||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._utils.argumentparsing import parse_sequence_arg
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -49,8 +49,11 @@ class WebhookInfo(TelegramObject):
|
|||
webhook certificate checks.
|
||||
pending_update_count (:obj:`int`): Number of updates awaiting delivery.
|
||||
ip_address (:obj:`str`, optional): Currently used webhook IP address.
|
||||
last_error_date (:obj:`int`, optional): Unix time for the most recent error that happened
|
||||
when trying to deliver an update via webhook.
|
||||
last_error_date (:class:`datetime.datetime`): Optional. Datetime for the most recent
|
||||
error that happened when trying to deliver an update via webhook.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
last_error_message (:obj:`str`, optional): Error message in human-readable format for the
|
||||
most recent error that happened when trying to deliver an update via webhook.
|
||||
max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS
|
||||
|
@ -62,18 +65,25 @@ class WebhookInfo(TelegramObject):
|
|||
.. versionchanged:: 20.0
|
||||
|sequenceclassargs|
|
||||
|
||||
last_synchronization_error_date (:obj:`int`, optional): Unix time of the most recent error
|
||||
that happened when trying to synchronize available updates with Telegram datacenters.
|
||||
last_synchronization_error_date (:class:`datetime.datetime`, optional): Datetime of the
|
||||
most recent error that happened when trying to synchronize available updates with
|
||||
Telegram datacenters.
|
||||
|
||||
.. versionadded:: 20.0
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
Attributes:
|
||||
url (:obj:`str`): Webhook URL, may be empty if webhook is not set up.
|
||||
has_custom_certificate (:obj:`bool`): :obj:`True`, if a custom certificate was provided for
|
||||
webhook certificate checks.
|
||||
pending_update_count (:obj:`int`): Number of updates awaiting delivery.
|
||||
ip_address (:obj:`str`): Optional. Currently used webhook IP address.
|
||||
last_error_date (:obj:`int`): Optional. Unix time for the most recent error that happened
|
||||
when trying to deliver an update via webhook.
|
||||
last_error_date (:class:`datetime.datetime`): Optional. Datetime for the most recent
|
||||
error that happened when trying to deliver an update via webhook.
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
last_error_message (:obj:`str`): Optional. Error message in human-readable format for the
|
||||
most recent error that happened when trying to deliver an update via webhook.
|
||||
max_connections (:obj:`int`): Optional. Maximum allowed number of simultaneous HTTPS
|
||||
|
@ -86,10 +96,14 @@ class WebhookInfo(TelegramObject):
|
|||
|
||||
* |tupleclassattrs|
|
||||
* |alwaystuple|
|
||||
last_synchronization_error_date (:obj:`int`): Optional. Unix time of the most recent error
|
||||
that happened when trying to synchronize available updates with Telegram datacenters.
|
||||
last_synchronization_error_date (:class:`datetime.datetime`, optional): Datetime of the
|
||||
most recent error that happened when trying to synchronize available updates with
|
||||
Telegram datacenters.
|
||||
|
||||
.. versionadded:: 20.0
|
||||
|
||||
.. versionchanged:: NEXT.VERSION
|
||||
|datetime_localization|
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
|
@ -154,9 +168,12 @@ class WebhookInfo(TelegramObject):
|
|||
if not data:
|
||||
return None
|
||||
|
||||
data["last_error_date"] = from_timestamp(data.get("last_error_date"))
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
|
||||
data["last_error_date"] = from_timestamp(data.get("last_error_date"), tzinfo=loc_tzinfo)
|
||||
data["last_synchronization_error_date"] = from_timestamp(
|
||||
data.get("last_synchronization_error_date")
|
||||
data.get("last_synchronization_error_date"), tzinfo=loc_tzinfo
|
||||
)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
|
|
@ -162,7 +162,7 @@ class TestDatetime:
|
|||
assert tg_dtm.from_timestamp(None) is None
|
||||
|
||||
def test_from_timestamp_naive(self):
|
||||
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, tzinfo=None)
|
||||
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, tzinfo=dtm.timezone.utc)
|
||||
assert tg_dtm.from_timestamp(1573431976, tzinfo=None) == datetime
|
||||
|
||||
def test_from_timestamp_aware(self, timezone):
|
||||
|
@ -174,3 +174,8 @@ class TestDatetime:
|
|||
tg_dtm.from_timestamp(1573431976.1 - timezone.utcoffset(test_datetime).total_seconds())
|
||||
== datetime
|
||||
)
|
||||
|
||||
def test_extract_tzinfo_from_defaults(self, tz_bot, bot, raw_bot):
|
||||
assert tg_dtm.extract_tzinfo_from_defaults(tz_bot) == tz_bot.defaults.tzinfo
|
||||
assert tg_dtm.extract_tzinfo_from_defaults(bot) is None
|
||||
assert tg_dtm.extract_tzinfo_from_defaults(raw_bot) is None
|
||||
|
|
|
@ -134,7 +134,7 @@ async def raw_bot(bot_info):
|
|||
"""Makes an regular Bot instance with the given bot_info"""
|
||||
async with PytestBot(
|
||||
bot_info["token"],
|
||||
private_key=PRIVATE_KEY,
|
||||
private_key=PRIVATE_KEY if TEST_WITH_OPT_DEPS else None,
|
||||
request=NonchalantHttpxRequest(8),
|
||||
get_updates_request=NonchalantHttpxRequest(1),
|
||||
) as _bot:
|
||||
|
|
|
@ -21,7 +21,7 @@ import datetime
|
|||
import pytest
|
||||
|
||||
from telegram import ChatInviteLink, User
|
||||
from telegram._utils.datetime import to_timestamp
|
||||
from telegram._utils.datetime import UTC, to_timestamp
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
|
||||
|
@ -107,6 +107,33 @@ class TestChatInviteLinkWithoutRequest(TestChatInviteLinkBase):
|
|||
assert invite_link.name == self.name
|
||||
assert invite_link.pending_join_request_count == self.pending_join_request_count
|
||||
|
||||
def test_de_json_localization(self, tz_bot, bot, raw_bot, creator):
|
||||
json_dict = {
|
||||
"invite_link": self.link,
|
||||
"creator": creator.to_dict(),
|
||||
"creates_join_request": self.creates_join_request,
|
||||
"is_primary": self.primary,
|
||||
"is_revoked": self.revoked,
|
||||
"expire_date": to_timestamp(self.expire_date),
|
||||
"member_limit": self.member_limit,
|
||||
"name": self.name,
|
||||
"pending_join_request_count": str(self.pending_join_request_count),
|
||||
}
|
||||
|
||||
invite_link_raw = ChatInviteLink.de_json(json_dict, raw_bot)
|
||||
invite_link_bot = ChatInviteLink.de_json(json_dict, bot)
|
||||
invite_link_tz = ChatInviteLink.de_json(json_dict, tz_bot)
|
||||
|
||||
# comparing utcoffsets because comparing timezones is unpredicatable
|
||||
invite_offset = invite_link_tz.expire_date.utcoffset()
|
||||
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
invite_link_tz.expire_date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
assert invite_link_raw.expire_date.tzinfo == UTC
|
||||
assert invite_link_bot.expire_date.tzinfo == UTC
|
||||
assert invite_offset == tz_bot_offset
|
||||
|
||||
def test_to_dict(self, invite_link):
|
||||
invite_link_dict = invite_link.to_dict()
|
||||
assert isinstance(invite_link_dict, dict)
|
||||
|
|
|
@ -98,6 +98,26 @@ class TestChatJoinRequestWithoutRequest(TestChatJoinRequestBase):
|
|||
assert chat_join_request.bio == self.bio
|
||||
assert chat_join_request.invite_link == self.invite_link
|
||||
|
||||
def test_de_json_localization(self, tz_bot, bot, raw_bot, time):
|
||||
json_dict = {
|
||||
"chat": self.chat.to_dict(),
|
||||
"from": self.from_user.to_dict(),
|
||||
"date": to_timestamp(time),
|
||||
"user_chat_id": self.from_user.id,
|
||||
}
|
||||
|
||||
chatjoin_req_raw = ChatJoinRequest.de_json(json_dict, raw_bot)
|
||||
chatjoin_req_bot = ChatJoinRequest.de_json(json_dict, bot)
|
||||
chatjoin_req_tz = ChatJoinRequest.de_json(json_dict, tz_bot)
|
||||
|
||||
# comparing utcoffsets because comparing timezones is unpredicatable
|
||||
chatjoin_req_offset = chatjoin_req_tz.date.utcoffset()
|
||||
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(chatjoin_req_tz.date.replace(tzinfo=None))
|
||||
|
||||
assert chatjoin_req_raw.date.tzinfo == UTC
|
||||
assert chatjoin_req_bot.date.tzinfo == UTC
|
||||
assert chatjoin_req_offset == tz_bot_offset
|
||||
|
||||
def test_to_dict(self, chat_join_request, time):
|
||||
chat_join_request_dict = chat_join_request.to_dict()
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ from telegram import (
|
|||
Dice,
|
||||
User,
|
||||
)
|
||||
from telegram._utils.datetime import to_timestamp
|
||||
from telegram._utils.datetime import UTC, to_timestamp
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
ignored = ["self", "api_kwargs"]
|
||||
|
@ -218,6 +218,24 @@ class TestChatMemberTypesWithoutRequest:
|
|||
for c_mem_type_at, const_c_mem_at in iter_args(chat_member_type, const_chat_member, True):
|
||||
assert c_mem_type_at == const_c_mem_at
|
||||
|
||||
def test_de_json_chatmemberbanned_localization(self, chat_member_type, tz_bot, bot, raw_bot):
|
||||
# We only test two classes because the other three don't have datetimes in them.
|
||||
if isinstance(chat_member_type, (ChatMemberBanned, ChatMemberRestricted)):
|
||||
json_dict = make_json_dict(chat_member_type, include_optional_args=True)
|
||||
chatmember_raw = ChatMember.de_json(json_dict, raw_bot)
|
||||
chatmember_bot = ChatMember.de_json(json_dict, bot)
|
||||
chatmember_tz = ChatMember.de_json(json_dict, tz_bot)
|
||||
|
||||
# comparing utcoffsets because comparing timezones is unpredicatable
|
||||
chatmember_offset = chatmember_tz.until_date.utcoffset()
|
||||
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
chatmember_tz.until_date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
assert chatmember_raw.until_date.tzinfo == UTC
|
||||
assert chatmember_bot.until_date.tzinfo == UTC
|
||||
assert chatmember_offset == tz_bot_offset
|
||||
|
||||
def test_de_json_invalid_status(self, chat_member_type, bot):
|
||||
json_dict = {"status": "invalid", "user": CMDefaults.user.to_dict()}
|
||||
chat_member_type = ChatMember.de_json(json_dict, bot)
|
||||
|
|
|
@ -137,6 +137,32 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
|
|||
assert chat_member_updated.new_chat_member == new_chat_member
|
||||
assert chat_member_updated.invite_link == invite_link
|
||||
|
||||
def test_de_json_localization(
|
||||
self, bot, raw_bot, tz_bot, user, chat, old_chat_member, new_chat_member, time, invite_link
|
||||
):
|
||||
json_dict = {
|
||||
"chat": chat.to_dict(),
|
||||
"from": user.to_dict(),
|
||||
"date": to_timestamp(time),
|
||||
"old_chat_member": old_chat_member.to_dict(),
|
||||
"new_chat_member": new_chat_member.to_dict(),
|
||||
"invite_link": invite_link.to_dict(),
|
||||
}
|
||||
|
||||
chat_member_updated_bot = ChatMemberUpdated.de_json(json_dict, bot)
|
||||
chat_member_updated_raw = ChatMemberUpdated.de_json(json_dict, raw_bot)
|
||||
chat_member_updated_tz = ChatMemberUpdated.de_json(json_dict, tz_bot)
|
||||
|
||||
# comparing utcoffsets because comparing timezones is unpredicatable
|
||||
message_offset = chat_member_updated_tz.date.utcoffset()
|
||||
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
chat_member_updated_tz.date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
assert chat_member_updated_raw.date.tzinfo == UTC
|
||||
assert chat_member_updated_bot.date.tzinfo == UTC
|
||||
assert message_offset == tz_bot_offset
|
||||
|
||||
def test_to_dict(self, chat_member_updated):
|
||||
chat_member_updated_dict = chat_member_updated.to_dict()
|
||||
assert isinstance(chat_member_updated_dict, dict)
|
||||
|
|
|
@ -56,6 +56,7 @@ from telegram import (
|
|||
Voice,
|
||||
WebAppData,
|
||||
)
|
||||
from telegram._utils.datetime import UTC
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.ext import Defaults
|
||||
from tests._passport.test_passport import RAW_PASSPORT_DATA
|
||||
|
@ -365,6 +366,46 @@ class TestMessageWithoutRequest(TestMessageBase):
|
|||
for slot in new.__slots__:
|
||||
assert not isinstance(new[slot], dict)
|
||||
|
||||
def test_de_json_localization(self, bot, raw_bot, tz_bot):
|
||||
json_dict = {
|
||||
"message_id": 12,
|
||||
"from_user": None,
|
||||
"date": int(datetime.now().timestamp()),
|
||||
"chat": None,
|
||||
"edit_date": int(datetime.now().timestamp()),
|
||||
"forward_date": int(datetime.now().timestamp()),
|
||||
}
|
||||
|
||||
message_raw = Message.de_json(json_dict, raw_bot)
|
||||
message_bot = Message.de_json(json_dict, bot)
|
||||
message_tz = Message.de_json(json_dict, tz_bot)
|
||||
|
||||
# comparing utcoffsets because comparing timezones is unpredicatable
|
||||
date_offset = message_tz.date.utcoffset()
|
||||
date_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(message_tz.date.replace(tzinfo=None))
|
||||
|
||||
edit_date_offset = message_tz.edit_date.utcoffset()
|
||||
edit_date_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
message_tz.edit_date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
forward_date_offset = message_tz.forward_date.utcoffset()
|
||||
forward_date_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
message_tz.forward_date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
assert message_raw.date.tzinfo == UTC
|
||||
assert message_bot.date.tzinfo == UTC
|
||||
assert date_offset == date_tz_bot_offset
|
||||
|
||||
assert message_raw.edit_date.tzinfo == UTC
|
||||
assert message_bot.edit_date.tzinfo == UTC
|
||||
assert edit_date_offset == edit_date_tz_bot_offset
|
||||
|
||||
assert message_raw.forward_date.tzinfo == UTC
|
||||
assert message_bot.forward_date.tzinfo == UTC
|
||||
assert forward_date_offset == forward_date_tz_bot_offset
|
||||
|
||||
def test_equality(self):
|
||||
id_ = 1
|
||||
a = Message(id_, self.date, self.chat, from_user=self.from_user)
|
||||
|
|
|
@ -20,7 +20,7 @@ from datetime import datetime, timedelta, timezone
|
|||
import pytest
|
||||
|
||||
from telegram import MessageEntity, Poll, PollAnswer, PollOption, User
|
||||
from telegram._utils.datetime import to_timestamp
|
||||
from telegram._utils.datetime import UTC, to_timestamp
|
||||
from telegram.constants import PollType
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
|
@ -208,6 +208,36 @@ class TestPollWithoutRequest(TestPollBase):
|
|||
assert abs(poll.close_date - self.close_date) < timedelta(seconds=1)
|
||||
assert to_timestamp(poll.close_date) == to_timestamp(self.close_date)
|
||||
|
||||
def test_de_json_localization(self, tz_bot, bot, raw_bot):
|
||||
json_dict = {
|
||||
"id": self.id_,
|
||||
"question": self.question,
|
||||
"options": [o.to_dict() for o in self.options],
|
||||
"total_voter_count": self.total_voter_count,
|
||||
"is_closed": self.is_closed,
|
||||
"is_anonymous": self.is_anonymous,
|
||||
"type": self.type,
|
||||
"allows_multiple_answers": self.allows_multiple_answers,
|
||||
"explanation": self.explanation,
|
||||
"explanation_entities": [self.explanation_entities[0].to_dict()],
|
||||
"open_period": self.open_period,
|
||||
"close_date": to_timestamp(self.close_date),
|
||||
}
|
||||
|
||||
poll_raw = Poll.de_json(json_dict, raw_bot)
|
||||
poll_bot = Poll.de_json(json_dict, bot)
|
||||
poll_bot_tz = Poll.de_json(json_dict, tz_bot)
|
||||
|
||||
# comparing utcoffsets because comparing timezones is unpredicatable
|
||||
poll_bot_tz_offset = poll_bot_tz.close_date.utcoffset()
|
||||
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
poll_bot_tz.close_date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
assert poll_raw.close_date.tzinfo == UTC
|
||||
assert poll_bot.close_date.tzinfo == UTC
|
||||
assert poll_bot_tz_offset == tz_bot_offset
|
||||
|
||||
def test_to_dict(self, poll):
|
||||
poll_dict = poll.to_dict()
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ from telegram import (
|
|||
VideoChatScheduled,
|
||||
VideoChatStarted,
|
||||
)
|
||||
from telegram._utils.datetime import to_timestamp
|
||||
from telegram._utils.datetime import UTC, to_timestamp
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
|
||||
|
@ -170,6 +170,23 @@ class TestVideoChatScheduledWithoutRequest:
|
|||
|
||||
assert abs(video_chat_scheduled.start_date - self.start_date) < dtm.timedelta(seconds=1)
|
||||
|
||||
def test_de_json_localization(self, tz_bot, bot, raw_bot):
|
||||
json_dict = {"start_date": to_timestamp(self.start_date)}
|
||||
|
||||
videochat_raw = VideoChatScheduled.de_json(json_dict, raw_bot)
|
||||
videochat_bot = VideoChatScheduled.de_json(json_dict, bot)
|
||||
videochat_tz = VideoChatScheduled.de_json(json_dict, tz_bot)
|
||||
|
||||
# comparing utcoffsets because comparing timezones is unpredicatable
|
||||
videochat_offset = videochat_tz.start_date.utcoffset()
|
||||
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
videochat_tz.start_date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
assert videochat_raw.start_date.tzinfo == UTC
|
||||
assert videochat_bot.start_date.tzinfo == UTC
|
||||
assert videochat_offset == tz_bot_offset
|
||||
|
||||
def test_to_dict(self):
|
||||
video_chat_scheduled = VideoChatScheduled(self.start_date)
|
||||
video_chat_scheduled_dict = video_chat_scheduled.to_dict()
|
||||
|
|
|
@ -22,7 +22,7 @@ from datetime import datetime
|
|||
import pytest
|
||||
|
||||
from telegram import LoginUrl, WebhookInfo
|
||||
from telegram._utils.datetime import from_timestamp
|
||||
from telegram._utils.datetime import UTC, from_timestamp
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
|
||||
|
@ -102,6 +102,40 @@ class TestWebhookInfoWithoutRequest(TestWebhookInfoBase):
|
|||
none = WebhookInfo.de_json(None, bot)
|
||||
assert none is None
|
||||
|
||||
def test_de_json_localization(self, bot, raw_bot, tz_bot):
|
||||
json_dict = {
|
||||
"url": self.url,
|
||||
"has_custom_certificate": self.has_custom_certificate,
|
||||
"pending_update_count": self.pending_update_count,
|
||||
"last_error_date": self.last_error_date,
|
||||
"max_connections": self.max_connections,
|
||||
"allowed_updates": self.allowed_updates,
|
||||
"ip_address": self.ip_address,
|
||||
"last_synchronization_error_date": self.last_synchronization_error_date,
|
||||
}
|
||||
webhook_info_bot = WebhookInfo.de_json(json_dict, bot)
|
||||
webhook_info_raw = WebhookInfo.de_json(json_dict, raw_bot)
|
||||
webhook_info_tz = WebhookInfo.de_json(json_dict, tz_bot)
|
||||
|
||||
# comparing utcoffsets because comparing timezones is unpredicatable
|
||||
last_error_date_offset = webhook_info_tz.last_error_date.utcoffset()
|
||||
last_error_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
webhook_info_tz.last_error_date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
sync_error_date_offset = webhook_info_tz.last_synchronization_error_date.utcoffset()
|
||||
sync_error_date_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
|
||||
webhook_info_tz.last_synchronization_error_date.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
assert webhook_info_raw.last_error_date.tzinfo == UTC
|
||||
assert webhook_info_bot.last_error_date.tzinfo == UTC
|
||||
assert last_error_date_offset == last_error_tz_bot_offset
|
||||
|
||||
assert webhook_info_raw.last_synchronization_error_date.tzinfo == UTC
|
||||
assert webhook_info_bot.last_synchronization_error_date.tzinfo == UTC
|
||||
assert sync_error_date_offset == sync_error_date_tz_bot_offset
|
||||
|
||||
def test_always_tuple_allowed_updates(self):
|
||||
webhook_info = WebhookInfo(
|
||||
self.url, self.has_custom_certificate, self.pending_update_count
|
||||
|
|
Loading…
Reference in a new issue