String-formatting SQL invites injection. Build a small immutable query builder that always uses ? placeholders.
Implement a class Query(table) with methods that each return a new Query (so a base query can be reused):
select(*columns) (default *)where(column, op, value): op is one of =, !=, <, <=, >, >=, LIKE or IN. For IN, value is a non-empty list and becomes IN (?, ?, ?). Multiple conditions are joined with AND.order_by(column, desc=False) and limit(n) (n must be a non-negative int)build(): return (sql, params)Table and column names must match ^[A-Za-z_][A-Za-z0-9_]*$, otherwise raise ValueError, and so must operators outside the allowed set.