Native model and generic ID (#971)

* Use a generic Protocol model for User instead of Pydantic

* Remove UserDB Pydantic schema

* Harmonize schema variable naming to avoid confusions

* Revamp OAuth account model management

* Revamp AccessToken DB strategy to adopt generic model approach

* Make ID a generic instead of forcing UUIDs

* Improve generic typing

* Improve Strategy typing

* Tweak base DB typing

* Don't set Pydantic schemas on FastAPIUsers class: pass it directly on router creation

* Add IntegerIdMixin and export related classes

* Start to revamp doc for V10

* Revamp OAuth documentation

* Fix code highlights

* Write the 9.x.x ➡️ 10.x.x migration doc

* Fix pyproject.toml
This commit is contained in:
François Voron
2022-05-05 14:51:19 +02:00
committed by GitHub
parent b7734fc8b0
commit 72aa68c462
124 changed files with 2144 additions and 2114 deletions

View File

@ -1,7 +1,7 @@
import contextlib
from app.db import get_async_session, get_user_db
from app.models import UserCreate
from app.schemas import UserCreate
from app.users import get_user_manager
from fastapi_users.manager import UserAlreadyExists

View File

@ -1,15 +1,17 @@
import motor.motor_asyncio
from fastapi_users.db import MongoDBUserDatabase
from .models import UserDB
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"]
collection = db["users"]
class User(BeanieBaseUser[PydanticObjectId]):
pass
async def get_user_db():
yield MongoDBUserDatabase(UserDB, collection)
yield BeanieUserDatabase(User)

View File

@ -0,0 +1,29 @@
import motor.motor_asyncio
from beanie import PydanticObjectId
from fastapi_users.db import BeanieBaseUser, BeanieUserDatabase
from fastapi_users_db_beanie.access_token import (
BeanieAccessTokenDatabase,
BeanieBaseAccessToken,
)
DATABASE_URL = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(
DATABASE_URL, uuidRepresentation="standard"
)
db = client["database_name"]
class User(BeanieBaseUser):
pass
class AccessToken(BeanieBaseAccessToken[PydanticObjectId]): # (1)!
pass
async def get_user_db():
yield BeanieUserDatabase(User)
async def get_access_token_db(): # (2)!
yield BeanieAccessTokenDatabase(AccessToken)

View File

@ -0,0 +1,24 @@
from typing import List
import motor.motor_asyncio
from beanie import PydanticObjectId
from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase
from pydantic import Field
DATABASE_URL = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(
DATABASE_URL, uuidRepresentation="standard"
)
db = client["database_name"]
class OAuthAccount(BaseOAuthAccount):
pass
class User(BeanieBaseUser[PydanticObjectId]):
oauth_accounts: List[OAuthAccount] = Field(default_factory=list)
async def get_user_db():
yield BeanieUserDatabase(User)

View File

@ -1,21 +0,0 @@
import motor.motor_asyncio
from fastapi_users.db import MongoDBUserDatabase
from fastapi_users_db_mongodb.access_token import MongoDBAccessTokenDatabase
from .models import AccessToken, UserDB
DATABASE_URL = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(
DATABASE_URL, uuidRepresentation="standard"
)
db = client["database_name"]
users_collection = db["users"]
access_tokens_collection = db["access_tokens"]
async def get_user_db():
yield MongoDBUserDatabase(UserDB, users_collection)
async def get_access_token_db():
yield MongoDBAccessTokenDatabase(AccessToken, access_tokens_collection)

View File

@ -1,24 +0,0 @@
import databases
import sqlalchemy
from fastapi_users.db import OrmarBaseUserModel, OrmarUserDatabase
from .models import UserDB
DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
database = databases.Database(DATABASE_URL)
class UserModel(OrmarBaseUserModel):
class Meta:
tablename = "users"
metadata = metadata
database = database
engine = sqlalchemy.create_engine(DATABASE_URL)
metadata.create_all(engine)
async def get_user_db():
yield OrmarUserDatabase(UserDB, UserModel)

View File

@ -1,18 +1,16 @@
from typing import AsyncGenerator
from fastapi import Depends
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import sessionmaker
from .models import UserDB
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base()
class UserTable(Base, SQLAlchemyBaseUserTable):
class User(SQLAlchemyBaseUserTableUUID, Base):
pass
@ -31,4 +29,4 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(UserDB, session, UserTable)
yield SQLAlchemyUserDatabase(session, User)

View File

@ -1,26 +1,24 @@
from typing import AsyncGenerator
from fastapi import Depends
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
from fastapi_users_db_sqlalchemy.access_token import (
SQLAlchemyAccessTokenDatabase,
SQLAlchemyBaseAccessTokenTable,
SQLAlchemyBaseAccessTokenTableUUID,
)
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import sessionmaker
from .models import AccessToken, UserDB
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base()
class UserTable(Base, SQLAlchemyBaseUserTable):
class User(SQLAlchemyBaseUserTableUUID, Base):
pass
class AccessTokenTable(SQLAlchemyBaseAccessTokenTable, Base):
class AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base): # (1)!
pass
@ -39,8 +37,10 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(UserDB, session, UserTable)
yield SQLAlchemyUserDatabase(session, User)
async def get_access_token_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyAccessTokenDatabase(AccessToken, session, AccessTokenTable)
async def get_access_token_db(
session: AsyncSession = Depends(get_async_session),
): # (2)!
yield SQLAlchemyAccessTokenDatabase(session, AccessToken)

View File

@ -1,29 +1,27 @@
from typing import AsyncGenerator
from typing import AsyncGenerator, List
from fastapi import Depends
from fastapi_users.db import (
SQLAlchemyBaseOAuthAccountTable,
SQLAlchemyBaseUserTable,
SQLAlchemyBaseOAuthAccountTableUUID,
SQLAlchemyBaseUserTableUUID,
SQLAlchemyUserDatabase,
)
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import relationship, sessionmaker
from .models import UserDB
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base()
class UserTable(Base, SQLAlchemyBaseUserTable):
oauth_accounts = relationship("OAuthAccountTable")
class OAuthAccountTable(SQLAlchemyBaseOAuthAccountTable, Base):
class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
pass
class User(SQLAlchemyBaseUserTableUUID, Base):
oauth_accounts: List[OAuthAccount] = relationship("OAuthAccount", lazy="joined")
engine = create_async_engine(DATABASE_URL)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
@ -39,4 +37,4 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(UserDB, session, UserTable, OAuthAccountTable)
yield SQLAlchemyUserDatabase(session, User, OAuthAccount)

View File

@ -1,14 +0,0 @@
from fastapi_users.db import TortoiseUserDatabase
from fastapi_users_db_tortoise.access_token import TortoiseAccessTokenDatabase
from .models import AccessToken, AccessTokenModel, UserDB, UserModel
DATABASE_URL = "sqlite://./test.db"
async def get_user_db():
yield TortoiseUserDatabase(UserDB, UserModel)
async def get_access_token_db():
yield TortoiseAccessTokenDatabase(AccessToken, AccessTokenModel)

View File

@ -1,38 +0,0 @@
from fastapi_users import models
from fastapi_users.authentication.strategy.db.models import BaseAccessToken
from fastapi_users.db import TortoiseBaseUserModel
from fastapi_users_db_tortoise.access_token import TortoiseBaseAccessTokenModel
from tortoise import fields
from tortoise.contrib.pydantic import PydanticModel
class User(models.BaseUser):
pass
class UserCreate(models.BaseUserCreate):
pass
class UserUpdate(models.BaseUserUpdate):
pass
class UserModel(TortoiseBaseUserModel):
pass
class UserDB(User, models.BaseUserDB, PydanticModel):
class Config:
orm_mode = True
orig_model = UserModel
class AccessTokenModel(TortoiseBaseAccessTokenModel):
user = fields.ForeignKeyField("models.UserModel", related_name="access_tokens")
class AccessToken(BaseAccessToken, PydanticModel):
class Config:
orm_mode = True
orig_model = AccessTokenModel

View File

@ -1,9 +0,0 @@
from fastapi_users.db import TortoiseUserDatabase
from .models import UserDB, UserModel
DATABASE_URL = "sqlite://./test.db"
async def get_user_db():
yield TortoiseUserDatabase(UserDB, UserModel)

View File

@ -1,25 +0,0 @@
from fastapi_users import models
from fastapi_users.db import TortoiseBaseUserModel
from tortoise.contrib.pydantic import PydanticModel
class User(models.BaseUser):
pass
class UserCreate(models.BaseUserCreate):
pass
class UserUpdate(models.BaseUserUpdate):
pass
class UserModel(TortoiseBaseUserModel):
pass
class UserDB(User, models.BaseUserDB, PydanticModel):
class Config:
orm_mode = True
orig_model = UserModel

View File

@ -1,9 +0,0 @@
from fastapi_users.db import TortoiseUserDatabase
from .models import OAuthAccount, UserDB, UserModel
DATABASE_URL = "sqlite://./test.db"
async def get_user_db():
yield TortoiseUserDatabase(UserDB, UserModel, OAuthAccount)

View File

@ -1,30 +0,0 @@
from fastapi_users import models
from fastapi_users.db import TortoiseBaseOAuthAccountModel, TortoiseBaseUserModel
from tortoise import fields
from tortoise.contrib.pydantic import PydanticModel
class User(models.BaseUser, models.BaseOAuthAccountMixin):
pass
class UserCreate(models.BaseUserCreate):
pass
class UserUpdate(models.BaseUserUpdate):
pass
class UserModel(TortoiseBaseUserModel):
pass
class UserDB(User, models.BaseUserDB, PydanticModel):
class Config:
orm_mode = True
orig_model = UserModel
class OAuthAccount(TortoiseBaseOAuthAccountModel):
user = fields.ForeignKeyField("models.UserModel", related_name="oauth_accounts")

View File

@ -1,29 +1,28 @@
import uuid
from typing import Optional
from fastapi import Depends, Request
from fastapi_users import BaseUserManager
from fastapi_users import BaseUserManager, UUIDIDMixin
from .db import get_user_db
from .models import UserCreate, UserDB
from .db import User, get_user_db
SECRET = "SECRET"
class UserManager(BaseUserManager[UserCreate, UserDB]):
user_db_model = UserDB
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
reset_password_token_secret = SECRET
verification_token_secret = SECRET
async def on_after_register(self, user: UserDB, request: Optional[Request] = None):
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: UserDB, token: str, request: Optional[Request] = None
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: UserDB, token: str, request: Optional[Request] = None
self, user: User, token: str, request: Optional[Request] = None
):
print(f"Verification requested for user {user.id}. Verification token: {token}")