mirror of
https://github.com/fastapi-users/fastapi-users.git
synced 2025-08-15 19:30:47 +08:00
189 lines
5.6 KiB
Markdown
189 lines
5.6 KiB
Markdown
# 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:
|
|
|
|
```py
|
|
# 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`:
|
|
|
|
```py
|
|
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:
|
|
|
|
```py
|
|
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](https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient).
|
|
|
|
|
|
### 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:
|
|
|
|
```sql
|
|
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.
|
|
|
|
```sql
|
|
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:
|
|
|
|
```sql
|
|
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:
|
|
|
|
```js
|
|
db.getCollection('users').find().forEach(function(user) {
|
|
var uuid = UUID(user.id);
|
|
db.getCollection('users').update({_id: user._id}, [{$set: {id: uuid}}]);
|
|
});
|
|
```
|
|
|
|
##### Python
|
|
|
|
```py
|
|
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
|
|
|
|
```py
|
|
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
|
|
|
|
```py
|
|
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.
|