diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index efcecfb93..558cab8bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,33 +30,19 @@ repos: name: TOML Structure - id: check-merge-conflict name: Merge Conflicts + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.0.254' + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black rev: 22.12.0 hooks: - - id: black - name: Black Formatting - language: python - types: [file, python] - language_version: python3.10 - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - name: flake8 Formatting - language: python - types: [file, python] - args: [--ignore=E203 E301 E302 E501 E402 E704 W503 W504] - additional_dependencies: - - flake8-annotations - - flake8-bandit - - flake8-docstrings - - flake8-bugbear - - flake8-comprehensions - - flake8-raise - - flake8-deprecated - - flake8-print - - git+https://github.com/python-formate/flake8-dunder-all - language_version: python3.10 + - id: black + name: Black Formatting + language: python + types: [ file, python ] + language_version: python3.10 # - repo: https://github.com/pycqa/isort # rev: 5.11.4 # hooks: @@ -64,10 +50,6 @@ repos: # name: isort Formatting # language: python # types: [file, python] - - repo: https://github.com/PyCQA/autoflake - rev: v2.0.0 - hooks: - - id: autoflake ci: autoupdate_branch: "unstable" autofix_prs: true diff --git a/interactions/api/events/processors/guild_events.py b/interactions/api/events/processors/guild_events.py index 52239d805..9be29347e 100644 --- a/interactions/api/events/processors/guild_events.py +++ b/interactions/api/events/processors/guild_events.py @@ -40,11 +40,11 @@ async def _on_raw_guild_create(self, event: "RawGatewayEvent") -> None: guild = self.cache.place_guild_data(event.data) - self._user._guild_ids.add(to_snowflake(event.data.get("id"))) # noqa : w0212 + self._user._guild_ids.add(to_snowflake(event.data.get("id"))) self._guild_event.set() - if self.fetch_members and not guild.chunked.is_set(): # noqa + if self.fetch_members and not guild.chunked.is_set(): # delays events until chunking has completed await guild.chunk() diff --git a/interactions/api/gateway/gateway.py b/interactions/api/gateway/gateway.py index c0443b7a4..0b2b9ea38 100644 --- a/interactions/api/gateway/gateway.py +++ b/interactions/api/gateway/gateway.py @@ -1,4 +1,4 @@ -"""This file outlines the interaction between interactions and Discord's Gateway API.""" +"""Outlines the interaction between interactions and Discord's Gateway API.""" import asyncio import sys import time @@ -160,7 +160,7 @@ async def run(self) -> None: self.sequence = seq if op == OPCODE.DISPATCH: - asyncio.create_task(self.dispatch_event(data, seq, event)) + _ = asyncio.create_task(self.dispatch_event(data, seq, event)) continue # This may try to reconnect the connection so it is best to wait @@ -215,17 +215,19 @@ async def dispatch_event(self, data, seq, event) -> None: case "RESUMED": self.logger.info(f"Successfully resumed connection! Session_ID: {self.session_id}") self.state.client.dispatch(events.Resume()) - return + return None case "GUILD_MEMBERS_CHUNK": - asyncio.create_task(self._process_member_chunk(data.copy())) + _ = asyncio.create_task(self._process_member_chunk(data.copy())) case _: # the above events are "special", and are handled by the gateway itself, the rest can be dispatched event_name = f"raw_{event.lower()}" if processor := self.state.client.processors.get(event_name): try: - asyncio.create_task(processor(events.RawGatewayEvent(data.copy(), override_name=event_name))) + _ = asyncio.create_task( + processor(events.RawGatewayEvent(data.copy(), override_name=event_name)) + ) except Exception as ex: self.logger.error(f"Failed to run event processor for {event_name}: {ex}") else: diff --git a/interactions/api/gateway/state.py b/interactions/api/gateway/state.py index 61cc442c7..a8a020d50 100644 --- a/interactions/api/gateway/state.py +++ b/interactions/api/gateway/state.py @@ -117,9 +117,9 @@ async def _ws_connect(self) -> None: except WebSocketClosed as ex: if ex.code == 4011: raise NaffException("Your bot is too large, you must use shards") from None - elif ex.code == 4013: + if ex.code == 4013: raise NaffException(f"Invalid Intents have been passed: {self.intents}") from None - elif ex.code == 4014: + if ex.code == 4014: raise NaffException( "You have requested privileged intents that have not been enabled or approved. Check the developer dashboard" ) from None diff --git a/interactions/api/gateway/websocket.py b/interactions/api/gateway/websocket.py index 7e73e6192..452f40f06 100644 --- a/interactions/api/gateway/websocket.py +++ b/interactions/api/gateway/websocket.py @@ -118,8 +118,7 @@ def average_latency(self) -> float: """Get the average latency of the connection (seconds).""" if self._latency: return sum(self._latency) / len(self._latency) - else: - return float("inf") + return float("inf") @property def latency(self) -> float: @@ -197,7 +196,7 @@ async def receive(self, force: bool = False) -> str: await self.reconnect(code=resp.data, resume=resp.data != 1000) continue - elif resp.type is WSMsgType.CLOSED: + if resp.type is WSMsgType.CLOSED: if force: raise RuntimeError("Discord unexpectedly closed the underlying socket during force receive!") diff --git a/interactions/api/http/http_client.py b/interactions/api/http/http_client.py index a2f9d4578..d8c06017f 100644 --- a/interactions/api/http/http_client.py +++ b/interactions/api/http/http_client.py @@ -76,6 +76,7 @@ def set_reset_time(self, delta: float) -> None: Sets the reset time to the current time + delta. To be called if a 429 is received. + Args: delta: The time to wait before resetting the calls. """ @@ -373,7 +374,7 @@ async def request( ) await lock.defer_unlock() # lock this route and wait for unlock continue - elif lock.remaining == 0: + if lock.remaining == 0: # Last call available in the bucket, lock until reset self.log_ratelimit( self.logger.debug, @@ -407,12 +408,11 @@ async def _raise_exception(self, response, route, result) -> None: if response.status == 403: raise Forbidden(response, response_data=result, route=route) - elif response.status == 404: + if response.status == 404: raise NotFound(response, response_data=result, route=route) - elif response.status >= 500: + if response.status >= 500: raise DiscordError(response, response_data=result, route=route) - else: - raise HTTPException(response, response_data=result, route=route) + raise HTTPException(response, response_data=result, route=route) def log_ratelimit(self, log_func: Callable, message: str) -> None: """ diff --git a/interactions/api/http/http_requests/threads.py b/interactions/api/http/http_requests/threads.py index c21fb789c..ef8e90cd3 100644 --- a/interactions/api/http/http_requests/threads.py +++ b/interactions/api/http/http_requests/threads.py @@ -198,10 +198,9 @@ async def create_thread( payload=payload, reason=reason, ) - else: - payload["type"] = thread_type or ChannelType.GUILD_PUBLIC_THREAD - payload["invitable"] = invitable - return await self.request(Route("POST", f"/channels/{channel_id}/threads"), payload=payload, reason=reason) + payload["type"] = thread_type or ChannelType.GUILD_PUBLIC_THREAD + payload["invitable"] = invitable + return await self.request(Route("POST", f"/channels/{channel_id}/threads"), payload=payload, reason=reason) async def create_forum_thread( self, diff --git a/interactions/api/voice/audio.py b/interactions/api/voice/audio.py index 137001801..65edb4d0e 100644 --- a/interactions/api/voice/audio.py +++ b/interactions/api/voice/audio.py @@ -1,6 +1,6 @@ import audioop import shutil -import subprocess # noqa: S404 +import subprocess import threading import time from abc import ABC, abstractmethod @@ -88,7 +88,7 @@ def read(self, frame_size: int) -> bytes: """ Reads frame_size ms of audio from source. - returns: + Returns: bytes of audio """ ... @@ -199,9 +199,7 @@ def _create_process(self, *, block: bool = True) -> None: cmd[1:1] = before cmd.extend(after) - self.process = subprocess.Popen( # noqa: S603 - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL - ) + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) self.read_ahead_task.start() if block: @@ -246,7 +244,7 @@ def read(self, frame_size: int) -> bytes: """ Reads frame_size bytes of audio from the buffer. - returns: + Returns: bytes of audio """ if not self.process: @@ -293,7 +291,7 @@ def read(self, frame_size: int) -> bytes: """ Reads frame_size ms of audio from source. - returns: + Returns: bytes of audio """ data = super().read(frame_size) diff --git a/interactions/api/voice/voice_gateway.py b/interactions/api/voice/voice_gateway.py index a08e44814..55d707fd7 100644 --- a/interactions/api/voice/voice_gateway.py +++ b/interactions/api/voice/voice_gateway.py @@ -115,7 +115,7 @@ async def receive(self, force=False) -> str: continue raise VoiceWebSocketClosed(resp.data) - elif resp.type is WSMsgType.CLOSED: + if resp.type is WSMsgType.CLOSED: if force: raise RuntimeError("Discord unexpectedly closed the underlying socket during force receive!") @@ -216,7 +216,7 @@ async def reconnect(self, *, resume: bool = False, code: int = 1012) -> None: self._kill_bee_gees.set() self.close() self.logger.debug("Terminating VoiceGateway due to disconnection") - return + return None self._voice_server_update.clear() diff --git a/interactions/client/auto_shard_client.py b/interactions/client/auto_shard_client.py index 83a35590f..1700a051c 100644 --- a/interactions/client/auto_shard_client.py +++ b/interactions/client/auto_shard_client.py @@ -60,8 +60,7 @@ def latency(self) -> float: if len(self._connection_states): latencies = sum((g.latency for g in self._connection_states)) return latencies / len(self._connection_states) - else: - return float("inf") + return float("inf") @property def latencies(self) -> dict[int, float]: diff --git a/interactions/client/client.py b/interactions/client/client.py index 3a570028a..d9246df43 100644 --- a/interactions/client/client.py +++ b/interactions/client/client.py @@ -541,7 +541,7 @@ def _sanity_check(self) -> None: if self.fetch_members and Intents.GUILD_MEMBERS not in self._connection_state.intents: raise BotException("Members Intent must be enabled in order to use fetch members") - elif self.fetch_members: + if self.fetch_members: self.logger.warning("fetch_members enabled; startup will be delayed") if len(self.processors) == 0: @@ -926,7 +926,7 @@ def start(self, token: str | None = None) -> None: if has_uvloop: self.logger.info("uvloop is installed, using it") if sys.version_info >= (3, 11): - with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner: # noqa: F821 + with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner: runner.run(self.astart(token)) else: uvloop.install() @@ -979,7 +979,7 @@ def dispatch(self, event: events.BaseEvent, *args, **kwargs) -> None: ) from e try: - asyncio.create_task(self._process_waits(event)) + _ = asyncio.create_task(self._process_waits(event)) except RuntimeError: # dispatch attempt before event loop is running self.async_startup_tasks.append(self._process_waits(event)) @@ -1048,7 +1048,7 @@ def predicate(event) -> bool: return False if not author: return True - elif author != to_snowflake(event.ctx.author): + if author != to_snowflake(event.ctx.author): return False return True @@ -1178,7 +1178,7 @@ def add_listener(self, listener: Listener) -> None: event_class_name = "".join([name.capitalize() for name in listener.event.split("_")]) if event_class := globals().get(event_class_name): - if required_intents := _INTENT_EVENTS.get(event_class): # noqa + if required_intents := _INTENT_EVENTS.get(event_class): if all(required_intent not in self.intents for required_intent in required_intents): self.logger.warning( f"Event `{listener.event}` will not work since the required intent is not set -> Requires any of: `{required_intents}`" @@ -1223,7 +1223,7 @@ def add_interaction(self, command: InteractionCommand) -> bool: if isinstance(command, SlashCommand): command._parse_parameters() - base, group, sub, *_ = command.resolved_name.split(" ") + [None, None] + base, group, sub, *_ = [*command.resolved_name.split(" "), None, None] for scope in command.scopes: if scope not in self.interactions_by_scope: @@ -1233,7 +1233,7 @@ def add_interaction(self, command: InteractionCommand) -> bool: raise ValueError(f"Duplicate Command! {scope}::{old_cmd.resolved_name}") # if self.enforce_interaction_perms: - # command.checks.append(command._permission_enforcer) # noqa : w0212 + # command.checks.append(command._permission_enforcer) self.interactions_by_scope[scope][command.resolved_name] = command @@ -1387,7 +1387,7 @@ async def wrap(*args, **kwargs) -> Absent[List[Dict]]: return MISSING results = await asyncio.gather(*[wrap(self.app.id, scope) for scope in bot_scopes]) - results = dict(zip(bot_scopes, results)) + results = dict(zip(bot_scopes, results, strict=False)) for scope, remote_cmds in results.items(): if remote_cmds == MISSING: @@ -1408,8 +1408,7 @@ async def wrap(*args, **kwargs) -> Absent[List[Dict]]: f"{'global' if scope == GLOBAL_SCOPE else scope}" ) continue - else: - found.add(cmd_name) + found.add(cmd_name) self._interaction_lookup[cmd.resolved_name] = cmd cmd.cmd_id[scope] = int(cmd_data["id"]) @@ -1720,7 +1719,7 @@ async def __dispatch_interaction( await ctx.send(str(response)) if self.post_run_callback: - asyncio.create_task(self.post_run_callback(ctx, **callback_kwargs)) + _ = asyncio.create_task(self.post_run_callback(ctx, **callback_kwargs)) except Exception as e: self.dispatch(error_callback(ctx=ctx, error=e)) finally: @@ -1793,7 +1792,7 @@ def __load_module(self, module, module_name, **load_kwargs) -> None: asyncio.get_running_loop() except RuntimeError: return - asyncio.create_task(self.synchronise_interactions()) + _ = asyncio.create_task(self.synchronise_interactions()) def load_extension( self, @@ -1836,7 +1835,7 @@ def unload_extension( raise ExtensionNotFound(f"No extension called {name} is loaded") with contextlib.suppress(AttributeError): - teardown = getattr(module, "teardown") + teardown = module.teardown teardown(**unload_kwargs) for ext in self.get_extensions(name): @@ -1850,7 +1849,7 @@ def unload_extension( asyncio.get_running_loop() except RuntimeError: return - asyncio.create_task(self.synchronise_interactions()) + _ = asyncio.create_task(self.synchronise_interactions()) def reload_extension( self, @@ -1900,7 +1899,7 @@ def reload_extension( self.logger.info(f"Reverted extension {name} to previous state") except Exception as ex: sys.modules.pop(name, None) - raise ex from e # noqa: R101 + raise ex from e async def fetch_guild(self, guild_id: "Snowflake_Type") -> Optional[Guild]: """ diff --git a/interactions/client/mixins/send.py b/interactions/client/mixins/send.py index 7f40437a6..129768fa7 100644 --- a/interactions/client/mixins/send.py +++ b/interactions/client/mixins/send.py @@ -98,5 +98,5 @@ async def send( if message_data: message = self.client.cache.place_message_data(message_data) if delete_after: - await message.delete(delay=delete_after) # noqa + await message.delete(delay=delete_after) return message diff --git a/interactions/client/smart_cache.py b/interactions/client/smart_cache.py index ca1e6398f..654c15ee1 100644 --- a/interactions/client/smart_cache.py +++ b/interactions/client/smart_cache.py @@ -228,7 +228,7 @@ def place_member_data( self.place_user_guild(user_id, guild_id) if guild := self.guild_cache.get(guild_id): # todo: this is slow, find a faster way - guild._member_ids.add(user_id) # noqa + guild._member_ids.add(user_id) return member def delete_member(self, guild_id: "Snowflake_Type", user_id: "Snowflake_Type") -> None: @@ -639,7 +639,7 @@ def delete_guild(self, guild_id: "Snowflake_Type") -> None: [self.delete_role(r) for r in guild.roles] if self.enable_emoji_cache: # todo: this is ungodly slow, find a better way to do this for emoji in self.emoji_cache.values(): - if emoji._guild_id == guild_id: # noqa + if emoji._guild_id == guild_id: self.delete_emoji(emoji) # endregion Guild cache diff --git a/interactions/client/utils/attr_converters.py b/interactions/client/utils/attr_converters.py index cecc746be..65cc9d48a 100644 --- a/interactions/client/utils/attr_converters.py +++ b/interactions/client/utils/attr_converters.py @@ -22,9 +22,9 @@ def timestamp_converter(value: Union[datetime, int, float, str]) -> Timestamp: """ if isinstance(value, str): return Timestamp.fromisoformat(value) - elif isinstance(value, (float, int)): + if isinstance(value, (float, int)): return Timestamp.fromtimestamp(float(value)) - elif isinstance(value, datetime): + if isinstance(value, datetime): return Timestamp.fromdatetime(value) raise TypeError("Timestamp must be one of: datetime, int, float, ISO8601 str") diff --git a/interactions/client/utils/cache.py b/interactions/client/utils/cache.py index 69d4467c3..cd5bc8760 100644 --- a/interactions/client/utils/cache.py +++ b/interactions/client/utils/cache.py @@ -154,8 +154,7 @@ def __contains__(self, item) -> bool: v = self._mapping.get(key, default=attrs.NOTHING, reset_expiration=False) if v is attrs.NOTHING: return False - else: - return v is value or v == value + return v is value or v == value def __iter__(self) -> Iterator[Tuple[Any, Any]]: for key in self._mapping: diff --git a/interactions/client/utils/input_utils.py b/interactions/client/utils/input_utils.py index c29589270..40d79de0d 100644 --- a/interactions/client/utils/input_utils.py +++ b/interactions/client/utils/input_utils.py @@ -40,8 +40,8 @@ def enc_hook(obj: Any) -> int: _quotes = { '"': '"', - "‘": "’", - "‚": "‛", + "‘": "’", # noqa RUF001 + "‚": "‛", # noqa RUF001 "“": "”", "„": "‟", "⹂": "⹂", @@ -50,10 +50,10 @@ def enc_hook(obj: Any) -> int: "〝": "〞", "﹁": "﹂", "﹃": "﹄", - """: """, + """: """, # noqa RUF001 "「": "」", "«": "»", - "‹": "›", + "‹": "›", # noqa RUF001 "《": "》", "〈": "〉", } diff --git a/interactions/client/utils/misc_utils.py b/interactions/client/utils/misc_utils.py index 9dfa7f034..2bc9f54d7 100644 --- a/interactions/client/utils/misc_utils.py +++ b/interactions/client/utils/misc_utils.py @@ -216,7 +216,8 @@ def get_event_name(event: Union[str, "events.BaseEvent"]) -> str: def get_object_name(x: Any) -> str: - """Gets the name of virtually any object. + """ + Gets the name of virtually any object. Args: x (Any): The object to get the name of. @@ -234,15 +235,14 @@ async def maybe_coroutine(func: Callable, *args, **kwargs) -> Any: """Allows running either a coroutine or a function.""" if inspect.iscoroutinefunction(func): return await func(*args, **kwargs) - else: - return func(*args, **kwargs) + return func(*args, **kwargs) def disable_components(*components: "BaseComponent") -> list["BaseComponent"]: """Disables all components in a list of components.""" for component in components: if component.type == ComponentType.ACTION_ROW: - disable_components(*component.components) # noqa + disable_components(*component.components) else: component.disabled = True return list(components) diff --git a/interactions/client/utils/serializer.py b/interactions/client/utils/serializer.py index 944979f6e..ea0a3af0b 100644 --- a/interactions/client/utils/serializer.py +++ b/interactions/client/utils/serializer.py @@ -81,17 +81,15 @@ def _to_dict_any(inst: T) -> dict | list | str | T: """ if has(inst.__class__): return to_dict(inst) - elif isinstance(inst, dict): + if isinstance(inst, dict): return {key: _to_dict_any(value) for key, value in inst.items()} - elif isinstance(inst, (list, tuple, set, frozenset)): + if isinstance(inst, (list, tuple, set, frozenset)): return [_to_dict_any(item) for item in inst] - elif isinstance(inst, datetime): + if isinstance(inst, datetime): if inst.tzinfo: return inst.isoformat() - else: - return inst.replace(tzinfo=timezone.utc).isoformat() - else: - return inst + return inst.replace(tzinfo=timezone.utc).isoformat() + return inst def dict_filter_none(data: dict) -> dict: @@ -175,13 +173,12 @@ def get_file_mimetype(file_data: bytes) -> str: if file_data.startswith(b"{"): return "application/json" - elif file_data.startswith((b"GIF87a", b"GIF89a")): + if file_data.startswith((b"GIF87a", b"GIF89a")): return "image/gif" - elif file_data.startswith(b"\x89PNG\x0D\x0A\x1A\x0A"): + if file_data.startswith(b"\x89PNG\x0D\x0A\x1A\x0A"): return "image/png" - elif file_data.startswith(b"\xff\xd8\xff"): + if file_data.startswith(b"\xff\xd8\xff"): return "image/jpeg" - elif file_data[0:4] == b"RIFF" and file_data[8:12] == b"WEBP": + if file_data[0:4] == b"RIFF" and file_data[8:12] == b"WEBP": return "image/webp" - else: - return "application/octet-stream" + return "application/octet-stream" diff --git a/interactions/client/utils/text_utils.py b/interactions/client/utils/text_utils.py index abf5dc099..44a7f5585 100644 --- a/interactions/client/utils/text_utils.py +++ b/interactions/client/utils/text_utils.py @@ -10,7 +10,8 @@ def mentions( *, tag_as_mention: bool = False, ) -> bool: - """Checks whether a query is present in a text. + """ + Checks whether a query is present in a text. Args: text: The text to search in @@ -22,12 +23,11 @@ def mentions( """ if isinstance(query, str): return query in text - elif isinstance(query, re.Pattern): + if isinstance(query, re.Pattern): return query.match(text) is not None - elif isinstance(query, models.BaseUser): + if isinstance(query, models.BaseUser): # mentions with <@!ID> aren't detected without the replacement return (query.mention in text.replace("@!", "@")) or (query.tag in text if tag_as_mention else False) - elif isinstance(query, (models.BaseChannel, models.Role)): + if isinstance(query, (models.BaseChannel, models.Role)): return query.mention in text - else: - return False + return False diff --git a/interactions/ext/console.py b/interactions/ext/console.py index cb8983aaf..782ba71b9 100644 --- a/interactions/ext/console.py +++ b/interactions/ext/console.py @@ -43,6 +43,6 @@ async def async_start_bot(self, token: str | None = None) -> None: with aiomonitor.start_monitor( loop=asyncio.get_event_loop(), port=self.port, console_port=self.console_port, locals=_locals ) as monitor: - self.client.logger.info(f"Started aiomonitor on {monitor._host}:{monitor._port}") # noqa + self.client.logger.info(f"Started aiomonitor on {monitor._host}:{monitor._port}") await old_start(self.client, token) diff --git a/interactions/ext/debug_extension/debug_application_cmd.py b/interactions/ext/debug_extension/debug_application_cmd.py index 34f8451f8..db5ae5628 100644 --- a/interactions/ext/debug_extension/debug_application_cmd.py +++ b/interactions/ext/debug_extension/debug_application_cmd.py @@ -117,7 +117,7 @@ async def send(cmd_json: dict) -> None: if perms: cmd["permissions"] = perms.get("permissions") return await send(cmd) - except Exception: # noqa: S110 + except Exception: pass return await ctx.send(f"Unable to locate any commands in {scope} with ID {cmd_id}!") @@ -143,8 +143,7 @@ async def list_scope(self, ctx: InteractionContext, scope: str) -> Message: [f"`{c['id']}` : `{c['name']}`" for c in cmds] ) return await ctx.send(embeds=e) - else: - return await ctx.send(f"No commands found in `{scope.strip()}`") + return await ctx.send(f"No commands found in `{scope.strip()}`") except Exception: return await ctx.send(f"No commands found in `{scope.strip()}`") diff --git a/interactions/ext/debug_extension/debug_exec.py b/interactions/ext/debug_extension/debug_exec.py index 8f7c789c2..d52f943cd 100644 --- a/interactions/ext/debug_extension/debug_exec.py +++ b/interactions/ext/debug_extension/debug_exec.py @@ -70,14 +70,14 @@ async def debug_exec(self, ctx: InteractionContext) -> Optional[Message]: to_compile = "async def func():\n%s" % textwrap.indent(body, " ") try: - exec(to_compile, env) # noqa: S102 + exec(to_compile, env) except SyntaxError: return await ctx.send(f"```py\n{traceback.format_exc()}\n```") func = env["func"] try: with redirect_stdout(stdout): - ret = await func() # noqa + ret = await func() except Exception: return await m_ctx.send(f"```py\n{stdout.getvalue()}{traceback.format_exc()}\n```") else: @@ -133,9 +133,8 @@ async def handle_exec_result(self, ctx: ModalContext, result: Any, value: Any, b if len(cmd_result := f"```py\n{result}```") <= 2000: return await ctx.send(cmd_result) - else: - paginator = Paginator.create_from_string(self.bot, result, prefix="```py", suffix="```", page_size=4000) - return await paginator.send(ctx) + paginator = Paginator.create_from_string(self.bot, result, prefix="```py", suffix="```", page_size=4000) + return await paginator.send(ctx) def setup(bot) -> None: diff --git a/interactions/ext/debug_extension/debug_exts.py b/interactions/ext/debug_extension/debug_exts.py index e10e3e5bb..e227a7f1d 100644 --- a/interactions/ext/debug_extension/debug_exts.py +++ b/interactions/ext/debug_extension/debug_exts.py @@ -30,7 +30,7 @@ async def drop_ext(self, ctx: PrefixedContext, module: str) -> None: async def reload_error(self, error: Exception, ctx: PrefixedContext, *args) -> None: if isinstance(error, CommandCheckFailure): return await ctx.send("You do not have permission to execute this command") - elif isinstance(error, ExtensionLoadException): + if isinstance(error, ExtensionLoadException): return await ctx.send(str(error)) raise diff --git a/interactions/ext/debug_extension/utils.py b/interactions/ext/debug_extension/utils.py index 7d0edc831..22b7c458c 100644 --- a/interactions/ext/debug_extension/utils.py +++ b/interactions/ext/debug_extension/utils.py @@ -115,7 +115,7 @@ def _make_data_line( if isinstance(aligns, str): aligns = [aligns for _ in column_widths] - line = (f"{str(value): {align}{width}}" for width, align, value in zip(column_widths, aligns, line)) + line = (f"{str(value): {align}{width}}" for width, align, value in zip(column_widths, aligns, line, strict=False)) return f"{left_char}{f'{middle_char}'.join(line)}{right_char}" @@ -135,13 +135,13 @@ def adjust_subcolumn( aligns: Union[list[str], str] = "<", ) -> None: """Converts column composed of list of subcolumns into aligned str representation.""" - column = list(zip(*rows))[column_index] - subcolumn_widths = _get_column_widths(zip(*column)) + column = list(zip(*rows, strict=False))[column_index] + subcolumn_widths = _get_column_widths(zip(*column, strict=False)) if isinstance(aligns, str): aligns = [aligns for _ in subcolumn_widths] column = [_make_data_line(subcolumn_widths, row, "", separator, "", aligns) for row in column] - for row, new_item in zip(rows, column): + for row, new_item in zip(rows, column, strict=False): row[column_index] = new_item @@ -154,7 +154,7 @@ def make_table(rows: list[list[Any]], labels: Optional[list[Any]] = None, center :param centered: If the items should be aligned to the center, else they are left aligned. :return: A table representing the rows passed in. """ - columns = zip(*rows) if labels is None else zip(*rows, labels) + columns = zip(*rows, strict=False) if labels is None else zip(*rows, labels, strict=False) column_widths = _get_column_widths(columns) align = "^" if centered else "<" align = [align for _ in column_widths] diff --git a/interactions/ext/paginators.py b/interactions/ext/paginators.py index abb8de0bc..0a87ae1cf 100644 --- a/interactions/ext/paginators.py +++ b/interactions/ext/paginators.py @@ -171,7 +171,8 @@ def author_id(self) -> Snowflake_Type: @classmethod def create_from_embeds(cls, client: "Client", *embeds: Embed, timeout: int = 0) -> "Paginator": - """Create a paginator system from a list of embeds. + """ + Create a paginator system from a list of embeds. Args: client: A reference to the client @@ -367,7 +368,7 @@ async def send(self, ctx: BaseContext) -> Message: if self.timeout_interval > 1: self._timeout_task = Timeout(self) - asyncio.create_task(self._timeout_task()) + _ = asyncio.create_task(self._timeout_task()) return self._message @@ -385,7 +386,7 @@ async def reply(self, ctx: "PrefixedContext") -> Message: if self.timeout_interval > 1: self._timeout_task = Timeout(self) - asyncio.create_task(self._timeout_task()) + _ = asyncio.create_task(self._timeout_task()) return self._message diff --git a/interactions/ext/prefixed_commands/command.py b/interactions/ext/prefixed_commands/command.py index 2142732b8..60f20f7c0 100644 --- a/interactions/ext/prefixed_commands/command.py +++ b/interactions/ext/prefixed_commands/command.py @@ -187,10 +187,9 @@ def _convert_to_bool(argument: str) -> bool: lowered = argument.lower() if lowered in {"yes", "y", "true", "t", "1", "enable", "on"}: return True - elif lowered in {"no", "n", "false", "f", "0", "disable", "off"}: + if lowered in {"no", "n", "false", "f", "0", "disable", "off"}: return False - else: - raise BadArgument(f"{argument} is not a recognised boolean option.") + raise BadArgument(f"{argument} is not a recognised boolean option.") def _get_from_anno_type(anno: Annotated) -> Any: @@ -211,12 +210,12 @@ def _get_converter(anno: type, name: str) -> Callable[["PrefixedContext", str], if isinstance(anno, Converter): return BaseCommand._get_converter_function(anno, name) - elif converter := MODEL_TO_CONVERTER.get(anno, None): + if converter := MODEL_TO_CONVERTER.get(anno, None): return BaseCommand._get_converter_function(converter, name) - elif typing.get_origin(anno) is Literal: + if typing.get_origin(anno) is Literal: literals = typing.get_args(anno) return _LiteralConverter(literals).convert - elif inspect.isroutine(anno): + if inspect.isroutine(anno): num_params = len(inspect.signature(anno).parameters.values()) match num_params: case 2: @@ -682,80 +681,77 @@ async def call_callback(self, callback: Callable, ctx: "PrefixedContext") -> Non if ctx.args and not self.ignore_extra: raise BadArgument(f"Too many arguments passed to {self.name}.") return await self.call_with_binding(callback, ctx) - else: - # this is slightly costly, but probably worth it - new_args: list[Any] = [] - kwargs: dict[str, Any] = {} - args = _PrefixedArgsIterator(tuple(ctx.args)) - param_index = 0 - - for arg in args: - while param_index < len(self.parameters): - param = self.parameters[param_index] - - if param.consume_rest: - arg = args.consume_rest() - - if param.variable: - args_to_convert = args.get_rest_of_args() - new_arg = [await _convert(param, ctx, arg) for arg in args_to_convert] - new_arg = tuple(arg[0] for arg in new_arg) - new_args.extend(new_arg) - param_index += 1 - break - - if param.greedy: - greedy_args, broke_off = await _greedy_convert(param, ctx, args) + # this is slightly costly, but probably worth it + new_args: list[Any] = [] + kwargs: dict[str, Any] = {} + args = _PrefixedArgsIterator(tuple(ctx.args)) + param_index = 0 + + for arg in args: + while param_index < len(self.parameters): + param = self.parameters[param_index] + + if param.consume_rest: + arg = args.consume_rest() + + if param.variable: + args_to_convert = args.get_rest_of_args() + new_arg = [await _convert(param, ctx, arg) for arg in args_to_convert] + new_arg = tuple(arg[0] for arg in new_arg) + new_args.extend(new_arg) + param_index += 1 + break - new_args.append(greedy_args) - param_index += 1 - if broke_off: - args.back() + if param.greedy: + greedy_args, broke_off = await _greedy_convert(param, ctx, args) - if param.default: - continue - else: - break + new_args.append(greedy_args) + param_index += 1 + if broke_off: + args.back() - converted, used_default = await _convert(param, ctx, arg) - if param.kind in { - inspect.Parameter.POSITIONAL_ONLY, - inspect.Parameter.VAR_POSITIONAL, - }: + if param.default: + continue + break + + converted, used_default = await _convert(param, ctx, arg) + if param.kind in { + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.VAR_POSITIONAL, + }: + new_args.append(converted) + else: + kwargs[param.name] = converted + param_index += 1 + + if not used_default: + break + + if param_index < len(self.parameters): + for param in self.parameters[param_index:]: + if param.no_argument: + converted, _ = await _convert(param, ctx, None) # type: ignore + if not param.consume_rest: new_args.append(converted) else: kwargs[param.name] = converted - param_index += 1 - - if not used_default: break - - if param_index < len(self.parameters): - for param in self.parameters[param_index:]: - if param.no_argument: - converted, _ = await _convert(param, ctx, None) # type: ignore - if not param.consume_rest: - new_args.append(converted) - else: - kwargs[param.name] = converted - break - continue - - if not param.optional: - raise BadArgument(f"{param.name} is a required argument that is missing.") - else: - if param.kind in { - inspect.Parameter.POSITIONAL_ONLY, - inspect.Parameter.VAR_POSITIONAL, - }: - new_args.append(param.default) - else: - kwargs[param.name] = param.default - break - elif not self.ignore_extra and not args.finished: - raise BadArgument(f"Too many arguments passed to {self.name}.") - - return await self.call_with_binding(callback, ctx, *new_args, **kwargs) + continue + + if not param.optional: + raise BadArgument(f"{param.name} is a required argument that is missing.") + if param.kind in { + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.VAR_POSITIONAL, + }: + new_args.append(param.default) + else: + kwargs[param.name] = param.default + break + elif not self.ignore_extra and not args.finished: + raise BadArgument(f"Too many arguments passed to {self.name}.") + + return await self.call_with_binding(callback, ctx, *new_args, **kwargs) def prefixed_command( diff --git a/interactions/ext/prefixed_commands/help.py b/interactions/ext/prefixed_commands/help.py index 27bf500cc..166fa41b0 100644 --- a/interactions/ext/prefixed_commands/help.py +++ b/interactions/ext/prefixed_commands/help.py @@ -120,6 +120,7 @@ async def _gather(self, ctx: PrefixedContext | None = None) -> dict[str, Prefixe Args: ctx: The context to use to establish usability. + Returns: dict[str, PrefixedCommand]: A list of commands fit the class attribute configuration. """ @@ -154,7 +155,7 @@ def _sanitise_mentions(self, text: str) -> str: """ Replace mentions with a format that won't ping or look weird in code blocks. - args: + Args: The text to sanitise. """ mappings = { @@ -172,7 +173,7 @@ def _generate_command_string(self, cmd: PrefixedCommand, ctx: PrefixedContext) - """ Generate a string based on a command, class attributes, and the context. - args: + Args: cmd: The command in question. ctx: The context for this command. """ diff --git a/interactions/ext/prefixed_commands/utils.py b/interactions/ext/prefixed_commands/utils.py index c17d511f3..379d68d1c 100644 --- a/interactions/ext/prefixed_commands/utils.py +++ b/interactions/ext/prefixed_commands/utils.py @@ -28,6 +28,7 @@ def when_mentioned_or( Args: prefixes: Prefixes to include alongside mentions. + Returns: A list of the bot's mentions plus whatever prefixes are provided. """ diff --git a/interactions/models/discord/activity.py b/interactions/models/discord/activity.py index 1658525b4..477c15344 100644 --- a/interactions/models/discord/activity.py +++ b/interactions/models/discord/activity.py @@ -112,7 +112,7 @@ def create(cls, name: str, type: ActivityType = ActivityType.GAME, url: Optional The new activity object """ - return cls(name=name, type=type, url=url) # noqa + return cls(name=name, type=type, url=url) def to_dict(self) -> dict: return dict_filter_none({"name": self.name, "type": self.type, "url": self.url}) diff --git a/interactions/models/discord/asset.py b/interactions/models/discord/asset.py index e5eedee7c..b5fa1b4ab 100644 --- a/interactions/models/discord/asset.py +++ b/interactions/models/discord/asset.py @@ -57,11 +57,11 @@ def as_url(self, *, extension: str | None = None, size: int = 4096) -> str: """ Get the url of this asset. - args: + Args: extension: The extension to override the assets default with size: The size of asset to return - returns: + Returns: A url for this asset with the given parameters """ if not extension: diff --git a/interactions/models/discord/auto_mod.py b/interactions/models/discord/auto_mod.py index c11dfbaf5..981f96316 100644 --- a/interactions/models/discord/auto_mod.py +++ b/interactions/models/discord/auto_mod.py @@ -30,7 +30,8 @@ @attrs.define(eq=False, order=False, hash=False, kw_only=True) class BaseAction(DictSerializationMixin): - """A base implementation of a moderation action + """ + A base implementation of a moderation action Attributes: type: The type of action that was taken @@ -55,7 +56,8 @@ def as_dict(self) -> dict: @attrs.define(eq=False, order=False, hash=False, kw_only=True) class BaseTrigger(DictSerializationMixin): - """A base implementation of an auto-mod trigger + """ + A base implementation of an auto-mod trigger Attributes: type: The type of event this trigger is for diff --git a/interactions/models/discord/channel.py b/interactions/models/discord/channel.py index 02777b323..194007af8 100644 --- a/interactions/models/discord/channel.py +++ b/interactions/models/discord/channel.py @@ -197,10 +197,9 @@ def for_target(cls, target_type: Union["models.Role", "models.Member", "models.U """ if isinstance(target_type, models.Role): return cls(type=OverwriteType.ROLE, id=target_type.id) - elif isinstance(target_type, (models.Member, models.User)): + if isinstance(target_type, (models.Member, models.User)): return cls(type=OverwriteType.MEMBER, id=target_type.id) - else: - raise TypeError("target_type must be a Role, Member or User") + raise TypeError("target_type must be a Role, Member or User") def add_allows(self, *perms: "Permissions") -> None: """ @@ -436,7 +435,7 @@ async def purge( if not predicate: def predicate(m) -> bool: - return True # noqa + return True to_delete = [] @@ -512,15 +511,15 @@ async def create_invite( if target_type: if target_type == InviteTargetType.STREAM and not target_user: raise ValueError("Stream target must include target user id.") - elif target_type == InviteTargetType.EMBEDDED_APPLICATION and not target_application: + if target_type == InviteTargetType.EMBEDDED_APPLICATION and not target_application: raise ValueError("Embedded Application target must include target application id.") if target_user and target_application: raise ValueError("Invite target must be either User or Embedded Application, not both.") - elif target_user: + if target_user: target_user = to_snowflake(target_user) target_type = InviteTargetType.STREAM - elif target_application: + if target_application: target_application = to_snowflake(target_application) target_type = InviteTargetType.EMBEDDED_APPLICATION @@ -577,10 +576,10 @@ async def create_thread( if self.type == ChannelType.GUILD_NEWS and not message: raise ValueError("News channel must include message to create thread from.") - elif message and (thread_type or invitable): + if message and (thread_type or invitable): raise ValueError("Message cannot be used with thread_type or invitable.") - elif thread_type != ChannelType.GUILD_PRIVATE_THREAD and invitable: + if thread_type != ChannelType.GUILD_PRIVATE_THREAD and invitable: raise ValueError("Invitable only applies to private threads.") thread_data = await self._client.http.create_thread( @@ -1065,13 +1064,11 @@ def permissions_for(self, instance: Snowflake_Type) -> Permissions: return permissions - else: - instance = to_snowflake(instance) - guild = self.guild - if instance := guild.get_member(instance) or guild.get_role(instance): - return self.permissions_for(instance) - else: - raise ValueError("Unable to find any member or role by given instance ID") + instance = to_snowflake(instance) + guild = self.guild + if instance := guild.get_member(instance) or guild.get_role(instance): + return self.permissions_for(instance) + raise ValueError("Unable to find any member or role by given instance ID") async def add_permission( self, @@ -2297,8 +2294,7 @@ async def disconnect(self) -> None: """ if self.voice_state: return await self.voice_state.disconnect() - else: - raise VoiceNotConnected + raise VoiceNotConnected @attrs.define(eq=False, order=False, hash=False, kw_only=True) diff --git a/interactions/models/discord/color.py b/interactions/models/discord/color.py index dd21a895e..0dae405a2 100644 --- a/interactions/models/discord/color.py +++ b/interactions/models/discord/color.py @@ -313,11 +313,11 @@ def process_color(color: Color | dict | COLOR_TYPES | None) -> int | None: """ if not color: return None - elif isinstance(color, Color): + if isinstance(color, Color): return color.value - elif isinstance(color, dict): + if isinstance(color, dict): return color["value"] - elif isinstance(color, (tuple, list, str, int)): + if isinstance(color, (tuple, list, str, int)): return Color(color).value raise ValueError(f"Invalid color: {type(color)}") diff --git a/interactions/models/discord/emoji.py b/interactions/models/discord/emoji.py index 8791c04fb..3f1b0341b 100644 --- a/interactions/models/discord/emoji.py +++ b/interactions/models/discord/emoji.py @@ -23,7 +23,7 @@ __all__ = ("PartialEmoji", "CustomEmoji", "process_emoji_req_format", "process_emoji") emoji_regex = re.compile(r"?") -unicode_emoji_reg = re.compile(r"[^\w\s,’‘“”…–—•◦‣⁃⁎⁏⁒⁓⁺⁻⁼⁽⁾ⁿ₊₋₌₍₎]") +unicode_emoji_reg = re.compile(r"[^\w\s,``“”…-—•◦‣-*⁏⁒~⁺⁻⁼⁽⁾ⁿ₊₋₌₍₎]") @attrs.define(eq=False, order=False, hash=False, kw_only=False) @@ -68,13 +68,12 @@ def from_str(cls, emoji_str: str, *, language: str = "alias") -> Optional["Parti parsed = tuple(filter(None, parsed[0])) if len(parsed) == 3: return cls(name=parsed[1], id=parsed[2], animated=True) - elif len(parsed) == 2: + if len(parsed) == 2: return cls(name=parsed[0], id=parsed[1]) - else: - _name = emoji.emojize(emoji_str, language=language) - _emoji_list = emoji.distinct_emoji_list(_name) - if _emoji_list: - return cls(name=_emoji_list[0]) + _name = emoji.emojize(emoji_str, language=language) + _emoji_list = emoji.distinct_emoji_list(_name) + if _emoji_list: + return cls(name=_emoji_list[0]) else: # check if it's a unicode emoji _emoji_list = emoji.distinct_emoji_list(emoji_str) @@ -110,8 +109,7 @@ def req_format(self) -> str: """Format used for web request.""" if self.id: return f"{self.name}:{self.id}" - else: - return self.name + return self.name @attrs.define(eq=False, order=False, hash=False, kw_only=True) diff --git a/interactions/models/discord/file.py b/interactions/models/discord/file.py index b1d84ab96..0f0143534 100644 --- a/interactions/models/discord/file.py +++ b/interactions/models/discord/file.py @@ -42,8 +42,7 @@ def open_file(self) -> BinaryIO: """ if isinstance(self.file, (IOBase, BinaryIO)): return self.file - else: - return open(str(self.file), "rb") + return open(str(self.file), "rb") def __enter__(self) -> "File": return self diff --git a/interactions/models/discord/message.py b/interactions/models/discord/message.py index ceb530e75..98db13734 100644 --- a/interactions/models/discord/message.py +++ b/interactions/models/discord/message.py @@ -141,7 +141,7 @@ def for_message(cls, message: "Message", fail_if_not_exists: bool = True) -> "Me """ Creates a reference to a message. - parameters + Parameters message: The target message to reference. fail_if_not_exists: Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message @@ -610,27 +610,26 @@ async def edit( file=file, tts=tts, ) - else: if self.flags == MessageFlags.EPHEMERAL: raise EphemeralEditException - message_payload = process_message_payload( - content=content, - embeds=embeds or embed, - components=components, - allowed_mentions=allowed_mentions, - attachments=attachments, - tts=tts, - flags=flags, - ) - if file: - if files: - files = [file, *files] - else: - files = [file] + message_payload = process_message_payload( + content=content, + embeds=embeds or embed, + components=components, + allowed_mentions=allowed_mentions, + attachments=attachments, + tts=tts, + flags=flags, + ) + if file: + if files: + files = [file, *files] + else: + files = [file] - message_data = await self._client.http.edit_message(message_payload, self._channel_id, self.id, files=files) - if message_data: - return self._client.cache.place_message_data(message_data) + message_data = await self._client.http.edit_message(message_payload, self._channel_id, self.id, files=files) + if message_data: + return self._client.cache.place_message_data(message_data) async def delete(self, delay: int = 0, *, context: "InteractionContext | None" = None) -> None: """ @@ -654,7 +653,7 @@ async def _delete() -> None: await self._client.http.delete_message(self._channel_id, self.id) if delay: - asyncio.create_task(_delete()) + _ = asyncio.create_task(_delete()) else: return await _delete() diff --git a/interactions/models/discord/modal.py b/interactions/models/discord/modal.py index 0bcf9199a..6247a8df3 100644 --- a/interactions/models/discord/modal.py +++ b/interactions/models/discord/modal.py @@ -64,9 +64,9 @@ def to_dict( @classmethod def from_dict(cls, data: dict[str, Any]) -> T: if data["style"] == TextStyles.SHORT: - cls = ShortText # noqa + cls = ShortText elif data["style"] == TextStyles.PARAGRAPH: - cls = ParagraphText # noqa + cls = ParagraphText return cls( label=data["label"], diff --git a/interactions/models/discord/reaction.py b/interactions/models/discord/reaction.py index cf2fcec43..a5b82936c 100644 --- a/interactions/models/discord/reaction.py +++ b/interactions/models/discord/reaction.py @@ -61,8 +61,7 @@ async def fetch(self) -> List["User"]: raise QueueEmpty self._more = len(users) == expected return [self.reaction._client.cache.place_user_data(u) for u in users] - else: - raise QueueEmpty + raise QueueEmpty @attrs.define(eq=False, order=False, hash=False, kw_only=True) diff --git a/interactions/models/discord/role.py b/interactions/models/discord/role.py index 2a8fe6401..a84bb962b 100644 --- a/interactions/models/discord/role.py +++ b/interactions/models/discord/role.py @@ -25,9 +25,7 @@ def sentinel_converter(value: bool | T | None, sentinel: T = attrs.NOTHING) -> bool | T: if value is sentinel: return False - elif value is None: - return True - return value + return True if value is None else value @attrs.define(eq=False, order=False, hash=False, kw_only=True) diff --git a/interactions/models/discord/timestamp.py b/interactions/models/discord/timestamp.py index f1d2ed3c9..42978b5fd 100644 --- a/interactions/models/discord/timestamp.py +++ b/interactions/models/discord/timestamp.py @@ -138,8 +138,7 @@ def format(self, style: Optional[Union[TimestampStyles, str]] = None) -> str: """ if not style: return f"" - else: - return f"" + return f"" def __str__(self) -> str: return self.format() diff --git a/interactions/models/discord/user.py b/interactions/models/discord/user.py index be713dcc6..1b21c5fe5 100644 --- a/interactions/models/discord/user.py +++ b/interactions/models/discord/user.py @@ -90,11 +90,11 @@ def avatar_url(self) -> str: async def fetch_dm(self) -> "DM": """Fetch the DM channel associated with this user.""" - return await self._client.cache.fetch_dm_channel(self.id) # noqa + return await self._client.cache.fetch_dm_channel(self.id) def get_dm(self) -> Optional["DM"]: """Get the DM channel associated with this user.""" - return self._client.cache.get_dm_channel(self.id) # noqa + return self._client.cache.get_dm_channel(self.id) @property def mutual_guilds(self) -> List["Guild"]: @@ -348,8 +348,7 @@ def __getattr__(self, name: str) -> Any: if name in self.__class__._user_ref: return getattr(self.user, name) - else: - raise AttributeError(f"Neither `User` or `Member` have attribute {name}") + raise AttributeError(f"Neither `User` or `Member` have attribute {name}") @property def nickname(self) -> str: diff --git a/interactions/models/internal/active_voice_state.py b/interactions/models/internal/active_voice_state.py index 7f429f012..0482e2d59 100644 --- a/interactions/models/internal/active_voice_state.py +++ b/interactions/models/internal/active_voice_state.py @@ -120,7 +120,7 @@ async def ws_connect(self) -> None: """Connect to the voice gateway for this voice state""" self.ws = VoiceGateway(self._client._connection_state, self._voice_state.data, self._voice_server.data) - asyncio.create_task(self._ws_connect()) + _ = asyncio.create_task(self._ws_connect()) await self.ws.wait_until_ready() def _guild_predicate(self, event) -> bool: @@ -158,7 +158,7 @@ async def connect(self, timeout: int = 5) -> None: self.logger.debug("Waiting for voice connection data...") try: - self._voice_state, self._voice_server = await asyncio.gather(*tasks) # noqa + self._voice_state, self._voice_server = await asyncio.gather(*tasks) except asyncio.TimeoutError: raise VoiceConnectionTimeout from None @@ -233,7 +233,7 @@ def play_no_wait(self, audio: "BaseAudio") -> None: Args: audio: The audio object to play """ - asyncio.create_task(self.play(audio)) + _ = asyncio.create_task(self.play(audio)) async def _voice_server_update(self, data) -> None: """ diff --git a/interactions/models/internal/application_commands.py b/interactions/models/internal/application_commands.py index eb3ee92bb..b659a1f40 100644 --- a/interactions/models/internal/application_commands.py +++ b/interactions/models/internal/application_commands.py @@ -308,7 +308,7 @@ def is_enabled(self, ctx: "BaseContext") -> bool: """ if not self.dm_permission and ctx.guild is None: return False - elif self.dm_permission and ctx.guild is None: + if self.dm_permission and ctx.guild is None: # remaining checks are impossible if this is a DM and DMs are enabled return True @@ -768,8 +768,7 @@ async def call_callback(self, callback: typing.Callable, ctx: "InteractionContex if not self.parameters: if self._uses_arg: return await self.call_with_binding(callback, ctx, *ctx.args) - else: - return await self.call_with_binding(callback, ctx) + return await self.call_with_binding(callback, ctx) kwargs_copy = ctx.kwargs.copy() diff --git a/interactions/models/internal/context.py b/interactions/models/internal/context.py index 4e45d8492..b3e27c532 100644 --- a/interactions/models/internal/context.py +++ b/interactions/models/internal/context.py @@ -300,7 +300,7 @@ def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: @property def command(self) -> InteractionCommand: - return self.client._interaction_lookup[self._command_name] # noqa W0212 + return self.client._interaction_lookup[self._command_name] @property def expires_at(self) -> typing.Optional[datetime.datetime]: @@ -735,6 +735,7 @@ async def edit_origin( files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files. file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files. tts: Should this message use Text To Speech. + Returns: The message after it was edited. """ @@ -806,7 +807,7 @@ def input_text(self) -> str: def option_processing_hook(self, option: dict) -> None: if option.get("focused", False): self.focussed_option = SlashCommandOption.from_dict(option) - return None + return async def send(self, choices: typing.Iterable[str | int | float | dict[str, int | float | str]]) -> None: """ diff --git a/interactions/models/internal/converters.py b/interactions/models/internal/converters.py index 92014ebf3..ce4c75cf1 100644 --- a/interactions/models/internal/converters.py +++ b/interactions/models/internal/converters.py @@ -89,7 +89,7 @@ async def convert(self, ctx: BaseContext, argument: str) -> Any: try: if (converted := converter(argument)) == arg: return converted - except Exception: # noqa + except Exception: continue literals_list = [str(a) for a in self.values.keys()] diff --git a/interactions/models/internal/cooldowns.py b/interactions/models/internal/cooldowns.py index 50cfc4cc2..b79f8ffbc 100644 --- a/interactions/models/internal/cooldowns.py +++ b/interactions/models/internal/cooldowns.py @@ -36,18 +36,17 @@ class Buckets(IntEnum): async def get_key(self, context: "BaseContext") -> Any: if self is Buckets.USER: return context.author.id - elif self is Buckets.GUILD: + if self is Buckets.GUILD: return context.guild_id if context.guild else context.author.id - elif self is Buckets.CHANNEL: + if self is Buckets.CHANNEL: return context.channel.id - elif self is Buckets.MEMBER: + if self is Buckets.MEMBER: return (context.guild_id, context.author.id) if context.guild else context.author.id - elif self is Buckets.CATEGORY: + if self is Buckets.CATEGORY: return await context.channel.parent_id if context.channel.parent else context.channel.id - elif self is Buckets.ROLE: - return context.channel.id if not context.guild else context.author.top_role.id - else: - return context.author.id + if self is Buckets.ROLE: + return context.author.top_role.id if context.guild else context.channel.id + return context.author.id def __call__(self, context: "BaseContext") -> Any: return self.get_key(context) diff --git a/interactions/models/internal/tasks/task.py b/interactions/models/internal/tasks/task.py index 0af7a21e9..45bb6158d 100644 --- a/interactions/models/internal/tasks/task.py +++ b/interactions/models/internal/tasks/task.py @@ -93,7 +93,7 @@ async def __call__(self) -> None: def _fire(self, fire_time: datetime) -> None: """Called when the task is being fired.""" self.trigger.last_call_time = fire_time - asyncio.create_task(self()) + _ = asyncio.create_task(self()) self.iteration += 1 async def _task_loop(self) -> None: diff --git a/main.py b/main.py index 9b48667fa..2c6449b4d 100644 --- a/main.py +++ b/main.py @@ -107,15 +107,14 @@ async def multi_image_embed_test(ctx: interactions.SlashContext): await ctx.send(embeds=embed) -def get_colour(colour: str): +def get_colour(colour: str) -> interactions.Colour: if colour in interactions.MaterialColors.__members__: return interactions.MaterialColors[colour] - elif colour in interactions.BrandColors.__members__: + if colour in interactions.BrandColors.__members__: return interactions.BrandColors[colour] - elif colour in interactions.FlatUIColours.__members__: + if colour in interactions.FlatUIColours.__members__: return interactions.FlatUIColours[colour] - else: - return interactions.BrandColors.BLURPLE + return interactions.BrandColors.BLURPLE @slash_command("test") diff --git a/pyproject.toml b/pyproject.toml index d1610db44..ae55f905d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,3 +95,81 @@ log_cli = "1" # log_cli_level = "DEBUG" log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" log_cli_date_format="%Y-%m-%d %H:%M:%S" + +[tool.ruff] +line-length = 120 +target-version = "py310" +ignore-init-module-imports = true +task-tags = ["TODO", "FIXME", "XXX", "HACK", "REVIEW", "NOTE"] +show-source = false # set to true if you want to see the source of the error/warning +select = ["E", "F", "B", "Q", "RUF", "D", "ANN", "RET", "C"] +ignore = [ + "Q0", "E501", + # These default to arguing with Black. We might configure some of them eventually + "ANN1", + # These insist that we have Type Annotations for self and cls. + "D105", "D107", + # Missing Docstrings in magic method and __init__ + "D401", + # First line should be in imperative mood; try rephrasing + "D400", "D415", + # First line should end with a period + "D106", + # Missing docstring in public nested class. This doesn't work well with Metadata classes. + "D417", + # Missing argument in the docstring + "D406", + # Section name should end with a newline + "D407", + # Missing dashed underline after section + "D212", + # Multi-line docstring summary should start at the first line + "D404", + # First word of the docstring should not be This + "D203", + # 1 blank line required before class docstring + + # Everything below this line is something we care about, but don't currently meet + "ANN001", + # Missing type annotation for function argument 'token' + "ANN002", + # Missing type annotation for *args + "ANN003", + # Missing type annotation for **kwargs + "ANN401", + # Dynamically typed expressions (typing.Any) are disallowed +# "B009", + # Do not call getattr with a constant attribute value, it is not any safer than normal property access. + "B010", + # Do not call setattr with a constant attribute value, it is not any safer than normal property access. + "D100", + # Missing docstring in public module + "D101", + # ... class + "D102", + # ... method + "D103", + # ... function + "D104", + # ... package + + # Plugins we don't currently include: flake8-return + "RET503", + # missing explicit return at the end of function ableto return non-None value. + "RET504", + # unecessary variable assignement before return statement. +] + +[tool.ruff.flake8-quotes] +docstring-quotes = "double" + +[tool.ruff.flake8-annotations] +mypy-init-return = true +suppress-dummy-args = true +suppress-none-returning = true + +[tool.ruff.flake8-errmsg] +max-string-length = 20 + +[tool.ruff.mccabe] +max-complexity = 5 diff --git a/tests/test_bot.py b/tests/test_bot.py index f22dd1081..5e2152a66 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -72,13 +72,16 @@ def event_loop() -> AbstractEventLoop: async def bot(github_commit) -> Client: bot = interactions.Client(activity="Testing someones code") await bot.login(TOKEN) - asyncio.create_task(bot.start_gateway()) + gw = asyncio.create_task(bot.start_gateway()) await bot._ready.wait() bot.suffix = github_commit log.info(f"Logged in as {bot.user} ({bot.user.id}) -- {bot.suffix}") yield bot + gw.cancel() + with suppress(asyncio.CancelledError): + await gw @pytest_asyncio.fixture(scope="module") @@ -111,9 +114,9 @@ async def channel(bot, guild) -> GuildText: @pytest_asyncio.fixture(scope="module") async def github_commit() -> str: - import subprocess # noqa: S404 + import subprocess - return subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode("ascii").strip() # noqa: S603, S607 + return subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode("ascii").strip() def ensure_attributes(target_object) -> None: diff --git a/tests/test_emoji.py b/tests/test_emoji.py index 2b13be6a7..2957c911c 100644 --- a/tests/test_emoji.py +++ b/tests/test_emoji.py @@ -109,21 +109,21 @@ def test_false_positives() -> None: assert PartialEmoji.from_str(_e) is None unicode_general_punctuation = [ - "’", - "‘", + "’", # noqa: RUF001 + "‘", # noqa: RUF001 "“", "”", "…", - "–", + "–", # noqa: RUF001 "—", "•", "◦", "‣", - "⁃", - "⁎", + "⁃", # noqa: RUF001 + "⁎", # noqa: RUF001 "⁏", "⁒", - "⁓", + "⁓", # noqa: RUF001 "⁺", "⁻", "⁼",