Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion monty/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
log = logging.getLogger(__name__)

try:
import uvloop # noqa: F401 # pyright: ignore[reportMissingImports]
import uvloop # pyright: ignore[reportMissingImports]

uvloop.install()
log.info("Using uvloop as event loop.")
Expand Down
2 changes: 1 addition & 1 deletion monty/aiohttp_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async def _request(
) -> aiohttp.ClientResponse:
"""Do the same thing as aiohttp does, but always cache the response."""
method = method.upper().strip()
cache_key = f"{method}:{str(str_or_url)}"
cache_key = f"{method}:{str_or_url!s}"
async with self.cache.lock(cache_key):
cached = await self.cache.get(cache_key)
if cached and use_cache:
Expand Down
32 changes: 15 additions & 17 deletions monty/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Monty(commands.Bot):

name = constants.Client.name

def __init__(self, redis_session: redis.asyncio.Redis, proxy: str = None, **kwargs) -> None:
def __init__(self, redis_session: redis.asyncio.Redis, proxy: str | None = None, **kwargs) -> None:
if TEST_GUILDS:
kwargs["test_guilds"] = TEST_GUILDS
log.warning("registering as test_guilds")
Expand Down Expand Up @@ -87,7 +87,7 @@ def db(self) -> async_sessionmaker[AsyncSession]:
"""Alias of `bot.db_session`."""
return self.db_session

def create_http_session(self, proxy: str = None) -> None:
def create_http_session(self, proxy: str | None = None) -> None:
"""Create the bot's aiohttp session."""
self.http_session = CachingClientSession(proxy=proxy)

Expand Down Expand Up @@ -163,17 +163,16 @@ async def _create_features(self) -> None:
"""Update the database with all features defined immediately upon launch. No more lazy creation."""
await self.wait_until_first_connect()

async with self._feature_db_lock:
async with self.db.begin() as session:
stmt = sa.select(Feature).options(selectinload(Feature.rollout))
result = await session.scalars(stmt)
existing_feature_names = {feature.name for feature in result.all()}
for feature_enum in constants.Feature:
if feature_enum.value in existing_feature_names:
continue
feature_instance = Feature(feature_enum.value)
session.add(feature_instance)
await session.commit() # this will error out if it cannot be made
async with self._feature_db_lock, self.db.begin() as session:
stmt = sa.select(Feature).options(selectinload(Feature.rollout))
result = await session.scalars(stmt)
existing_feature_names = {feature.name for feature in result.all()}
for feature_enum in constants.Feature:
if feature_enum.value in existing_feature_names:
continue
feature_instance = Feature(feature_enum.value)
session.add(feature_instance)
await session.commit() # this will error out if it cannot be made

await self.refresh_features()

Expand Down Expand Up @@ -228,9 +227,8 @@ async def guild_has_feature(
self.features[feature] = feature_instance
# we're defaulting to non-existing features as None, rather than False.
# this might change later.
if include_feature_status and feature_instance:
if feature_instance.enabled is not None:
return feature_instance.enabled
if include_feature_status and feature_instance and feature_instance.enabled is not None:
return feature_instance.enabled

# the feature's enabled status is None, so we should check the guild
# support the guild being None to make it easier to use
Expand Down Expand Up @@ -353,7 +351,7 @@ def remove_command(self, name: str) -> commands.Command | None:
command = super().remove_command(name)
if command is None:
# Even if it's a root alias, there's no way to get the Bot instance to remove the alias.
return
return None

self.dispatch("command_remove", command)
self._remove_root_aliases(command)
Expand Down
6 changes: 3 additions & 3 deletions monty/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@


__all__ = (
"METADATA",
"GROUP_TO_ATTR",
"CATEGORY_TO_ATTR",
"GROUP_TO_ATTR",
"METADATA",
"Category",
"ConfigAttrMetadata",
"SelectGroup",
"FreeResponseMetadata",
"SelectGroup",
"SelectOptionMetadata",
"get_category_choices",
)
Expand Down
6 changes: 4 additions & 2 deletions monty/config/_validate_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ def _check_config_metadata(metadata: dict[str, ConfigAttrMetadata]) -> None:
assert isinstance(m.select_option, SelectOptionMetadata)
assert m.type is bool
if m.modal:
assert m.description and len(m.description) <= 45
assert m.description
assert len(m.description) <= 45
if m.depends_on_features:
for feature in m.depends_on_features:
assert feature in constants.Feature
for c in Category:
if not any(c in m.categories for m in metadata.values()):
raise ValueError(f"Category {c} has no associated config attributes")
msg = f"Category {c} has no associated config attributes"
raise ValueError(msg)
2 changes: 1 addition & 1 deletion monty/config/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@


__all__ = (
"METADATA",
"CATEGORY_TO_ATTR",
"GROUP_TO_ATTR",
"METADATA",
)


Expand Down
6 changes: 3 additions & 3 deletions monty/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Client:

class Database:
postgres_bind: str = environ.get("DB_BIND", "")
run_migrations: bool = not (environ.get("DB_RUN_MIGRATIONS", "true").lower() == "false")
run_migrations: bool = environ.get("DB_RUN_MIGRATIONS", "true").lower() != "false"
migration_target: str = environ.get("DB_MIGRATION_TARGET", "head")


Expand All @@ -97,7 +97,7 @@ class Monitoring:
debug_logging = environ.get("LOG_DEBUG", "true").lower() == "true"
sentry_enabled = bool(environ.get("SENTRY_DSN"))
trace_loggers = environ.get("BOT_TRACE_LOGGERS")
log_mode: Literal["daily", "dev"] = "daily" if "daily" == environ.get("BOT_LOG_MODE", "dev").lower() else "dev"
log_mode: Literal["daily", "dev"] = "daily" if environ.get("BOT_LOG_MODE", "dev").lower() == "daily" else "dev"

public_status_page: str | None = environ.get("UPTIME_STATUS_PAGE") or None
ping_url: str = environ.get("UPTIME_URL", "")
Expand Down Expand Up @@ -207,7 +207,7 @@ class Icons:
"%3Fv%3D1/https/cdn.discordapp.com/emojis/654080405988966419.png?width=20&height=20"
)
github_avatar_url = "https://avatars1.githubusercontent.com/u/9919"
python_discourse = "https://global.discourse-cdn.com/business6/uploads/python1/optimized/1X/4c06143de7870c35963b818b15b395092a434991_2_180x180.png" # noqa: E501
python_discourse = "https://global.discourse-cdn.com/business6/uploads/python1/optimized/1X/4c06143de7870c35963b818b15b395092a434991_2_180x180.png"


## Authentication and Endpoint management for external services
Expand Down
2 changes: 1 addition & 1 deletion monty/database/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ class Feature(MappedAsDataclass, Base):
def validate_name(self, key: str, name: str) -> str:
"""Validate the `name` attribute meets the regex requirement."""
if not NAME_REGEX.fullmatch(name):
err = f"The provided feature name '{name}' does not match the name regex {str(NAME_REGEX)}"
err = f"The provided feature name '{name}' does not match the name regex {NAME_REGEX!s}"
raise ValueError(err)
return name
2 changes: 1 addition & 1 deletion monty/database/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Guild(Base):
__tablename__ = "guilds"

id: Mapped[int] = mapped_column(sa.BigInteger, primary_key=True, autoincrement=False)
# todo: this should be a many to many relationship
# TODO: this should be a many to many relationship
feature_ids: Mapped[list[str]] = mapped_column(
MutableList.as_mutable(sa.ARRAY(sa.String(length=50))),
name="features",
Expand Down
2 changes: 1 addition & 1 deletion monty/database/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ def base_url(self, value: str | None) -> None:
def validate_name(self, key: str, name: str) -> str:
"""Validate all names are of the format of valid python package names."""
if not NAME_REGEX.fullmatch(name):
err = f"The provided package name '{name}' does not match the name regex {str(NAME_REGEX)}"
err = f"The provided package name '{name}' does not match the name regex {NAME_REGEX!s}"
raise ValueError(err)
return name
14 changes: 5 additions & 9 deletions monty/exts/core/bot_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,13 +486,10 @@ def _get_feature_page(
)
)
# we are here because there are no features
elif guild_to_check:
components[-1].children.append(disnake.ui.TextDisplay(f"No features are overridden for {guild_to_check}"))
else:
if guild_to_check:
components[-1].children.append(
disnake.ui.TextDisplay(f"No features are overridden for {guild_to_check}")
)
else:
components[-1].children.append(disnake.ui.TextDisplay("No features are overridden."))
components[-1].children.append(disnake.ui.TextDisplay("No features are overridden."))

# Paginator buttons
if total >= FEATURES_PER_PAGE:
Expand Down Expand Up @@ -830,9 +827,8 @@ async def guild_feature_toggle_listener(self, inter: disnake.MessageInteraction)
if not guild_has_feature:
if feature.name not in guild_db.feature_ids:
guild_db.feature_ids.append(feature.name)
else:
if feature.name in guild_db.feature_ids:
guild_db.feature_ids.remove(feature.name)
elif feature.name in guild_db.feature_ids:
guild_db.feature_ids.remove(feature.name)
guild_db = await session.merge(guild_db)
self.bot.guild_db[guild.id] = guild_db
await session.commit()
Expand Down
2 changes: 1 addition & 1 deletion monty/exts/core/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async def handle_v1_buttons(self, inter: disnake.MessageInteraction) -> None:
# get the button from the view
components = disnake.ui.components_from_message(inter.message)
for comp in disnake.ui.walk_components(components):
if VIEW_DELETE_ID_V1 == getattr(comp, "custom_id", None) and isinstance(comp, disnake.ui.Button):
if getattr(comp, "custom_id", None) == VIEW_DELETE_ID_V1 and isinstance(comp, disnake.ui.Button):
break
else:
raise RuntimeError("view doesn't contain the button that was clicked.")
Expand Down
3 changes: 1 addition & 2 deletions monty/exts/core/error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ async def handle_user_input_error(
async def handle_bot_missing_perms(
self, ctx: AnyContext, error: commands.BotMissingPermissions | disnake.Forbidden
) -> None:
"""Handles bot missing permissing by dming the user if they have a permission which may be able to fix this.""" # noqa: E501
"""Handles bot missing permissing by dming the user if they have a permission which may be able to fix this."""
embed = self.error_embed("Permissions Failure", str(error))
if isinstance(ctx, commands.Context):
app_permissions = ctx.channel.permissions_for(ctx.me) # pyright: ignore[reportArgumentType]
Expand Down Expand Up @@ -327,7 +327,6 @@ async def on_command_error(self, ctx: AnyContext, error: Exception) -> None:
# two times is not the charm.
self.bot.dispatch("slash_command_error", ctx, exc)
should_respond = False
pass

elif isinstance(error, (commands.CommandInvokeError, commands.ConversionError)):
if isinstance(error.original, disnake.Forbidden):
Expand Down
8 changes: 4 additions & 4 deletions monty/exts/core/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ async def load_command(self, ctx: commands.Context, *extensions: Extension) -> N
Load extensions given their fully qualified or unqualified names.

If '\*' or '\*\*' is given as the name, all unloaded extensions will be loaded.
""" # noqa: W605
"""
if not extensions:
await invoke_help_command(ctx)
return
Expand All @@ -146,7 +146,7 @@ async def unload_command(self, ctx: commands.Context, *extensions: Extension) ->
Unload currently loaded extensions given their fully qualified or unqualified names.

If '\*' or '\*\*' is given as the name, all loaded extensions will be unloaded.
""" # noqa: W605
"""
if not extensions:
await invoke_help_command(ctx)
return
Expand Down Expand Up @@ -178,7 +178,7 @@ async def reload_command(self, ctx: commands.Context, *extensions: Extension) ->

If '\*' is given as the name, all currently loaded extensions will be reloaded.
If '\*\*' is given as the name, all extensions, including unloaded ones, will be reloaded.
""" # noqa: W605
"""
if not extensions:
await invoke_help_command(ctx)
return
Expand Down Expand Up @@ -327,7 +327,7 @@ async def _autoreload_action(
channel: disnake.abc.Messageable,
extra_mods_to_path: dict[str, str],
) -> None:
import subprocess # noqa: F401
import subprocess

modified_extensions = set()
extra_path_message = ""
Expand Down
2 changes: 1 addition & 1 deletion monty/exts/core/global_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def set_invite_link(self) -> None:
if self._bot_invite_link:
return

# todo: don't require a fake guild object
# TODO: don't require a fake guild object
class FakeGuild:
id: str = "{guild_id}"

Expand Down
2 changes: 1 addition & 1 deletion monty/exts/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ async def on_application_command(self, inter: disnake.ApplicationCommandInteract
"""Log the start of an application command."""
spl = str(inter.filled_options).replace("\n", " ")
spl = spl.split("\n")
# todo: fix this in disnake
# TODO: fix this in disnake
if inter.application_command is disnake.utils.MISSING:
return
qualname = inter.application_command.qualified_name
Expand Down
4 changes: 2 additions & 2 deletions monty/exts/core/rollouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ async def cmd_rollouts_list(self, ctx: commands.Context) -> None:
embed.description = "\n".join(names)
await ctx.send(embed=embed, components=button)

# todo: make this easier to use (selects, modals, buttons)
# TODO: make this easier to use (selects, modals, buttons)
# while the interface is clumsy right now, this is currently in development
@cmd_rollouts.command("create")
async def cmd_rollouts_create(
Expand All @@ -170,7 +170,7 @@ async def cmd_rollouts_create(
if result.one_or_none():
raise commands.BadArgument("A rollout with that name already exists.")

if percent_goal not in range(0, 100 + 1):
if percent_goal not in range(100 + 1):
raise commands.BadArgument("percent_goal must be within 0 to 100 inclusive.")

# pick a random starting number divisible by 100
Expand Down
27 changes: 14 additions & 13 deletions monty/exts/filters/codeblock/_instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,18 @@ def _get_no_ticks_message(content: str) -> str | None:
"""If `content` is Python/REPL code, return instructions on using code blocks."""
log.trace("Creating instructions for a missing code block.")

if _parsing.is_python_code(content):
example_blocks = _get_example("py")
return (
"It looks like you're trying to paste code into this channel.\n\n"
"Discord has support for Markdown, which allows you to post code with full "
"syntax highlighting. Please use these whenever you paste code, as this "
"helps improve the legibility and makes it easier for us to help you.\n\n"
f"**To do this, use the following method:**\n{example_blocks}"
)
else:
if not _parsing.is_python_code(content):
log.trace("Aborting missing code block instructions: content is not Python code.")
return None

example_blocks = _get_example("py")
return (
"It looks like you're trying to paste code into this channel.\n\n"
"Discord has support for Markdown, which allows you to post code with full "
"syntax highlighting. Please use these whenever you paste code, as this "
"helps improve the legibility and makes it easier for us to help you.\n\n"
f"**To do this, use the following method:**\n{example_blocks}"
)


def _get_bad_lang_message(content: str) -> str | None:
Expand All @@ -90,7 +91,7 @@ def _get_bad_lang_message(content: str) -> str | None:
info = _parsing.parse_bad_language(content)
if not info:
log.trace("Aborting bad language instructions: language specified isn't Python.")
return
return None

lines: list[str] = []
language = info.language
Expand All @@ -108,7 +109,7 @@ def _get_bad_lang_message(content: str) -> str | None:

if not lines:
log.trace("Nothing wrong with the language specifier; no instructions to return.")
return
return None

joined_lines = " ".join(lines)
example_blocks = _get_example(language)
Expand Down Expand Up @@ -153,7 +154,7 @@ def get_instructions(content: str) -> str | None:
blocks = _parsing.find_code_blocks(content)
if blocks is None:
log.trace("At least one valid code block found; no instructions to return.")
return
return None

if not blocks:
log.trace("No code blocks were found in message.")
Expand Down
15 changes: 6 additions & 9 deletions monty/exts/info/discord.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,10 @@ async def fetch_app_info_for_client(self, client_id: int) -> disnake.AppInfo:
@commands.slash_command()
async def discord(self, inter: disnake.CommandInteraction) -> None:
"""Commands that interact with discord."""
pass

@discord.sub_command_group()
async def api(self, inter: disnake.CommandInteraction) -> None:
"""Commands that interact with the discord api."""
pass

@api.sub_command(name="app-info")
async def info_app(self, inter: disnake.CommandInteraction, client_id: LargeInt, ephemeral: bool = True) -> None:
Expand Down Expand Up @@ -189,15 +187,14 @@ async def app_invite(
else:
components.append(disnake.ui.Button(url=url, style=disnake.ButtonStyle.link, label=title))

elif raw_link:
message += f"\n{urls}"
else:
if raw_link:
message += f"\n{urls}"
else:
components.append(
disnake.ui.Button(
url=str(urls), style=disnake.ButtonStyle.link, label=f"Click to invite {app_info.name}!"
)
components.append(
disnake.ui.Button(
url=str(urls), style=disnake.ButtonStyle.link, label=f"Click to invite {app_info.name}!"
)
)

await inter.response.send_message(
message,
Expand Down
Loading