Skip to content
You're not viewing the latest version. Click here to go to latest.

# 9.x.x ➡️ 10.x.x

Version 10 marks important changes in how we manage User models and their ID.

Before, we were relying only on Pydantic models to work with users. In particular the `current_user` dependency would return you an instance of `UserDB`, a Pydantic model. This proved to be quite problematic with some ORM if you ever needed to retrieve relationship data or make specific requests.

Now, FastAPI Users is designed to always return you a native object for your ORM model, whether it's an SQLAlchemy model or a Beanie document. Pydantic models are now only used for validation and serialization inside the API.

Before, we were forcing the use of UUID as primary key ID; a consequence of the design above. This proved to be quite problematic on some databases, like MongoDB which uses a special ObjectID format by default. Some SQL folks also prefer to use traditional auto-increment integers.

Now, FastAPI Users is designed to use generic ID type. It means that you can use any type you want for your user's ID. By default, SQLAlchemy adapter still use UUID; but you can quite easily switch to another thing, like an integer. Beanie adapter for MongoDB will use native ObjectID by default, but it also can be overriden.

As you may have guessed, those changes imply quite a lot of breaking changes.

## User models and database adapter

### SQLAlchemy ORM

We've removed the old SQLAlchemy dependency support, so the dependency is now `fastapi-users[sqlalchemy]`.

BeforeAfter

    fastapi
    fastapi-users[sqlalchemy2]
    uvicorn[standard]
    aiosqlite

    fastapi
    fastapi-users[sqlalchemy]
    uvicorn[standard]
    aiosqlite

The User model base class for SQLAlchemy slightly changed to support UUID by default.

We changed the name of the class from `UserTable` to `User`: it's not a compulsory change, but since there is no risk of confusion with Pydantic models anymore, it's probably a more idiomatic naming.

BeforeAfter

    class UserTable(Base, SQLAlchemyBaseUserTable):
        pass

    class User(SQLAlchemyBaseUserTableUUID, Base):
        pass

Instantiating the `SQLAlchemyUserDatabase` adapter now only expects this `User` model. `UserDB` is removed.

BeforeAfter

    async def get_user_db(session: AsyncSession = Depends(get_async_session)):
        yield SQLAlchemyUserDatabase(UserDB, session, UserTable)

    async def get_user_db(session: AsyncSession = Depends(get_async_session)):
        yield SQLAlchemyUserDatabase(session, User)

### MongoDB

MongoDB support is now only provided through Beanie ODM. Even if you don't use it for the rest of your project, it's a very light addition that shouldn't interfere much.

BeforeAfter

    fastapi
    fastapi-users[mongodb]
    uvicorn[standard]
    aiosqlite

    fastapi
    fastapi-users[beanie]
    uvicorn[standard]
    aiosqlite

You now need to define a proper User model using Beanie.

BeforeAfter

    import os

    import motor.motor_asyncio
    from fastapi_users.db import MongoDBUserDatabase

    from app.models import UserDB

    DATABASE_URL = os.environ["DATABASE_URL"]
    client = motor.motor_asyncio.AsyncIOMotorClient(
        DATABASE_URL, uuidRepresentation="standard"
    )
    db = client["database_name"]
    collection = db["users"]

    async def get_user_db():
        yield MongoDBUserDatabase(UserDB, collection)

    import motor.motor_asyncio
    from beanie import PydanticObjectId
    from fastapi_users.db import BeanieBaseUser, BeanieUserDatabase

    DATABASE_URL = "mongodb://localhost:27017"
    client = motor.motor_asyncio.AsyncIOMotorClient(
        DATABASE_URL, uuidRepresentation="standard"
    )
    db = client["database_name"]

    class User(BeanieBaseUser[PydanticObjectId]):
        pass

    async def get_user_db():
        yield BeanieUserDatabase(User)

ID are now ObjectID by default

By default, User ID will now be native MongoDB ObjectID. If you don't want to make the transition and keep UUID you can do so by overriding the `id` field:

    import uuid

    from pydantic import Field

    class User(BeanieBaseUser[uuid.UUID]):
        id: uuid.UUID = Field(default_factory=uuid.uuid4)

Beanie also needs to be initialized in a startup event handler of your FastAPI app:

    from beanie import init_beanie

    @app.on_event("startup")
    async def on_startup():
        await init_beanie(
            database=db,
            document_models=[
                User,
            ],
        )

### Tortoise ORM and ormar

Unfortunately, we sometimes need to make difficult choices to keep things sustainable. That's why we decided to not support Tortoise ORM and ormar anymore. It appeared they were not widely used.

You can still add support for those ORM yourself by implementing the necessary adapter. You can take inspiration from the SQLAlchemy one.

## `UserManager`

There is some slight changes on the `UserManager` class. In particular, it now needs a `parse_id` method that can be provided through built-in mixins.

Generic typing now expects your native User model class and the type of ID.

The `user_db_model` class property is removed.

BeforeAfter

    class UserManager(BaseUserManager[UserCreate, UserDB]):
        user_db_model = UserDB
        reset_password_token_secret = SECRET
        verification_token_secret = SECRET

        async def on_after_register(self, user: UserDB, request: Optional[Request] = None):
            print(f"User {user.id} has registered.")

        async def on_after_forgot_password(
            self, user: UserDB, token: str, request: Optional[Request] = None
        ):
            print(f"User {user.id} has forgot their password. Reset token: {token}")

        async def on_after_request_verify(
            self, user: UserDB, token: str, request: Optional[Request] = None
        ):
            print(f"Verification requested for user {user.id}. Verification token: {token}")

    class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
        reset_password_token_secret = SECRET
        verification_token_secret = SECRET

        async def on_after_register(self, user: User, request: Optional[Request] = None):
            print(f"User {user.id} has registered.")

        async def on_after_forgot_password(
            self, user: User, token: str, request: Optional[Request] = None
        ):
            print(f"User {user.id} has forgot their password. Reset token: {token}")

        async def on_after_request_verify(
            self, user: User, token: str, request: Optional[Request] = None
        ):
            print(f"Verification requested for user {user.id}. Verification token: {token}")

If you need to support other types of ID, you can read more about it in the dedicated section.

## Pydantic models

To better distinguish them from the ORM models, Pydantic models are now called schemas.

`UserDB` has been removed in favor of native models.

We changed the name of `User` to `UserRead`: it's not a compulsory change, but since there is a risk of confusion with the native model, it's highly recommended.

Besides, the `BaseUser` schema now accepts a generic type to specify the type of ID you use.

BeforeAfter

    from fastapi_users import models

    class User(models.BaseUser):
        pass

    class UserCreate(models.BaseUserCreate):
        pass

    class UserUpdate(models.BaseUserUpdate):
        pass

    class UserDB(User, models.BaseUserDB):
        pass

    import uuid

    from fastapi_users import schemas

    class UserRead(schemas.BaseUser[uuid.UUID]):
        pass

    class UserCreate(schemas.BaseUserCreate):
        pass

    class UserUpdate(schemas.BaseUserUpdate):
        pass

## FastAPI Users and routers

Pydantic schemas are now way less important in this new design. As such, you don't need to pass them when initializing the `FastAPIUsers` class:

BeforeAfter

    fastapi_users = FastAPIUsers(
        get_user_manager,
        [auth_backend],
        User,
        UserCreate,
        UserUpdate,
        UserDB,
    )

    fastapi_users = FastAPIUsers[User, uuid.UUID](
        get_user_manager,
        [auth_backend],
    )

As a consequence, those schemas need to be passed when initializing the router that needs them: `get_register_router`, `get_verify_router` and `get_users_router`.

BeforeAfter

    app.include_router(
        fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"]
    )
    app.include_router(fastapi_users.get_register_router(), prefix="/auth", tags=["auth"])
    app.include_router(
        fastapi_users.get_reset_password_router(),
        prefix="/auth",
        tags=["auth"],
    )
    app.include_router(
        fastapi_users.get_verify_router(),
        prefix="/auth",
        tags=["auth"],
    )
    app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])

    app.include_router(
        fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"]
    )
    app.include_router(
        fastapi_users.get_register_router(UserRead, UserCreate),
        prefix="/auth",
        tags=["auth"],
    )
    app.include_router(
        fastapi_users.get_reset_password_router(),
        prefix="/auth",
        tags=["auth"],
    )
    app.include_router(
        fastapi_users.get_verify_router(UserRead),
        prefix="/auth",
        tags=["auth"],
    )
    app.include_router(
        fastapi_users.get_users_router(UserRead, UserUpdate),
        prefix="/users",
        tags=["users"],
    )

## Lost?

If you're unsure or a bit lost, make sure to check the full working examples.

Back to top