AuthX is a generic class that supports custom model types through Python’s TypeVar system. This enables full type hinting and IDE autocompletion when working with user models or custom subject types.
Why use custom models?
By default, AuthX uses dict[str, Any] for user data. Custom models provide:
- Type safety: Catch errors at development time instead of runtime
- IDE support: Full autocompletion and inline documentation
- Data validation: Automatic validation with Pydantic models
- Clear contracts: Explicit definition of user data structure
Basic usage
Pass your model type when initializing AuthX:
from authx import AuthX, AuthXConfig
from pydantic import BaseModel
class User(BaseModel):
id: str
username: str
email: str
is_admin: bool = False
# Initialize AuthX with your model type
auth = AuthX[User](config=AuthXConfig())
The type parameter AuthX[User] is optional but highly recommended for type checking. Without it, AuthX defaults to dict[str, Any].
Generic type variable
AuthX uses Python’s TypeVar system (from authx/types.py:14):
from typing import TypeVar
T = TypeVar("T")
The AuthX class definition (from authx/main.py:35):
class AuthX(_CallbackHandler[T], _ErrorHandler):
"""The base class for AuthX.
Args:
config (AuthXConfig, optional): Configuration instance to use.
model (Optional[T], optional): Model type hint. Defaults to dict[str, Any].
Note:
AuthX is a Generic python object.
Its TypeVar is not mandatory but helps type hinting during development.
"""
def __init__(
self,
config: AuthXConfig = AuthXConfig(),
model: Optional[T] = None
) -> None:
self.model: Union[T, dict[str, Any]] = model if model is not None else {}
# ...
Setting up a subject getter
To retrieve your custom model from the database, set a subject getter callback:
from authx import AuthX, AuthXConfig
from pydantic import BaseModel
from typing import Optional
class User(BaseModel):
id: str
username: str
email: str
role: str
# Initialize with type parameter
auth = AuthX[User](config=AuthXConfig(
JWT_SECRET_KEY="your-secret-key"
))
# Mock database
USERS_DB = {
"user1": User(id="user1", username="alice", email="[email protected]", role="user"),
"user2": User(id="user2", username="bob", email="[email protected]", role="admin"),
}
# Set the callback to retrieve users
@auth.set_subject_getter
def get_user_from_db(uid: str) -> Optional[User]:
"""Retrieve user from database by ID."""
return USERS_DB.get(uid)
Define your model
Create a Pydantic model or dataclass representing your user/subject.
Initialize AuthX with type
Pass the type parameter when creating the AuthX instance: AuthX[User].
Set subject getter
Use the @auth.set_subject_getter decorator to register your retrieval function.
Use typed dependencies
The auth.CURRENT_SUBJECT dependency now returns your custom type.
Complete example with FastAPI
from fastapi import FastAPI, Depends, HTTPException
from authx import AuthX, AuthXConfig
from pydantic import BaseModel, EmailStr
from typing import Optional
# Define user model
class User(BaseModel):
id: str
username: str
email: EmailStr
full_name: str
is_active: bool = True
is_admin: bool = False
class LoginRequest(BaseModel):
username: str
password: str
# Initialize FastAPI
app = FastAPI()
# Initialize AuthX with User type
auth = AuthX[User](config=AuthXConfig(
JWT_SECRET_KEY="your-secret-key",
JWT_ALGORITHM="HS256",
))
auth.handle_errors(app)
# Mock database
USERS_DB = {
"alice": User(
id="alice",
username="alice",
email="[email protected]",
full_name="Alice Smith",
is_admin=True,
),
"bob": User(
id="bob",
username="bob",
email="[email protected]",
full_name="Bob Johnson",
),
}
PASSWORDS = {
"alice": "password123",
"bob": "password456",
}
# Set up subject getter
@auth.set_subject_getter
def get_user(uid: str) -> Optional[User]:
"""Retrieve user from database.
This function is called by auth.CURRENT_SUBJECT dependency.
"""
return USERS_DB.get(uid)
@app.post("/login")
def login(data: LoginRequest):
"""Login endpoint."""
# Validate credentials
if data.username not in PASSWORDS:
raise HTTPException(status_code=401, detail="Invalid credentials")
if PASSWORDS[data.username] != data.password:
raise HTTPException(status_code=401, detail="Invalid credentials")
# Create access token with user ID as subject
access_token = auth.create_access_token(uid=data.username)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/me")
def get_current_user(
# This now returns User type, not dict!
current_user: User = Depends(auth.CURRENT_SUBJECT)
):
"""Get current authenticated user.
The current_user parameter has full type hints.
Your IDE will autocomplete .username, .email, etc.
"""
return {
"id": current_user.id,
"username": current_user.username,
"email": current_user.email,
"full_name": current_user.full_name,
"is_admin": current_user.is_admin,
}
@app.get("/admin/dashboard")
def admin_dashboard(
current_user: User = Depends(auth.CURRENT_SUBJECT)
):
"""Admin-only endpoint with type-safe user object."""
# Type checker knows current_user is User type
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
return {
"message": f"Welcome to admin dashboard, {current_user.full_name}",
"users": list(USERS_DB.keys()),
}
@app.get("/profile/{username}")
def get_user_profile(
username: str,
# Type-safe current user
current_user: User = Depends(auth.CURRENT_SUBJECT)
):
"""Get any user's profile (if active user)."""
if not current_user.is_active:
raise HTTPException(status_code=403, detail="Account not active")
user = USERS_DB.get(username)
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Full type safety
return {
"username": user.username,
"full_name": user.full_name,
"email": user.email if current_user.is_admin else None,
}
Async subject getters
You can use async functions for database queries:
from motor.motor_asyncio import AsyncIOMotorClient
from typing import Optional
# MongoDB setup
client = AsyncIOMotorClient("mongodb://localhost:27017")
db = client.myapp
users_collection = db.users
auth = AuthX[User](config=AuthXConfig(
JWT_SECRET_KEY="your-secret-key"
))
@auth.set_subject_getter
async def get_user_from_mongodb(uid: str) -> Optional[User]:
"""Async database query."""
user_data = await users_collection.find_one({"_id": uid})
if user_data:
return User(**user_data)
return None
AuthX automatically detects whether your subject getter is sync or async and handles it appropriately (authx/_internal/_callback.py:84).
Using dataclasses
You can also use Python dataclasses instead of Pydantic models:
from dataclasses import dataclass
from authx import AuthX, AuthXConfig
from typing import Optional
@dataclass
class User:
id: str
username: str
email: str
is_active: bool = True
# Works the same way
auth = AuthX[User](config=AuthXConfig(
JWT_SECRET_KEY="your-secret-key"
))
@auth.set_subject_getter
def get_user(uid: str) -> Optional[User]:
# Your retrieval logic
return User(
id=uid,
username="example",
email="[email protected]"
)
Type safety benefits
With custom models, you get full IDE support:
from fastapi import Depends
@app.get("/protected")
def protected_route(
current_user: User = Depends(auth.CURRENT_SUBJECT)
):
# ✅ IDE autocompletes these
print(current_user.username)
print(current_user.email)
print(current_user.is_admin)
# ❌ IDE shows error - attribute doesn't exist
print(current_user.nonexistent_field) # Type checker catches this!
return {"user": current_user.username}
Use Pydantic models for automatic validation and serialization, or dataclasses for simpler use cases.
Callback handler internals
The callback system (authx/_internal/_callback.py:14) handles both sync and async getters:
class _CallbackHandler(Generic[T]):
"""Base class for callback handlers in AuthX."""
def __init__(self, model: Optional[T] = None) -> None:
self._model: Optional[T] = model
self.callback_get_model_instance: Optional[ModelCallback[T]] = None
def set_subject_getter(self, callback: ModelCallback[T]) -> None:
"""Set the callback to run for subject retrieval."""
self.set_callback_get_model_instance(callback)
async def _get_current_subject(
self, uid: str, **kwargs
) -> Optional[T]:
"""Get current model instance from callback."""
self._check_model_callback_is_set()
callback = self.callback_get_model_instance
if callback is None:
return None
# Automatically handle sync or async
if iscoroutinefunction(callback):
return await callback(uid, **kwargs)
return cast(Optional[T], callback(uid, **kwargs))
Best practices
Always use type parameters
Initialize as AuthX[YourModel] for full type safety and IDE support.
Set subject getter early
Register your subject getter during application startup, before defining routes.
Use Pydantic for validation
Pydantic models provide automatic validation when deserializing from databases.
Return Optional types
Your subject getter should return Optional[T] to handle cases where the user doesn’t exist.
Use async for I/O
If your subject getter performs database queries or API calls, make it async.
If you don’t set a subject getter, using auth.CURRENT_SUBJECT will raise an AttributeError.