Files
ad/molodetz/database/core.py
T

117 lines
2.6 KiB
Python

# retoor <retoor@molodetz.nl>
import logging
import re
from datetime import datetime, timezone
import dataset
import sqlalchemy
import uuid_utils
from sqlalchemy.pool import NullPool
from molodetz import config
logger = logging.getLogger(__name__)
ON_CONNECT_STATEMENTS = [
"PRAGMA journal_mode=WAL",
"PRAGMA synchronous=NORMAL",
"PRAGMA busy_timeout=30000",
"PRAGMA cache_size=-8000",
"PRAGMA temp_store=MEMORY",
"PRAGMA mmap_size=268435456",
]
config.ensure_data_dirs()
db = dataset.connect(
config.DATABASE_URL,
engine_kwargs={
"connect_args": {"timeout": 30, "check_same_thread": False},
"poolclass": NullPool,
},
on_connect_statements=ON_CONNECT_STATEMENTS,
)
def generate_uid():
return str(uuid_utils.uuid7())
def now_iso():
return datetime.now(timezone.utc).isoformat()
def parse_iso(value):
if not value:
return None
try:
parsed = datetime.fromisoformat(str(value))
except ValueError:
return None
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed
def refresh_snapshot():
try:
if getattr(db.local, "tx", None):
return
conn = db.executable
if conn.in_transaction():
conn.commit()
except Exception as exc:
logger.warning("refresh_snapshot failed: %s", exc)
def table_exists(name):
return name in db.tables
def in_clause(values, prefix="v"):
params = {f"{prefix}{index}": value for index, value in enumerate(values)}
placeholders = ", ".join(f":{key}" for key in params)
return placeholders, params
def atomic_update(sql, **params):
with db:
result = db.executable.execute(sqlalchemy.text(sql), params)
return result.rowcount
def run_write(sql, **params):
with db:
db.query(sql, **params)
def slugify(text):
lowered = (text or "").lower()
replaced = re.sub(r"[^a-z0-9-]", "-", lowered)
return re.sub(r"-+", "-", replaced).strip("-")
def make_combined_slug(text, uid):
tail = uid.replace("-", "")[-12:]
slug = slugify(text)
return f"{slug}-{tail}" if slug else tail
def shard_path(uid):
tail = uid.replace("-", "")
return f"{tail[-2:]}/{tail[-4:-2]}"
def resolve_by_slug(table_name, slug, include_deleted=False):
if table_name not in db.tables:
return None
table = db[table_name]
for key in ("slug", "uid"):
criteria = {key: slug}
if not include_deleted:
criteria["deleted_at"] = None
row = table.find_one(**criteria)
if row:
return row
return None