Skip to content

Commit 66d9e4a

Browse files
EepyElvyraToricanepre-commit-ci[bot]FayeDel
authored
refactor!: optimize sync behaviour (#776)
* refactor: optimize sync behavior * ooops * Update interactions/api/http/interaction.py Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * Update interactions/client/bot.pyi Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * Update interactions/api/http/scheduledEvent.py Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * Update interactions/client/bot.py Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * Update bot.py * ci: correct from checks. * fix: Fix command check for user and member decorator * Update interactions/client/bot.py Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * fix!: Fix autocomplete when sync is disabled * Update bot.py * Update bot.py * Update bot.pyi * ci: correct from checks. * Update bot.py * fix: fix option checks and autocomplete dispatch with command names * ci * refactor: unnecessary if checks * fix!: Fix synchronisation by properly checking attributes and their logic comparators. * refactor: Remove print statements. * refactor: move sync and autocomplete into _ready * doc: add warning * Update interactions/client/bot.py Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * refactor!: Consider extensions in the sync process * ci: correct from checks. * i hate merge conflicts * purge: remove debugging changes * Update interactions/client/bot.py Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * Update interactions/client/bot.py Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * fix: consider loading after bot start * ci: correct from checks. * refactor: make compare sync static * ci: correct from checks. * fix: consider sync toggle on load Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: DeltaX <33706469+DeltaXWizard@users.noreply.github.com>
1 parent ca5a95e commit 66d9e4a

File tree

3 files changed

+69
-33
lines changed

3 files changed

+69
-33
lines changed

interactions/api/models/role.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class Role(DictSerializerMixin):
5454
"managed",
5555
"mentionable",
5656
"tags",
57+
"flags",
5758
"permissions",
5859
"_client",
5960
)

interactions/client/bot.py

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ async def __register_name_autocomplete(self) -> None:
139139
name=f"autocomplete_{_command}_{self.__name_autocomplete[key]['name']}",
140140
)
141141

142-
async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict]:
142+
@staticmethod
143+
async def __compare_sync(
144+
data: dict, pool: List[dict]
145+
) -> Tuple[bool, dict]: # sourcery no-metrics
143146
"""
144147
Compares an application command during the synchronization process.
145148
@@ -150,10 +153,14 @@ async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict
150153
:return: Whether the command has changed or not.
151154
:rtype: bool
152155
"""
156+
157+
# sourcery skip: none-compare
153158
attrs: List[str] = [
154159
name
155160
for name in ApplicationCommand.__slots__
156-
if not name.startswith("_") and not name.endswith("id") and name != "version"
161+
if not name.startswith("_")
162+
and not name.endswith("id")
163+
and name not in {"version", "default_permission"}
157164
]
158165

159166
option_attrs: List[str] = [name for name in Option.__slots__ if not name.startswith("_")]
@@ -252,6 +259,14 @@ async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict
252259
return clean, _command
253260
else:
254261
continue
262+
elif option_attr == "required":
263+
if (
264+
option.get(option_attr) == None # noqa: E711
265+
and _option.get(option_attr)
266+
== False # noqa: E712
267+
):
268+
# API not including if False
269+
continue
255270
elif option.get(option_attr) != _option.get(
256271
option_attr
257272
):
@@ -271,6 +286,14 @@ async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict
271286
# This is an API/Version difference.
272287
continue
273288

289+
elif (
290+
attr == "dm_permission"
291+
and data.get(attr) == True # noqa: E712
292+
and command.get(attr) == None # noqa: E711
293+
):
294+
# idk, it encountered me and synced unintentionally
295+
continue
296+
274297
# elif data.get(attr, None) and command.get(attr) == data.get(attr):
275298
elif command.get(attr, None) == data.get(attr, None):
276299
# hasattr checks `dict.attr` not `dict[attr]`
@@ -708,22 +731,27 @@ def __check_options(_option: Option, _names: list, _sub_command: Option = MISSIN
708731
def __check_coro():
709732
__indent = 4
710733
log.debug(f"{' ' * __indent}Checking coroutine: '{coro.__name__}'")
711-
if not len(coro.__code__.co_varnames):
734+
_ismethod = hasattr(coro, "__func__")
735+
if not len(coro.__code__.co_varnames) ^ (
736+
_ismethod and len(coro.__code__.co_varnames) == 1
737+
):
712738
raise InteractionException(
713739
11, message="Your command needs at least one argument to return context."
714740
)
715741
elif "kwargs" in coro.__code__.co_varnames:
716742
return
717-
elif _sub_cmds_present and len(coro.__code__.co_varnames) < 2:
743+
elif _sub_cmds_present and len(coro.__code__.co_varnames) < (3 if _ismethod else 2):
718744
raise InteractionException(
719745
11, message="Your command needs one argument for the sub_command."
720746
)
721-
elif _sub_groups_present and len(coro.__code__.co_varnames) < 3:
747+
elif _sub_groups_present and len(coro.__code__.co_varnames) < (4 if _ismethod else 3):
722748
raise InteractionException(
723749
11,
724750
message="Your command needs one argument for the sub_command and one for the sub_command_group.",
725751
)
726-
add: int = 1 + abs(_sub_cmds_present) + abs(_sub_groups_present)
752+
add: int = (
753+
1 + abs(_sub_cmds_present) + abs(_sub_groups_present) + 1 if _ismethod else +0
754+
)
727755

728756
if len(coro.__code__.co_varnames) - add < len(set(_options_names)):
729757
log.debug(
@@ -896,7 +924,11 @@ def decorator(coro: Coroutine) -> Callable[..., Any]:
896924
coro=coro,
897925
)
898926

899-
coro._command_data = commands
927+
if hasattr(coro, "__func__"):
928+
coro.__func__._command_data = commands
929+
else:
930+
coro._command_data = commands
931+
900932
self.__command_coroutines.append(coro)
901933

902934
if scope is not MISSING:
@@ -1268,19 +1300,28 @@ def remove(self, name: str, package: Optional[str] = None) -> None:
12681300
log.error(f"Extension {name} has not been loaded before. Skipping.")
12691301
return
12701302

1271-
try:
1272-
extension.teardown() # made for Extension, usable by others
1273-
except AttributeError:
1274-
pass
1275-
12761303
if isinstance(extension, ModuleType): # loaded as a module
12771304
for ext_name, ext in getmembers(
12781305
extension, lambda x: isinstance(x, type) and issubclass(x, Extension)
12791306
):
1280-
self.remove(ext_name)
1307+
1308+
if ext_name != "Extension":
1309+
_extension = self._extensions.get(ext_name)
1310+
try:
1311+
self._loop.create_task(
1312+
_extension.teardown()
1313+
) # made for Extension, usable by others
1314+
except AttributeError:
1315+
pass
12811316

12821317
del sys.modules[_name]
12831318

1319+
else:
1320+
try:
1321+
self._loop.create_task(extension.teardown()) # made for Extension, usable by others
1322+
except AttributeError:
1323+
pass
1324+
12841325
del self._extensions[_name]
12851326

12861327
log.debug(f"Removed extension {name}.")
@@ -1291,6 +1332,9 @@ def reload(
12911332
r"""
12921333
"Reloads" an extension off of current client from an import resolve.
12931334
1335+
.. warning::
1336+
This will remove and re-add application commands, counting towards your daily application command creation limit.
1337+
12941338
:param name: The name of the extension.
12951339
:type name: str
12961340
:param package?: The package of the extension.
@@ -1307,8 +1351,7 @@ def reload(
13071351

13081352
if extension is None:
13091353
log.warning(f"Extension {name} could not be reloaded because it was never loaded.")
1310-
self.load(name, package)
1311-
return
1354+
return self.load(name, package)
13121355

13131356
self.remove(name, package)
13141357
return self.load(name, package, *args, **kwargs)
@@ -1429,7 +1472,6 @@ def __new__(cls, client: Client, *args, **kwargs) -> "Extension":
14291472
# This gets every coroutine in a way that we can easily change them
14301473
# cls
14311474
for name, func in getmembers(self, predicate=iscoroutinefunction):
1432-
14331475
# TODO we can make these all share the same list, might make it easier to load/unload
14341476
if hasattr(func, "__listener_name__"): # set by extension_listener
14351477
func = client.event(
@@ -1497,32 +1539,24 @@ def __new__(cls, client: Client, *args, **kwargs) -> "Extension":
14971539

14981540
client._extensions[cls.__name__] = self
14991541

1542+
if client._websocket.ready.is_set() and client._automate_sync:
1543+
client._loop.create_task(client._Client__sync())
1544+
15001545
return self
15011546

1502-
def teardown(self):
1547+
async def teardown(self):
15031548
for event, funcs in self._listeners.items():
15041549
for func in funcs:
15051550
self.client._websocket._dispatch.events[event].remove(func)
15061551

15071552
for cmd, funcs in self._commands.items():
15081553
for func in funcs:
1554+
_index = self.client._Client__command_coroutines.index(func)
1555+
self.client._Client__command_coroutines.pop(_index)
15091556
self.client._websocket._dispatch.events[cmd].remove(func)
15101557

1511-
clean_cmd_names = [cmd[7:] for cmd in self._commands.keys()]
1512-
cmds = filter(
1513-
lambda cmd_data: cmd_data["name"] in clean_cmd_names,
1514-
self.client._http.cache.interactions.view,
1515-
)
1516-
15171558
if self.client._automate_sync:
1518-
[
1519-
self.client._loop.create_task(
1520-
self.client._http.delete_application_command(
1521-
cmd["application_id"], cmd["id"], cmd["guild_id"]
1522-
)
1523-
)
1524-
for cmd in cmds
1525-
]
1559+
await self.client._Client__sync()
15261560

15271561

15281562
@wraps(command)

interactions/client/bot.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ class Client:
4646
def start(self) -> None: ...
4747
def __register_events(self) -> None: ...
4848
async def __register_name_autocomplete(self) -> None: ...
49-
async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict]: ...
49+
@staticmethod
50+
async def __compare_sync(data: dict, pool: List[dict]) -> Tuple[bool, dict]: ...
5051
async def _ready(self) -> None: ...
5152
async def _login(self) -> None: ...
5253
async def wait_until_ready(self) -> None: ...
@@ -120,7 +121,7 @@ class Extension:
120121
_commands: dict
121122
_listeners: dict
122123
def __new__(cls, client: Client, *args, **kwargs) -> Extension: ...
123-
def teardown(self) -> None: ...
124+
async def teardown(self) -> None: ...
124125

125126
def extension_command(
126127
*,

0 commit comments

Comments
 (0)