Skip to content

0.8.x ➡️ 1.x.x

1.0 version introduces major breaking changes that need you to update some of your code and migrate your data.

Id. are UUID

Users and OAuth accounts id. are now represented as real UUID objects instead of plain strings. This change was introduced to leverage efficient storage and indexing for DBMS that supports UUID (especially PostgreSQL and Mongo).

In Python code

If you were doing comparison betwen a user id. and a string (in unit tests for example), you should now cast the id. to string:

# Before
assert "d35d213e-f3d8-4f08-954a-7e0d1bea286f" == user.id

# Now
assert "d35d213e-f3d8-4f08-954a-7e0d1bea286f" == str(user.id)

If you were refering to user id. in your Pydantic models, the field should now be of UUID4 type instead of str:

from pydantic import BaseModel, UUID4

# Before
class Model(BaseModel):
    user_id: str

# After
class Model(BaseModel):
    user_id: UUID4

MongoDB

To avoid any issues, it's recommended to use the standard UUID representation when instantiating the MongoDB client:

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

This parameter controls how the UUID values will be encoded in the database. By default, it's set to pythonLegacy but new applications should consider setting this to standard for cross language compatibility. Read more about this.

In database

Id. were before stored as strings in the database. You should make a migration to convert string data to UUID data.

Danger

Scripts below are provided as guidelines. Please review them carefully, adapt them and check that they are working on a test database before applying them to production. BE CAREFUL. THEY CAN DESTROY YOUR DATA..

PostgreSQL

PostgreSQL supports UUID type. If not already, you should enable the uuid-ossp extension:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

To convert the existing id. string column, we can:

  1. Create a new column with UUID type.
  2. Fill it with the id. converted to UUID.
  3. Drop the original id. column.
  4. Make the new column a primary key and rename it.
ALTER TABLE "user" ADD uuid_id UUID;
UPDATE "user" SET uuid_id = uuid(id);
ALTER TABLE "user" DROP id;
ALTER TABLE "user" ADD PRIMARY KEY (uuid_id);
ALTER TABLE "user" RENAME COLUMN uuid_id TO id;

MySQL

MySQL doesn't support UUID type. We'll just convert the column to CHAR(36) type:

ALTER TABLE "user" MODIFY id CHAR(36);

MongoDB

Mongo shell

For MongoDB, we can use a forEach iterator to convert the id. for each document:

db.getCollection('users').find().forEach(function(user) {
  var uuid = UUID(user.id);
  db.getCollection('users').update({_id: user._id}, [{$set: {id: uuid}}]);
});
Python
import uuid

import motor.motor_asyncio


async def migrate_uuid():
    client = motor.motor_asyncio.AsyncIOMotorClient(
        DATABASE_URL, uuidRepresentation="standard"
    )
    db = client["database_name"]
    users = db["users"]

    async for user in users.find({}):
        await users.update_one(
            {"_id": user["_id"]},
            {"$set": {"id": uuid.UUID(user["id"])}},
        )

Splitted routers

You now have the responsibility to wire the routers. FastAPI Users doesn't give a bloated users router anymore.

Event handlers are also removed. You have to provide your "after-" logic as a parameter of the router generator.

Before

jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)

app = FastAPI()
fastapi_users = FastAPIUsers(
    user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])


@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
    print(f"User {user.id} has registered.")


@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
    print(f"User {user.id} has forgot their password. Reset token: {token}")

After

def on_after_register(user: UserDB, request: Request):
    print(f"User {user.id} has registered.")


def on_after_forgot_password(user: UserDB, token: str, request: Request):
    print(f"User {user.id} has forgot their password. Reset token: {token}")


jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)

app = FastAPI()
fastapi_users = FastAPIUsers(
    user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(
    fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
    fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
    fastapi_users.get_reset_password_router(
        SECRET, after_forgot_password=on_after_forgot_password
    ),
    prefix="/auth",
    tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])

Important things to notice:

  • FastAPIUsers takes two arguments less (reset_password_token_secret and reset_password_token_lifetime_seconds).
  • You have more flexibility to choose the prefix and tags of the routers.
  • The /login//logout are now your responsibility to include for each backend. The path will change (before /login/jwt, after /jwt/login).
  • If you don't care about some of those routers, you can discard them.