audit_trail.query¶
Read path: grouped listing, object history and entry details.
AuditQuery.list_groups lists audit entries grouped by database
transaction, newest first, with keyset pagination. It runs in two stages:
- One query per requested severity (
LIMIT limit + 1each, merged in Python) collects the ids of the page's transactions. A single-severity query reads the monthly partitions in order and stops at the limit; a query over several severities would read all of their rows. - The complete groups are fetched with
transaction_id IN (...)and explicitcreated_atbounds, so only the partitions of the page's time range are read, and theiraudit_transactionrows likewise.
Both stages run after SET LOCAL plan_cache_mode = 'force_custom_plan': a
generic plan of a prepared statement (asyncpg, psycopg after a few executions)
cannot prune partitions by the query's parameters at plan time.
ActivityRow ¶
Bases: TypedDict
One audit_activity row, as fetched from the database.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
int
|
Row id. |
transaction_id |
int
|
Id of the audit transaction the row belongs to. |
verb |
str
|
Event verb, e.g. |
severity |
int
|
Severity value. |
object_type |
str | None
|
Type of the changed object. |
object_id |
str | None
|
Id of the changed object. |
object_label |
str | None
|
Label of the object when the row was written. |
target_type |
str | None
|
Type of the parent object. |
target_id |
str | None
|
Id of the parent object. |
actor_id |
str | None
|
Actor of the event. |
scope_id |
str | None
|
Scope, e.g. a tenant. |
correlation_id |
UUID | None
|
Correlation id of the transaction. |
created_at |
datetime
|
Start of the database transaction. |
data |
ActivityData
|
The |
Cursor ¶
Bases: NamedTuple
Position in the listing: (created_at, id) of an activity row.
list_groups continues with the rows after it in
(created_at DESC, id DESC) order.
Attributes:
| Name | Type | Description |
|---|---|---|
created_at |
datetime
|
|
id |
int
|
|
encode ¶
Return the cursor as an opaque, URL-safe token.
Returns:
| Type | Description |
|---|---|
str
|
Unpadded URL-safe base64 of |
Source code in audit_trail/query.py
decode
classmethod
¶
Parse a token made by encode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
The token. |
required |
Returns:
| Type | Description |
|---|---|
Cursor
|
The cursor. |
Raises:
| Type | Description |
|---|---|
ValueError
|
The token is malformed or its datetime has no offset. |
Source code in audit_trail/query.py
Visibility
dataclass
¶
Visibility(
object_types: Collection[str] | None = None,
verbs: Collection[str] | None = None,
scope_ids: Collection[str] | None = None,
)
What the caller may see; applied to every row the query returns.
The library knows nothing about the host's permissions: the host builds a
Visibility from its own rules. It restricts both which transactions
are listed and which of their entries a group shows.
Each field is a set of allowed values. None means no restriction; an
empty collection allows nothing. A row whose column is NULL never
passes a restriction that is set, so access control fails closed: with
scope_ids={"t1"}, entries without a scope are hidden.
Attributes:
| Name | Type | Description |
|---|---|---|
object_types |
Collection[str] | None
|
Allowed |
verbs |
Collection[str] | None
|
Allowed |
scope_ids |
Collection[str] | None
|
Allowed |
Raises:
| Type | Description |
|---|---|
TypeError
|
A field is a |
predicate ¶
Return the SQL condition on audit_activity rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
The |
required |
Returns:
| Type | Description |
|---|---|
ColumnElement[bool]
|
The condition; |
Source code in audit_trail/query.py
TransactionHeader
dataclass
¶
TransactionHeader(
id: int,
issued_at: datetime,
actor_type: str | None,
actor_id: str | None,
actor_label: str | None,
remote_addr: str | None,
user_agent: str | None,
method: str | None,
path: str | None,
channel: str | None,
auth_method: str | None,
request_id: UUID | None,
correlation_id: UUID | None,
scope_id: str | None,
meta: dict[str, JSONValue] | None,
from_snapshot: bool,
)
Who, where and how of one group: its audit_transaction row.
When the row no longer exists (retention), the header is built from the
data.context snapshot of the group's earliest visible entry and from
that entry's columns, and from_snapshot is True.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
int
|
The transaction id. |
issued_at |
datetime
|
Start of the database transaction ( |
actor_type |
str | None
|
Kind of actor; |
actor_id |
str | None
|
Actor id; from a snapshot, the entry's |
actor_label |
str | None
|
Actor label when the transaction started. |
remote_addr |
str | None
|
Client address. |
user_agent |
str | None
|
Client user agent. |
method |
str | None
|
Request method. |
path |
str | None
|
Request path. |
channel |
str | None
|
Channel, e.g. |
auth_method |
str | None
|
Authentication method. |
request_id |
UUID | None
|
Request id. |
correlation_id |
UUID | None
|
Correlation id. |
scope_id |
str | None
|
|
meta |
dict[str, JSONValue] | None
|
Extra host context. |
from_snapshot |
bool
|
Whether the header was rebuilt from an entry. |
Group
dataclass
¶
The entries of one database transaction.
Attributes:
| Name | Type | Description |
|---|---|---|
transaction |
TransactionHeader
|
The transaction header. |
activities |
list[ActivityRow]
|
The visible entries of every severity, in |
Page
dataclass
¶
One page of groups.
A page may hold fewer groups than the limit even when more follow (a
group whose entries compaction hides entirely is left out); only
next_cursor is None ends the listing.
Attributes:
| Name | Type | Description |
|---|---|---|
groups |
list[Group]
|
Groups, newest first. |
next_cursor |
Cursor | None
|
Cursor of the next page, or |
AuditQuery ¶
Queries over the audit tables.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tables
|
AuditTables
|
The audit tables. |
required |
severities
|
Iterable[int]
|
Every severity in use; |
required |
Source code in audit_trail/query.py
list_groups ¶
list_groups(
session: Session,
*,
severities: Collection[int] | None = None,
since: datetime | None = None,
until: datetime | None = None,
actor_id: str | None = None,
object_type: str | None = None,
object_id: str | None = None,
target_type: str | None = None,
target_id: str | None = None,
verbs: Collection[str] | None = None,
scope_ids: Collection[str] | None = None,
correlation_id: UUID | None = None,
visibility: Visibility | None = None,
extra_predicate: ColumnElement[bool] | None = None,
cursor: Cursor | None = None,
limit: int = 50,
compact: bool = True,
) -> Page
List entries grouped by database transaction, newest first.
The filters select which transactions are listed: a transaction is
listed when at least one of its entries matches all of them and
visibility. A group then shows every entry of the transaction
that visibility allows, of any severity: severities and the
other filters narrow the list, they do not control access.
Collection filters take the allowed values: None means no
restriction, an empty collection matches nothing. A NULL column
never matches a restriction that is set.
Pagination counts transactions. A transaction appears whole on the
page holding its newest matching entry and on no other, so following
next_cursor returns every matching transaction exactly once, also
when several transactions share a created_at.
Statements run in the session's current transaction (one is begun if
needed) after SET LOCAL plan_cache_mode = 'force_custom_plan',
which stays in effect until that transaction ends. On an
AUTOCOMMIT connection the setting has no effect, so prepared
statements may fall back to generic plans that read every partition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
The session to query with. |
required |
severities
|
Collection[int] | None
|
Severities to list; |
None
|
since
|
datetime | None
|
Only entries with |
None
|
until
|
datetime | None
|
Only entries with |
None
|
actor_id
|
str | None
|
Only entries of this actor. |
None
|
object_type
|
str | None
|
Only entries on objects of this type. |
None
|
object_id
|
str | None
|
Only entries on the object with this id. |
None
|
target_type
|
str | None
|
Only entries whose parent object has this type. |
None
|
target_id
|
str | None
|
Only entries whose parent object has this id. |
None
|
verbs
|
Collection[str] | None
|
Only entries with one of these verbs. |
None
|
scope_ids
|
Collection[str] | None
|
Only entries in one of these scopes. |
None
|
correlation_id
|
UUID | None
|
Only entries with this correlation id. |
None
|
visibility
|
Visibility | None
|
What the caller may see; |
None
|
extra_predicate
|
ColumnElement[bool] | None
|
Further condition on |
None
|
cursor
|
Cursor | None
|
|
None
|
limit
|
int
|
Maximum number of groups on the page. |
50
|
compact
|
bool
|
Merge each object's rows within a transaction (see
|
True
|
Returns:
| Type | Description |
|---|---|
Page
|
The page. |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
TypeError
|
A collection filter is a |
Source code in audit_trail/query.py
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 | |
alist_groups
async
¶
alist_groups(
session: AsyncSession,
*,
severities: Collection[int] | None = None,
since: datetime | None = None,
until: datetime | None = None,
actor_id: str | None = None,
object_type: str | None = None,
object_id: str | None = None,
target_type: str | None = None,
target_id: str | None = None,
verbs: Collection[str] | None = None,
scope_ids: Collection[str] | None = None,
correlation_id: UUID | None = None,
visibility: Visibility | None = None,
extra_predicate: ColumnElement[bool] | None = None,
cursor: Cursor | None = None,
limit: int = 50,
compact: bool = True,
) -> Page
Async variant of list_groups, with the same arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
The async session to query with. |
required |
severities
|
Collection[int] | None
|
See |
None
|
since
|
datetime | None
|
See |
None
|
until
|
datetime | None
|
See |
None
|
actor_id
|
str | None
|
See |
None
|
object_type
|
str | None
|
See |
None
|
object_id
|
str | None
|
See |
None
|
target_type
|
str | None
|
See |
None
|
target_id
|
str | None
|
See |
None
|
verbs
|
Collection[str] | None
|
See |
None
|
scope_ids
|
Collection[str] | None
|
See |
None
|
correlation_id
|
UUID | None
|
See |
None
|
visibility
|
Visibility | None
|
See |
None
|
extra_predicate
|
ColumnElement[bool] | None
|
See |
None
|
cursor
|
Cursor | None
|
See |
None
|
limit
|
int
|
See |
50
|
compact
|
bool
|
See |
True
|
Returns:
| Type | Description |
|---|---|
Page
|
The page. |
Raises:
| Type | Description |
|---|---|
ValueError
|
See |
TypeError
|
See |
Source code in audit_trail/query.py
changed_fields ¶
Return the names of the fields an entry changed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
activity
|
ActivityRow
|
An entry. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
The keys of |