luplo.core.id_resolve
=====================
.. py:module:: luplo.core.id_resolve
.. autoapi-nested-parse::
UUID prefix resolution for human-typed identifiers.
luplo identifies most rows by UUIDv4 primary keys; the CLI displays them
as 8-character prefixes (``id[:8]``) and accepts the same prefixes back
on input. This module is the single place where prefix → full UUID
resolution happens.
Behaviour:
* A 36-character canonical UUID is returned as-is (fast path; no DB call).
* A hex prefix of at least :data:`MIN_PREFIX_LENGTH` characters is looked
up against ``
.id::text LIKE prefix || '%'`` with ``LIMIT 2``.
Dashes in the input are ignored so users can paste partially-formatted
ids.
* Zero matches → :class:`NotFoundError` is the caller's job; this
function returns ``None`` instead so callers keep their existing
not-found shapes.
* One match → the full UUID is returned.
* Two or more matches → :class:`AmbiguousIdError` is raised carrying the
sampled rows. The caller never has to choose silently.
The module is intentionally generic: it takes a table name, an optional
project scope, and a label column used only for ambiguity messages. Each
domain module wraps this with its own ``resolve_*_id`` helper.
Attributes
----------
.. autoapisummary::
luplo.core.id_resolve.MIN_PREFIX_LENGTH
Functions
---------
.. autoapisummary::
luplo.core.id_resolve.resolve_uuid_prefix
luplo.core.id_resolve.build_seed_clause
Module Contents
---------------
.. py:data:: MIN_PREFIX_LENGTH
:value: 8
Minimum hex characters required for a prefix lookup.
8 hex characters = 32 bits, which keeps birthday-paradox collision
probability under ~1% for project-scoped tables holding fewer than
~10,000 rows. Below this threshold, requiring more characters is
cheaper than relying on collision luck.
.. py:function:: resolve_uuid_prefix(conn: psycopg.AsyncConnection[Any], table: str, value: str, *, project_id: str | None = None, label_column: str = 'title', project_column: str = 'project_id') -> str | None
:async:
Resolve a UUID or hex prefix against ``.id``.
:param conn: Open async connection.
:param table: Unquoted table name (e.g. ``"items"``).
:param value: Either a full canonical UUID string or a hex prefix.
:param project_id: Optional project scope. If supplied, the lookup is
constrained to ``project_column = project_id`` so prefixes
from other projects do not collide.
:param label_column: Column used to label sampled rows in the
:class:`AmbiguousIdError` message. Tables without a useful
label can pass ``"id"`` to get the id back as the label.
:param project_column: Column name to scope by; defaults to
``"project_id"``.
:returns: The full UUID string when exactly one row matches, or ``None``
when no row matches.
:raises InvalidIdFormatError: When *value* is neither a full UUID nor a
valid hex prefix (after stripping dashes).
:raises IdTooShortError: When *value* is a hex prefix shorter than
:data:`MIN_PREFIX_LENGTH`.
:raises AmbiguousIdError: When the prefix matches more than one row.
.. py:function:: build_seed_clause(value: str, params: dict[str, Any]) -> psycopg.sql.Composable
Build a SQL fragment that selects rows matching *value* by id.
Returns a clause suitable for a WHERE position (no leading ``WHERE``).
Mutates *params* in place: adds the binding under the key ``"seed"``.
Use this when the caller needs to embed prefix matching inside a
larger query (e.g. a recursive CTE that walks a supersede chain
forward from any matching seed). For standalone lookups, prefer
:func:`resolve_uuid_prefix`.
:raises InvalidIdFormatError: When *value* is not a UUID or hex prefix.
:raises IdTooShortError: When the hex prefix is shorter than the minimum.