Add docstrings to tracer.py and utils functions

Document get_task_id, trace_child_tasks, trace, capture_traces, and
AsgiTracer in tracer.py, and get_outbound_foreign_keys,
get_all_foreign_keys, detect_spatialite, filters_should_redirect,
format_bytes, and escape_fts in utils/__init__.py.  No logic changes.
This commit is contained in:
Brandon Sheffield 2026-04-05 14:13:41 -05:00
commit a2e7950e7a
2 changed files with 100 additions and 0 deletions

View file

@ -14,6 +14,14 @@ trace_task_id = ContextVar("trace_task_id", default=None)
def get_task_id():
"""Return a stable identifier for the current asyncio task.
Checks the ``trace_task_id`` context variable first (set by
:func:`trace_child_tasks`) so that child tasks spawned inside a traced
block share the parent's ID and are captured by the same tracer.
Falls back to the ``id()`` of the running asyncio task, or ``None``
when called outside an event loop.
"""
current = trace_task_id.get(None)
if current is not None:
return current
@ -26,6 +34,14 @@ def get_task_id():
@contextmanager
def trace_child_tasks():
"""Context manager that propagates the current task's trace ID to child tasks.
Normally each asyncio task gets its own task ID, so traces recorded inside
``asyncio.create_task()`` calls would be invisible to the parent tracer.
Wrapping the ``create_task`` call in this context manager pins the parent's
ID onto the ``trace_task_id`` context variable so child tasks are attributed
to the same tracer bucket.
"""
token = trace_task_id.set(get_task_id())
yield
trace_task_id.reset(token)
@ -33,6 +49,23 @@ def trace_child_tasks():
@contextmanager
def trace(trace_type, **kwargs):
"""Context manager that records a single timed trace entry.
If no tracer is active for the current task (i.e. the code is not running
inside a :func:`capture_traces` block) the context manager is a no-op and
simply yields ``kwargs`` unchanged.
When a tracer *is* active, the yielded dict is populated with any extra
context added inside the ``with`` block, and on exit a trace entry is
appended containing ``type``, ``start``, ``end``, ``duration_ms``,
``traceback``, ``error``, plus all keyword arguments.
Args:
trace_type: A string label for this trace entry (e.g. ``"sql"``).
**kwargs: Arbitrary extra key/value pairs included in the trace entry.
Must not use any of the reserved keys: ``type``, ``start``,
``end``, ``duration_ms``, ``traceback``.
"""
assert not TRACE_RESERVED_KEYS.intersection(
kwargs.keys()
), f".trace() keyword parameters cannot include {TRACE_RESERVED_KEYS}"
@ -67,6 +100,15 @@ def trace(trace_type, **kwargs):
@contextmanager
def capture_traces(tracer):
"""Context manager that activates trace collection for the current task.
While the block is active, any :func:`trace` calls made from the same
asyncio task will append entries to ``tracer``. The list is de-registered
when the block exits so that subsequent work is not attributed to it.
Args:
tracer: A list to which trace entry dicts will be appended.
"""
# tracer is a list
task_id = get_task_id()
if task_id is None:
@ -78,6 +120,16 @@ def capture_traces(tracer):
class AsgiTracer:
"""ASGI middleware that appends timing traces to responses when ``?_trace=1`` is set.
When the ``_trace=1`` query parameter is present the middleware collects
all :func:`trace` entries produced while handling the request and injects
a ``_trace`` object into JSON responses, or a ``<pre>`` block before
``</body>`` in HTML responses. Responses larger than
:attr:`max_body_bytes` are passed through unchanged to avoid excessive
memory use.
"""
# If the body is larger than this we don't attempt to append the trace
max_body_bytes = 1024 * 256 # 256 KB

View file

@ -583,6 +583,13 @@ def detect_primary_keys(conn, table):
def get_outbound_foreign_keys(conn, table):
"""Return a list of outbound foreign keys for ``table``.
Each entry is a dict with ``column``, ``other_table``, and ``other_column``
keys. Compound foreign keys (where a single constraint spans multiple
columns) are excluded because they cannot be represented in this flat
format.
"""
infos = conn.execute(f"PRAGMA foreign_key_list([{table}])").fetchall()
fks = []
for info in infos:
@ -611,6 +618,21 @@ def get_outbound_foreign_keys(conn, table):
def get_all_foreign_keys(conn):
"""Return a mapping of every table to its incoming and outgoing foreign keys.
Returns a dict of the form::
{
"table_name": {
"incoming": [{"other_table": ..., "column": ..., "other_column": ...}, ...],
"outgoing": [{"other_table": ..., "column": ..., "other_column": ...}, ...],
},
...
}
Both lists are sorted for deterministic ordering. Compound foreign keys
and references to non-existent tables are silently skipped.
"""
tables = [
r[0]
for r in conn.execute(
@ -650,6 +672,11 @@ def get_all_foreign_keys(conn):
def detect_spatialite(conn):
"""Return True if the SpatiaLite extension is loaded on ``conn``.
Detection is based on the presence of the ``geometry_columns`` table,
which SpatiaLite creates when it initialises a spatial database.
"""
rows = conn.execute(
'select 1 from sqlite_master where tbl_name = "geometry_columns"'
).fetchall()
@ -720,6 +747,14 @@ filter_column_re = re.compile(r"^_filter_column_\d+$")
def filters_should_redirect(special_args):
"""Convert legacy ``_filter_column`` / ``_filter_op`` / ``_filter_value``
query parameters into the compact ``column__op=value`` form used by the
table view, and signal that a redirect is needed.
Returns a list of ``(key, value)`` pairs to add to the redirect URL.
A ``value`` of ``None`` means the key should be removed from the URL.
Returns an empty list when no legacy filter parameters are present.
"""
redirect_params = []
# Handle _filter_column=foo&_filter_op=exact&_filter_value=...
filter_column = special_args.get("_filter_column")
@ -954,6 +989,12 @@ class LoadExtension(click.ParamType):
def format_bytes(bytes):
"""Format a byte count as a human-readable string.
Steps through ``bytes``, ``KB``, ``MB``, ``GB``, and ``TB``, dividing by
1024 at each step, and returns the value formatted to one decimal place
(or as an integer for plain bytes).
"""
current = float(bytes)
for unit in ("bytes", "KB", "MB", "GB", "TB"):
if current < 1024:
@ -969,6 +1010,13 @@ _escape_fts_re = re.compile(r'\s+|(".*?")')
def escape_fts(query):
"""Escape a full-text search query for safe use with SQLite FTS.
Splits the query on whitespace, wrapping each bare token in double quotes
so that special FTS operators (``AND``, ``OR``, ``*``, etc.) are treated
as literals. Tokens that are already quoted are left unchanged.
An unbalanced leading quote is closed before processing.
"""
# If query has unbalanced ", add one at end
if query.count('"') % 2:
query += '"'