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:
To convert the existing id. string column, we can:
- Create a new column with UUID type.
- Fill it with the id. converted to UUID.
- Drop the original id. column.
- 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:
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
andreset_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.