implement eval command, to execute sql and output tabulated data (#58)
3 files changed, 47 insertions(+), 3 deletions(-) M beryllia/__init__.py M beryllia/database/__init__.py M requirements.txt
M beryllia/__init__.py => beryllia/__init__.py +22-1
@@ 3,6 3,7 @@ from datetime import datetime, timedelta from json import loads as json_loads from re import compile as re_compile from shlex import split as shlex_split from tabulate import tabulate from typing import Dict, List, Optional, Sequence, Tuple @@ from irctokens import build, hostmask as hostmask_parse, Hostmask, Line 13,7 14,7 @@ from ircstates.numerics import RPL_ENDOFMOTD, ERR_NOMOTD, RPL_WELCOME, RPL_YOURE from ircrobots.ircv3 import Capability from .config import Config from .database import Database from .database import Database, DatabaseError from .database.common import NickUserHost from .database.kline import DBKLine @@ from .normalise import RFC1459SearchNormaliser 39,6 40,7 @@ class Caller: PREFERENCES: Dict[str, type] = {"statsp": bool, "knag": bool} EVAL_MAX = 10 @@ class Server(BaseServer): 465,6 467,25 @@ class Server(BaseServer): await db.preference.set(caller.oper, key, value) return [f"set {key} to {value}"] async def cmd_eval(self, caller: Caller, args: Sequence[str]) -> Sequence[str]: try: # list() because readonly_eval returns an immutable sequence outs_eval = list(await self.database.readonly_eval(args[0])) except DatabaseError as e: return [f"error: {str(e)}"] if not outs_eval: return ["no results"] headers = outs_eval.pop(0) outs = tabulate(outs_eval[:EVAL_MAX], headers=headers).split("\n") if len(outs_eval) > EVAL_MAX: overflow = len(outs_eval) - EVAL_MAX outs.append(f"(and {overflow} more)") return outs def line_preread(self, line: Line): print(f"< {line.format()}")
M beryllia/database/__init__.py => beryllia/database/__init__.py +24-1
@@ 1,5 1,5 @@ import asyncpg from typing import Optional from typing import Any, Optional, Sequence, Tuple from .cliconn import CliconnTable, CliexitTable @@ from .nick_change import NickChangeTable 19,8 19,13 @@ from .freeze_tag import FreezeTagTable from ..normalise import SearchNormaliser class DatabaseError(Exception): pass class Database(object): def __init__(self, pool: asyncpg.Pool, normaliser: SearchNormaliser): self._pool = pool self.kline = KLineTable(pool, normaliser) @@ self.kline_reject = KLineRejectTable(pool, normaliser) 50,3 55,21 @@ class Database(object): user=username, password=password, host=hostname, database=db_name ) return Database(pool, normaliser) async def readonly_eval(self, query: str) -> Sequence[Tuple[Any, ...]]: async with self._pool.acquire() as conn, conn.transaction(readonly=True): try: rows = await conn.fetch(query) except asyncpg.PostgresError as e: raise DatabaseError(str(e)) if not rows: return [] headers = tuple(key for key in rows[0].keys()) outs = [headers] for row in rows: outs.append(tuple(value for value in row.values())) return outs
M requirements.txt => requirements.txt +1-1