Skip to content

Commit 512753a

Browse files
committed
complete migration
1 parent b216c27 commit 512753a

13 files changed

+91
-85
lines changed

example.settings.toml

+3-7
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@
22
token = "NTU2NTI4MDcwNjMxMjI3NDAy.XI1xJF.xl6iW44dagmEjnZVjzBHTL_w-pM"
33
prefix = "!"
44

5-
[database]
6-
host = "localhost"
7-
port = 10700
8-
user = "pink"
9-
password = "pass"
10-
database = "pink"
11-
125
[redis]
136
host = "127.0.0.1"
147
port = 6379
@@ -23,6 +16,9 @@ ids = [439205512425504771, 80528701850124288]
2316
# either "combine" or "overwrite"
2417
mode = "combine"
2518

19+
[db]
20+
path = "/data/pink.db"
21+
2622
[cog.servermyserver]
2723
server = 391987311468085248
2824
user_log_channel = 399649344443383810

requirements/base.in

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
# discord framework/extensions
44
discord.py[speed]>=2.0
55

6-
# databases
7-
asyncpg
8-
96
# api libs for commands
107
googletrans-py==4.0.0
118

@@ -18,7 +15,7 @@ Pillow
1815
# error reporting
1916
sentry-sdk
2017

21-
# perfomance
18+
# performance
2219
orjson
2320
uvloop
2421

requirements/base.txt

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ aiosignal==1.3.1
99
# via aiohttp
1010
anyio==4.3.0
1111
# via httpx
12-
asyncpg==0.29.0
1312
attrs==23.2.0
1413
# via aiohttp
1514
brotli==1.1.0

requirements/dev.in

-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,4 @@ mypy
55
ruff
66

77
# typing
8-
# https://github.com/MagicStack/asyncpg/issues/569
9-
asyncpg-stubs
108
types-redis

requirements/dev.txt

+1-6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ aiosignal==1.3.1
88
# via aiohttp
99
anyio==4.3.0
1010
# via httpx
11-
asyncpg==0.29.0
12-
# via asyncpg-stubs
13-
asyncpg-stubs==0.29.1
1411
attrs==23.2.0
1512
# via aiohttp
1613
brotli==1.1.0
@@ -80,9 +77,7 @@ types-redis==4.6.0.20240425
8077
types-setuptools==69.5.0.20240423
8178
# via types-cffi
8279
typing-extensions==4.11.0
83-
# via
84-
# asyncpg-stubs
85-
# mypy
80+
# via mypy
8681
urllib3==2.2.1
8782
# via sentry-sdk
8883
uvloop==0.19.0

scripts/migrate_postgres_to_sqlite.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ async def main() -> None:
2222
with Path("schema.sql").open() as f:
2323
db.executescript(f.read())
2424

25-
prefixes = [r async for r in pg.fetch("SELECT * FROM prefixes")]
25+
prefixes = await pg.fetch("SELECT * FROM prefixes")
2626
print("inserting", len(prefixes), "prefixes")
2727
db.executemany("INSERT INTO prefixes (guild_id, prefix) VALUES (?, ?)", prefixes)
2828

29-
accents = [r async for r in pg.fetch("SELECT * FROM accents")]
29+
accents = await pg.fetch("SELECT * FROM accents")
3030
print("inserting", len(accents), "accents")
3131
db.executemany("INSERT INTO accents (guild_id, user_id, name, severity) VALUES (?, ?, ?, ?)", accents)
3232

src/__main__.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import platform
44

55
import aiohttp
6-
import asyncpg
76
import discord
87
import redis.asyncio as redis
98

@@ -39,13 +38,6 @@ async def main() -> None:
3938
log.warning("skipped sentry initialization")
4039

4140
session = aiohttp.ClientSession(headers={"user-agent": "PINK bot"})
42-
pg = asyncpg.create_pool(
43-
host=settings.database.host,
44-
port=settings.database.port,
45-
user=settings.database.user,
46-
password=settings.database.password,
47-
database=settings.database.database,
48-
)
4941
rd: redis.Redis[bytes] = redis.Redis(
5042
host=settings.redis.host,
5143
port=settings.redis.port,
@@ -54,7 +46,6 @@ async def main() -> None:
5446

5547
pink = PINK(
5648
session=session,
57-
pg=pg,
5849
redis=rd,
5950
version=version,
6051
command_prefix=settings.bot.prefix,
@@ -70,7 +61,7 @@ async def main() -> None:
7061
),
7162
)
7263

73-
async with pink, session, pg, rd:
64+
async with pink, session, rd:
7465
await pink.start(settings.bot.token)
7566

7667

src/bot.py

+41-18
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
import asyncio
44
import logging
55
import re
6+
import sqlite3
67
import time
78

89
from collections.abc import Iterator
910
from pathlib import Path
1011
from typing import Any, Optional
1112

1213
import aiohttp
13-
import asyncpg
1414
import discord
1515
import sentry_sdk
1616

@@ -45,21 +45,26 @@ def __init__(self, bot: PINK, *, prefix: str):
4545
self.prefix_re = mention_or_prefix_regex(bot.user.id, self.prefix) # type: ignore
4646

4747
@classmethod
48-
def from_pg(cls, bot: PINK, data: asyncpg.Record) -> Prefix:
48+
def from_db(cls, bot: PINK, data: sqlite3.Row) -> Prefix:
4949
return cls(bot, prefix=data["prefix"])
5050

51-
async def write(self, ctx: Context) -> None:
52-
await ctx.pg.fetchrow(
53-
"INSERT INTO prefixes (guild_id, prefix) VALUES ($1, $2) "
51+
def write(self, ctx: Context) -> None:
52+
ctx.db.execute(
53+
"INSERT INTO prefixes (guild_id, prefix) VALUES (?, ?) "
5454
"ON CONFLICT (guild_id) DO UPDATE "
5555
"SET prefix = EXCLUDED.prefix",
56-
ctx.guild.id, # type: ignore
57-
self.prefix,
56+
(
57+
ctx.guild.id, # type: ignore
58+
self.prefix,
59+
),
5860
)
5961

6062
@staticmethod
61-
async def delete(ctx: Context) -> None:
62-
await ctx.pg.fetchrow("DELETE FROM prefixes WHERE guild_id = $1", ctx.guild.id) # type: ignore
63+
def delete(ctx: Context) -> None:
64+
ctx.db.execute(
65+
"DELETE FROM prefixes WHERE guild_id = ?",
66+
(ctx.guild.id,), # type: ignore
67+
)
6368

6469
def __repr__(self) -> str:
6570
return f"<{type(self).__name__} prefix={self.prefix}>"
@@ -70,38 +75,56 @@ def __init__(
7075
self,
7176
*,
7277
session: aiohttp.ClientSession,
73-
pg: asyncpg.Pool[asyncpg.Record],
7478
redis: Redis[bytes],
7579
version: Version,
7680
**kwargs: Any,
7781
) -> None:
7882
super().__init__(**kwargs)
7983

8084
self.session = session
81-
self.pg = pg
8285
self.redis = redis
8386
self.version = version
8487

8588
self.prefixes: dict[int, Prefix] = {}
8689
self.owner_ids: set[int] = set()
8790

91+
def init_db(self) -> None:
92+
with Path("schema.sql").open() as f:
93+
db = self.db_cursor()
94+
db.executescript(f.read())
95+
96+
def db_cursor(self) -> sqlite3.Cursor:
97+
conn = sqlite3.connect(settings.db.path)
98+
conn.isolation_level = None
99+
conn.execute("PRAGMA journal_mode=wal")
100+
conn.execute("PRAGMA foreign_keys=ON")
101+
conn.row_factory = sqlite3.Row
102+
103+
return conn.cursor()
104+
88105
# --- overloads ---
89106
async def setup_hook(self) -> None:
90107
self.launched_at = time.monotonic()
91108

92-
await self._load_prefixes()
109+
self.init_db()
93110

94-
await self._load_cogs()
95-
96-
asyncio.gather(
111+
await asyncio.gather(
112+
self._load_prefixes(),
113+
self._load_cogs(),
97114
self._fetch_owners(),
98115
)
99116

100117
async def _load_prefixes(self) -> None:
101-
self._default_prefix_re = mention_or_prefix_regex(self.user.id, settings.bot.prefix) # type: ignore
118+
self._default_prefix_re = mention_or_prefix_regex(
119+
self.user.id, # type: ignore
120+
settings.bot.prefix,
121+
)
122+
123+
db = self.db_cursor()
124+
db.execute("SELECT guild_id, prefix FROM prefixes")
102125

103-
for guild in await self.pg.fetch("SELECT guild_id, prefix FROM prefixes"):
104-
self.prefixes[guild["guild_id"]] = Prefix.from_pg(self, guild)
126+
for guild in db.fetchall():
127+
self.prefixes[guild["guild_id"]] = Prefix.from_db(self, guild)
105128

106129
async def _fetch_owners(self) -> None:
107130
owners = settings.owners.ids.copy()

src/cogs/accents/cog.py

+25-13
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ async def cog_load(self) -> None:
6060
# current name: PINK
6161
self.accent_wh_name = f"{self.bot.user.name} bot accent webhook" # type: ignore
6262

63-
for settings in await self.bot.pg.fetch("SELECT guild_id, user_id, name, severity FROM accents"):
63+
db = self.bot.db_cursor()
64+
db.execute("SELECT guild_id, user_id, name, severity FROM accents")
65+
for settings in db.fetchall():
6466
if (accent_cls := ALL_ACCENTS.get(settings["name"].lower())) is None:
6567
log.error(
6668
"unknown accent: guild=%s user=%s %s", settings["guild_id"], settings["user_id"], settings["name"]
@@ -247,24 +249,32 @@ async def _add_accents(self, ctx: Context, member: discord.Member, accents: _Use
247249

248250
self.set_user_accents(member, all_accents)
249251

250-
for accent in all_accents:
251-
await self.bot.pg.fetchrow(
252-
"INSERT INTO accents (guild_id, user_id, name, severity) VALUES ($1, $2, $3, $4) "
253-
"ON CONFLICT (guild_id, user_id, name) DO UPDATE "
254-
"SET name = EXCLUDED.name",
252+
rows = (
253+
(
255254
ctx.guild.id, # type: ignore
256255
member.id,
257256
accent.name(),
258257
accent.severity,
259258
)
259+
for accent in all_accents
260+
)
261+
262+
ctx.db.executemany(
263+
"INSERT INTO accents (guild_id, user_id, name, severity) VALUES (?, ?, ?, ?) "
264+
"ON CONFLICT (guild_id, user_id, name) DO UPDATE "
265+
"SET name = EXCLUDED.name",
266+
rows,
267+
)
260268

261269
async def _remove_accents(self, ctx: Context, member: discord.Member, accents: _UserAccentsType) -> None:
262270
# a special case. empty iterable means remove everything
263271
if not accents:
264-
await self.bot.pg.fetchrow(
265-
"DELETE FROM accents WHERE guild_id = $1 AND user_id = $2",
266-
ctx.guild.id, # type: ignore
267-
member.id,
272+
await ctx.db.execute(
273+
"DELETE FROM accents WHERE guild_id = ? AND user_id = ?",
274+
(
275+
ctx.guild.id, # type: ignore
276+
member.id,
277+
),
268278
)
269279

270280
self.set_user_accents(member, [])
@@ -286,13 +296,15 @@ async def _remove_accents(self, ctx: Context, member: discord.Member, accents: _
286296

287297
self.set_user_accents(member, name_to_accent.values())
288298

289-
for accent in to_remove:
290-
await self.bot.pg.fetchrow(
291-
"DELETE FROM accents WHERE guild_id = $1 AND user_id = $2 AND name = $3",
299+
rows = (
300+
(
292301
ctx.guild.id, # type: ignore
293302
member.id,
294303
accent.name(),
295304
)
305+
for accent in to_remove
306+
)
307+
ctx.db.executemany("DELETE FROM accents WHERE guild_id = ? AND user_id = ? AND name = ?", rows)
296308

297309
async def _update_nick(self, ctx: Context) -> None:
298310
new_nick = ctx.me.name

src/cogs/meta.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ async def set(self, ctx: Context, *, prefix: str) -> None:
351351
return
352352

353353
settings = Prefix(ctx.bot, prefix=prefix.lower())
354-
await settings.write(ctx)
354+
settings.write(ctx)
355355

356356
ctx.bot.prefixes[ctx.guild.id] = settings
357357

@@ -367,8 +367,7 @@ async def unset(self, ctx: Context) -> None:
367367
assert ctx.guild is not None
368368

369369
if ctx.guild.id in ctx.bot.prefixes:
370-
await Prefix.delete(ctx)
371-
370+
Prefix.delete(ctx)
372371
del ctx.bot.prefixes[ctx.guild.id]
373372

374373
await ctx.ok()

src/cogs/techadmin.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
import io
77
import random
88
import re
9+
import sqlite3
910
import textwrap
1011
import traceback
1112

1213
from contextlib import redirect_stdout
1314
from pprint import pformat
1415
from typing import Any, Optional, Union
1516

16-
import asyncpg
1717
import discord
1818

1919
from discord.ext import commands
@@ -167,8 +167,9 @@ async def sql(self, ctx: Context, *, code: Code) -> None:
167167

168168
async with ctx.typing():
169169
try:
170-
data = await ctx.pg.fetch(query)
171-
except asyncpg.PostgresError as e:
170+
db = ctx.db.execute(query)
171+
data = db.fetchall()
172+
except sqlite3.Error as e:
172173
return await ctx.send(f"Error: **{type(e).__name__}**: `{e}`")
173174

174175
if not data:
@@ -266,15 +267,15 @@ async def _exec(self, _: Context, arguments: str) -> str:
266267

267268
return result
268269

269-
async def _sql_table(self, result: list[asyncpg.Record]) -> str:
270+
async def _sql_table(self, result: list[sqlite3.Row]) -> str:
270271
# convert to list because otherwise iterator is exhausted
271272
columns = list(result[0].keys())
272273

273274
no_named_columns = not list(filter(lambda c: c != PG_UNNAMED_COLUMN, columns))
274275
col_widths = [0 if no_named_columns and c == PG_UNNAMED_COLUMN else len(c) for c in columns]
275276

276277
for row in result:
277-
for i, value in enumerate(row.values()):
278+
for i, value in enumerate(row):
278279
col_widths[i] = max((col_widths[i], len(str(value))))
279280

280281
if no_named_columns:
@@ -289,8 +290,7 @@ def sanitize_value(value: Any) -> str:
289290
return str(value).replace("\n", "\\n")
290291

291292
body = "\n".join(
292-
" | ".join(f"{sanitize_value(value):<{col_widths[i]}}" for i, value in enumerate(row.values()))
293-
for row in result
293+
" | ".join(f"{sanitize_value(value):<{col_widths[i]}}" for i, value in enumerate(row)) for row in result
294294
)
295295

296296
return f"```prolog\n{header}{body}```"

0 commit comments

Comments
 (0)