Skip to content

Commit 7397c7c

Browse files
Damegopre-commit-ci[bot]EepyElvyra
authored
refactor(cache): optimize caching and dispatching behaviour (#1039)
* refactor: just refactor 1. Moved getting object ID into separated method. 2. Add method to get object ids. Needs for `message_delete_bulk`, `emoji_update` and `stickers_update` 3. Cache `GuildMember` like `Member` 4. Deleting messages from cache on `message_delete_bulk` event * chore: run pre-commit * refactor: merge conditions into one * refactor: add emojis to guild cache in attrs_post_init * fix: typo * refactor: move message cache control to method - Remove message from cache on `message_delete` event * ci: correct from checks. * fix the fix of bug * docs: capitalize return and ` to ' * refactor: leave only `message` * ref: move `__modify_guild_cache` to own method * ci: correct from checks. * docs: add and update docstrings * refactor: properly caching members and roles * ci: correct from checks. * refactor: refuse from raw remove events and ... put `obj` to `__modify_guild_cache` if guild_obj is None * ci: correct from checks. * fix: `guild_obj` referenced before assignment * fix: help me There are only two models which can be converted to model without `Guild` prefix. * refactor: use walrus operator Co-authored-by: EdVraz <88881326+EdVraz@users.noreply.github.com> * fix: add missed obj to dispatch * refactor: remove extra dispatch * refactor: message already removed from cache? * refactor: remove `__delete_message_cache` * refactor: dispatch `guild_emojis_update` without `raw` * ci: correct from checks. * refactor: change condition for `ids` * refactor: change condition in getting ids * feat: add support for `_execution` events * refactor: dispatch update event if there are no id & ids * revert: add support for `_execution` event * refactor: some stuff * refactor: use `cache.merge` to resolve guild overloading Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: EdVraz <88881326+EdVraz@users.noreply.github.com>
1 parent e5a4502 commit 7397c7c

File tree

2 files changed

+189
-71
lines changed

2 files changed

+189
-71
lines changed

interactions/api/gateway/client.py

Lines changed: 187 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@
3232
from ..http.client import HTTPClient
3333
from ..models.flags import Intents
3434
from ..models.guild import Guild
35+
from ..models.gw import GuildMember, GuildRole
3536
from ..models.member import Member
37+
from ..models.message import Message
3638
from ..models.misc import Snowflake
3739
from ..models.presence import ClientPresence
40+
from ..models.role import Role
3841
from .heartbeat import _Heartbeat
3942
from .ratelimit import WSRateLimit
4043

@@ -437,87 +440,64 @@ def _dispatch_event(self, event: str, data: dict) -> None:
437440
elif event not in {"TYPING_START", "VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"}:
438441
name: str = event.lower()
439442
try:
443+
data["_client"] = self._http
440444

441445
_event_path: list = [section.capitalize() for section in name.split("_")]
442446
_name: str = _event_path[0] if len(_event_path) < 3 else "".join(_event_path[:-1])
443447
model = getattr(__import__(path), _name)
444-
445-
data["_client"] = self._http
446448
obj = model(**data)
447449

448-
_cache: "Storage" = self._http.cache[model]
450+
guild_obj = guild_model = None
451+
if model is GuildRole:
452+
guild_obj = Role(**role_data) if (role_data := data.get("role")) else None
453+
guild_model = Role
454+
elif model is GuildMember:
455+
guild_obj = Member(**data)
456+
guild_model = Member
449457

450-
if isinstance(obj, Member):
451-
id = (Snowflake(data["guild_id"]), obj.id)
452-
else:
453-
id = getattr(obj, "id", None)
458+
_cache: "Storage" = self._http.cache[model]
459+
_guild_cache: "Storage" = self._http.cache[guild_model]
454460

461+
ids = None
462+
id = self.__get_object_id(data, obj, model)
455463
if id is None:
456-
if model.__name__ == "GuildScheduledEventUser":
457-
id = model.guild_scheduled_event_id
458-
elif model.__name__ == "Presence":
459-
id = obj.user.id
460-
elif model.__name__ in [
461-
"Invite",
462-
"GuildBan",
463-
"ChannelPins",
464-
"MessageReaction",
465-
"MessageReactionRemove",
466-
"MessageDelete",
467-
# Extend this for everything that should not be cached
468-
]:
469-
id = None
470-
elif model.__name__.startswith("Guild"):
471-
model_name = model.__name__[5:]
472-
if _data := getattr(obj, model_name, None):
473-
id = (
474-
getattr(_data, "id")
475-
if not isinstance(_data, dict)
476-
else Snowflake(_data["id"])
477-
)
478-
elif hasattr(obj, f"{model_name}_id"):
479-
id = getattr(obj, f"{model_name}_id", None)
480-
481-
def __modify_guild_cache():
482-
if not (
483-
(guild_id := data.get("guild_id"))
484-
and not isinstance(obj, Guild)
485-
and "message" not in name
486-
and id is not None
487-
):
488-
return
489-
if guild := self._http.cache[Guild].get(Snowflake(guild_id)):
490-
model_name: str = model.__name__
491-
if "guild" in model_name:
492-
model_name = model_name[5:]
493-
elif model_name == "threadmembers":
494-
return
495-
_obj = getattr(guild, f"{model_name.lower()}s", None)
496-
if _obj is not None and isinstance(_obj, list):
497-
if "_create" in name or "_add" in name:
498-
_obj.append(obj)
499-
for index, __obj in enumerate(_obj):
500-
if __obj.id == id:
501-
if "_remove" in name or "_delete" in name:
502-
_obj.remove(__obj)
503-
504-
elif "_update" in name and hasattr(obj, "id"):
505-
_obj[index] = obj
506-
break
507-
setattr(guild, f"{model_name}s", _obj)
508-
self._http.cache[Guild].add(guild)
464+
ids = self.__get_object_ids(obj, model)
509465

510466
if "_create" in name or "_add" in name:
467+
self._dispatch.dispatch(f"on_{name}", obj)
468+
511469
if id:
512470
_cache.merge(obj, id)
513-
self._dispatch.dispatch(f"on_{name}", obj)
514-
__modify_guild_cache()
471+
if guild_obj:
472+
_guild_cache.add(guild_obj, id)
473+
474+
self.__modify_guild_cache(
475+
name, data, guild_model or model, guild_obj or obj, id, ids
476+
)
515477

516478
elif "_update" in name:
517479
self._dispatch.dispatch(f"on_raw_{name}", obj)
518-
if not id:
480+
481+
if not id and ids is None:
482+
return self._dispatch.dispatch(f"on_{name}", obj)
483+
484+
self.__modify_guild_cache(
485+
name, data, guild_model or model, guild_obj or obj, id, ids
486+
)
487+
if ids is not None:
488+
# Not cached but it needed for guild_emojis_update and guild_stickers_update events
489+
return self._dispatch.dispatch(f"on_{name}", obj)
490+
if id is None:
519491
return
520-
old_obj = self._http.cache[model].get(id)
492+
493+
if guild_obj:
494+
old_guild_obj = _guild_cache.get(id)
495+
if old_guild_obj:
496+
old_guild_obj.update(**guild_obj._json)
497+
else:
498+
_guild_cache.add(guild_obj, id)
499+
500+
old_obj = _cache.get(id)
521501
if old_obj:
522502
before = model(**old_obj._json)
523503
old_obj.update(**obj._json)
@@ -526,27 +506,163 @@ def __modify_guild_cache():
526506
old_obj = obj
527507

528508
_cache.add(old_obj, id)
529-
__modify_guild_cache()
530-
531509
self._dispatch.dispatch(
532510
f"on_{name}", before, old_obj
533511
) # give previously stored and new one
534512

535513
elif "_remove" in name or "_delete" in name:
536-
self._dispatch.dispatch(f"on_raw_{name}", obj)
537-
__modify_guild_cache()
514+
self._dispatch.dispatch(
515+
f"on_raw_{name}", obj
516+
) # Deprecated. Remove this in the future.
517+
518+
old_obj = None
538519
if id:
520+
_guild_cache.pop(id)
521+
self.__modify_guild_cache(
522+
name, data, guild_model or model, guild_obj or obj, id, ids
523+
)
539524
old_obj = _cache.pop(id)
540-
self._dispatch.dispatch(f"on_{name}", old_obj)
541-
elif "_delete_bulk" in name:
542-
self._dispatch.dispatch(f"on_{name}", obj)
525+
526+
elif ids is not None and "message" in name:
527+
# currently only message has '_delete_bulk' event but ig better keep this condition for future.
528+
_message_cache: "Storage" = self._http.cache[Message]
529+
for message_id in ids:
530+
_message_cache.pop(message_id)
531+
532+
self._dispatch.dispatch(f"on_{name}", old_obj or obj)
543533

544534
else:
545535
self._dispatch.dispatch(f"on_{name}", obj)
546536

547537
except AttributeError as error:
548538
log.warning(f"An error occurred dispatching {name}: {error}")
549539

540+
def __get_object_id(
541+
self, data: dict, obj: Any, model: Any
542+
) -> Optional[Union[Snowflake, Tuple[Snowflake, Snowflake]]]:
543+
"""
544+
Gets an ID from object.
545+
546+
:param data: The data for the event.
547+
:type data: dict
548+
:param obj: The object of the event.
549+
:type obj: Any
550+
:param model: The model of the event.
551+
:type model: Any
552+
:return: Object ID
553+
:rtype: Optional[Union[Snowflake, Tuple[Snowflake, Snowflake]]]
554+
"""
555+
if isinstance(obj, (Member, GuildMember)):
556+
id = (Snowflake(data["guild_id"]), obj.id)
557+
else:
558+
id = getattr(obj, "id", None)
559+
if id is not None:
560+
return id
561+
562+
if model.__name__ == "GuildScheduledEventUser":
563+
id = obj.guild_scheduled_event_id
564+
elif model.__name__ == "Presence":
565+
id = obj.user.id
566+
elif model.__name__ in [
567+
"GuildBan",
568+
# Extend this for everything that starts with 'Guild' and should not be cached
569+
]:
570+
id = None
571+
elif model.__name__.startswith("Guild"):
572+
model_name = model.__name__[5:].lower()
573+
if (_data := getattr(obj, model_name, None)) and not isinstance(_data, list):
574+
id = getattr(_data, "id") if not isinstance(_data, dict) else Snowflake(_data["id"])
575+
elif hasattr(obj, f"{model_name}_id"):
576+
id = getattr(obj, f"{model_name}_id", None)
577+
578+
return id
579+
580+
def __get_object_ids(self, obj: Any, model: Any) -> Optional[List[Snowflake]]:
581+
"""
582+
Gets a list of ids of object.
583+
584+
:param obj: The object of the event.
585+
:type obj: Any
586+
:param model: The model of the event.
587+
:type model: Any
588+
:return: Object IDs
589+
:rtype: Optional[Union[Snowflake, Tuple[Snowflake, Snowflake]]]
590+
"""
591+
ids = getattr(obj, "ids", None)
592+
if ids is not None:
593+
return ids
594+
595+
if model.__name__.startswith("Guild"):
596+
model_name = model.__name__[5:].lower()
597+
if (_data := getattr(obj, model_name, None)) is not None:
598+
ids = [
599+
getattr(_obj, "id") if not isinstance(_obj, dict) else Snowflake(_obj["id"])
600+
for _obj in _data
601+
]
602+
603+
return ids
604+
605+
def __modify_guild_cache(
606+
self,
607+
name: str,
608+
data: dict,
609+
model: Any,
610+
obj: Any,
611+
id: Optional[Snowflake] = None,
612+
ids: Optional[List[Snowflake]] = None,
613+
):
614+
"""
615+
Modifies guild cache.
616+
617+
:param event: The name of the event.
618+
:type event: str
619+
:param data: The data for the event.
620+
:type data: dict
621+
:param obj: The object of the event.
622+
:type obj: Any
623+
:param model: The model of the event.
624+
:type model: Any
625+
"""
626+
if not (
627+
(guild_id := data.get("guild_id"))
628+
and not isinstance(obj, Guild)
629+
and "message" not in name
630+
and (id is not None or ids is not None)
631+
and (guild := self._http.cache[Guild].get(Snowflake(guild_id)))
632+
):
633+
return
634+
635+
attr: str = model.__name__.lower()
636+
637+
if attr.startswith("guild"):
638+
attr = attr[5:]
639+
if attr == "threadmembers": # TODO: Figure out why this here
640+
return
641+
if not attr.endswith("s"):
642+
attr = f"{attr}s"
643+
iterable = getattr(guild, attr, None)
644+
if iterable is not None and isinstance(iterable, list):
645+
if "_create" in name or "_add" in name:
646+
iterable.append(obj)
647+
if id:
648+
_id = id[1] if isinstance(id, tuple) else id
649+
for index, __obj in enumerate(iterable):
650+
if __obj.id == _id:
651+
if "_remove" in name or "_delete" in name:
652+
iterable.remove(__obj)
653+
654+
elif "_update" in name and hasattr(obj, "id"):
655+
iterable[index] = obj
656+
break
657+
elif ids is not None and "_update" in name:
658+
objs = getattr(obj, attr, None)
659+
if objs is not None:
660+
iterable.clear()
661+
iterable.extend(objs)
662+
setattr(guild, attr, iterable)
663+
664+
self._http.cache[Guild].add(guild)
665+
550666
def __contextualize(self, data: dict) -> "_Context":
551667
"""
552668
Takes raw data given back from the Gateway

interactions/api/models/guild.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,8 @@ def __attrs_post_init__(self): # sourcery skip: last-if-guard
471471
self.member_count = guild.member_count
472472
if not self.presences:
473473
self.presences = guild.presences
474+
if not self.emojis:
475+
self.emojis = guild.emojis
474476

475477
if self.members:
476478
for member in self.members:

0 commit comments

Comments
 (0)