From 144aafccdf87160a92f50010439d598d7919e6c1 Mon Sep 17 00:00:00 2001 From: SeaswimmerTheFsh Date: Thu, 2 Nov 2023 20:59:45 -0400 Subject: [PATCH] misc(moderation): working on sqlalchemy migration --- moderation/info.json | 2 +- moderation/moderation.py | 413 +++++++++++++++++++-------------------- poetry.lock | 160 ++++++++++++++- pyproject.toml | 1 + 4 files changed, 365 insertions(+), 211 deletions(-) diff --git a/moderation/info.json b/moderation/info.json index 1f7d713..92d0562 100644 --- a/moderation/info.json +++ b/moderation/info.json @@ -5,7 +5,7 @@ "short" : "Custom cog intended for use on the Galaxy discord server.", "description" : "Custom cog intended for use on the Galaxy discord server.", "end_user_data_statement" : "This cog does not store any End User Data.", - "requirements": ["mysql-connector-python", "humanize", "pytimeparse2"], + "requirements": ["sqlalchemy", "humanize", "pytimeparse2"], "hidden": false, "disabled": false } diff --git a/moderation/moderation.py b/moderation/moderation.py index 092ad3a..acf5eae 100644 --- a/moderation/moderation.py +++ b/moderation/moderation.py @@ -4,7 +4,11 @@ from datetime import datetime, timedelta, timezone from typing import Union import discord import humanize -import mysql.connector +from sqlalchemy import MetaData, Table, Column, Integer, String, Boolean, DateTime, Text, BigInteger, func, or_, and_ +from sqlalchemy.exc import OperationalError, NoSuchTableError +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine from discord.ext import tasks from pytimeparse2 import disable_dateutil, parse from redbot.core import app_commands, checks, Config, commands @@ -19,10 +23,8 @@ class Moderation(commands.Cog): self.bot = bot self.config = Config.get_conf(self, identifier=481923957134912) self.config.register_global( - mysql_address= " ", - mysql_database = " ", - mysql_username = " ", - mysql_password = " " + db_use_sqlite = True, + db_connection_string = " " ) self.config.register_guild( ignore_other_bots = True, @@ -105,78 +107,69 @@ class Moderation(commands.Cog): moderation_type = 'UNMUTE' else: return - await self.mysql_log(entry.guild.id, entry.user.id, moderation_type, entry.target.id, duration, reason) + await self.sql_log(entry.guild.id, entry.user.id, moderation_type, entry.target.id, duration, reason) async def connect(self): """Connects to the MySQL database, and returns a connection object.""" - conf = await self.check_conf([ - 'mysql_address', - 'mysql_database', - 'mysql_username', - 'mysql_password' - ]) - if conf: - raise LookupError("MySQL connection details not set properly!") try: - connection = mysql.connector.connect( - host=await self.config.mysql_address(), - user=await self.config.mysql_username(), - password=await self.config.mysql_password(), - database=await self.config.mysql_database() - ) - return connection - except mysql.connector.ProgrammingError as e: - self.logger.fatal("Unable to access the MySQL database!\nError:\n%s", e.msg) - raise ConnectionRefusedError(f"Unable to access the MySQL Database!\n{e.msg}") from e + if await self.config.db_use_sqlite() is False and await self.config.db_connection_string() != " ": + engine = create_async_engine(await self.config.db_connection_string()) + else: + engine = create_async_engine('sqlite:///moderation.db') + except OperationalError as e: + self.logger.fatal("Unable to access the database!\nError:\n%s", e.msg) + raise ConnectionRefusedError(f"Unable to access the Database!\n{e.msg}") from e + return engine + + async def fetch_table(self, engine, guild_id): + table = Table(f'moderation_{guild_id}', MetaData(), autoload_with=engine) + return table async def create_guild_table(self, guild: discord.Guild): - database = await self.connect() - cursor = database.cursor() + engine = await self.connect() + metadata = MetaData() try: - cursor.execute(f"SELECT * FROM `moderation_{guild.id}`") + await self.fetch_table(engine, guild.id) self.logger.info("MySQL Table exists for server %s (%s)", guild.name, guild.id) - except mysql.connector.errors.ProgrammingError: - query = f""" - CREATE TABLE `moderation_{guild.id}` ( - moderation_id INT UNIQUE PRIMARY KEY NOT NULL, - timestamp INT NOT NULL, - moderation_type LONGTEXT NOT NULL, - target_id LONGTEXT NOT NULL, - moderator_id LONGTEXT NOT NULL, - duration LONGTEXT, - end_timestamp INT, - reason LONGTEXT, - resolved BOOL NOT NULL, - resolved_by LONGTEXT, - resolve_reason LONGTEXT, - expired BOOL NOT NULL - ) - """ - cursor.execute(query) - index_query_1 = "CREATE INDEX idx_target_id ON moderation_%s(target_id(25));" - cursor.execute(index_query_1, (guild.id,)) - index_query_2 = "CREATE INDEX idx_moderator_id ON moderation_%s(moderator_id(25));" - cursor.execute(index_query_2, (guild.id,)) - index_query_3 = "CREATE INDEX idx_moderation_id ON moderation_%s(moderation_id);" - cursor.execute(index_query_3, (guild.id,)) - insert_query = f""" - INSERT INTO `moderation_{guild.id}` - (moderation_id, timestamp, moderation_type, target_id, moderator_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """ - insert_values = (0, 0, "NULL", 0, 0, "NULL", 0, "NULL", 0, "NULL", "NULL", 0) - cursor.execute(insert_query, insert_values) - database.commit() - self.logger.info("MySQL Table (moderation_%s) created for %s (%s)", guild.id, guild.name, guild.id) - database.close() - - async def check_conf(self, config: list): - """Checks if any required config options are not set.""" - not_found_list = [] - for item in config: - if await self.config.item() == " ": - not_found_list.append(item) - return not_found_list + except NoSuchTableError: + moderation = Table( + f'moderation_{guild.id}', metadata, + Column('moderationId', Integer, primary_key=True, autoincrement=True, index=True), + Column('timestamp', Integer, nullable=False), + Column('moderationType', String(20), nullable=False), + Column('targetId', BigInteger, nullable=False, index=True), + Column('moderatorId', BigInteger, nullable=False, index=True), + Column('roleId', BigInteger), + Column('duration', String(40)), + Column('endTimestamp', Integer), + Column('reason', Text), + Column('resolved', Boolean, nullable=False), + Column('resolvedBy', BigInteger), + Column('resolveReason', Text), + Column('expired', Boolean, nullable=False) + ) + ins = moderation.insert() + ins = moderation.insert().values( + moderationId=0, + timestamp=0, + moderationType="NULL", + targetId=0, + moderatorId=0, + roleId=0, + duration="NULL", + endTimestamp=0, + reason="NULL", + resolved=0, + resolvedBy=0, + resolveReason="NULL", + expired=0 + ) + async with engine.begin() as conn: + await conn.run_sync(metadata.create_all) + await conn.execute(ins) + conn.commit() + conn.close() + self.logger.info("Database Table (moderation_%s) created for %s (%s)", guild.id, guild.name, guild.id) def check_permissions(self, user: discord.User, permissions: list, ctx: Union[commands.Context, discord.Interaction] = None, guild: discord.Guild = None): """Checks if a user has a specific permission (or a list of permissions) in a channel.""" @@ -193,31 +186,45 @@ class Moderation(commands.Cog): return permission return False - async def mysql_log(self, guild_id: str, author_id: str, moderation_type: str, target_id: int, duration, reason: str): + async def sql_log(self, guild_id: str, author_id: str, moderation_type: str, target_id: int, duration, reason: str): timestamp = int(time.time()) if duration != "NULL": end_timedelta = datetime.fromtimestamp(timestamp) + duration end_timestamp = int(end_timedelta.timestamp()) else: end_timestamp = 0 - database = await self.connect() - cursor = database.cursor() - moderation_id = await self.get_next_case_number(guild_id=guild_id, cursor=cursor) - sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_id, moderator_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" - val = (moderation_id, timestamp, moderation_type, target_id, author_id, duration, end_timestamp, f"{reason}", 0, "NULL", "NULL", 0) - cursor.execute(sql, val) - database.commit() - database.close() - self.logger.debug("MySQL row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, 0, NULL, NULL, 0", guild_id, moderation_id, timestamp, moderation_type, target_id, author_id, duration, end_timestamp, reason) + engine = await self.connect() + table = await self.fetch_table(engine, guild_id) + moderation_id = await self.get_next_case_number(guild_id=guild_id, engine=engine) + sql = table.insert().values( + moderation_id=moderation_id, + timestamp=timestamp, + moderation_type=moderation_type, + target_id=target_id, + moderator_id=author_id, + duration=duration, + end_timestamp=end_timestamp, + reason=reason, + resolved=0, + resolved_by="NULL", + resolve_reason="NULL", + expired=0 + ) + async with engine.connect() as conn: + conn.execute(sql) + conn.commit() + conn.close() + self.logger.debug("Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, 0, NULL, NULL, 0", guild_id, moderation_id, timestamp, moderation_type, target_id, author_id, duration, end_timestamp, reason) return moderation_id - async def get_next_case_number(self, guild_id: str, cursor = None): - """This method returns the next case number from the MySQL table for a specific guild.""" - if not cursor: - database = await self.connect() - cursor = database.cursor() - cursor.execute(f"SELECT moderation_id FROM `moderation_{guild_id}` ORDER BY moderation_id DESC LIMIT 1") - return cursor.fetchone()[0] + 1 + async def get_next_case_number(self, guild_id: str, engine: AsyncEngine): + """This method returns the next case number from the database table for a specific guild.""" + table = await self.fetch_table(engine, guild_id) + async with engine.connect() as conn: + result = await conn.execute(table.select().order_by(table.c.moderationId.desc()).limit(1)) + result = result.fetchone() + conn.close() + return result[0] + 1 if result else 1 def generate_dict(self, result): case: dict = { @@ -226,13 +233,14 @@ class Moderation(commands.Cog): "moderation_type": result[2], "target_id": result[3], "moderator_id": result[4], - "duration": result[5], - "end_timestamp": result[6], - "reason": result[7], - "resolved": result[8], - "resolved_by": result[9], - "resolve_reason": result[10], - "expired": result[11] + "role_id": result[5], + "duration": result[6], + "end_timestamp": result[7], + "reason": result[8], + "resolved": result[9], + "resolved_by": result[10], + "resolve_reason": result[11], + "expired": result[12] } return case @@ -346,13 +354,12 @@ class Moderation(commands.Cog): async def fetch_case(self, moderation_id: int, guild_id: str): """This method fetches a case from the database and returns the case's dictionary.""" - database = await self.connect() - cursor = database.cursor() - query = "SELECT * FROM moderation_%s WHERE moderation_id = %s;" - cursor.execute(query, (guild_id, moderation_id)) - result = cursor.fetchone() - cursor.close() - database.close() + engine = await self.connect() + table = await self.fetch_table(engine, guild_id) + async with engine.connect() as conn: + result = await conn.execute(table.select().where(table.c.moderation_id == moderation_id)) + result = result.fetchone() + conn.close() return self.generate_dict(result) async def log(self, interaction: discord.Interaction, moderation_id: int, resolved: bool = False): @@ -397,7 +404,7 @@ class Moderation(commands.Cog): await target.send(embed=embed) except discord.errors.HTTPException: pass - moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'NOTE', target.id, 'NULL', reason) + moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'NOTE', target.id, 'NULL', reason) await self.log(interaction, moderation_id) @app_commands.command(name="warn") @@ -429,7 +436,7 @@ class Moderation(commands.Cog): await target.send(embed=embed) except discord.errors.HTTPException: pass - moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'WARN', target.id, 'NULL', reason) + moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'WARN', target.id, 'NULL', reason) await self.log(interaction, moderation_id) @app_commands.command(name="mute") @@ -479,7 +486,7 @@ class Moderation(commands.Cog): await target.send(embed=embed) except discord.errors.HTTPException: pass - moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'MUTE', target.id, parsed_time, reason) + moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'MUTE', target.id, parsed_time, reason) await self.log(interaction, moderation_id) @app_commands.command(name="unmute") @@ -523,7 +530,7 @@ class Moderation(commands.Cog): await target.send(embed=embed) except discord.errors.HTTPException: pass - moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'UNMUTE', target.id, 'NULL', reason) + moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'UNMUTE', target.id, 'NULL', reason) await self.log(interaction, moderation_id) @app_commands.command(name="kick") @@ -560,7 +567,7 @@ class Moderation(commands.Cog): except discord.errors.HTTPException: pass await target.kick(f"Kicked by {interaction.user.id} for: {reason}") - moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'KICK', target.id, 'NULL', reason) + moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'KICK', target.id, 'NULL', reason) await self.log(interaction, moderation_id) @app_commands.command(name="ban") @@ -618,7 +625,7 @@ class Moderation(commands.Cog): except discord.errors.HTTPException: pass await interaction.guild.ban(target, reason=f"Tempbanned by {interaction.user.id} for: {reason} (Duration: {parsed_time})", delete_message_seconds=delete_messages) - await self.mysql_log(interaction.guild.id, interaction.user.id, 'TEMPBAN', target.id, parsed_time, reason) + await self.sql_log(interaction.guild.id, interaction.user.id, 'TEMPBAN', target.id, parsed_time, reason) else: await interaction.response.send_message(content=f"{target.mention} has been banned!\n**Reason** - `{reason}`") if silent is None: @@ -630,7 +637,7 @@ class Moderation(commands.Cog): except discord.errors.HTTPException: pass await interaction.guild.ban(target, reason=f"Banned by {interaction.user.id} for: {reason}", delete_message_seconds=delete_messages) - moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'BAN', target.id, 'NULL', reason) + moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'BAN', target.id, 'NULL', reason) await self.log(interaction, moderation_id) @app_commands.command(name="unban") @@ -676,7 +683,7 @@ class Moderation(commands.Cog): await target.send(embed=embed) except discord.errors.HTTPException: pass - moderation_id = await self.mysql_log(interaction.guild.id, interaction.user.id, 'UNBAN', target.id, 'NULL', reason) + moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'UNBAN', target.id, 'NULL', reason) await self.log(interaction, moderation_id) @app_commands.command(name="history") @@ -699,37 +706,26 @@ class Moderation(commands.Cog): if permissions: await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True) return - database = await self.connect() - cursor = database.cursor() - if target: - query = """SELECT * - FROM moderation_%s - WHERE target_id = %s - ORDER BY moderation_id DESC;""" - cursor.execute(query, (interaction.guild.id, target.id)) - elif moderator: - query = """SELECT * - FROM moderation_%s - WHERE moderator_id = %s - ORDER BY moderation_id DESC;""" - cursor.execute(query, (interaction.guild.id, moderator.id)) - else: - query = """SELECT * - FROM moderation_%s - ORDER BY moderation_id DESC;""" - cursor.execute(query, (interaction.guild.id,)) - results = cursor.fetchall() + start_index = (page - 1) * pagesize + end_index = page * pagesize + engine = await self.connect() + table = await self.fetch_table(engine, interaction.guild.id) + async with engine.connect() as conn: + if target: + results = conn.execute(table.select().where(table.c.targetId == target.id).order_by(table.c.moderationId.desc()).offset(start_index).limit(end_index - start_index + 1)) + case_quantity = conn.execute(table.select(func.count()).where(table.c.targetId == target.id)) + elif moderator: + results = conn.execute(table.select().where(table.c.moderatorId == moderator.id).order_by(table.c.moderationId.desc()).offset(start_index).limit(end_index - start_index + 1)) + case_quantity = conn.execute(table.select(func.count()).where(table.c.moderatorId == moderator.id)) + else: + results = conn.execute(table.select().order_by(table.c.moderationId.desc()).offset(start_index).limit(pagesize)) + case_quantity = conn.execute(table.select(func.count())) - 1 # account for case 0 techincally existing + conn.close() result_dict_list = [] for result in results: case_dict = self.generate_dict(result) result_dict_list.append(case_dict) - if target or moderator: - case_quantity = len(result_dict_list) - else: - case_quantity = len(result_dict_list) - 1 # account for case 0 technically existing page_quantity = round(case_quantity / pagesize) - start_index = (page - 1) * pagesize - end_index = page * pagesize embed = discord.Embed(color=await self.bot.get_embed_color(None)) embed.set_author(icon_url=interaction.guild.icon.url, name='Infraction History') embed.set_footer(text=f"Page {page}/{page_quantity} | {case_quantity} Results") @@ -765,56 +761,49 @@ class Moderation(commands.Cog): if permissions: await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True) return - conf = await self.check_conf(['mysql_database']) - if conf: - raise(LookupError) - database = await self.connect() - cursor = database.cursor() - db = await self.config.mysql_database() - query_1 = "SELECT * FROM moderation_%s WHERE moderation_id = %s;" - cursor.execute(query_1, (interaction.guild.id, case_number)) - result_1 = cursor.fetchone() - if result_1 is None or case_number == 0: - await interaction.response.send_message(content=f"There is no moderation with a case number of {case_number}.", ephemeral=True) - return - query_2 = "SELECT * FROM moderation_%s WHERE moderation_id = %s AND resolved = 0;" - cursor.execute(query_2, (interaction.guild.id, case_number)) - result_2 = cursor.fetchone() - if result_2 is None: - await interaction.response.send_message(content=f"This moderation has already been resolved!\nUse `/case {case_number}` for more information.", ephemeral=True) - return - case = self.generate_dict(result_2) - if reason is None: - reason = "No reason given." - if case['moderation_type'] in ['UNMUTE', 'UNBAN']: - await interaction.response.send_message(content="You cannot resolve this type of moderation!", ephemeral=True) - if case['moderation_type'] in ['MUTE', 'TEMPBAN', 'BAN']: - if case['moderation_type'] == 'MUTE': - try: - member = await interaction.guild.fetch_member(case['target_id']) - await member.timeout(None, reason=f"Case #{case_number} resolved by {interaction.user.id}") - except discord.NotFound: - pass - if case['moderation_type'] in ['TEMPBAN', 'BAN']: - try: - user = await interaction.client.fetch_user(case['target_id']) - await interaction.guild.unban(user, reason=f"Case #{case_number} resolved by {interaction.user.id}") - except discord.NotFound: - pass - resolve_query = f"UPDATE `{db}`.`moderation_{interaction.guild.id}` SET resolved = 1, expired = 1, resolved_by = %s, resolve_reason = %s WHERE moderation_id = %s" - else: - resolve_query = f"UPDATE `{db}`.`moderation_{interaction.guild.id}` SET resolved = 1, resolved_by = %s, resolve_reason = %s WHERE moderation_id = %s" - cursor.execute(resolve_query, (interaction.user.id, reason, case_number)) - database.commit() - response_query = "SELECT * FROM moderation_%s WHERE moderation_id = %s;" - cursor.execute(response_query, (interaction.guild.id, case_number)) - result = cursor.fetchone() + engine = await self.connect() + table = await self.fetch_table(engine, interaction.guild.id) + async with engine.connect() as conn: + result_1 = conn.execute(table.select().where(table.c.moderationId == case_number))[0] + if result_1 is None or case_number == 0: + await interaction.response.send_message(content=f"There is no moderation with a case number of {case_number}.", ephemeral=True) + conn.close() + return + result_2 = conn.execute(table.select().where(table.c.moderationId == case_number).where(table.c.resolved == 0))[0] + if result_2 is None: + await interaction.response.send_message(content=f"This moderation has already been resolved!\nUse `/case {case_number}` for more information.", ephemeral=True) + conn.close() + return + case = self.generate_dict(result_2) + if reason is None: + reason = "No reason given." + if case['moderation_type'] in ['UNMUTE', 'UNBAN']: + await interaction.response.send_message(content="You cannot resolve this type of moderation!", ephemeral=True) + conn.close() + return + if case['moderation_type'] in ['MUTE', 'TEMPBAN', 'BAN']: + if case['moderation_type'] == 'MUTE': + try: + member = await interaction.guild.fetch_member(case['target_id']) + await member.timeout(None, reason=f"Case #{case_number} resolved by {interaction.user.id}") + except discord.NotFound: + pass + if case['moderation_type'] in ['TEMPBAN', 'BAN']: + try: + user = await interaction.client.fetch_user(case['target_id']) + await interaction.guild.unban(user, reason=f"Case #{case_number} resolved by {interaction.user.id}") + except discord.NotFound: + pass + conn.execute(table.update().where(table.c.moderationId == case_number).values(resolved=1, expired=1, resolvedBy=interaction.user.id, resolveReason=reason)) + else: + conn.execute(table.update().where(table.c.moderationId == case_number).values(resolved=1, resolvedBy=interaction.user.id, resolveReason=reason)) + conn.commit() + result = conn.execute(table.select().where(table.c.moderationId == case_number))[0] + conn.close() case_dict = self.generate_dict(result) embed = await self.embed_factory('case', interaction=interaction, case_dict=case_dict) await interaction.response.send_message(content=f"✅ Moderation #{case_number} resolved!", embed=embed) await self.log(interaction, case_number, True) - cursor.close() - database.close() @app_commands.command(name="case") async def case(self, interaction: discord.Interaction, case_number: int, ephemeral: bool = False): @@ -840,36 +829,44 @@ class Moderation(commands.Cog): @tasks.loop(minutes=1) async def handle_expiry(self): - conf = await self.check_conf(['mysql_database']) - if conf: - raise(LookupError) - database = await self.connect() - cursor = database.cursor() + engine = await self.connect() db = await self.config.mysql_database() guilds: list[discord.Guild] = self.bot.guilds - for guild in guilds: - if not await self.bot.cog_disabled_in_guild(self, guild): - tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= %s AND moderation_type = 'TEMPBAN' AND expired = 0" - try: - cursor.execute(tempban_query, (time.time(),)) - result = cursor.fetchall() - except mysql.connector.errors.ProgrammingError: - continue - target_ids = [row[0] for row in result] - moderation_ids = [row[1] for row in result] - for target_id, moderation_id in zip(target_ids, moderation_ids): - user: discord.User = await self.bot.fetch_user(target_id) - await guild.unban(user, reason=f"Automatic unban from case #{moderation_id}") - embed = await self.embed_factory('message', guild, f'Automatic unban from case #{moderation_id}', 'unbanned') - try: - await user.send(embed=embed) - except discord.errors.HTTPException: - pass - expiry_query = f"UPDATE `{db}`.`moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= %s AND expired = 0) OR (expired = 0 AND resolved = 1)" - cursor.execute(expiry_query, (time.time(),)) - database.commit() - cursor.close() - database.close() + async with engine.connect() as conn: + for guild in guilds: + if not await self.bot.cog_disabled_in_guild(self, guild): + table = await self.fetch_table(engine, guild.id) + result = conn.execute( + table.select().where(table.c.moderationType == 'TEMPBAN').where(table.c.expired == 0).where(table.c.endTimestamp != 0).where(table.c.endTimestamp <= time.time())) + target_ids = [row[0] for row in result] + moderation_ids = [row[1] for row in result] + for target_id, moderation_id in zip(target_ids, moderation_ids): + user: discord.User = await self.bot.fetch_user(target_id) + await guild.unban(user, reason=f"Automatic unban from case #{moderation_id}") + embed = await self.embed_factory('message', guild, f'Automatic unban from case #{moderation_id}', 'unbanned') + try: + await user.send(embed=embed) + except discord.errors.HTTPException: + pass + conn.execute( + table.update(). + where( + or_( + and_( + table.c.end_timestamp != 0, + table.c.end_timestamp <= time.time(), + table.c.expired == 0 + ), + and_( + table.c.expired == 0, + table.c.resolved == 1 + ) + ) + ). + values(expired=1) + ) + conn.commit() + conn.close() @commands.group(autohelp=True) @checks.admin() diff --git a/poetry.lock b/poetry.lock index 9413e06..1dab71d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiodns" @@ -730,6 +730,76 @@ files = [ {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, ] +[[package]] +name = "greenlet" +version = "3.0.1" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"}, + {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"}, + {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"}, + {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"}, + {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"}, + {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"}, + {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"}, + {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"}, + {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"}, + {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"}, + {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"}, + {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"}, + {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"}, + {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"}, + {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"}, + {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"}, + {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"}, + {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"}, + {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"}, + {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"}, + {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"}, + {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"}, + {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"}, + {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"}, + {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"}, + {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"}, + {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"}, +] + +[package.extras] +docs = ["Sphinx"] +test = ["objgraph", "psutil"] + [[package]] name = "h11" version = "0.14.0" @@ -2015,6 +2085,92 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.22" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, + {file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"}, + {file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", optional = true, markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or extra == \"asyncio\""} +typing-extensions = ">=4.2.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] + [[package]] name = "tomli" version = "2.0.1" @@ -2281,4 +2437,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "84ec60771c14ef544d86eb389954a47c2767491c95ee63a0632aa5be68205468" +content-hash = "353a8b2f960633927176885381eefa3eee9441798d53026556152a635f75cd10" diff --git a/pyproject.toml b/pyproject.toml index 7b7bd98..44eefb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ prisma = "^0.10.0" mysql-connector-python = "^8.1.0" humanize = "^4.8.0" pytube = "^15.0.0" +sqlalchemy = {extras = ["asyncio"], version = "^2.0.22"} [tool.poetry.group.dev] optional = true