from __future__ import annotations import subprocess from dataclasses import dataclass def sql_literal(value: object) -> str: if value is None: return "NULL" if isinstance(value, bool): return "TRUE" if value else "FALSE" text = str(value) return "'" + text.replace("'", "''") + "'" @dataclass class Database: url: str def execute(self, sql: str) -> None: subprocess.run( ["psql", self.url, "-X", "-v", "ON_ERROR_STOP=1", "-q"], input=sql, text=True, check=True, ) def rows(self, sql: str) -> list[dict[str, str | None]]: result = subprocess.run( [ "psql", self.url, "-X", "-v", "ON_ERROR_STOP=1", "-q", "-P", "footer=off", "-A", "-F", "\t", "-c", sql, ], text=True, check=True, capture_output=True, ) lines = [line for line in result.stdout.splitlines() if line.strip()] if not lines: return [] headers = lines[0].split("\t") out: list[dict[str, str | None]] = [] for line in lines[1:]: values = line.split("\t") out.append( { key: None if idx >= len(values) or values[idx] == "" else values[idx] for idx, key in enumerate(headers) } ) return out def one(self, sql: str) -> dict[str, str | None] | None: rows = self.rows(sql) return rows[0] if rows else None