diff --git a/aurora/aurora.py b/aurora/aurora.py index 1efb03c..0b4463a 100644 --- a/aurora/aurora.py +++ b/aurora/aurora.py @@ -7,11 +7,11 @@ import json import os -import sqlite3 import time from datetime import datetime, timedelta, timezone from math import ceil +import aiosqlite import discord from discord import Object from discord.ext import tasks @@ -50,21 +50,21 @@ class Aurora(commands.Cog): if requester == "discord_deleted_user": await config.user_from_id(user_id).clear() - database = connect() - cursor = database.cursor() + database = await connect() + cursor = await database.cursor() - cursor.execute("SHOW TABLES;") + await cursor.execute("SHOW TABLES;") tables = [table[0] for table in cursor.fetchall()] condition = "target_id = %s OR moderator_id = %s;" for table in tables: delete_query = f"DELETE FROM {table[0]} WHERE {condition}" - cursor.execute(delete_query, (user_id, user_id)) + await cursor.execute(delete_query, (user_id, user_id)) - database.commit() - cursor.close() - database.close() + await database.commit() + await cursor.close() + await database.close() if requester == "owner": await config.user_from_id(user_id).clear() if requester == "user": @@ -121,15 +121,13 @@ class Aurora(commands.Cog): async def addrole_on_member_join(self, member: discord.Member): """This method automatically adds roles to users when they join the server.""" if not await self.bot.cog_disabled_in_guild(self, member.guild): - query = f"""SELECT moderation_id, role_id, reason FROM moderation_{member.guild.id} WHERE target_id = ? AND moderation_type = 'ADDROLE' AND expired = 0 AND resolved = 0;""" - database = connect() - cursor = database.cursor() - cursor.execute(query, (member.id,)) - results = cursor.fetchall() - for result in results: - role = member.guild.get_role(result[1]) - reason = result[2] - await member.add_roles(role, reason=f"Role automatically added on member rejoin for: {reason} (Case #{result[0]:,})") + async with connect() as database: + query = f"""SELECT moderation_id, role_id, reason FROM moderation_{member.guild.id} WHERE target_id = ? AND moderation_type = 'ADDROLE' AND expired = 0 AND resolved = 0;""" + async with database.execute(query, (member.id,)) as cursor: + async for row in cursor: + role = member.guild.get_role(row[1]) + reason = row[2] + await member.add_roles(role, reason=f"Role automatically added on member rejoin for: {reason} (Case #{row[0]:,})") @commands.Cog.listener("on_audit_log_entry_create") async def autologger(self, entry: discord.AuditLogEntry): @@ -175,7 +173,7 @@ class Aurora(commands.Cog): else: return - Moderation.log( + await Moderation.log( self.bot, entry.guild.id, entry.user.id, @@ -233,7 +231,7 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -292,7 +290,7 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -398,7 +396,7 @@ class Aurora(commands.Cog): content=f"{target.mention} has been given the {role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`" ) - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -504,7 +502,7 @@ class Aurora(commands.Cog): content=f"{target.mention} has had the {role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`" ) - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -592,7 +590,7 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -728,7 +726,7 @@ class Aurora(commands.Cog): await target.kick(reason=f"Kicked by {interaction.user.id} for: {reason}") - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -836,7 +834,7 @@ class Aurora(commands.Cog): delete_message_seconds=delete_messages_seconds, ) - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -880,7 +878,7 @@ class Aurora(commands.Cog): delete_message_seconds=delete_messages_seconds, ) - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -957,7 +955,7 @@ class Aurora(commands.Cog): except discord.errors.HTTPException: pass - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -1001,7 +999,7 @@ class Aurora(commands.Cog): await channel.edit(slowmode_delay=interval) await interaction.response.send_message(f"Slowmode in {channel.mention} has been set to {interval} seconds!\n**Reason** - `{reason}`") - moderation = Moderation.log( + moderation = await Moderation.log( interaction.client, interaction.guild.id, interaction.user.id, @@ -1089,7 +1087,7 @@ class Aurora(commands.Cog): ) return - database = connect() + database = await connect() if export: try: @@ -1099,7 +1097,7 @@ class Aurora(commands.Cog): + f"moderation_{interaction.guild.id}.json" ) - cases = Moderation.get_latest(bot=interaction.client, guild_id=interaction.guild.id) + cases = await Moderation.get_latest(bot=interaction.client, guild_id=interaction.guild.id) with open(filename, "w", encoding="utf-8") as f: dump(obj=cases, fp=f, indent=2) @@ -1120,15 +1118,15 @@ class Aurora(commands.Cog): + box(e, "py"), ephemeral=ephemeral, ) - database.close() + await database.close() return if target: - moderations = Moderation.find_by_target(interaction.client, interaction.guild.id, target.id) + moderations = await Moderation.find_by_target(interaction.client, interaction.guild.id, target.id) elif moderator: - moderations = Moderation.find_by_moderator(interaction.client, interaction.guild.id, moderator.id) + moderations = await Moderation.find_by_moderator(interaction.client, interaction.guild.id, moderator.id) else: - moderations = Moderation.get_latest(interaction.client, interaction.guild.id) + moderations = await Moderation.get_latest(interaction.client, interaction.guild.id) case_quantity = len(moderations) page_quantity = ceil(case_quantity / pagesize) @@ -1214,7 +1212,7 @@ class Aurora(commands.Cog): return try: - moderation = Moderation.find_by_id(interaction.client, case, interaction.guild.id) + moderation = await Moderation.find_by_id(interaction.client, case, interaction.guild.id) except ValueError: await interaction.response.send_message( content=error(f"Case #{case:,} does not exist!"), ephemeral=True @@ -1298,7 +1296,7 @@ class Aurora(commands.Cog): ) try: - mod = Moderation.find_by_id(interaction.client, case, interaction.guild.id) + mod = await Moderation.find_by_id(interaction.client, case, interaction.guild.id) except ValueError: await interaction.response.send_message( content=error(f"Case #{case:,} does not exist!"), ephemeral=True @@ -1391,7 +1389,7 @@ class Aurora(commands.Cog): return try: - moderation = Moderation.find_by_id(interaction.client, case, interaction.guild.id) + moderation = await Moderation.find_by_id(interaction.client, case, interaction.guild.id) old_moderation = moderation except ValueError: await interaction.response.send_message( @@ -1469,7 +1467,7 @@ class Aurora(commands.Cog): "end_timestamp": moderation.end_timestamp, })) - moderation.update() + await moderation.update() embed = await case_factory(interaction=interaction, moderation=moderation) await interaction.response.send_message( @@ -1485,8 +1483,8 @@ class Aurora(commands.Cog): async def handle_expiry(self): await self.bot.wait_until_red_ready() current_time = time.time() - database = connect() - cursor = database.cursor() + database = await connect() + cursor = await database.cursor() global_unban_num = 0 global_addrole_num = 0 global_removerole_num = 0 @@ -1499,9 +1497,9 @@ class Aurora(commands.Cog): tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL 0 AND end_timestamp <= ? AND moderation_type = 'TEMPBAN' AND expired = 0" try: - cursor.execute(tempban_query, (time.time(),)) + await cursor.execute(tempban_query, (time.time(),)) result = cursor.fetchall() - except sqlite3.OperationalError: + except aiosqlite.OperationalError: continue target_ids = [row[0] for row in result] @@ -1558,9 +1556,9 @@ class Aurora(commands.Cog): removerole_num = 0 addrole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND moderation_type = 'ADDROLE' AND expired = 0" try: - cursor.execute(addrole_query, (time.time(),)) - result = cursor.fetchall() - except sqlite3.OperationalError: + await cursor.execute(addrole_query, (time.time(),)) + result = await cursor.fetchall() + except aiosqlite.OperationalError: continue target_ids = [row[0] for row in result] moderation_ids = [row[1] for row in result] @@ -1593,9 +1591,9 @@ class Aurora(commands.Cog): addrole_num = 0 removerole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL 0 AND end_timestamp <= ? AND moderation_type = 'REMOVEROLE' AND expired = 0" try: - cursor.execute(removerole_query, (time.time(),)) - result = cursor.fetchall() - except sqlite3.OperationalError: + await cursor.execute(removerole_query, (time.time(),)) + result = await cursor.fetchall() + except aiosqlite.OperationalError: continue target_ids = [row[0] for row in result] moderation_ids = [row[1] for row in result] @@ -1621,7 +1619,7 @@ class Aurora(commands.Cog): continue expiry_query = f"UPDATE `moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp IS NOT NULL AND end_timestamp <= ? AND expired = 0) OR (expired = 0 AND resolved = 1);" - cursor.execute(expiry_query, (time.time(),)) + await cursor.execute(expiry_query, (time.time(),)) per_guild_completion_time = (time.time() - time_per_guild) * 1000 logger.debug( @@ -1637,9 +1635,9 @@ class Aurora(commands.Cog): global_addrole_num = global_addrole_num + addrole_num global_removerole_num = global_removerole_num + removerole_num - database.commit() - cursor.close() - database.close() + await database.commit() + await cursor.close() + await database.close() completion_time = (time.time() - current_time) * 1000 logger.debug( diff --git a/aurora/importers/aurora.py b/aurora/importers/aurora.py index 87091bd..f154dab 100644 --- a/aurora/importers/aurora.py +++ b/aurora/importers/aurora.py @@ -27,14 +27,10 @@ class ImportAuroraView(ui.View): "Deleting original table...", ephemeral=True ) - database = connect() - cursor = database.cursor() - - query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};" - cursor.execute(query) - - cursor.close() - database.commit() + async with connect() as database: + query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};" + database.execute(query) + database.commit() await interaction.edit_original_response(content="Creating new table...") @@ -96,7 +92,7 @@ class ImportAuroraView(ui.View): duration = None try: - Moderation.log( + await Moderation.log( bot=interaction.client, guild_id=self.ctx.guild.id, moderator_id=case["moderator_id"], diff --git a/aurora/importers/galacticbot.py b/aurora/importers/galacticbot.py index a7c2d27..453cac0 100644 --- a/aurora/importers/galacticbot.py +++ b/aurora/importers/galacticbot.py @@ -26,14 +26,14 @@ class ImportGalacticBotView(ui.View): "Deleting original table...", ephemeral=True ) - database = connect() - cursor = database.cursor() + database = await connect() + cursor = await database.cursor() query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};" - cursor.execute(query) + await cursor.execute(query) - cursor.close() - database.commit() + await cursor.close() + await database.commit() await interaction.edit_original_response(content="Creating new table...") @@ -124,7 +124,7 @@ class ImportGalacticBotView(ui.View): else: reason = None - Moderation.log( + await Moderation.log( self.ctx.guild.id, case["executor"], case["type"], diff --git a/aurora/info.json b/aurora/info.json index d232f7e..34b74c9 100644 --- a/aurora/info.json +++ b/aurora/info.json @@ -9,7 +9,7 @@ "disabled": false, "min_bot_version": "3.5.0", "min_python_version": [3, 10, 0], - "requirements": ["pydantic"], + "requirements": ["pydantic", "aiosqlite"], "tags": [ "mod", "moderate", diff --git a/aurora/models/moderation.py b/aurora/models/moderation.py index f527fff..31912d1 100644 --- a/aurora/models/moderation.py +++ b/aurora/models/moderation.py @@ -1,11 +1,11 @@ import json import sqlite3 from datetime import datetime, timedelta -from sqlite3 import Cursor from time import time from typing import Dict, Iterable, List, Optional, Tuple, Union import discord +from aiosqlite import Cursor from discord import NotFound from redbot.core.bot import Red @@ -115,15 +115,15 @@ class Moderation(AuroraGuildModel): "user_id": resolved_by, })) - self.update() + await self.update() - def update(self) -> None: + async def update(self) -> None: from ..utilities.database import connect from ..utilities.json import dumps query = f"UPDATE moderation_{self.guild_id} SET timestamp = ?, moderation_type = ?, target_type = ?, moderator_id = ?, role_id = ?, duration = ?, end_timestamp = ?, reason = ?, resolved = ?, resolved_by = ?, resolve_reason = ?, expired = ?, changes = ?, metadata = ? WHERE moderation_id = ?;" - with connect() as database: - database.execute(query, ( + async with connect() as database: + await database.execute(query, ( self.timestamp.timestamp(), self.moderation_type, self.target_type, @@ -206,7 +206,7 @@ class Moderation(AuroraGuildModel): return cls.from_dict(bot=bot, data=case) @classmethod - def execute(cls, bot: Red, guild_id: int, query: str, parameters: tuple | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + async def execute(cls, bot: Red, guild_id: int, query: str, parameters: tuple | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: from ..utilities.database import connect logger.trace("Executing query: %s", query) logger.trace("With parameters: %s", parameters) @@ -214,16 +214,16 @@ class Moderation(AuroraGuildModel): parameters = () if not cursor: no_cursor = True - database = connect() - cursor = database.cursor() + database = await connect() + cursor = await database.cursor() else: no_cursor = False - cursor.execute(query, parameters) - results = cursor.fetchall() + await cursor.execute(query, parameters) + results = await cursor.fetchall() if no_cursor: - cursor.close() - database.close() + await cursor.close() + await database.close() if results: cases = [] @@ -235,7 +235,7 @@ class Moderation(AuroraGuildModel): return () @classmethod - def get_latest(cls, bot: Red, guild_id: int, limit: int | None = None, offset: int = 0, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + async def get_latest(cls, bot: Red, guild_id: int, limit: int | None = None, offset: int = 0, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: params = [] query = f"SELECT * FROM moderation_{guild_id} ORDER BY moderation_id DESC" if types: @@ -245,41 +245,41 @@ class Moderation(AuroraGuildModel): query += " LIMIT ? OFFSET ?" params.extend((limit, offset)) query += ";" - return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=tuple(params) if params else (), cursor=cursor) + return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=tuple(params) if params else (), cursor=cursor) @classmethod - def get_next_case_number(cls, bot: Red, guild_id: int, cursor: Cursor | None = None) -> int: - result = cls.get_latest(bot=bot, guild_id=guild_id, cursor=cursor, limit=1) + async def get_next_case_number(cls, bot: Red, guild_id: int, cursor: Cursor | None = None) -> int: + result = await cls.get_latest(bot=bot, guild_id=guild_id, cursor=cursor, limit=1) return (result[0].moderation_id + 1) if result else 1 @classmethod - def find_by_id(cls, bot: Red, moderation_id: int, guild_id: int, cursor: Cursor | None = None) -> "Moderation": + async def find_by_id(cls, bot: Red, moderation_id: int, guild_id: int, cursor: Cursor | None = None) -> "Moderation": query = f"SELECT * FROM moderation_{guild_id} WHERE moderation_id = ?;" - case = cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderation_id,), cursor=cursor) + case = await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderation_id,), cursor=cursor) if case: return case[0] raise ValueError(f"Case {moderation_id} not found in moderation_{guild_id}!") @classmethod - def find_by_target(cls, bot: Red, guild_id: int, target: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + async def find_by_target(cls, bot: Red, guild_id: int, target: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: query = f"SELECT * FROM moderation_{guild_id} WHERE target_id = ?" if types: query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})" query += " ORDER BY moderation_id DESC;" - return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(target, *types) if types else (target,), cursor=cursor) + return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(target, *types) if types else (target,), cursor=cursor) @classmethod - def find_by_moderator(cls, bot: Red, guild_id: int, moderator: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + async def find_by_moderator(cls, bot: Red, guild_id: int, moderator: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: query = f"SELECT * FROM moderation_{guild_id} WHERE moderator_id = ?" if types: query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})" query += " ORDER BY moderation_id DESC;" - return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderator, *types) if types else (moderator,), cursor=cursor) + return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderator, *types) if types else (moderator,), cursor=cursor) @classmethod - def log( + async def log( cls, bot: Red, guild_id: int, @@ -335,13 +335,12 @@ class Moderation(AuroraGuildModel): role_id = None if not database: - database = connect() + database = await connect() close_db = True else: close_db = False - cursor = database.cursor() - moderation_id = cls.get_next_case_number(bot=bot, guild_id=guild_id, cursor=cursor) + moderation_id = cls.get_next_case_number(bot=bot, guild_id=guild_id) case = { "moderation_id": moderation_id, @@ -363,12 +362,12 @@ class Moderation(AuroraGuildModel): } sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(sql, tuple(case.values())) + await database.execute(sql, tuple(case.values())) - cursor.close() - database.commit() + await database.close() + await database.commit() if close_db: - database.close() + await database.close() logger.debug( "Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s", @@ -392,5 +391,5 @@ class Moderation(AuroraGuildModel): ) if return_obj: - return cls.find_by_id(bot=bot, moderation_id=moderation_id, guild_id=guild_id) + return await cls.find_by_id(bot=bot, moderation_id=moderation_id, guild_id=guild_id) return moderation_id diff --git a/aurora/utilities/database.py b/aurora/utilities/database.py index 588ad35..3894bce 100644 --- a/aurora/utilities/database.py +++ b/aurora/utilities/database.py @@ -1,22 +1,22 @@ # pylint: disable=cyclic-import import json -import sqlite3 +import aiosqlite from discord import Guild from redbot.core import data_manager from .logger import logger -def connect() -> sqlite3.Connection: +async def connect() -> aiosqlite.Connection: """Connects to the SQLite database, and returns a connection object.""" try: - connection = sqlite3.connect( + connection = await aiosqlite.connect( database=data_manager.cog_data_path(raw_name="Aurora") / "aurora.db" ) return connection - except sqlite3.OperationalError as e: + except aiosqlite.OperationalError as e: logger.error("Unable to access the SQLite database!\nError:\n%s", e.msg) raise ConnectionRefusedError( f"Unable to access the SQLite Database!\n{e.msg}" @@ -24,14 +24,13 @@ def connect() -> sqlite3.Connection: async def create_guild_table(guild: Guild): - database = connect() - cursor = database.cursor() + database = await connect() try: - cursor.execute(f"SELECT * FROM `moderation_{guild.id}`") + await database.execute(f"SELECT * FROM `moderation_{guild.id}`") logger.debug("SQLite Table exists for server %s (%s)", guild.name, guild.id) - except sqlite3.OperationalError: + except aiosqlite.OperationalError: query = f""" CREATE TABLE `moderation_{guild.id}` ( moderation_id INTEGER PRIMARY KEY, @@ -52,16 +51,16 @@ async def create_guild_table(guild: Guild): metadata JSON NOT NULL ) """ - cursor.execute(query) + await database.execute(query) index_query_1 = f"CREATE INDEX IF NOT EXISTS idx_target_id ON moderation_{guild.id}(target_id);" - cursor.execute(index_query_1) + await database.execute(index_query_1) index_query_2 = f"CREATE INDEX IF NOT EXISTS idx_moderator_id ON moderation_{guild.id}(moderator_id);" - cursor.execute(index_query_2) + await database.execute(index_query_2) index_query_3 = f"CREATE INDEX IF NOT EXISTS idx_moderation_id ON moderation_{guild.id}(moderation_id);" - cursor.execute(index_query_3) + await database.execute(index_query_3) insert_query = f""" INSERT INTO `moderation_{guild.id}` @@ -86,9 +85,9 @@ async def create_guild_table(guild: Guild): json.dumps([]), json.dumps({}), ) - cursor.execute(insert_query, insert_values) + await database.execute(insert_query, insert_values) - database.commit() + await database.commit() logger.debug( "SQLite Table (moderation_%s) created for %s (%s)", diff --git a/aurora/utilities/utils.py b/aurora/utilities/utils.py index ea39d65..49346dc 100644 --- a/aurora/utilities/utils.py +++ b/aurora/utilities/utils.py @@ -120,7 +120,7 @@ async def log(interaction: Interaction, moderation_id: int, resolved: bool = Fal logging_channel = interaction.guild.get_channel(logging_channel_id) try: - moderation = Moderation.find_by_id(interaction.client, moderation_id, interaction.guild_id) + moderation = await Moderation.find_by_id(interaction.client, moderation_id, interaction.guild_id) embed = await log_factory( interaction=interaction, moderation=moderation, resolved=resolved ) @@ -145,7 +145,7 @@ async def send_evidenceformat(interaction: Interaction, moderation_id: int) -> N if send_evidence_bool is False: return - moderation = Moderation.find_by_id(interaction.client, moderation_id, interaction.guild.id) + moderation = await Moderation.find_by_id(interaction.client, moderation_id, interaction.guild.id) content = await evidenceformat_factory(moderation=moderation) await interaction.followup.send(content=content, ephemeral=True) diff --git a/poetry.lock b/poetry.lock index 96437ae..be5a40b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -123,6 +123,24 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "aiosqlite" +version = "0.20.0" +description = "asyncio bridge to the standard sqlite3 module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"}, + {file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"}, +] + +[package.dependencies] +typing_extensions = ">=4.0" + +[package.extras] +dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"] +docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] + [[package]] name = "annotated-types" version = "0.7.0" @@ -2655,4 +2673,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.12" -content-hash = "3f732c0b0b0eb2a31fb9484c7cf699cd3c26474d6779528102af4c509a48351e" +content-hash = "22b824824f73dc3dc1a9a0a01060371ee1f6414e5bef39cb7455d21121988b47" diff --git a/pyproject.toml b/pyproject.toml index a2f2034..644cbdf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ pydantic = "^2.7.1" colorthief = "^0.2.1" beautifulsoup4 = "^4.12.3" markdownify = "^0.12.1" +aiosqlite = "^0.20.0" [tool.poetry.group.dev] optional = true