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

# 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.

Back to top