@@ -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 )
0 commit comments