Custom adapters¶
The DB layer is the Adapter ABC (src/fastauth/adapters/adapters.py). SQLAlchemy ships included (SQLAlchemySessionAdapter, SQLAlchemyJWTAdapter); any other ORM is a subclass away. Model compatibility is structural — see Config for the mixins, src/fastauth/protocols.py for the contracts.
Implement every abstract method (refresh-token methods default to NotImplementedError, so JWT-only adapters can skip the session side and vice versa — check which your strategy calls):
get_extra_fields(model)/get_response_fields(model)— classmethods reading thefastauth_input/fastauth_returnedcolumn flags for dynamic schemasget_user_by_email,get_user_by_idcreate_user(data)— hashdata["password"]intohashed_passwordissue_credential(user)/resolve_credential(token)/revoke_credential(token)require_password_reset_model()/require_oauth_account_model()— raiseValueErrorwhen unsetget_oidc_account/create_user_from_oidc,get_oauth2_account/create_user_from_oauth2issue_refresh_token/consume_refresh_token(single-use burn) /revoke_refresh_token/purge_expired_refresh_tokenscreate_password_reset_token/consume_password_reset_token(single-use, 15-minute) /set_password/revoke_credentials_on_password_reset
Rules: flush, never commit — the route commits user + account (+ refresh) rows atomically with the credential. The only exception is the rate-limiter adapter, whose check() commits immediately because it runs as a route dependency. Raise EmailAlreadyRegistered (from fastauth.adapters.exceptions) when an OAuth email collides with an existing user so routes return 400 instead of taking the account over.
ensure_model_compliance(model, Protocol, name=...) from src/fastauth/protocols.py fails fast at startup when a model is missing attributes — call it in your constructor like the built-ins do. Every method signature and mixin column: API reference.