audit_trail.config¶
Library configuration: AuditTrail and per-model AuditOptions.
DURABLE_POOL_SIZE
module-attribute
¶
Connections in the durable engine the library builds.
DURABLE_POOL_TIMEOUT
module-attribute
¶
Seconds a durable write waits for a connection of that engine.
Target ¶
Bases: NamedTuple
A parent object referred to by type name and primary key.
Returned by the target option and accepted by AuditTrail.log.
A plain (type, id) tuple works too.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
str
|
Name stored in |
id |
object
|
The primary key; a tuple for a composite key. |
AuditOptions
dataclass
¶
AuditOptions(
severity: IntEnum | None = None,
verb_severity: dict[str, IntEnum] = dict(),
track_relationships: Collection[str] = frozenset(),
snapshot_on_load: Collection[str] = frozenset(),
object_type: str | None = None,
label: Callable[[Any], str | None] | None = None,
scope: Callable[[Any], object] | None = None,
target: Callable[
[Any], Target | tuple[str, object] | None
]
| None = None,
)
Audit settings for one model, set as __audit__ on the class.
label, scope and target run while the session flushes and get
a read-only view of the instance instead of the instance itself. They may
read only attributes already loaded on it: reading an expired or unloaded
attribute emits no SQL, logs a warning on audit_trail.diff (once per
model, attribute and option) and the option falls back (label and
target to None, scope to the context). A column that was
never set on a new instance reads as None, as stored, unless it has a
server-side default. Reading a
functools.cached_property raises AuditOptionError. Any other
exception they raise propagates and aborts the flush.
Attributes:
| Name | Type | Description |
|---|---|---|
severity |
IntEnum | None
|
Severity of the model's |
verb_severity |
dict[str, IntEnum]
|
Per-verb severity overrides, keyed by verb
(for example |
track_relationships |
Collection[str]
|
Relationship names whose membership changes are
recorded as |
snapshot_on_load |
Collection[str]
|
JSON columns deep-copied on load and refresh, so an in-place mutation still has an old value. |
object_type |
str | None
|
Name stored in |
label |
Callable[[Any], str | None] | None
|
Returns the object's label, stored in |
scope |
Callable[[Any], object] | None
|
Returns the scope id, stored in |
target |
Callable[[Any], Target | tuple[str, object] | None] | None
|
Returns the parent object as a |
AuditTrail ¶
AuditTrail(
engine: Engine | AsyncEngine,
*,
schema: str = "audit",
severities: type[IntEnum] | None = None,
default_severity: IntEnum | None = None,
system_severity: IntEnum | None = None,
events: Iterable[type[AuditEvent]] | None = None,
durable_engine: Engine | AsyncEngine | None = None,
on_error: OnError = "log",
pseudonymize_key: bytes
| dict[int, bytes]
| None = None,
session_provider: Callable[
[], Session | AsyncSession | None
]
| None = None,
context_provider: Callable[[], Any] | None = None,
json_encoder: type[JSONEncoder] | None = None,
indexes: Collection[str] | None = None,
global_redact: Collection[str] = (),
warn_on_bulk: bool = True,
auto_create_partitions: bool = False,
allow_scrub: bool = False,
default_query_window: timedelta | None = None,
)
Entry point of the library: configuration plus the public operations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
Engine | AsyncEngine
|
Sync or async engine used for DDL and maintenance. |
required |
schema
|
str
|
Database schema holding the audit tables. |
'audit'
|
severities
|
type[IntEnum] | None
|
The application's severity |
None
|
default_severity
|
IntEnum | None
|
Severity of |
None
|
system_severity
|
IntEnum | None
|
Severity of the library's own |
None
|
events
|
Iterable[type[AuditEvent]] | None
|
Event classes to register. |
None
|
durable_engine
|
Engine | AsyncEngine | None
|
Engine for durable writes: sync for |
None
|
on_error
|
OnError
|
|
'log'
|
pseudonymize_key
|
bytes | dict[int, bytes] | None
|
HMAC key as |
None
|
session_provider
|
Callable[[], Session | AsyncSession | None] | None
|
Returns the current session, or |
None
|
context_provider
|
Callable[[], Any] | None
|
Returns the current audit context. |
None
|
json_encoder
|
type[JSONEncoder] | None
|
Extra encoder for types the built-in one does not handle. |
None
|
indexes
|
Collection[str] | None
|
Names of the indexes to create. |
None
|
global_redact
|
Collection[str]
|
Attribute keys or column names redacted in
|
()
|
warn_on_bulk
|
bool
|
Log a warning when a bulk |
True
|
auto_create_partitions
|
bool
|
Let durable writes create a missing partition and retry once. Needs DDL privileges. |
False
|
allow_scrub
|
bool
|
Enable |
False
|
default_query_window
|
timedelta | None
|
How far back |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
severities |
type[IntEnum]
|
The severity enum in use. |
registry |
The registered events. |
|
tables |
The audit tables built from |
|
keys |
Key ring built from |
|
maintenance |
Partition management on |
|
query |
Read queries, for example |
|
durable_engine |
Engine | AsyncEngine
|
The engine durable writes use. |
Raises:
| Type | Description |
|---|---|
EventRegistryError
|
An event or a severity setting is invalid. |
ValueError
|
A table name or an index key is invalid,
|
TypeError
|
|
Source code in audit_trail/config.py
install ¶
install(
session_factory: type[Session | AsyncSession]
| sessionmaker[Any]
| async_sessionmaker[Any],
) -> None
Audit the sessions of a factory.
Registers the listeners on the factory's session class only (for a
sessionmaker, the class it builds), so other sessions, a second
sessionmaker and tools such as Alembic are not audited. Set
session.info["audit_enabled"] = False to switch capture off for
one session of that class.
An AsyncSession runs a sync Session in a greenlet, and the
listeners are registered on that class: give the async factory your
own Session subclass, as in
async_sessionmaker(engine, sync_session_class=AppSession) or as
sync_session_class of an AsyncSession subclass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_factory
|
type[Session | AsyncSession] | sessionmaker[Any] | async_sessionmaker[Any]
|
A |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
The session class is the base |
Source code in audit_trail/config.py
pseudonymize ¶
Replace a value with a keyed pseudonym, e.g. audit.<purpose>.v1:<hex>.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
object
|
The value. Must not be |
required |
purpose
|
str
|
Snake_case name separating unrelated uses. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The pseudonym token. |
Raises:
| Type | Description |
|---|---|
ValueError
|
No |
TypeError
|
|
UnserializableValueError
|
|
Source code in audit_trail/config.py
dispose ¶
Close the pooled connections of a durable engine the library built.
A durable_engine passed in is the host's to dispose; this leaves it
alone.
Raises:
| Type | Description |
|---|---|
TypeError
|
The library's durable engine is async; use |
Source code in audit_trail/config.py
adispose
async
¶
Async dispose, for a durable engine the library built async.
Raises:
| Type | Description |
|---|---|
TypeError
|
The library's durable engine is sync; use |
Source code in audit_trail/config.py
log ¶
log(
session_or_event: Session | AuditEvent | None,
event: AuditEvent | None = None,
/,
*,
obj: object | None = None,
target: Target | object | None = None,
payload: Mapping[str, object] | BaseModel | None = None,
actor: Actor | None = None,
durable: bool | None = None,
) -> None
Record an explicit event.
Called as log(session, event, ...) or, with a session_provider
configured, as log(event, ...): without a session (or with
None), the entry goes to the session session_provider()
returns.
A non-durable entry is inserted immediately on the session's
connection, in the same database transaction as the session's changes:
it is committed or rolled back with them, and shares their
audit_transaction row. Write failures follow on_error. With
obj, the connection is the one obj is flushed on; without, the
one for the audit tables: their bind in Session(binds=...), else
the session's default bind.
A durable entry (the event's durable or fail_closed, or
durable=True) is written on a connection of durable_engine in
a transaction of its own, with its own audit_transaction row built
from the current context, and committed before this returns, so it
survives a rollback of the session. A failed write is logged with
on_error="log"; with on_error="raise", and always for a
fail_closed event, it raises AuditWriteError. With
auto_create_partitions, a missing partition is created and the
write retried once. Creating a partition waits for every open
transaction that has written audit rows, including the session's own:
if it has, the retry fails after the ensure_partitions lock
timeout (about 5 to 10 seconds) and a fail_closed event raises
AuditWriteError. Run ensure_partitions ahead of time rather
than relying on the retry.
Either kind is written even when session.info["audit_enabled"] is
False, which only switches off the automatic entity.* entries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_or_event
|
Session | AuditEvent | None
|
A session of a class this |
required |
event
|
AuditEvent | None
|
A registered host event. |
None
|
obj
|
object | None
|
The object the event is about; sets |
None
|
target
|
Target | object | None
|
The parent object, as an instance or a |
None
|
payload
|
Mapping[str, object] | BaseModel | None
|
The payload, validated against the event's schema.
|
None
|
actor
|
Actor | None
|
The actor of this entry. It sets the entry's |
None
|
durable
|
bool | None
|
Overrides the event's |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
No session is passed and there is no
|
TypeError
|
The arguments are not |
ValueError
|
The event uses a reserved prefix, |
UnknownEventError
|
The event is not registered. |
PayloadError
|
The payload does not match the event's schema, or a
|
AuditWriteError
|
A durable write failed and the policy says to raise. |
UnboundExecutionError
|
A non-durable entry without |
Source code in audit_trail/config.py
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 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | |
alog
async
¶
alog(
session_or_event: AsyncSession | AuditEvent | None,
event: AuditEvent | None = None,
/,
*,
obj: object | None = None,
target: Target | object | None = None,
payload: Mapping[str, object] | BaseModel | None = None,
actor: Actor | None = None,
durable: bool | None = None,
) -> None
Async log, for an AsyncSession of an installed factory.
Called as alog(session, event, ...) or, with a
session_provider configured, as alog(event, ...); see log.
A non-durable entry is written in the session's transaction, a
durable one on the async durable_engine; see log.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_or_event
|
AsyncSession | AuditEvent | None
|
An |
required |
event
|
AuditEvent | None
|
A registered host event. |
None
|
obj
|
object | None
|
The object the event is about; see |
None
|
target
|
Target | object | None
|
The parent object; see |
None
|
payload
|
Mapping[str, object] | BaseModel | None
|
The payload; see |
None
|
actor
|
Actor | None
|
The actor of this entry; see |
None
|
durable
|
bool | None
|
Overrides the event's |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
No session is passed and there is no
|
TypeError
|
The arguments are not |
ValueError
|
As for |
UnknownEventError
|
The event is not registered. |
PayloadError
|
The payload does not match the event's schema. |
AuditWriteError
|
A durable write failed and the policy says to raise. |
UnboundExecutionError
|
As for |
Source code in audit_trail/config.py
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 | |
scrub ¶
scrub(
object_type: str,
object_id: str,
*,
include_targets: bool = True,
since: datetime | None = None,
scope_ids: Collection[str] | None = None,
) -> ScrubResult
Erase the values the entries of one object hold.
Every non-null value in data.changes and data.payload of the
object's entries becomes "[erased]" (field keys stay, null
stays null) and object_label becomes NULL. data.context
is left to scrub_actor. Runs in one transaction on engine
together with an audit.scrubbed entry recording who scrubbed
which object and how many rows, without any erased value; nothing is
committed if either fails. With auto_create_partitions, the
partitions of the current month are ensured first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
object_type
|
str
|
Stored |
required |
object_id
|
str
|
Stored |
required |
include_targets
|
bool
|
Also erase entries whose target is the object, such as changes of its children. |
True
|
since
|
datetime | None
|
Only entries created at or after this aware datetime,
which also limits the partitions scanned. |
None
|
scope_ids
|
Collection[str] | None
|
Only entries whose |
None
|
Returns:
| Type | Description |
|---|---|
ScrubResult
|
A |
ScrubResult
|
erased (entries already erased are not counted); |
ScrubResult
|
|
Raises:
| Type | Description |
|---|---|
ScrubNotAllowedError
|
|
TypeError
|
|
ValueError
|
|
Source code in audit_trail/config.py
ascrub
async
¶
ascrub(
object_type: str,
object_id: str,
*,
include_targets: bool = True,
since: datetime | None = None,
scope_ids: Collection[str] | None = None,
) -> ScrubResult
Async scrub, for an AuditTrail built with an AsyncEngine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
object_type
|
str
|
Stored |
required |
object_id
|
str
|
Stored |
required |
include_targets
|
bool
|
Also erase entries whose target is the object. |
True
|
since
|
datetime | None
|
Only entries created at or after this aware datetime. |
None
|
scope_ids
|
Collection[str] | None
|
Only entries with one of these |
None
|
Returns:
| Type | Description |
|---|---|
ScrubResult
|
A |
Raises:
| Type | Description |
|---|---|
ScrubNotAllowedError
|
|
TypeError
|
|
ValueError
|
As for |
Source code in audit_trail/config.py
scrub_actor ¶
Clear the personal context stored for one actor.
audit_transaction rows created with this actor_id get
actor_label, remote_addr, user_agent and meta set to
NULL, and entries with this actor_id lose those keys from
data.context. actor_id stays. A transaction row keeps the
actor it was created with: a request that wrote its first entry
anonymously and called set_actor afterwards keeps its address
and user agent on that row. Runs in one transaction on engine
with an audit.scrubbed entry, as scrub does.
With scope_ids, only entries whose scope_id is one of them
lose the keys, and a transaction row is cleared only when it has
entries and all of them, whoever their actor, are in those scopes:
its fields are shared by all its entries, so a request that also
wrote entries of another scope or without one keeps them, as does a
transaction row without entries. Clearing an actor completely
across scopes takes a run without scope_ids.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
actor_id
|
str
|
The actor's |
required |
scope_ids
|
Collection[str] | None
|
The scopes the scrub is restricted to, such as the
tenants of an admin. |
None
|
Returns:
| Type | Description |
|---|---|
ScrubResult
|
A |
ScrubResult
|
rows changed. |
Raises:
| Type | Description |
|---|---|
ScrubNotAllowedError
|
|
TypeError
|
|
ValueError
|
|
Source code in audit_trail/config.py
ascrub_actor
async
¶
Async scrub_actor, for an AuditTrail built with an AsyncEngine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
actor_id
|
str
|
The actor's |
required |
scope_ids
|
Collection[str] | None
|
The scopes the scrub is restricted to; see
|
None
|
Returns:
| Type | Description |
|---|---|
ScrubResult
|
A |
ScrubResult
|
rows changed. |
Raises:
| Type | Description |
|---|---|
ScrubNotAllowedError
|
|
TypeError
|
|
ValueError
|
|