mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-26 03:13:45 +03:00
22124b5ced
This is another attempt at reducing CPU usage similar to:
1b6b4a57d9
In addition it simplifies some of the code and opens up new
ideas for the state cache as well.
147 lines
5.2 KiB
Python
147 lines
5.2 KiB
Python
import itertools
|
|
|
|
from . import utils
|
|
from .tl import types
|
|
|
|
# Which updates have the following fields?
|
|
_has_user_id = []
|
|
_has_chat_id = []
|
|
_has_channel_id = []
|
|
_has_peer = []
|
|
_has_dialog_peer = []
|
|
_has_message = []
|
|
|
|
# Note: We don't bother checking for some rare:
|
|
# * `UpdateChatParticipantAdd.inviter_id` integer.
|
|
# * `UpdateNotifySettings.peer` dialog peer.
|
|
# * `UpdatePinnedDialogs.order` list of dialog peers.
|
|
# * `UpdateReadMessagesContents.messages` list of messages.
|
|
# * `UpdateChatParticipants.participants` list of participants.
|
|
#
|
|
# There are also some uninteresting `update.message` of type string.
|
|
|
|
|
|
def _fill():
|
|
for name in dir(types):
|
|
update = getattr(types, name)
|
|
if getattr(update, 'SUBCLASS_OF_ID', None) == 0x9f89304e:
|
|
cid = update.CONSTRUCTOR_ID
|
|
doc = update.__init__.__doc__ or ''
|
|
if ':param int user_id:' in doc:
|
|
_has_user_id.append(cid)
|
|
if ':param int chat_id:' in doc:
|
|
_has_chat_id.append(cid)
|
|
if ':param int channel_id:' in doc:
|
|
_has_channel_id.append(cid)
|
|
if ':param TypePeer peer:' in doc:
|
|
_has_peer.append(cid)
|
|
if ':param TypeDialogPeer peer:' in doc:
|
|
_has_dialog_peer.append(cid)
|
|
if ':param TypeMessage message:' in doc:
|
|
_has_message.append(cid)
|
|
|
|
# Future-proof check: if the documentation format ever changes
|
|
# then we won't be able to pick the update types we are interested
|
|
# in, so we must make sure we have at least an update for each field
|
|
# which likely means we are doing it right.
|
|
if not all((_has_user_id, _has_chat_id, _has_channel_id,
|
|
_has_peer, _has_dialog_peer)):
|
|
raise RuntimeError('FIXME: Did the generated docs or updates change?')
|
|
|
|
|
|
# We use a function to avoid cluttering the globals (with name/update/cid/doc)
|
|
_fill()
|
|
|
|
|
|
class EntityCache:
|
|
"""
|
|
In-memory input entity cache, defaultdict-like behaviour.
|
|
"""
|
|
def add(self, entities):
|
|
"""
|
|
Adds the given entities to the cache, if they weren't saved before.
|
|
"""
|
|
if not utils.is_list_like(entities):
|
|
# Invariant: all "chats" and "users" are always iterables,
|
|
# and "user" never is (so we wrap it inside a list).
|
|
entities = itertools.chain(
|
|
getattr(entities, 'chats', []),
|
|
getattr(entities, 'users', []),
|
|
(hasattr(entities, 'user') and [entities.user]) or []
|
|
)
|
|
|
|
for entity in entities:
|
|
try:
|
|
pid = utils.get_peer_id(entity)
|
|
if pid not in self.__dict__:
|
|
# Note: `get_input_peer` already checks for `access_hash`
|
|
self.__dict__[pid] = utils.get_input_peer(entity)
|
|
except TypeError:
|
|
pass
|
|
|
|
def __getitem__(self, item):
|
|
"""
|
|
Gets the corresponding :tl:`InputPeer` for the given ID or peer,
|
|
or raises ``KeyError`` on any error (i.e. cannot be found).
|
|
"""
|
|
if not isinstance(item, int) or item < 0:
|
|
try:
|
|
return self.__dict__[utils.get_peer_id(item)]
|
|
except TypeError:
|
|
raise KeyError('Invalid key will not have entity') from None
|
|
|
|
for cls in (types.PeerUser, types.PeerChat, types.PeerChannel):
|
|
result = self.__dict__.get(utils.get_peer_id(cls(item)))
|
|
if result:
|
|
return result
|
|
|
|
raise KeyError('No cached entity for the given key')
|
|
|
|
def ensure_cached(
|
|
self,
|
|
update,
|
|
has_user_id=frozenset(_has_user_id),
|
|
has_channel_id=frozenset(_has_channel_id),
|
|
has_peer=frozenset(_has_peer + _has_dialog_peer),
|
|
has_message=frozenset(_has_message)
|
|
):
|
|
"""
|
|
Ensures that all the relevant entities in the given update are cached.
|
|
"""
|
|
# This method is called pretty often and we want it to have the lowest
|
|
# overhead possible. For that, we avoid `isinstance` and constantly
|
|
# getting attributes out of `types.` by "caching" the constructor IDs
|
|
# in sets inside the arguments, and using local variables.
|
|
dct = self.__dict__
|
|
cid = update.CONSTRUCTOR_ID
|
|
if cid in has_user_id and \
|
|
update.user_id not in dct:
|
|
return False
|
|
|
|
if cid in _has_chat_id and \
|
|
utils.get_peer_id(types.PeerChat(update.chat_id)) not in dct:
|
|
return False
|
|
|
|
if cid in has_channel_id and \
|
|
utils.get_peer_id(types.PeerChannel(update.channel_id)) not in dct:
|
|
return False
|
|
|
|
if cid in has_peer and \
|
|
utils.get_peer_id(update.peer) not in dct:
|
|
return False
|
|
|
|
if cid in has_message:
|
|
x = update.message
|
|
y = getattr(x, 'to_id', None) # handle MessageEmpty
|
|
if y and utils.get_peer_id(y) not in dct:
|
|
return False
|
|
|
|
y = getattr(x, 'from_id', None)
|
|
if y and y not in dct:
|
|
return False
|
|
|
|
# We don't quite worry about entities anywhere else.
|
|
# This is enough.
|
|
|
|
return True
|