Parent
#16 — Database Backend Abstraction for Multi-ORM Support
What to build
Define built-in admin models (User, Role, Permission, AuditLog, LoginAttempt) using a hybrid approach: schema-first for built-in models + protocol for custom user models.
The Three Layers
1. Protocol Layer — Contract Definition
Define AdminUserProtocol in fastapi_admin_kit/auth/protocol.py — the contract that any user model must satisfy to work with admin RBAC:
@runtime_checkable
class AdminUserProtocol(Protocol):
id: Any
email: str
hashed_password: str
is_active: bool
is_superuser: bool
def verify_password(self, password: str) -> bool: ...
def hash_password(self, password: str) -> None: ...
@property
def roles(self) -> list: ... # Must return role objects with .permissions
Similarly define AdminRoleProtocol and AdminPermissionProtocol.
2. Schema-First Layer — Built-in Model Definitions
Define schemas in fastapi_admin_kit/schemas/builtin.py:
USER_SCHEMA = Schema(
table_name="admin_users",
fields=[
Field("id", type="integer", primary_key=True, auto_increment=True),
Field("email", type="string", max_length=255, unique=True, nullable=False),
Field("hashed_password", type="string", max_length=255),
Field("is_active", type="boolean", default=True),
Field("is_superuser", type="boolean", default=False),
Field("last_login", type="datetime", nullable=True),
Field("created_at", type="datetime", server_default="now()"),
],
relations=[
Relation("roles", target="admin_roles", type="many_to_many",
through="admin_user_roles"),
],
)
Same for ROLE_SCHEMA, PERMISSION_SCHEMA, AUDIT_LOG_SCHEMA, LOGIN_ATTEMPT_SCHEMA.
3. Materialization Layer — Backend Converts Schemas to Native Models
Each backend implements materialize(schema):
# SQLAlchemy backend
class SQLAlchemyBackend:
def materialize(self, schema: Schema) -> type:
"""Convert schema → SQLAlchemy model class."""
columns = []
for f in schema.fields:
columns.append(Column(f.name, self._map_type(f.type), ...))
return type(schema.table_name, (Base,), {"__tablename__": schema.table_name, ...})
# Beanie backend
class MongoBackend:
def materialize(self, schema: Schema) -> type:
"""Convert schema → Beanie Document class."""
fields = {f.name: self._map_type(f.type) for f in schema.fields}
return type(schema.table_name, (Document,), {**fields, ...})
# Django backend
class DjangoBackend:
def materialize(self, schema: Schema) -> type:
"""Convert schema → Django model class."""
fields_dict = {f.name: self._map_type(f.type) for f in schema.fields}
return type(schema.table_name, (models.Model,), {**fields_dict, ...})
How It Connects
┌──────────────────────────────────────────────────┐
│ Protocols (AdminUserProtocol, etc.) │
│ Contract: what fields/methods are required │
└──────────────────────┬───────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Schema- │ │ User's │ │ User's │
│ First │ │ Custom │ │ Custom │
│ Built-in │ │ SQLAlchemy │ │ Beanie │
│ Models │ │ Model │ │ Document │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────┐
│ Backend.materialize(schema) │
│ + Validates user model satisfies protocol │
└──────────────────────┬──────────────────────┘
▼
┌───────────────┐
│ RBAC System │
│ Auth System │
│ Audit System │
└───────────────┘
User Configures
# Option 1: Use built-in models (default)
admin = Admin(app=app, backend=SQLAlchemyBackend(engine=engine))
# Option 2: Custom user model (SQLAlchemy)
class MyUser(Base):
__tablename__ = "my_users"
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True)
hashed_password = Column(String(255))
is_active = Column(Boolean, default=True)
is_superuser = Column(Boolean, default=False)
phone = Column(String(20)) # custom field
roles = relationship("Role", secondary=my_user_roles, ...)
admin = Admin(app=app, backend=SQLAlchemyBackend(engine=engine), auth_model=MyUser)
# Option 3: Custom user model (Beanie/MongoDB)
class MyUser(Document):
email: str
hashed_password: str
is_active: bool = True
is_superuser: bool = False
phone: str | None = None
roles: list[Link[Role]] = []
admin = Admin(app=app, backend=MongoBackend(...), auth_model=MyUser)
Files to Create/Modify
New Files
fastapi_admin_kit/schemas/__init__.py — Schema/Field/Relation dataclasses
fastapi_admin_kit/schemas/builtin.py — Built-in model schemas
fastapi_admin_kit/schemas/types.py — Field type mappings
fastapi_admin_kit/auth/protocol.py — AdminUserProtocol, AdminRoleProtocol, AdminPermissionProtocol
Modified Files
fastapi_admin_kit/auth/models.py — Becomes generated from schema OR thin wrapper
fastapi_admin_kit/audit/models.py — Same
fastapi_admin_kit/backends/sqlalchemy.py — Add materialize() method
fastapi_admin_kit/admin/core.py — Accept auth_model param, validate protocol compliance
Acceptance Criteria
Blocked by
#23 (Introspection Adapter) — needs introspection protocol to understand how models are described
Parent
#16 — Database Backend Abstraction for Multi-ORM Support
What to build
Define built-in admin models (User, Role, Permission, AuditLog, LoginAttempt) using a hybrid approach: schema-first for built-in models + protocol for custom user models.
The Three Layers
1. Protocol Layer — Contract Definition
Define
AdminUserProtocolinfastapi_admin_kit/auth/protocol.py— the contract that any user model must satisfy to work with admin RBAC:Similarly define
AdminRoleProtocolandAdminPermissionProtocol.2. Schema-First Layer — Built-in Model Definitions
Define schemas in
fastapi_admin_kit/schemas/builtin.py:Same for ROLE_SCHEMA, PERMISSION_SCHEMA, AUDIT_LOG_SCHEMA, LOGIN_ATTEMPT_SCHEMA.
3. Materialization Layer — Backend Converts Schemas to Native Models
Each backend implements
materialize(schema):How It Connects
User Configures
Files to Create/Modify
New Files
fastapi_admin_kit/schemas/__init__.py— Schema/Field/Relation dataclassesfastapi_admin_kit/schemas/builtin.py— Built-in model schemasfastapi_admin_kit/schemas/types.py— Field type mappingsfastapi_admin_kit/auth/protocol.py— AdminUserProtocol, AdminRoleProtocol, AdminPermissionProtocolModified Files
fastapi_admin_kit/auth/models.py— Becomes generated from schema OR thin wrapperfastapi_admin_kit/audit/models.py— Samefastapi_admin_kit/backends/sqlalchemy.py— Addmaterialize()methodfastapi_admin_kit/admin/core.py— Acceptauth_modelparam, validate protocol complianceAcceptance Criteria
AdminUserProtocoldefined with required fields and methodsAdminRoleProtocolandAdminPermissionProtocoldefinedSchema,Field,Relationdataclasses inschemas/schemas/builtin.pySqlAlchemyBackend.materialize()converts schema → SQLAlchemy modelAdminacceptsauth_modelparameterAdminvalidatesauth_modelsatisfiesAdminUserProtocolauth_modelBlocked by
#23 (Introspection Adapter) — needs introspection protocol to understand how models are described