trainlog/tools/trainlog_sqlite.py

49 lines
2 KiB
Python

"""Canonical SQLite connection configuration for Trainlog Python tools."""
import sqlite3
import uuid
PROFILE_NAMESPACE = uuid.UUID("4f4c8ea6-18f6-5e48-9d65-a54a24889149")
def profile_revision(parent, recording, tracking, fields):
"""Return the frozen deterministic profile revision shared with C/Android."""
if not all(isinstance(value, str) for value in (parent, recording, tracking)):
raise ValueError("invalid profile revision")
if type(fields) is not int:
raise ValueError("invalid profile revision")
name = f"{parent}\n{recording}\n{tracking}\n{fields}"
return "pr2_" + str(uuid.uuid5(PROFILE_NAMESPACE, name))
def prototype_profile_valid(token, recording, tracking, fields):
"""Recognize only the unpublished pr1 prototype accepted by migration v17."""
if not isinstance(token, str) or not isinstance(recording, str) or \
not isinstance(tracking, str) or type(fields) is not int:
return 0
valid = recording in ("sets", "continuous") and tracking in ("reps", "duration") and \
not (recording == "continuous" and tracking != "duration") and \
fields >= 0 and fields & ~3 == 0 and not (recording == "sets" and fields != 0)
return int(valid and token == f"pr1|{recording}|{tracking}|{fields}")
def configure_connection(connection):
"""Install every process-local function referenced by Trainlog schema SQL.
CONTRACT: callers must use this before preparing statements against a
Trainlog database. Persisted triggers resolve UDF names at statement
preparation even when their WHEN clause suppresses the trigger body.
"""
connection.create_function(
"trainlog_profile_revision", 4, profile_revision, deterministic=True,
)
connection.create_function(
"trainlog_pr1_valid", 4, prototype_profile_valid, deterministic=True,
)
return connection
def connect_database(database, *args, **kwargs):
"""Open and canonically configure one production Trainlog connection."""
return configure_connection(sqlite3.connect(database, *args, **kwargs))