Skip to content

UserRepository

The adapter between crudauth's logical field names and your user model's actual columns. Configure the mapping with column_map= on CRUDAuth.

crudauth.repository.UserRepository

UserRepository(
    model: type[Any],
    column_map: dict[str, str] | None = None,
    register_extra_fields: Iterable[str] | None = None,
)

col

col(logical: str) -> str

Resolve a logical field name to the actual model attribute name.

has

has(logical: str) -> bool

Whether the model actually has the column for logical.

get

get(user: Any, logical: str, default: Any = None) -> Any

Read a logical field off user, or default if the column is absent.

set_field

set_field(user: Any, logical: str, value: Any) -> None

Set a logical field on user by its resolved column name.

get_by_id async

get_by_id(db: AsyncSession, user_id: Any) -> Any | None

Fetch the user by primary key, or None.

Parameters:

Name Type Description Default
db AsyncSession

Active async session.

required
user_id Any

PK value; coerced to the column's Python type first (so a string "42" from a token matches an int PK on Postgres).

required

Returns:

Type Description
Any | None

The user row, or None if absent or user_id can't be coerced.

get_by_email async

get_by_email(db: AsyncSession, email: str) -> Any | None

Fetch the user by (canonicalized) email, or None.

get_by_username async

get_by_username(
    db: AsyncSession, username: str
) -> Any | None

Fetch the user by username, or None.

get_by_identifier async

get_by_identifier(
    db: AsyncSession, identifier: str
) -> Any | None

Look up by email when the identifier contains @, else by username.

get_by_oauth async

get_by_oauth(
    db: AsyncSession, provider: str, provider_user_id: str
) -> Any | None

Look up by {provider}_id.

Note

Assumes provider is a validated/registered provider name (the OAuth router checks it before this is reached) - it builds an attribute name from the argument, so an unvalidated value would probe arbitrary columns (it returns None for any column the model lacks, so the blast radius is a missed lookup, not a leak).

gated_register_fields

gated_register_fields(
    schema_fields: Iterable[str],
) -> set[str]

Which of schema_fields are crudauth privileged fields (always dropped).

droppable_register_fields

droppable_register_fields(
    schema_fields: Iterable[str],
) -> set[str]

Which of schema_fields map to a real model column but are not privileged and not opted in, so registration silently drops them.

These are the fields a developer most likely expects to persist (e.g. a full_name column they added to register_schema) and won't, until they add the name to register_extra_fields.

filter_registration_data

filter_registration_data(
    data: dict[str, Any],
) -> dict[str, Any]

Keep only the allowlisted registration fields; drop everything else.

A field survives only if it is in :data:REGISTRATION_ALLOWED_FIELDS or was opted in via register_extra_fields (matched by logical or mapped column name) - and is never one of crudauth's privileged logical fields (is_superuser, email_verified, hashed_password, the oauth linkage, the PK), which stay gated even if mistakenly opted in. Unknown app columns (role, credits, ...) are dropped unless explicitly opted in, so a custom register_schema can't turn /register into a privilege-escalation or mass-assignment endpoint.

create async

create(db: AsyncSession, data: dict[str, Any]) -> Any

Insert a user from logical-field data and return the row.

Note

Owns the transaction boundary - commits and refreshes on the passed-in session. Apps using a request-scoped "commit at the end" pattern should know auth writes commit eagerly.

Note

Email is canonicalized off the resolved column: kwargs is keyed by actual column names, so a column_map that renames email would otherwise be stored un-normalized.

update async

update(
    db: AsyncSession, user: Any, data: dict[str, Any]
) -> Any

Apply logical-field data to user and return it.

Note

Commits and refreshes eagerly on the passed-in session (see create).

token_version

token_version(user: Any) -> int

The user's credential epoch (0 if the model has no such column).

increment_token_version async

increment_token_version(
    db: AsyncSession, user: Any
) -> None

Bump the credential epoch, revoking outstanding bearer tokens.

A no-op when the model has no token_version column (bearer tokens then simply aren't epoch-revocable; the limitation is documented).

to_dict

to_dict(user: Any) -> dict[str, Any]

Project a user row onto the logical contract (for hooks).

Note

Contract-only by design - the dict holds the crudauth logical fields (id, email, ...), not your app's own columns. A hook that needs full_name should re-load the row via db using the id, not expect it in this dict.