Update docs for SQLAlchemy 2.0

This commit is contained in:
François Voron
2023-02-13 17:27:16 +01:00
parent 15c60525d4
commit 5c48283155
10 changed files with 57 additions and 39 deletions

View File

@@ -21,7 +21,7 @@ We are providing a base model with those fields for each database we are support
We'll expand from the basic SQLAlchemy configuration. We'll expand from the basic SQLAlchemy configuration.
```py hl_lines="5-8 21-22 43-46" ```py hl_lines="5-8 23-24 45-48"
--8<-- "docs/src/db_sqlalchemy_access_tokens.py" --8<-- "docs/src/db_sqlalchemy_access_tokens.py"
``` ```
@@ -37,8 +37,8 @@ We'll expand from the basic SQLAlchemy configuration.
```py ```py
class AccessToken(SQLAlchemyBaseAccessTokenTable[int], Base): class AccessToken(SQLAlchemyBaseAccessTokenTable[int], Base):
@declared_attr @declared_attr
def user_id(cls): def user_id(cls) -> Mapped[int]:
return Column(Integer, ForeignKey("user.id", ondelete="cascade"), nullable=False) return mapped_column(Integer, ForeignKey("user.id", ondelete="cascade"), nullable=False)
``` ```
Notice that `SQLAlchemyBaseAccessTokenTable` expects a generic type to define the actual type of ID you use. Notice that `SQLAlchemyBaseAccessTokenTable` expects a generic type to define the actual type of ID you use.

View File

@@ -20,7 +20,7 @@ For the sake of this tutorial from now on, we'll use a simple SQLite database.
As for any SQLAlchemy ORM model, we'll create a `User` model. As for any SQLAlchemy ORM model, we'll create a `User` model.
```py hl_lines="13-14" ```py hl_lines="15-16"
--8<-- "docs/src/db_sqlalchemy.py" --8<-- "docs/src/db_sqlalchemy.py"
``` ```
@@ -31,7 +31,7 @@ As you can see, **FastAPI Users** provides a base class that will include base f
```py ```py
class User(SQLAlchemyBaseUserTable[int], Base): class User(SQLAlchemyBaseUserTable[int], Base):
id = Column(Integer, primary_key=True) id: Mapped[int] = mapped_column(Integer, primary_key=True)
``` ```
Notice that `SQLAlchemyBaseUserTable` expects a generic type to define the actual type of ID you use. Notice that `SQLAlchemyBaseUserTable` expects a generic type to define the actual type of ID you use.
@@ -40,7 +40,7 @@ As you can see, **FastAPI Users** provides a base class that will include base f
We'll now create an utility function to create all the defined tables. We'll now create an utility function to create all the defined tables.
```py hl_lines="21-23" ```py hl_lines="23-25"
--8<-- "docs/src/db_sqlalchemy.py" --8<-- "docs/src/db_sqlalchemy.py"
``` ```
@@ -53,7 +53,7 @@ This function can be called, for example, during the initialization of your Fast
The database adapter of **FastAPI Users** makes the link between your database configuration and the users logic. It should be generated by a FastAPI dependency. The database adapter of **FastAPI Users** makes the link between your database configuration and the users logic. It should be generated by a FastAPI dependency.
```py hl_lines="26-33" ```py hl_lines="28-34"
--8<-- "docs/src/db_sqlalchemy.py" --8<-- "docs/src/db_sqlalchemy.py"
``` ```

View File

@@ -32,11 +32,11 @@ google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
You'll need to define the SQLAlchemy model for storing OAuth accounts. We provide a base one for this: You'll need to define the SQLAlchemy model for storing OAuth accounts. We provide a base one for this:
```py hl_lines="5 17-18 22 39-40" ```py hl_lines="5 19-20 24-26 43-44"
--8<-- "docs/src/db_sqlalchemy_oauth.py" --8<-- "docs/src/db_sqlalchemy_oauth.py"
``` ```
Notice that we also manually added a `relationship` on the `UserTable` so that SQLAlchemy can properly retrieve the OAuth accounts of the user. Notice that we also manually added a `relationship` on `User` so that SQLAlchemy can properly retrieve the OAuth accounts of the user.
Besides, when instantiating the database adapter, we need pass this SQLAlchemy model as third argument. Besides, when instantiating the database adapter, we need pass this SQLAlchemy model as third argument.
@@ -45,11 +45,11 @@ Besides, when instantiating the database adapter, we need pass this SQLAlchemy m
```py ```py
class OAuthAccount(SQLAlchemyBaseOAuthAccountTable[int], Base): class OAuthAccount(SQLAlchemyBaseOAuthAccountTable[int], Base):
id = Column(Integer, primary_key=True) id: Mapped[int] = mapped_column(Integer, primary_key=True)
@declared_attr @declared_attr
def user_id(cls): def user_id(cls) -> Mapped[int]:
return Column(Integer, ForeignKey("user.id", ondelete="cascade"), nullable=False) return mapped_column(Integer, ForeignKey("user.id", ondelete="cascade"), nullable=False)
``` ```

View File

@@ -2,11 +2,14 @@ from typing import AsyncGenerator
from fastapi import Depends from fastapi import Depends
from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeMeta, declarative_base, sessionmaker from sqlalchemy.orm import DeclarativeBase
DATABASE_URL = "sqlite+aiosqlite:///./test.db" DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base()
class Base(DeclarativeBase):
pass
class User(SQLAlchemyBaseUserTableUUID, Base): class User(SQLAlchemyBaseUserTableUUID, Base):
@@ -14,7 +17,7 @@ class User(SQLAlchemyBaseUserTableUUID, Base):
engine = create_async_engine(DATABASE_URL) engine = create_async_engine(DATABASE_URL)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async_session_maker = async_sessionmaker(engine, expire_on_commit=False)
async def create_db_and_tables(): async def create_db_and_tables():

View File

@@ -6,11 +6,14 @@ from fastapi_users_db_sqlalchemy.access_token import (
SQLAlchemyAccessTokenDatabase, SQLAlchemyAccessTokenDatabase,
SQLAlchemyBaseAccessTokenTableUUID, SQLAlchemyBaseAccessTokenTableUUID,
) )
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeMeta, declarative_base, sessionmaker from sqlalchemy.orm import DeclarativeBase
DATABASE_URL = "sqlite+aiosqlite:///./test.db" DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base()
class Base(DeclarativeBase):
pass
class User(SQLAlchemyBaseUserTableUUID, Base): class User(SQLAlchemyBaseUserTableUUID, Base):
@@ -22,7 +25,7 @@ class AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base): # (1)!
engine = create_async_engine(DATABASE_URL) engine = create_async_engine(DATABASE_URL)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async_session_maker = async_sessionmaker(engine, expire_on_commit=False)
async def create_db_and_tables(): async def create_db_and_tables():

View File

@@ -6,11 +6,14 @@ from fastapi_users.db import (
SQLAlchemyBaseUserTableUUID, SQLAlchemyBaseUserTableUUID,
SQLAlchemyUserDatabase, SQLAlchemyUserDatabase,
) )
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeMeta, declarative_base, relationship, sessionmaker from sqlalchemy.orm import DeclarativeBase, Mapped, relationship
DATABASE_URL = "sqlite+aiosqlite:///./test.db" DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base()
class Base(DeclarativeBase):
pass
class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base): class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
@@ -18,11 +21,13 @@ class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
class User(SQLAlchemyBaseUserTableUUID, Base): class User(SQLAlchemyBaseUserTableUUID, Base):
oauth_accounts: List[OAuthAccount] = relationship("OAuthAccount", lazy="joined") oauth_accounts: Mapped[List[OAuthAccount]] = relationship(
"OAuthAccount", lazy="joined"
)
engine = create_async_engine(DATABASE_URL) engine = create_async_engine(DATABASE_URL)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async_session_maker = async_sessionmaker(engine, expire_on_commit=False)
async def create_db_and_tables(): async def create_db_and_tables():

View File

@@ -6,11 +6,14 @@ from fastapi_users.db import (
SQLAlchemyBaseUserTableUUID, SQLAlchemyBaseUserTableUUID,
SQLAlchemyUserDatabase, SQLAlchemyUserDatabase,
) )
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeMeta, declarative_base, relationship, sessionmaker from sqlalchemy.orm import DeclarativeBase, Mapped, relationship
DATABASE_URL = "sqlite+aiosqlite:///./test.db" DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base()
class Base(DeclarativeBase):
pass
class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base): class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
@@ -18,11 +21,13 @@ class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
class User(SQLAlchemyBaseUserTableUUID, Base): class User(SQLAlchemyBaseUserTableUUID, Base):
oauth_accounts: List[OAuthAccount] = relationship("OAuthAccount", lazy="joined") oauth_accounts: Mapped[List[OAuthAccount]] = relationship(
"OAuthAccount", lazy="joined"
)
engine = create_async_engine(DATABASE_URL) engine = create_async_engine(DATABASE_URL)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async_session_maker = async_sessionmaker(engine, expire_on_commit=False)
async def create_db_and_tables(): async def create_db_and_tables():

View File

@@ -2,11 +2,14 @@ from typing import AsyncGenerator
from fastapi import Depends from fastapi import Depends
from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeMeta, declarative_base, sessionmaker from sqlalchemy.orm import DeclarativeBase
DATABASE_URL = "sqlite+aiosqlite:///./test.db" DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base()
class Base(DeclarativeBase):
pass
class User(SQLAlchemyBaseUserTableUUID, Base): class User(SQLAlchemyBaseUserTableUUID, Base):
@@ -14,7 +17,7 @@ class User(SQLAlchemyBaseUserTableUUID, Base):
engine = create_async_engine(DATABASE_URL) engine = create_async_engine(DATABASE_URL)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async_session_maker = async_sessionmaker(engine, expire_on_commit=False)
async def create_db_and_tables(): async def create_db_and_tables():

View File

@@ -156,7 +156,7 @@ class BaseUserManager(Generic[models.UP, models.ID]):
refresh_token: Optional[str] = None, refresh_token: Optional[str] = None,
request: Optional[Request] = None, request: Optional[Request] = None,
*, *,
associate_by_email: bool = False associate_by_email: bool = False,
) -> models.UOAP: ) -> models.UOAP:
""" """
Handle the callback after a successful OAuth authentication. Handle the callback after a successful OAuth authentication.

View File

@@ -63,9 +63,9 @@ def forgot_password_token(user_manager: UserManagerMock[UserModel]):
@pytest.fixture @pytest.fixture
def create_oauth2_password_request_form() -> Callable[ def create_oauth2_password_request_form() -> (
[str, str], OAuth2PasswordRequestForm Callable[[str, str], OAuth2PasswordRequestForm]
]: ):
def _create_oauth2_password_request_form(username, password): def _create_oauth2_password_request_form(username, password):
return OAuth2PasswordRequestForm(username=username, password=password, scope="") return OAuth2PasswordRequestForm(username=username, password=password, scope="")
@@ -252,7 +252,6 @@ class TestOAuthAssociateCallback:
user_manager_oauth: UserManagerMock[UserOAuthModel], user_manager_oauth: UserManagerMock[UserOAuthModel],
superuser_oauth: UserOAuthModel, superuser_oauth: UserOAuthModel,
): ):
user = await user_manager_oauth.oauth_associate_callback( user = await user_manager_oauth.oauth_associate_callback(
superuser_oauth, superuser_oauth,
"service1", "service1",