Appearance
Python
Python's standard-library sqlite3 module can load extensions on most builds — no third-party driver needed.
Load and set up
python
import json, os, sqlite3
con = sqlite3.connect("app.db")
con.enable_load_extension(True)
con.load_extension("dist/rls") # no ".so" — the loader appends it
con.enable_load_extension(False) # re-disable
con.executescript("""
CREATE TABLE documents(id INTEGER PRIMARY KEY, owner_id INT NOT NULL, title TEXT);
INSERT INTO documents(owner_id, title) VALUES (1,'Alice'), (2,'Bob');
""")
con.execute("SELECT rls_protect(?)", (json.dumps({
"table": "documents",
"select": "owner_id = rls_ctx('user_id')",
"insert": "NEW.owner_id = rls_ctx('user_id')",
}),))
con.execute("SELECT rls_config('resolver', ?)",
("SELECT id AS user_id, role FROM _rls_users WHERE token = ?1",))
con.execute("SELECT rls_seal(?)", (os.environ["RLS_TOKEN"],))
con.execute("SELECT rls_guard()")Serve queries
python
def set_user(claims):
con.execute("SELECT rls_set_context(?)", (json.dumps(claims),))
set_user({"user_id": 1, "role": "user"})
print(con.execute("SELECT title FROM documents").fetchall()) # [('Alice',)]
# or resolve from the store:
con.execute("SELECT rls_auth(?)", ("the-session-token",))
print(con.execute("SELECT title FROM documents").fetchall())A tiny per-request helper
python
def query_as(claims, sql, params=()):
con.execute("SELECT rls_set_context(?)", (json.dumps(claims),))
return con.execute(sql, params).fetchall()
query_as({"user_id": 2}, "SELECT title FROM documents") # [('Bob',)]If extension loading is unavailable
Some Python builds bundle a SQLite compiled with SQLITE_OMIT_LOAD_EXTENSION; then enable_load_extension raises AttributeError or OperationalError. The official python.org builds and most Linux distribution packages support it — use one of those. You can check quickly:
python
import sqlite3
print(hasattr(sqlite3.connect(":memory:"), "enable_load_extension")) # True == OKNotes
- Write results:
cursor.rowcountis0for writes through a policy view. UseRETURNING ...andfetchall(), orSELECT rls_last_rowid()after an insert. See the caveats. - With a pool or per-thread connections, run the setup (load + protect + config + seal + guard) once per connection and bind the context per query.
sqlite3autocommit/transaction behavior is unchanged — the context can switch between statements inside a transaction, just like any other driver.