diff --git a/README.md b/README.md index 9778c10d..7bf5d796 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,8 @@ Add quickly a registration and authentication system to your [FastAPI](https://f * [X] Dependency callables to inject current user in route * [X] Pluggable password validation * [X] Customizable database backend - * [X] [SQLAlchemy ORM async](https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html) backend included - * [X] MongoDB async backend included thanks to [mongodb/motor](https://github.com/mongodb/motor) - * [X] [Tortoise ORM](https://tortoise-orm.readthedocs.io/en/latest/) backend included - * [X] [ormar](https://collerek.github.io/ormar/) backend included + * [X] [SQLAlchemy ORM async](https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html) included + * [X] [MongoDB with Beanie ODM](https://github.com/roman-right/beanie/) included * [X] Multiple customizable authentication backends * [X] Transports: Authorization header, Cookie * [X] Strategies: JWT, Database, Redis diff --git a/docs/configuration/authentication/strategies/database.md b/docs/configuration/authentication/strategies/database.md index 58bd5972..f35e5f27 100644 --- a/docs/configuration/authentication/strategies/database.md +++ b/docs/configuration/authentication/strategies/database.md @@ -4,68 +4,72 @@ The most natural way for storing tokens is of course the very same database you' ## Configuration -The configuration of this strategy is a bit more complex than the others as it requires you to configure models and a database adapter, [exactly like we did for users](../../overview.md#database-adapters). +The configuration of this strategy is a bit more complex than the others as it requires you to configure models and a database adapter, [exactly like we did for users](../../overview.md#user-model-and-database-adapters). -### Model +### Database adapters -You should define an `AccessToken` Pydantic model inheriting from `BaseAccessToken`. - -```py -from fastapi_users.authentication.strategy.db import BaseAccessToken - - -class AccessToken(BaseAccessToken): - pass -``` - -It is structured like this: +An access token will be structured like this in your database: * `token` (`str`) – Unique identifier of the token. It's generated automatically upon login by the strategy. -* `user_id` (`UUID4`) – User id. of the user associated to this token. +* `user_id` (`ID`) – User id. of the user associated to this token. * `created_at` (`datetime`) – Date and time of creation of the token. It's used to determine if the token is expired or not. -### Database adapter +We are providing a base model with those fields for each database we are supporting. -=== "SQLAlchemy" +#### SQLAlchemy - ```py hl_lines="5-8 13 23-24 45-46" - --8<-- "docs/src/db_sqlalchemy_access_tokens.py" +We'll expand from the basic SQLAlchemy configuration. + +```py hl_lines="5 19-20 41-42" +--8<-- "docs/src/db_sqlalchemy_access_tokens.py" +``` + +1. We define an `AccessToken` ORM model inheriting from `SQLAlchemyBaseAccessTokenTableUUID`. + +2. We define a dependency to instantiate the `SQLAlchemyAccessTokenDatabase` class. Just like the user database adapter, it expects a fresh SQLAlchemy session and the `AccessToken` model class we defined above. + +!!! tip "`user_id` foreign key is defined as UUID" + By default, we use UUID as a primary key ID for your user, so we follow the same convention to define the foreign key pointing to the user. + + If you want to use another type, like an auto-incremented integer, you can use `SQLAlchemyBaseAccessTokenTable` as base class and define your own `user_id` column. + + ```py + class AccessToken(SQLAlchemyBaseAccessTokenTable[int], Base): + @declared_attr + def user_id(cls): + return Column(Integer, ForeignKey("user.id", ondelete="cascade"), nullable=False) ``` -=== "Tortoise ORM" + Notice that `SQLAlchemyBaseAccessTokenTable` expects a generic type to define the actual type of ID you use. - With Tortoise ORM, you need to define a proper Tortoise model for `AccessToken` and manually specify the user foreign key. Besides, you need to modify the Pydantic model a bit so that it works well with this Tortoise model. +#### Beanie - === "model.py" - ```py hl_lines="2 4 31-38" - --8<-- "docs/src/db_tortoise_access_tokens_model.py" - ``` +We'll expand from the basic Beanie configuration. - === "adapter.py" - ```py hl_lines="2 4 13-14" - --8<-- "docs/src/db_tortoise_access_tokens_adapter.py" - ``` +```py hl_lines="4 17-18 25-26" +--8<-- "docs/src/db_beanie_access_tokens.py" +``` -=== "MongoDB" +1. We define an `AccessToken` ODM model inheriting from `BeanieBaseAccessToken`. Notice that we set a generic type to define the type of the `user_id` reference. By default, it's a standard MongoDB ObjectID. - ```py hl_lines="3 5 13 20-21" - --8<-- "docs/src/db_mongodb_access_tokens.py" - ``` +2. We define a dependency to instantiate the `BeanieAccessTokenDatabase` class. Just like the user database adapter, it expects the `AccessToken` model class we defined above. ### Strategy ```py +import uuid + from fastapi import Depends from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy -from .models import AccessToken, UserCreate, UserDB +from .db import AccessToken, User def get_database_strategy( access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db), -) -> DatabaseStrategy[UserCreate, UserDB, AccessToken]: +) -> DatabaseStrategy: return DatabaseStrategy(access_token_db, lifetime_seconds=3600) ``` diff --git a/docs/configuration/databases/beanie.md b/docs/configuration/databases/beanie.md new file mode 100644 index 00000000..38da029a --- /dev/null +++ b/docs/configuration/databases/beanie.md @@ -0,0 +1,73 @@ +# Beanie + +**FastAPI Users** provides the necessary tools to work with MongoDB databases using the [Beanie ODM](https://github.com/roman-right/beanie). + +## Setup database connection and collection + +The first thing to do is to create a MongoDB connection using [mongodb/motor](https://github.com/mongodb/motor) (automatically installed with Beanie). + +```py hl_lines="5-9" +--8<-- "docs/src/db_beanie.py" +``` + +You can choose any name for the database. + +## Create the User model + +As for any Beanie ODM model, we'll create a `User` model. + +```py hl_lines="12-13" +--8<-- "docs/src/db_beanie.py" +``` + +As you can see, **FastAPI Users** provides a base class that will include base fields for our `User` table. You can of course add you own fields there to fit to your needs! + +!!! tip "Document ID is a MongoDB ObjectID" + Beanie [automatically manages document ID](https://roman-right.github.io/beanie/tutorial/defining-a-document/#id) by encoding/decoding MongoDB ObjectID. + + If you want to use another type, like UUID, you can override the `id` field: + + ```py + import uuid + + from pydantic import Field + + + class User(BeanieBaseUser[uuid.UUID]): + id: uuid.UUID = Field(default_factory=uuid.uuid4) + ``` + + Notice that `BeanieBaseUser` expects a generic type to define the actual type of ID you use. + +!!! info + The base class is configured to automatically create a [unique index](https://roman-right.github.io/beanie/tutorial/defining-a-document/#indexes) on `id` and `email`. + +## Create the database adapter + +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="16-17" +--8<-- "docs/src/db_beanie.py" +``` + +Notice that we pass a reference to the `User` model we defined above. + +## Initialize Beanie + +When initializing your FastAPI app, it's important that you [**initialize Beanie**](https://roman-right.github.io/beanie/tutorial/initialization/) so it can discover your models. We can achieve this using a startup event handler on the FastAPI app: + +```py + +@app.on_event("startup") +async def on_startup(): + await init_beanie( + database=db, # (1)! + document_models=[ + User, # (2)! + ], + ) +``` + +1. This is the `db` Motor database instance we defined above. + +2. This is the Beanie `User` model we defined above. Don't forget to also add your very own models! diff --git a/docs/configuration/databases/mongodb.md b/docs/configuration/databases/mongodb.md deleted file mode 100644 index d4bfe4a1..00000000 --- a/docs/configuration/databases/mongodb.md +++ /dev/null @@ -1,32 +0,0 @@ -# MongoDB - -**FastAPI Users** provides the necessary tools to work with MongoDB databases thanks to [mongodb/motor](https://github.com/mongodb/motor) package for full async support. - -## Setup database connection and collection - -Let's create a MongoDB connection and instantiate a collection. - -```py hl_lines="6 7 8 9 10 11" ---8<-- "docs/src/db_mongodb.py" -``` - -You can choose any name for the database and the collection. - -!!! warning - You may have noticed the `uuidRepresentation` parameter. It 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). - -## Create the database adapter - -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="14 15" ---8<-- "docs/src/db_mongodb.py" -``` - -Notice that we pass a reference to your [`UserDB` model](../models.md). - -!!! info - The database adapter will automatically create a [unique index](https://docs.mongodb.com/manual/core/index-unique/) on `id` and `email`. - -!!! warning - **FastAPI Users** will use its defined [`id` UUID](../models.md) as unique identifier for the user, rather than the builtin MongoDB `_id`. diff --git a/docs/configuration/databases/ormar.md b/docs/configuration/databases/ormar.md deleted file mode 100644 index 4538395c..00000000 --- a/docs/configuration/databases/ormar.md +++ /dev/null @@ -1,49 +0,0 @@ -# Ormar - -**FastAPI Users** provides the necessary tools to work with ormar. - -## Installation - -Install the database driver that corresponds to your DBMS: - -```sh -pip install asyncpg psycopg2 -``` - -```sh -pip install aiomysql pymysql -``` - -```sh -pip install aiosqlite -``` - -For the sake of this tutorial from now on, we'll use a simple SQLite database. - -## Setup User table - -Let's declare our User ORM model. - -```py hl_lines="12-16" ---8<-- "docs/src/db_ormar.py" -``` - -As you can see, **FastAPI Users** provides an abstract model that will -include base fields for our User table. You can of course add you own fields -there to fit to your needs! - -## Create the database adapter - -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="23-24" ---8<-- "docs/src/db_ormar.py" -``` - -Notice that we pass a reference to your [`UserDB` model](../models.md). - -!!! warning - In production, it's strongly recommended to setup a migration system to - update your SQL schemas. See - [Alembic](https://alembic.sqlalchemy.org/en/latest/). diff --git a/docs/configuration/databases/sqlalchemy.md b/docs/configuration/databases/sqlalchemy.md index ccfc6d36..5495ae1a 100644 --- a/docs/configuration/databases/sqlalchemy.md +++ b/docs/configuration/databases/sqlalchemy.md @@ -2,9 +2,6 @@ **FastAPI Users** provides the necessary tools to work with SQL databases thanks to [SQLAlchemy ORM with asyncio](https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html). -!!! warning - The previous adapter using `encode/databases` is now deprecated but can still be installed using `fastapi-users[sqlalchemy]`. - ## Asynchronous driver To work with your DBMS, you'll need to install the corresponding asyncio driver. The common choices are: @@ -14,21 +11,31 @@ To work with your DBMS, you'll need to install the corresponding asyncio driver. For the sake of this tutorial from now on, we'll use a simple SQLite databse. -## Setup User table +## Create the User model -Let's declare our SQLAlchemy `User` table. +As for any SQLAlchemy ORM model, we'll create a `User` model. -```py hl_lines="15 16" +```py hl_lines="14-15" --8<-- "docs/src/db_sqlalchemy.py" ``` -As you can see, **FastAPI Users** provides a mixin that will include base fields for our `User` table. You can of course add you own fields there to fit to your needs! +As you can see, **FastAPI Users** provides a base class that will include base fields for our `User` table. You can of course add you own fields there to fit to your needs! + +!!! tip "Primary key is defined as UUID" + By default, we use UUID as a primary key ID for your user. If you want to use another type, like an auto-incremented integer, you can use `SQLAlchemyBaseUserTable` as base class and define your own `id` column. + + ```py + class User(SQLAlchemyBaseUserTable[int], Base): + id = Column(Integer, primary_key=True) + ``` + + Notice that `SQLAlchemyBaseUserTable` expects a generic type to define the actual type of ID you use. ## Implement a function to create the tables We'll now create an utility function to create all the defined tables. -```py hl_lines="23-25" +```py hl_lines="22-24" --8<-- "docs/src/db_sqlalchemy.py" ``` @@ -41,14 +48,13 @@ 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. -```py hl_lines="28-34" +```py hl_lines="27-33" --8<-- "docs/src/db_sqlalchemy.py" ``` Notice that we define first a `get_async_session` dependency returning us a fresh SQLAlchemy session to interact with the database. -It's then used inside the `get_user_db` dependency to generate our adapter. Notice that we pass it three things: +It's then used inside the `get_user_db` dependency to generate our adapter. Notice that we pass it two things: -* A reference to your [`UserDB` model](../models.md). * The `session` instance we just injected. -* The `UserTable` variable, which is the actual SQLAlchemy model. +* The `User` class, which is the actual SQLAlchemy model. diff --git a/docs/configuration/databases/tortoise.md b/docs/configuration/databases/tortoise.md deleted file mode 100644 index ef318ca6..00000000 --- a/docs/configuration/databases/tortoise.md +++ /dev/null @@ -1,69 +0,0 @@ -# Tortoise ORM - -**FastAPI Users** provides the necessary tools to work with Tortoise ORM. - -## Installation - -Install the database driver that corresponds to your DBMS: - -```sh -pip install asyncpg -``` - -```sh -pip install aiomysql -``` - -```sh -pip install aiosqlite -``` - -For the sake of this tutorial from now on, we'll use a simple SQLite databse. - -## Setup User table and model - -Let's declare our User ORM model. - -```py hl_lines="18 19" ---8<-- "docs/src/db_tortoise_model.py" -``` - -As you can see, **FastAPI Users** provides an abstract model that will include base fields for our User table. You can of course add you own fields there to fit to your needs! - -In order to make the Pydantic model and the Tortoise ORM model working well together, you'll have to add a mixin and some configuration options to your `UserDB` model. Tortoise ORM provides [utilities to ease the integration with Pydantic](https://tortoise-orm.readthedocs.io/en/latest/contrib/pydantic.html) and we'll use them here. - -```py hl_lines="22 23 24 25 26" ---8<-- "docs/src/db_tortoise_model.py" -``` - -The `PydanticModel` mixin adds methods used internally by Tortoise ORM to the Pydantic model so that it can easily transform it back to an ORM model. It expects then that you provide the property `orig_model` which should point to the **User ORM model we defined just above**. - -## Create the database adapter - -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="8 9" ---8<-- "docs/src/db_tortoise_adapter.py" -``` - -Notice that we pass a reference to your [`UserDB` model](../models.md). - -## Register Tortoise - -For using Tortoise ORM we must register our models and database. - -Tortoise ORM supports integration with FastAPI out-of-the-box. It will automatically bind startup and shutdown events. - -```py -from tortoise.contrib.fastapi import register_tortoise - -register_tortoise( - app, - db_url=DATABASE_URL, - modules={"models": ["models"]}, - generate_schemas=True, -) -``` - -!!! warning - In production, it's strongly recommended to setup a migration system to update your SQL schemas. See [https://tortoise-orm.readthedocs.io/en/latest/migration.html](https://tortoise-orm.readthedocs.io/en/latest/migration.html). diff --git a/docs/configuration/full-example.md b/docs/configuration/full-example.md index a5cb56b6..133f5cb7 100644 --- a/docs/configuration/full-example.md +++ b/docs/configuration/full-example.md @@ -34,10 +34,10 @@ Here is a full working example with JWT authentication to help get you started. --8<-- "examples/sqlalchemy/app/db.py" ``` -=== "app/models.py" +=== "app/schemas.py" ```py - --8<-- "examples/sqlalchemy/app/models.py" + --8<-- "examples/sqlalchemy/app/schemas.py" ``` === "app/users.py" @@ -46,84 +46,44 @@ Here is a full working example with JWT authentication to help get you started. --8<-- "examples/sqlalchemy/app/users.py" ``` -## MongoDB +## Beanie -[Open :material-open-in-new:](https://github.com/fastapi-users/fastapi-users/tree/master/examples/mongodb) +[Open :material-open-in-new:](https://github.com/fastapi-users/fastapi-users/tree/master/examples/beanie) === "requirements.txt" ``` - --8<-- "examples/mongodb/requirements.txt" + --8<-- "examples/beanie/requirements.txt" ``` === "main.py" ```py - --8<-- "examples/mongodb/main.py" + --8<-- "examples/beanie/main.py" ``` === "app/app.py" ```py - --8<-- "examples/mongodb/app/app.py" + --8<-- "examples/beanie/app/app.py" ``` === "app/db.py" ```py - --8<-- "examples/mongodb/app/db.py" + --8<-- "examples/beanie/app/db.py" ``` -=== "app/models.py" +=== "app/schemas.py" ```py - --8<-- "examples/mongodb/app/models.py" + --8<-- "examples/beanie/app/schemas.py" ``` === "app/users.py" ```py - --8<-- "examples/mongodb/app/users.py" - ``` - -## Tortoise ORM - -[Open :material-open-in-new:](https://github.com/fastapi-users/fastapi-users/tree/master/examples/tortoise) - -=== "requirements.txt" - - ``` - --8<-- "examples/tortoise/requirements.txt" - ``` - -=== "main.py" - - ```py - --8<-- "examples/tortoise/main.py" - ``` - -=== "app/app.py" - - ```py - --8<-- "examples/tortoise/app/app.py" - ``` - -=== "app/db.py" - - ```py - --8<-- "examples/tortoise/app/db.py" - ``` - -=== "app/models.py" - - ```py - --8<-- "examples/tortoise/app/models.py" - ``` - -=== "app/users.py" - - ```py - --8<-- "examples/tortoise/app/users.py" + --8<-- "examples/beanie/app/users.py" ``` ## What now? diff --git a/docs/configuration/models.md b/docs/configuration/models.md deleted file mode 100644 index 9cfdea6c..00000000 --- a/docs/configuration/models.md +++ /dev/null @@ -1,69 +0,0 @@ -# Models - -**FastAPI Users** defines a minimal User model for authentication purposes. It is structured like this: - -* `id` (`UUID4`) – Unique identifier of the user. Defaults to a **UUID4**. -* `email` (`str`) – Email of the user. Validated by [`email-validator`](https://github.com/JoshData/python-email-validator). -* `is_active` (`bool`) – Whether or not the user is active. If not, login and forgot password requests will be denied. Defaults to `True`. -* `is_verified` (`bool`) – Whether or not the user is verified. Optional but helpful with the [`verify` router](./routers/verify.md) logic. Defaults to `False`. -* `is_superuser` (`bool`) – Whether or not the user is a superuser. Useful to implement administration logic. Defaults to `False`. - -## Define your models - -There are four Pydantic models variations provided as mixins: - -* `BaseUser`, which provides the basic fields and validation ; -* `BaseCreateUser`, dedicated to user registration, which consists of compulsory `email` and `password` fields ; -* `BaseUpdateUser`, dedicated to user profile update, which adds an optional `password` field ; -* `BaseUserDB`, which is a representation of the user in database, adding a `hashed_password` field. - -You should define each of those variations, inheriting from each mixin: - -```py -from fastapi_users import models - - -class User(models.BaseUser): - pass - - -class UserCreate(models.BaseUserCreate): - pass - - -class UserUpdate(models.BaseUserUpdate): - pass - - -class UserDB(User, models.BaseUserDB): - pass -``` - -### Adding your own fields - -You can of course add your own properties there to fit to your needs. In the example below, we add a required string property, `first_name`, and an optional date property, `birthdate`. - -```py -import datetime - -from fastapi_users import models - - -class User(models.BaseUser): - first_name: str - birthdate: Optional[datetime.date] - - -class UserCreate(models.BaseUserCreate): - first_name: str - birthdate: Optional[datetime.date] - - -class UserUpdate(models.BaseUserUpdate): - first_name: Optional[str] - birthdate: Optional[datetime.date] - - -class UserDB(User, models.BaseUserDB): - pass -``` diff --git a/docs/configuration/overview.md b/docs/configuration/overview.md index 44b1a611..a14392e6 100644 --- a/docs/configuration/overview.md +++ b/docs/configuration/overview.md @@ -4,28 +4,23 @@ The schema below shows you how the library is structured and how each part fit t ```mermaid -flowchart LR +flowchart TB FASTAPI_USERS{FastAPIUsers} USER_MANAGER{UserManager} + USER_MODEL{User model} DATABASE_DEPENDENCY[[get_user_db]] USER_MANAGER_DEPENDENCY[[get_user_manager]] CURRENT_USER[[current_user]] - subgraph MODELS[Models] - direction RL + subgraph SCHEMAS[Schemas] USER[User] USER_CREATE[UserCreate] USER_UPDATE[UserUpdate] - USER_DB[UserDB] end subgraph DATABASE[Database adapters] - direction RL SQLALCHEMY[SQLAlchemy] - MONGODB[MongoDB] - TORTOISE[Tortoise ORM] - ORMAR[Ormar] + BEANIE[Beanie] end subgraph ROUTERS[Routers] - direction RL AUTH[[get_auth_router]] OAUTH[[get_oauth_router]] REGISTER[[get_register_router]] @@ -34,24 +29,21 @@ flowchart LR USERS[[get_users_router]] end subgraph AUTH_BACKENDS[Authentication] - direction RL subgraph TRANSPORTS[Transports] - direction RL COOKIE[CookieTransport] BEARER[BearerTransport] end subgraph STRATEGIES[Strategies] - direction RL + DB[DatabaseStrategy] JWT[JWTStrategy] + REDIS[RedisStrategy] end AUTH_BACKEND{AuthenticationBackend} end DATABASE --> DATABASE_DEPENDENCY + USER_MODEL --> DATABASE_DEPENDENCY DATABASE_DEPENDENCY --> USER_MANAGER - MODELS --> USER_MANAGER - MODELS --> FASTAPI_USERS - USER_MANAGER --> USER_MANAGER_DEPENDENCY USER_MANAGER_DEPENDENCY --> FASTAPI_USERS @@ -60,30 +52,21 @@ flowchart LR TRANSPORTS --> AUTH_BACKEND STRATEGIES --> AUTH_BACKEND - AUTH_BACKEND --> AUTH - AUTH_BACKEND --> OAUTH + AUTH_BACKEND --> ROUTERS AUTH_BACKEND --> FASTAPI_USERS FASTAPI_USERS --> CURRENT_USER + + SCHEMAS --> ROUTERS ``` -## Models +## User model and database adapters -Pydantic models representing the data structure of a user. Base classes are provided with the required fields to make authentication work. You should sub-class each of them and add your own fields there. - -➡️ [Configure the models](./models.md) - -## Database adapters - -FastAPI Users is compatible with various databases and ORM. To build the interface between those database tools and the library, we provide database adapters classes that you need to instantiate and configure. +FastAPI Users is compatible with various **databases and ORM**. To build the interface between those database tools and the library, we provide database adapters classes that you need to instantiate and configure. ➡️ [I'm using SQLAlchemy](databases/sqlalchemy.md) -➡️ [I'm using MongoDB](databases/mongodb.md) - -➡️ [I'm using Tortoise ORM](databases/tortoise.md) - -➡️ [I'm using ormar](databases/ormar.md) +➡️ [I'm using Beanie](databases/beanie.md) ## Authentication backends @@ -101,6 +84,12 @@ This `UserManager` object should be provided through a FastAPI dependency, `get_ ➡️ [Configure `UserManager`](./user-manager.md) +## Schemas + +FastAPI is heavily using [Pydantic models](https://pydantic-docs.helpmanual.io/) to validate request payloads and serialize responses. **FastAPI Users** is no exception and will expect you to provide Pydantic schemas representing a user when it's read, created and updated. + +➡️ [Configure schemas](./schemas.md) + ## `FastAPIUsers` and routers Finally, `FastAPIUsers` object is the main class from which you'll be able to generate routers for classic routes like registration or login, but also get the `current_user` dependency factory to inject the authenticated user in your own routes. diff --git a/docs/configuration/routers/auth.md b/docs/configuration/routers/auth.md index c15616ce..fb9828b3 100644 --- a/docs/configuration/routers/auth.md +++ b/docs/configuration/routers/auth.md @@ -7,16 +7,16 @@ Check the [routes usage](../../usage/routes.md) to learn how to use them. ## Setup ```py +import uuid + from fastapi import FastAPI from fastapi_users import FastAPIUsers -fastapi_users = FastAPIUsers( +from .db import User + +fastapi_users = FastAPIUsers[User, uuid.UUID]( get_user_manager, [auth_backend], - User, - UserCreate, - UserUpdate, - UserDB, ) app = FastAPI() diff --git a/docs/configuration/routers/index.md b/docs/configuration/routers/index.md index c1799992..7b0aecd9 100644 --- a/docs/configuration/routers/index.md +++ b/docs/configuration/routers/index.md @@ -4,29 +4,33 @@ We're almost there! The last step is to configure the `FastAPIUsers` object that ## Configure `FastAPIUsers` -Configure `FastAPIUsers` object with all the elements we defined before. More precisely: +Configure `FastAPIUsers` object with the elements we defined before. More precisely: * `get_user_manager`: Dependency callable getter to inject the user manager class instance. See [UserManager](../user-manager.md). * `auth_backends`: List of authentication backends. See [Authentication](../authentication/index.md). -* `user_model`: Pydantic model of a user. -* `user_create_model`: Pydantic model for creating a user. -* `user_update_model`: Pydantic model for updating a user. -* `user_db_model`: Pydantic model of a DB representation of a user. ```py +import uuid + from fastapi_users import FastAPIUsers -fastapi_users = FastAPIUsers( +from .db import User + +fastapi_users = FastAPIUsers[User, uuid.UUID]( get_user_manager, [auth_backend], - User, - UserCreate, - UserUpdate, - UserDB, ) ``` +!!! note "Typing: User and ID generic types are expected" + You can see that we define two generic types when instantiating: + + * `User`, which is the user model we defined in the database part + * The ID, which should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID. + + It'll help you to have **good type-checking and auto-completion**. + ## Available routers This helper class will let you generate useful routers to setup the authentication system. Each of them is **optional**, so you can pick only the one that you are interested in! Here are the routers provided: diff --git a/docs/configuration/routers/register.md b/docs/configuration/routers/register.md index 8d243bfe..fff34b8a 100644 --- a/docs/configuration/routers/register.md +++ b/docs/configuration/routers/register.md @@ -7,23 +7,22 @@ Check the [routes usage](../../usage/routes.md) to learn how to use them. ## Setup ```py +import uuid + from fastapi import FastAPI from fastapi_users import FastAPIUsers -SECRET = "SECRET" +from .db import User +from .schemas import UserCreate, UserRead -fastapi_users = FastAPIUsers( +fastapi_users = FastAPIUsers[User, uuid.UUID]( get_user_manager, [auth_backend], - User, - UserCreate, - UserUpdate, - UserDB, ) app = FastAPI() app.include_router( - fastapi_users.get_register_router(), + fastapi_users.get_register_router(UserRead, UserCreate), prefix="/auth", tags=["auth"], ) diff --git a/docs/configuration/routers/reset.md b/docs/configuration/routers/reset.md index d91a4882..ac99c5d7 100644 --- a/docs/configuration/routers/reset.md +++ b/docs/configuration/routers/reset.md @@ -7,18 +7,16 @@ Check the [routes usage](../../usage/routes.md) to learn how to use them. ## Setup ```py +import uuid + from fastapi import FastAPI from fastapi_users import FastAPIUsers -SECRET = "SECRET" +from .db import User -fastapi_users = FastAPIUsers( +fastapi_users = FastAPIUsers[User, uuid.UUID]( get_user_manager, [auth_backend], - User, - UserCreate, - UserUpdate, - UserDB, ) app = FastAPI() diff --git a/docs/configuration/routers/users.md b/docs/configuration/routers/users.md index 61edc4ef..d055138b 100644 --- a/docs/configuration/routers/users.md +++ b/docs/configuration/routers/users.md @@ -5,23 +5,22 @@ This router provides routes to manage users. Check the [routes usage](../../usag ## Setup ```py +import uuid + from fastapi import FastAPI from fastapi_users import FastAPIUsers -SECRET = "SECRET" +from .db import User +from .schemas import UserRead, UserUpdate -fastapi_users = FastAPIUsers( +fastapi_users = FastAPIUsers[User, uuid.UUID]( get_user_manager, [auth_backend], - User, - UserCreate, - UserUpdate, - UserDB, ) app = FastAPI() app.include_router( - fastapi_users.get_users_router(), + fastapi_users.get_users_router(UserRead, UserUpdate), prefix="/users", tags=["users"], ) @@ -33,7 +32,7 @@ You can require the user to be **verified** (i.e. `is_verified` property set to ```py app.include_router( - fastapi_users.get_users_router(requires_verification=True), + fastapi_users.get_users_router(UserRead, UserUpdate, requires_verification=True), prefix="/users", tags=["users"], ) diff --git a/docs/configuration/routers/verify.md b/docs/configuration/routers/verify.md index 49f99235..2cc048ba 100644 --- a/docs/configuration/routers/verify.md +++ b/docs/configuration/routers/verify.md @@ -8,23 +8,22 @@ This router provides routes to manage user email verification. Check the [routes ## Setup ```py +import uuid + from fastapi import FastAPI from fastapi_users import FastAPIUsers -SECRET = "SECRET" +from .db import User +from .schemas import UserRead -fastapi_users = FastAPIUsers( +fastapi_users = FastAPIUsers[User, uuid.UUID]( get_user_manager, [auth_backend], - User, - UserCreate, - UserUpdate, - UserDB, ) app = FastAPI() app.include_router( - fastapi_users.get_verify_router(), + fastapi_users.get_verify_router(UserRead), prefix="/auth", tags=["auth"], ) diff --git a/docs/configuration/schemas.md b/docs/configuration/schemas.md new file mode 100644 index 00000000..3cb98e7a --- /dev/null +++ b/docs/configuration/schemas.md @@ -0,0 +1,73 @@ +# Schemas + +FastAPI is heavily using [Pydantic models](https://pydantic-docs.helpmanual.io/) to validate request payloads and serialize responses. **FastAPI Users** is no exception and will expect you to provide Pydantic schemas representing a user when it's read, created and updated. + +It's **different from your `User` model**, which is an object that actually interacts with the database. Those schemas on the other hand are here to validate data and serialize correct it in the API. + +**FastAPI Users** provides a base structure to cover its needs. It is structured like this: + +* `id` (`ID`) – Unique identifier of the user. It matches the type of your ID, like UUID or integer. +* `email` (`str`) – Email of the user. Validated by [`email-validator`](https://github.com/JoshData/python-email-validator). +* `is_active` (`bool`) – Whether or not the user is active. If not, login and forgot password requests will be denied. Defaults to `True`. +* `is_verified` (`bool`) – Whether or not the user is verified. Optional but helpful with the [`verify` router](./routers/verify.md) logic. Defaults to `False`. +* `is_superuser` (`bool`) – Whether or not the user is a superuser. Useful to implement administration logic. Defaults to `False`. + +## Define your schemas + +There are four Pydantic models variations provided as mixins: + +* `BaseUser`, which provides the basic fields and validation; +* `BaseCreateUser`, dedicated to user registration, which consists of compulsory `email` and `password` fields; +* `BaseUpdateUser`, dedicated to user profile update, which adds an optional `password` field; + +You should define each of those variations, inheriting from each mixin: + +```py +import uuid + +from fastapi_users import schemas + + +class UserRead(schemas.BaseUser[uuid.UUID]): + pass + + +class UserCreate(schemas.BaseUserCreate): + pass + + +class UserUpdate(schemas.BaseUserUpdate): + pass +``` + +!!! note "Typing: ID generic type is expected" + You can see that we define a generic type when extending the `BaseUser` class. It should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID. + +### Adding your own fields + +You can of course add your own properties there to fit to your needs. In the example below, we add a required string property, `first_name`, and an optional date property, `birthdate`. + +```py +import datetime +import uuid + +from fastapi_users import schemas + + +class UserRead(schemas.BaseUser[uuid.UUID]): + first_name: str + birthdate: Optional[datetime.date] + + +class UserCreate(schemas.BaseUserCreate): + first_name: str + birthdate: Optional[datetime.date] + + +class UserUpdate(schemas.BaseUserUpdate): + first_name: Optional[str] + birthdate: Optional[datetime.date] +``` + +!!! warning "Make sure to mirror this in your database model" + The `User` model you defined earlier for your specific database will be the central object that will actually store the data. Therefore, you need to define the very same fields in it so the data can be actually stored. diff --git a/docs/configuration/user-manager.md b/docs/configuration/user-manager.md index 6d5f753b..a7496d56 100644 --- a/docs/configuration/user-manager.md +++ b/docs/configuration/user-manager.md @@ -2,18 +2,57 @@ The `UserManager` class is the core logic of FastAPI Users. We provide the `BaseUserManager` class which you should extend to set some parameters and define logic, for example when a user just registered or forgot its password. -It's designed to be easily extensible and customizable so that you can integrate less generic logic. +It's designed to be easily extensible and customizable so that you can integrate your very own logic. ## Create your `UserManager` class You should define your own version of the `UserManager` class to set various parameters. -```py hl_lines="12-28" +```py hl_lines="13-28" --8<-- "docs/src/user_manager.py" ``` As you can see, you have to define here various attributes and methods. You can find the complete list of those below. +!!! note "Typing: User and ID generic types are expected" + You can see that we define two generic types when extending the base class: + + * `User`, which is the user model we defined in the database part + * The ID, which should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID. + + It'll help you to have **good type-checking and auto-completion** when implementing the custom methods. + +### The ID parser mixin + +Since the user ID is fully generic, we need a way to **parse it reliably when it'll come from API requests**, typically as URL path attributes. + +That's why we added the `UUIDIDMixin` in the example above. It implements the `parse_id` method, ensuring UUID are valid and correctly parsed. + +Of course, it's important that this logic **matches the type of your ID**. To help you with this, we provide mixins for the most common cases: + +* `UUIDIDMixin`, for UUID ID. +* `IntegerIDMixin`, for integer ID. +* `ObjectIDIDMixin` (provided by `fastapi_users_db_beanie`), for MongoDB ObjectID. + +!!! tip "Inheritance order matters" + Notice in your example that **the mixin comes first in our `UserManager` inheritance**. Because of the Method-Resolution-Order (MRO) of Python, the left-most element takes precedence. + +If you need another type of ID, you can simply overload the `parse_id` method on your `UserManager` class: + +```py +from fastapi_users import BaseUserManager, InvalidID + + +class UserManager(BaseUserManager[User, MyCustomID]): + def parse_id(self, value: Any) -> MyCustomID: + try: + return MyCustomID(value) + except ValueError as e: + raise InvalidID() from e # (1)! +``` + +1. If the ID can't be parsed into the desired type, you'll need to raise an `InvalidID` exception. + ## Create `get_user_manager` dependency The `UserManager` class will be injected at runtime using a FastAPI dependency. This way, you can run it in a database session or swap it with a mock during testing. @@ -28,7 +67,6 @@ Notice that we use the `get_user_db` dependency we defined earlier to inject the ### Attributes -* `user_db_model`: Pydantic model of a DB representation of a user. * `reset_password_token_secret`: Secret to encode reset password token. **Use a strong passphrase and keep it secure.** * `reset_password_token_lifetime_seconds`: Lifetime of reset password token. Defaults to 3600. * `reset_password_token_audience`: JWT audience of reset password token. Defaults to `fastapi-users:reset`. @@ -54,15 +92,15 @@ This function should return `None` if the password is valid or raise `InvalidPas **Example** ```py -from fastapi_users import BaseUserManager, InvalidPasswordException +from fastapi_users import BaseUserManager, InvalidPasswordException, UUIDIDMixin -class UserManager(BaseUserManager[UserCreate, UserDB]): +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): # ... async def validate_password( self, password: str, - user: Union[UserCreate, UserDB], + user: Union[UserCreate, User], ) -> None: if len(password) < 8: raise InvalidPasswordException( @@ -82,18 +120,18 @@ Typically, you'll want to **send a welcome e-mail** or add it to your marketing **Arguments** -* `user` (`UserDB`): the registered user. +* `user` (`User`): the registered user. * `request` (`Optional[Request]`): optional FastAPI request object that triggered the operation. Defaults to None. **Example** ```py -from fastapi_users import BaseUserManager +from fastapi_users import BaseUserManager, UUIDIDMixin -class UserManager(BaseUserManager[UserCreate, UserDB]): +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): # ... - 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.") ``` @@ -105,21 +143,21 @@ It may be useful, for example, if you wish to update your user in a data analyti **Arguments** -* `user` (`UserDB`): the updated user. +* `user` (`User`): the updated user. * `update_dict` (`Dict[str, Any]`): dictionary with the updated user fields. * `request` (`Optional[Request]`): optional FastAPI request object that triggered the operation. Defaults to None. **Example** ```py -from fastapi_users import BaseUserManager +from fastapi_users import BaseUserManager, UUIDIDMixin -class UserManager(BaseUserManager[UserCreate, UserDB]): +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): # ... async def on_after_update( self, - user: UserDB, + user: User, update_dict: Dict[str, Any], request: Optional[Request] = None, ): @@ -134,20 +172,20 @@ Typically, you'll want to **send an e-mail** with the link (and the token) that **Arguments** -* `user` (`UserDB`): the user to verify. +* `user` (`User`): the user to verify. * `token` (`str`): the verification token. * `request` (`Optional[Request]`): optional FastAPI request object that triggered the operation. Defaults to None. **Example** ```py -from fastapi_users import BaseUserManager +from fastapi_users import BaseUserManager, UUIDIDMixin -class UserManager(BaseUserManager[UserCreate, UserDB]): +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): # ... 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}") ``` @@ -160,19 +198,19 @@ This may be useful if you wish to send another e-mail or store this information **Arguments** -* `user` (`UserDB`): the verified user. +* `user` (`User`): the verified user. * `request` (`Optional[Request]`): optional FastAPI request object that triggered the operation. Defaults to None. **Example** ```py -from fastapi_users import BaseUserManager +from fastapi_users import BaseUserManager, UUIDIDMixin -class UserManager(BaseUserManager[UserCreate, UserDB]): +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): # ... async def on_after_verify( - self, user: UserDB, request: Optional[Request] = None + self, user: User, request: Optional[Request] = None ): print(f"User {user.id} has been verified") ``` @@ -185,20 +223,20 @@ Typically, you'll want to **send an e-mail** with the link (and the token) that **Arguments** -* `user` (`UserDB`): the user that forgot its password. +* `user` (`User`): the user that forgot its password. * `token` (`str`): the forgot password token * `request` (`Optional[Request]`): optional FastAPI request object that triggered the operation. Defaults to None. **Example** ```py -from fastapi_users import BaseUserManager +from fastapi_users import BaseUserManager, UUIDIDMixin -class UserManager(BaseUserManager[UserCreate, UserDB]): +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): # ... 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}") ``` @@ -211,17 +249,17 @@ For example, you may want to **send an e-mail** to the concerned user to warn hi **Arguments** -* `user` (`UserDB`): the user that reset its password. +* `user` (`User`): the user that reset its password. * `request` (`Optional[Request]`): optional FastAPI request object that triggered the operation. Defaults to None. **Example** ```py -from fastapi_users import BaseUserManager +from fastapi_users import BaseUserManager, UUIDIDMixin -class UserManager(BaseUserManager[UserCreate, UserDB]): +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): # ... - async def on_after_reset_password(self, user: UserDB, request: Optional[Request] = None): + async def on_after_reset_password(self, user: User, request: Optional[Request] = None): print(f"User {user.id} has reset their password.") ``` diff --git a/docs/installation.md b/docs/installation.md index e7e41ae0..29ff1c68 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -5,27 +5,15 @@ You can add **FastAPI Users** to your FastAPI project in a few easy steps. First ## With SQLAlchemy support ```sh -pip install 'fastapi-users[sqlalchemy2]' +pip install 'fastapi-users[sqlalchemy]' ``` -## With MongoDB support +## With Beanie support ```sh pip install 'fastapi-users[mongodb]' ``` -## With Tortoise ORM support - -```sh -pip install 'fastapi-users[tortoise-orm]' -``` - -## With ormar support - -```sh -pip install 'fastapi-users[ormar]' -``` - --- -That's it! Now, let's have a look at our [User model](./configuration/models.md). +That's it! In the next section, we'll have an [overview](./configuration/overview.md) of how things work. diff --git a/docs/src/db_mongodb.py b/docs/src/db_beanie.py similarity index 54% rename from docs/src/db_mongodb.py rename to docs/src/db_beanie.py index 12b026f8..58accc17 100644 --- a/docs/src/db_mongodb.py +++ b/docs/src/db_beanie.py @@ -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) diff --git a/docs/src/db_beanie_access_tokens.py b/docs/src/db_beanie_access_tokens.py new file mode 100644 index 00000000..098f7432 --- /dev/null +++ b/docs/src/db_beanie_access_tokens.py @@ -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) diff --git a/docs/src/db_mongodb_access_tokens.py b/docs/src/db_mongodb_access_tokens.py deleted file mode 100644 index fded3797..00000000 --- a/docs/src/db_mongodb_access_tokens.py +++ /dev/null @@ -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) diff --git a/docs/src/db_ormar.py b/docs/src/db_ormar.py deleted file mode 100644 index 430f3f8d..00000000 --- a/docs/src/db_ormar.py +++ /dev/null @@ -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) diff --git a/docs/src/db_sqlalchemy.py b/docs/src/db_sqlalchemy.py index fc2baa6d..05d0f38b 100644 --- a/docs/src/db_sqlalchemy.py +++ b/docs/src/db_sqlalchemy.py @@ -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) diff --git a/docs/src/db_sqlalchemy_access_tokens.py b/docs/src/db_sqlalchemy_access_tokens.py index cc83c4e7..257fd831 100644 --- a/docs/src/db_sqlalchemy_access_tokens.py +++ b/docs/src/db_sqlalchemy_access_tokens.py @@ -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) diff --git a/docs/src/db_sqlalchemy_oauth.py b/docs/src/db_sqlalchemy_oauth.py index 7ed34081..fc7772e2 100644 --- a/docs/src/db_sqlalchemy_oauth.py +++ b/docs/src/db_sqlalchemy_oauth.py @@ -10,7 +10,7 @@ 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 +from .db import UserDB DATABASE_URL = "sqlite+aiosqlite:///./test.db" Base: DeclarativeMeta = declarative_base() diff --git a/docs/src/db_tortoise_access_tokens_adapter.py b/docs/src/db_tortoise_access_tokens_adapter.py deleted file mode 100644 index 1d46242f..00000000 --- a/docs/src/db_tortoise_access_tokens_adapter.py +++ /dev/null @@ -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) diff --git a/docs/src/db_tortoise_access_tokens_model.py b/docs/src/db_tortoise_access_tokens_model.py deleted file mode 100644 index 0c5176be..00000000 --- a/docs/src/db_tortoise_access_tokens_model.py +++ /dev/null @@ -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 diff --git a/docs/src/db_tortoise_adapter.py b/docs/src/db_tortoise_adapter.py deleted file mode 100644 index 7506c886..00000000 --- a/docs/src/db_tortoise_adapter.py +++ /dev/null @@ -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) diff --git a/docs/src/db_tortoise_model.py b/docs/src/db_tortoise_model.py deleted file mode 100644 index e55ecc42..00000000 --- a/docs/src/db_tortoise_model.py +++ /dev/null @@ -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 diff --git a/docs/src/db_tortoise_oauth_adapter.py b/docs/src/db_tortoise_oauth_adapter.py deleted file mode 100644 index bf0b4af7..00000000 --- a/docs/src/db_tortoise_oauth_adapter.py +++ /dev/null @@ -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) diff --git a/docs/src/db_tortoise_oauth_model.py b/docs/src/db_tortoise_oauth_model.py deleted file mode 100644 index 9d470192..00000000 --- a/docs/src/db_tortoise_oauth_model.py +++ /dev/null @@ -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") diff --git a/docs/src/user_manager.py b/docs/src/user_manager.py index 2fd9f5b5..55f435fd 100644 --- a/docs/src/user_manager.py +++ b/docs/src/user_manager.py @@ -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}") diff --git a/examples/mongodb/app/__init__.py b/examples/beanie/app/__init__.py similarity index 100% rename from examples/mongodb/app/__init__.py rename to examples/beanie/app/__init__.py diff --git a/examples/beanie/app/app.py b/examples/beanie/app/app.py new file mode 100644 index 00000000..5550983a --- /dev/null +++ b/examples/beanie/app/app.py @@ -0,0 +1,47 @@ +from beanie import init_beanie +from fastapi import Depends, FastAPI + +from app.db import User, db +from app.schemas import UserCreate, UserRead, UserUpdate +from app.users import auth_backend, current_active_user, fastapi_users + +app = FastAPI() + +app.include_router( + fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] +) +app.include_router( + fastapi_users.get_register_router(UserRead, UserCreate), + prefix="/auth", + tags=["auth"], +) +app.include_router( + fastapi_users.get_reset_password_router(), + prefix="/auth", + tags=["auth"], +) +app.include_router( + fastapi_users.get_verify_router(UserRead), + prefix="/auth", + tags=["auth"], +) +app.include_router( + fastapi_users.get_users_router(UserRead, UserUpdate), + prefix="/users", + tags=["users"], +) + + +@app.get("/authenticated-route") +async def authenticated_route(user: User = Depends(current_active_user)): + return {"message": f"Hello {user.email}!"} + + +@app.on_event("startup") +async def on_startup(): + await init_beanie( + database=db, + document_models=[ + User, + ], + ) diff --git a/examples/beanie/app/db.py b/examples/beanie/app/db.py new file mode 100644 index 00000000..58accc17 --- /dev/null +++ b/examples/beanie/app/db.py @@ -0,0 +1,17 @@ +import motor.motor_asyncio +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"] + + +class User(BeanieBaseUser[PydanticObjectId]): + pass + + +async def get_user_db(): + yield BeanieUserDatabase(User) diff --git a/examples/beanie/app/schemas.py b/examples/beanie/app/schemas.py new file mode 100644 index 00000000..f9b2b9a1 --- /dev/null +++ b/examples/beanie/app/schemas.py @@ -0,0 +1,14 @@ +from beanie import PydanticObjectId +from fastapi_users import schemas + + +class UserRead(schemas.BaseUser[PydanticObjectId]): + pass + + +class UserCreate(schemas.BaseUserCreate): + pass + + +class UserUpdate(schemas.BaseUserUpdate): + pass diff --git a/examples/mongodb/app/users.py b/examples/beanie/app/users.py similarity index 60% rename from examples/mongodb/app/users.py rename to examples/beanie/app/users.py index c38242ad..a96772e6 100644 --- a/examples/mongodb/app/users.py +++ b/examples/beanie/app/users.py @@ -1,5 +1,6 @@ from typing import Optional +from beanie import PydanticObjectId from fastapi import Depends, Request from fastapi_users import BaseUserManager, FastAPIUsers from fastapi_users.authentication import ( @@ -7,34 +8,32 @@ from fastapi_users.authentication import ( BearerTransport, JWTStrategy, ) -from fastapi_users.db import MongoDBUserDatabase +from fastapi_users.db import BeanieUserDatabase, ObjectIDIDMixin -from app.db import get_user_db -from app.models import User, UserCreate, UserDB, UserUpdate +from app.db import User, get_user_db SECRET = "SECRET" -class UserManager(BaseUserManager[UserCreate, UserDB]): - user_db_model = UserDB +class UserManager(ObjectIDIDMixin, BaseUserManager[User, PydanticObjectId]): 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}") -async def get_user_manager(user_db: MongoDBUserDatabase = Depends(get_user_db)): +async def get_user_manager(user_db: BeanieUserDatabase = Depends(get_user_db)): yield UserManager(user_db) @@ -50,13 +49,7 @@ auth_backend = AuthenticationBackend( transport=bearer_transport, get_strategy=get_jwt_strategy, ) -fastapi_users = FastAPIUsers( - get_user_manager, - [auth_backend], - User, - UserCreate, - UserUpdate, - UserDB, -) + +fastapi_users = FastAPIUsers[User, PydanticObjectId](get_user_manager, [auth_backend]) current_active_user = fastapi_users.current_user(active=True) diff --git a/examples/beanie/main.py b/examples/beanie/main.py new file mode 100644 index 00000000..eb1edd12 --- /dev/null +++ b/examples/beanie/main.py @@ -0,0 +1,4 @@ +import uvicorn + +if __name__ == "__main__": + uvicorn.run("app.app:app", host="0.0.0.0", log_level="info") diff --git a/examples/mongodb/requirements.txt b/examples/beanie/requirements.txt similarity index 53% rename from examples/mongodb/requirements.txt rename to examples/beanie/requirements.txt index 745d009c..ea30f98f 100644 --- a/examples/mongodb/requirements.txt +++ b/examples/beanie/requirements.txt @@ -1,3 +1,3 @@ fastapi -fastapi-users[mongodb] +fastapi-users[beanie] uvicorn[standard] diff --git a/examples/mongodb-oauth/app/models.py b/examples/mongodb-oauth/app/models.py index 04050f8a..28563211 100644 --- a/examples/mongodb-oauth/app/models.py +++ b/examples/mongodb-oauth/app/models.py @@ -1,17 +1,17 @@ -from fastapi_users import models +from fastapi_users import models, schemas -class User(models.BaseUser, models.BaseOAuthAccountMixin): +class User(schemas.BaseUser, schemas.BaseOAuthAccountMixin): pass -class UserCreate(models.BaseUserCreate): +class UserCreate(schemas.BaseUserCreate): pass -class UserUpdate(models.BaseUserUpdate): +class UserUpdate(schemas.BaseUserUpdate): pass -class UserDB(User, models.BaseUserDB): +class UserDB(User, schemas.BaseUserDB): pass diff --git a/examples/mongodb/app/app.py b/examples/mongodb/app/app.py deleted file mode 100644 index a2171027..00000000 --- a/examples/mongodb/app/app.py +++ /dev/null @@ -1,27 +0,0 @@ -from fastapi import Depends, FastAPI - -from app.models import UserDB -from app.users import auth_backend, current_active_user, fastapi_users - -app = FastAPI() - -app.include_router( - fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] -) -app.include_router(fastapi_users.get_register_router(), prefix="/auth", tags=["auth"]) -app.include_router( - fastapi_users.get_reset_password_router(), - prefix="/auth", - tags=["auth"], -) -app.include_router( - fastapi_users.get_verify_router(), - prefix="/auth", - tags=["auth"], -) -app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"]) - - -@app.get("/authenticated-route") -async def authenticated_route(user: UserDB = Depends(current_active_user)): - return {"message": f"Hello {user.email}!"} diff --git a/examples/mongodb/app/db.py b/examples/mongodb/app/db.py deleted file mode 100644 index e9e3ce77..00000000 --- a/examples/mongodb/app/db.py +++ /dev/null @@ -1,17 +0,0 @@ -import os - -import motor.motor_asyncio -from fastapi_users.db import MongoDBUserDatabase - -from app.models import UserDB - -DATABASE_URL = os.environ["DATABASE_URL"] -client = motor.motor_asyncio.AsyncIOMotorClient( - DATABASE_URL, uuidRepresentation="standard" -) -db = client["database_name"] -collection = db["users"] - - -async def get_user_db(): - yield MongoDBUserDatabase(UserDB, collection) diff --git a/examples/mongodb/app/models.py b/examples/mongodb/app/models.py deleted file mode 100644 index e2392e25..00000000 --- a/examples/mongodb/app/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from fastapi_users import models - - -class User(models.BaseUser): - pass - - -class UserCreate(models.BaseUserCreate): - pass - - -class UserUpdate(models.BaseUserUpdate): - pass - - -class UserDB(User, models.BaseUserDB): - pass diff --git a/examples/mongodb/main.py b/examples/mongodb/main.py deleted file mode 100644 index 1b47137b..00000000 --- a/examples/mongodb/main.py +++ /dev/null @@ -1,4 +0,0 @@ -import uvicorn - -if __name__ == "__main__": - uvicorn.run("app.app:app", host="0.0.0.0", port=5000, log_level="info") diff --git a/examples/sqlalchemy-oauth/app/models.py b/examples/sqlalchemy-oauth/app/models.py index 04050f8a..28563211 100644 --- a/examples/sqlalchemy-oauth/app/models.py +++ b/examples/sqlalchemy-oauth/app/models.py @@ -1,17 +1,17 @@ -from fastapi_users import models +from fastapi_users import models, schemas -class User(models.BaseUser, models.BaseOAuthAccountMixin): +class User(schemas.BaseUser, schemas.BaseOAuthAccountMixin): pass -class UserCreate(models.BaseUserCreate): +class UserCreate(schemas.BaseUserCreate): pass -class UserUpdate(models.BaseUserUpdate): +class UserUpdate(schemas.BaseUserUpdate): pass -class UserDB(User, models.BaseUserDB): +class UserDB(User, schemas.BaseUserDB): pass diff --git a/examples/sqlalchemy/app/app.py b/examples/sqlalchemy/app/app.py index c898d816..034089cb 100644 --- a/examples/sqlalchemy/app/app.py +++ b/examples/sqlalchemy/app/app.py @@ -1,7 +1,7 @@ from fastapi import Depends, FastAPI -from app.db import create_db_and_tables -from app.models import UserDB +from app.db import User, create_db_and_tables +from app.schemas import UserCreate, UserRead, UserUpdate from app.users import auth_backend, current_active_user, fastapi_users app = FastAPI() @@ -9,22 +9,30 @@ app = FastAPI() app.include_router( fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] ) -app.include_router(fastapi_users.get_register_router(), prefix="/auth", tags=["auth"]) +app.include_router( + fastapi_users.get_register_router(UserRead, UserCreate), + prefix="/auth", + tags=["auth"], +) app.include_router( fastapi_users.get_reset_password_router(), prefix="/auth", tags=["auth"], ) app.include_router( - fastapi_users.get_verify_router(), + fastapi_users.get_verify_router(UserRead), prefix="/auth", tags=["auth"], ) -app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"]) +app.include_router( + fastapi_users.get_users_router(UserRead, UserUpdate), + prefix="/users", + tags=["users"], +) @app.get("/authenticated-route") -async def authenticated_route(user: UserDB = Depends(current_active_user)): +async def authenticated_route(user: User = Depends(current_active_user)): return {"message": f"Hello {user.email}!"} diff --git a/examples/sqlalchemy/app/db.py b/examples/sqlalchemy/app/db.py index 58ac1822..05d0f38b 100644 --- a/examples/sqlalchemy/app/db.py +++ b/examples/sqlalchemy/app/db.py @@ -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 app.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) diff --git a/examples/sqlalchemy/app/models.py b/examples/sqlalchemy/app/models.py deleted file mode 100644 index e2392e25..00000000 --- a/examples/sqlalchemy/app/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from fastapi_users import models - - -class User(models.BaseUser): - pass - - -class UserCreate(models.BaseUserCreate): - pass - - -class UserUpdate(models.BaseUserUpdate): - pass - - -class UserDB(User, models.BaseUserDB): - pass diff --git a/examples/sqlalchemy/app/schemas.py b/examples/sqlalchemy/app/schemas.py new file mode 100644 index 00000000..de1169e4 --- /dev/null +++ b/examples/sqlalchemy/app/schemas.py @@ -0,0 +1,15 @@ +import uuid + +from fastapi_users import schemas + + +class UserRead(schemas.BaseUser[uuid.UUID]): + pass + + +class UserCreate(schemas.BaseUserCreate): + pass + + +class UserUpdate(schemas.BaseUserUpdate): + pass diff --git a/examples/sqlalchemy/app/users.py b/examples/sqlalchemy/app/users.py index 04678ac0..479c49e2 100644 --- a/examples/sqlalchemy/app/users.py +++ b/examples/sqlalchemy/app/users.py @@ -1,7 +1,8 @@ +import uuid from typing import Optional from fastapi import Depends, Request -from fastapi_users import BaseUserManager, FastAPIUsers +from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin from fastapi_users.authentication import ( AuthenticationBackend, BearerTransport, @@ -9,27 +10,25 @@ from fastapi_users.authentication import ( ) from fastapi_users.db import SQLAlchemyUserDatabase -from app.db import get_user_db -from app.models import User, UserCreate, UserDB, UserUpdate +from app.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}") @@ -50,13 +49,7 @@ auth_backend = AuthenticationBackend( transport=bearer_transport, get_strategy=get_jwt_strategy, ) -fastapi_users = FastAPIUsers( - get_user_manager, - [auth_backend], - User, - UserCreate, - UserUpdate, - UserDB, -) + +fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend]) current_active_user = fastapi_users.current_user(active=True) diff --git a/examples/sqlalchemy/requirements.txt b/examples/sqlalchemy/requirements.txt index 9b18bbc8..6407e81e 100644 --- a/examples/sqlalchemy/requirements.txt +++ b/examples/sqlalchemy/requirements.txt @@ -1,4 +1,4 @@ fastapi -fastapi-users[sqlalchemy2] +fastapi-users[sqlalchemy] uvicorn[standard] aiosqlite diff --git a/examples/tortoise-oauth/app/models.py b/examples/tortoise-oauth/app/models.py index 9d470192..c7da4abe 100644 --- a/examples/tortoise-oauth/app/models.py +++ b/examples/tortoise-oauth/app/models.py @@ -1,18 +1,18 @@ -from fastapi_users import models +from fastapi_users import models, schemas from fastapi_users.db import TortoiseBaseOAuthAccountModel, TortoiseBaseUserModel from tortoise import fields from tortoise.contrib.pydantic import PydanticModel -class User(models.BaseUser, models.BaseOAuthAccountMixin): +class User(schemas.BaseUser, schemas.BaseOAuthAccountMixin): pass -class UserCreate(models.BaseUserCreate): +class UserCreate(schemas.BaseUserCreate): pass -class UserUpdate(models.BaseUserUpdate): +class UserUpdate(schemas.BaseUserUpdate): pass @@ -20,7 +20,7 @@ class UserModel(TortoiseBaseUserModel): pass -class UserDB(User, models.BaseUserDB, PydanticModel): +class UserDB(User, schemas.BaseUserDB, PydanticModel): class Config: orm_mode = True orig_model = UserModel diff --git a/examples/tortoise/app/models.py b/examples/tortoise/app/models.py index e55ecc42..54fc0217 100644 --- a/examples/tortoise/app/models.py +++ b/examples/tortoise/app/models.py @@ -1,17 +1,17 @@ -from fastapi_users import models +from fastapi_users import models, schemas from fastapi_users.db import TortoiseBaseUserModel from tortoise.contrib.pydantic import PydanticModel -class User(models.BaseUser): +class User(schemas.BaseUser): pass -class UserCreate(models.BaseUserCreate): +class UserCreate(schemas.BaseUserCreate): pass -class UserUpdate(models.BaseUserUpdate): +class UserUpdate(schemas.BaseUserUpdate): pass @@ -19,7 +19,7 @@ class UserModel(TortoiseBaseUserModel): pass -class UserDB(User, models.BaseUserDB, PydanticModel): +class UserDB(User, schemas.BaseUserDB, PydanticModel): class Config: orm_mode = True orig_model = UserModel diff --git a/fastapi_users/__init__.py b/fastapi_users/__init__.py index 13ecb84f..7837a512 100644 --- a/fastapi_users/__init__.py +++ b/fastapi_users/__init__.py @@ -6,10 +6,10 @@ from fastapi_users import models, schemas # noqa: F401 from fastapi_users.fastapi_users import FastAPIUsers # noqa: F401 from fastapi_users.manager import ( # noqa: F401 BaseUserManager, - InvalidPasswordException, - InvalidID, - UUIDIDMixin, IntegerIDMixin, + InvalidID, + InvalidPasswordException, + UUIDIDMixin, ) __all__ = [ diff --git a/fastapi_users/db/__init__.py b/fastapi_users/db/__init__.py index d29c354b..5e3382f7 100644 --- a/fastapi_users/db/__init__.py +++ b/fastapi_users/db/__init__.py @@ -2,48 +2,35 @@ from fastapi_users.db.base import BaseUserDatabase, UserDatabaseDependency __all__ = ["BaseUserDatabase", "UserDatabaseDependency"] -try: # pragma: no cover - from fastapi_users_db_mongodb import MongoDBUserDatabase # noqa: F401 - - __all__.append("MongoDBUserDatabase") -except ImportError: # pragma: no cover - pass try: # pragma: no cover from fastapi_users_db_sqlalchemy import ( # noqa: F401 SQLAlchemyBaseOAuthAccountTable, + SQLAlchemyBaseOAuthAccountTableUUID, SQLAlchemyBaseUserTable, + SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase, ) - __all__.append("SQLAlchemyBaseOAuthAccountTable") __all__.append("SQLAlchemyBaseUserTable") + __all__.append("SQLAlchemyBaseUserTableUUID") + __all__.append("SQLAlchemyBaseOAuthAccountTable") + __all__.append("SQLAlchemyBaseOAuthAccountTableUUID") __all__.append("SQLAlchemyUserDatabase") except ImportError: # pragma: no cover pass try: # pragma: no cover - from fastapi_users_db_tortoise import ( # noqa: F401 - TortoiseBaseOAuthAccountModel, - TortoiseBaseUserModel, - TortoiseUserDatabase, + from fastapi_users_db_beanie import ( # noqa: F401 + BaseOAuthAccount, + BeanieBaseUser, + BeanieUserDatabase, + ObjectIDIDMixin, ) - __all__.append("TortoiseBaseOAuthAccountModel") - __all__.append("TortoiseBaseUserModel") - __all__.append("TortoiseUserDatabase") -except ImportError: # pragma: no cover - pass - -try: # pragma: no cover - from fastapi_users_db_ormar import ( # noqa: F401 - OrmarBaseOAuthAccountModel, - OrmarBaseUserModel, - OrmarUserDatabase, - ) - - __all__.append("OrmarBaseOAuthAccountModel") - __all__.append("OrmarBaseUserModel") - __all__.append("OrmarUserDatabase") + __all__.append("BeanieBaseUser") + __all__.append("BaseOAuthAccount") + __all__.append("BeanieUserDatabase") + __all__.append("ObjectIDIDMixin") except ImportError: # pragma: no cover pass diff --git a/mkdocs.yml b/mkdocs.yml index 81fe714d..9384b64e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,7 +24,6 @@ theme: - navigation.instant - navigation.top - navigation.sections - - navigation.indexes - search.suggest - search.highlight - content.code.annotate @@ -71,12 +70,9 @@ nav: - installation.md - Configuration: - configuration/overview.md - - configuration/models.md - - Database adapters: + - User model and databases: - configuration/databases/sqlalchemy.md - - configuration/databases/mongodb.md - - configuration/databases/tortoise.md - - configuration/databases/ormar.md + - configuration/databases/beanie.md - Authentication backends: - Introduction: configuration/authentication/index.md - Transports: @@ -88,6 +84,7 @@ nav: - configuration/authentication/strategies/redis.md - configuration/authentication/backend.md - configuration/user-manager.md + - configuration/schemas.md - Routers: - Introduction: configuration/routers/index.md - configuration/routers/auth.md diff --git a/pyproject.toml b/pyproject.toml index 1e10b09c..f34e04f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,19 +106,10 @@ dev = [ "uvicorn", ] sqlalchemy = [ - "fastapi-users-db-sqlalchemy >=1.1.0,<2.0.0", + "fastapi-users-db-sqlalchemy >=3.0.0", ] -sqlalchemy2 = [ - "fastapi-users-db-sqlalchemy >=2.0.0", -] -mongodb = [ - "fastapi-users-db-mongodb >=1.1.0", -] -tortoise-orm = [ - "fastapi-users-db-tortoise >=1.1.0", -] -ormar = [ - "fastapi-users-db-ormar >=1.0.0", +beanie = [ + "fastapi-users-db-beanie >=1.0.0", ] oauth = [ "httpx-oauth >=0.4,<0.7" diff --git a/tests/conftest.py b/tests/conftest.py index c998fa78..3a6ddd48 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -75,7 +75,7 @@ class UserOAuthModel(UserModel): oauth_accounts: List[OAuthAccountModel] = dataclasses.field(default_factory=list) -class User(schemas.BaseUser): +class User(schemas.BaseUser[IDType]): first_name: Optional[str]