mirror of
				https://github.com/fastapi-users/fastapi-users.git
				synced 2025-11-04 06:37:51 +08:00 
			
		
		
		
	Tortoise ORM support (#59)
* add tortoise to dependencies * add tortoise as optional dependency in pyproject.toml * add tortoise support (tests needed) * Add tortoise support (also defined orm_mode in pydantic model * tests for tortoise support * format by black * docs for tortoise * delete type annotations * delete underscore * do it in 1 line * add 1 line before yield * fix in docs * fix bug and add annotation for test * Tweak documentation and fix Tortoise error about id update * Improve Tortoise coverage by using get instead of filter * Fix Pipfile.lock
This commit is contained in:
		
				
					committed by
					
						
						François Voron
					
				
			
			
				
	
			
			
			
						parent
						
							358150bbff
						
					
				
				
					commit
					b5b0bbbb01
				
			
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -48,7 +48,7 @@ coverage.xml
 | 
				
			|||||||
.pytest_cache/
 | 
					.pytest_cache/
 | 
				
			||||||
junit/
 | 
					junit/
 | 
				
			||||||
junit.xml
 | 
					junit.xml
 | 
				
			||||||
test.db
 | 
					test.db*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Translations
 | 
					# Translations
 | 
				
			||||||
*.mo
 | 
					*.mo
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							@ -34,6 +34,7 @@ databases = "==0.2.6"
 | 
				
			|||||||
pyjwt = "==1.7.1"
 | 
					pyjwt = "==1.7.1"
 | 
				
			||||||
python-multipart = "==0.0.5"
 | 
					python-multipart = "==0.0.5"
 | 
				
			||||||
motor = "==2.0.0"
 | 
					motor = "==2.0.0"
 | 
				
			||||||
 | 
					tortoise-orm = "==0.15.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[requires]
 | 
					[requires]
 | 
				
			||||||
python_version = "3.7"
 | 
					python_version = "3.7"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										35
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@ -1,7 +1,7 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "_meta": {
 | 
					    "_meta": {
 | 
				
			||||||
        "hash": {
 | 
					        "hash": {
 | 
				
			||||||
            "sha256": "4d00b916bd84a62b9047900bbec33a5eecf31c43baa93058122fb4b73bceedc4"
 | 
					            "sha256": "78d0c10e48c0662beb7525d9173388d78804f2a9ba7e4d993b50c63e9d57059b"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "pipfile-spec": 6,
 | 
					        "pipfile-spec": 6,
 | 
				
			||||||
        "requires": {
 | 
					        "requires": {
 | 
				
			||||||
@ -16,6 +16,12 @@
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "default": {
 | 
					    "default": {
 | 
				
			||||||
 | 
					        "aiosqlite": {
 | 
				
			||||||
 | 
					            "hashes": [
 | 
				
			||||||
 | 
					                "sha256:ad84fbd7516ca7065d799504fc41d6845c938e5306d1b7dd960caaeda12e22a9"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "version": "==0.10.0"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "bcrypt": {
 | 
					        "bcrypt": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89",
 | 
					                "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89",
 | 
				
			||||||
@ -77,6 +83,12 @@
 | 
				
			|||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "version": "==1.13.2"
 | 
					            "version": "==1.13.2"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "ciso8601": {
 | 
				
			||||||
 | 
					            "hashes": [
 | 
				
			||||||
 | 
					                "sha256:307342e8bb362ae41a3f3a089c11b374116823bce6fbe5d784e2a2dc37f2c753"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "version": "==2.1.2"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "databases": {
 | 
					        "databases": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:a04db1d158a91db7bd49db16e14266e8e6c7336f06f88c700147690683c769a3"
 | 
					                "sha256:a04db1d158a91db7bd49db16e14266e8e6c7336f06f88c700147690683c769a3"
 | 
				
			||||||
@ -198,6 +210,12 @@
 | 
				
			|||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "version": "==3.9.0"
 | 
					            "version": "==3.9.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "pypika": {
 | 
				
			||||||
 | 
					            "hashes": [
 | 
				
			||||||
 | 
					                "sha256:2d23365f7d30e313d6d3f9a1670f2ac9ddb72b391a21ad4737644ace797c6ae1"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "version": "==0.35.16"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "python-multipart": {
 | 
					        "python-multipart": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"
 | 
					                "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"
 | 
				
			||||||
@ -224,6 +242,21 @@
 | 
				
			|||||||
                "sha256:c2ac9a42e0e0328ad20fe444115ac5e3760c1ee2ac1ff8cdb5ec915c4a453411"
 | 
					                "sha256:c2ac9a42e0e0328ad20fe444115ac5e3760c1ee2ac1ff8cdb5ec915c4a453411"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "version": "==0.12.9"
 | 
					            "version": "==0.12.9"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "tortoise-orm": {
 | 
				
			||||||
 | 
					            "hashes": [
 | 
				
			||||||
 | 
					                "sha256:590a036f224cc3627fbcd950b7492ec98b8411df3491b725eb49c8b5edeaf54c"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "index": "pypi",
 | 
				
			||||||
 | 
					            "version": "==0.15.1"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "typing-extensions": {
 | 
				
			||||||
 | 
					            "hashes": [
 | 
				
			||||||
 | 
					                "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2",
 | 
				
			||||||
 | 
					                "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d",
 | 
				
			||||||
 | 
					                "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "version": "==3.7.4.1"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "develop": {
 | 
					    "develop": {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										53
									
								
								docs/configuration/databases/tortoise.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								docs/configuration/databases/tortoise.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Let's declare our User model.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```py hl_lines="9 10"
 | 
				
			||||||
 | 
					{!./src/db_tortoise.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!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Create the database adapter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The database adapter of **FastAPI Users** makes the link between your database configuration and the users logic. Create it like this.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```py hl_lines="13"
 | 
				
			||||||
 | 
					{!./src/db_tortoise.py!}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Register Tortoise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For using Tortoise ORM we must register our models and database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Tortoise ORM supports integration with Starlette/FastAPI out-of-the-box. It will automatically bind startup and shutdown events.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```py hl_lines="16"
 | 
				
			||||||
 | 
					{!./src/db_tortoise.py!}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Next steps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We will now configure an [authentication method](../authentication/index.md).
 | 
				
			||||||
@ -10,6 +10,10 @@ Here is a full working example with JWT authentication to help get you started.
 | 
				
			|||||||
{!./src/full_mongodb.py!}
 | 
					{!./src/full_mongodb.py!}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```py tab="Tortoise ORM"
 | 
				
			||||||
 | 
					{!./src/full_tortoise.py!}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## What now?
 | 
					## What now?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You're ready to go! Be sure to check the [Usage](../usage/routes.md) section to understand how yo work with **FastAPI Users**.
 | 
					You're ready to go! Be sure to check the [Usage](../usage/routes.md) section to understand how yo work with **FastAPI Users**.
 | 
				
			||||||
 | 
				
			|||||||
@ -28,3 +28,5 @@ Depending on your database backend, database configuration will differ a bit.
 | 
				
			|||||||
[I'm using SQLAlchemy](databases/sqlalchemy.md)
 | 
					[I'm using SQLAlchemy](databases/sqlalchemy.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[I'm using MongoDB](databases/mongodb.md)
 | 
					[I'm using MongoDB](databases/mongodb.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[I'm using Tortoise ORM](databases/tortoise.md)
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,12 @@ pip install fastapi-users[sqlalchemy]
 | 
				
			|||||||
pip install fastapi-users[mongodb]
 | 
					pip install fastapi-users[mongodb]
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## With Tortoise ORM support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```sh
 | 
				
			||||||
 | 
					pip install fastapi-users[tortoise-orm]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
That's it! Now, let's have a look at our [User model](./configuration/model.md).
 | 
					That's it! Now, let's have a look at our [User model](./configuration/model.md).
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								docs/src/db_tortoise.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								docs/src/db_tortoise.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					from fastapi import FastAPI
 | 
				
			||||||
 | 
					from fastapi_users.db.tortoise import BaseUserModel, TortoiseUserDatabase
 | 
				
			||||||
 | 
					from tortoise import Model
 | 
				
			||||||
 | 
					from tortoise.contrib.starlette import register_tortoise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DATABASE_URL = "sqlite://./test.db"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserModel(BaseUserModel, Model):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					user_db = TortoiseUserDatabase(UserModel)
 | 
				
			||||||
 | 
					app = FastAPI()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					register_tortoise(app, modules={"models": ["path_to_your_package"]})
 | 
				
			||||||
							
								
								
									
										36
									
								
								docs/src/full_tortoise.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								docs/src/full_tortoise.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					from fastapi import FastAPI
 | 
				
			||||||
 | 
					from fastapi_users import BaseUser, FastAPIUsers
 | 
				
			||||||
 | 
					from fastapi_users.authentication import JWTAuthentication
 | 
				
			||||||
 | 
					from fastapi_users.db.tortoise import BaseUserModel, TortoiseUserDatabase
 | 
				
			||||||
 | 
					from tortoise import Model
 | 
				
			||||||
 | 
					from tortoise.contrib.starlette import register_tortoise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DATABASE_URL = "sqlite://./test.db"
 | 
				
			||||||
 | 
					SECRET = "SECRET"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserModel(BaseUserModel, Model):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class User(BaseUser):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auth = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
 | 
				
			||||||
 | 
					user_db = TortoiseUserDatabase(UserModel)
 | 
				
			||||||
 | 
					app = FastAPI()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					register_tortoise(app, db_url=DATABASE_URL, modules={"models": ["test"]})
 | 
				
			||||||
 | 
					fastapi_users = FastAPIUsers(user_db, auth, User, SECRET)
 | 
				
			||||||
 | 
					app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@fastapi_users.on_after_register()
 | 
				
			||||||
 | 
					def on_after_register(user: User):
 | 
				
			||||||
 | 
					    print(f"User {user.id} has registered.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@fastapi_users.on_after_forgot_password()
 | 
				
			||||||
 | 
					def on_after_forgot_password(user: User, token: str):
 | 
				
			||||||
 | 
					    print(f"User {user.id} has forgot their password. Reset token: {token}")
 | 
				
			||||||
							
								
								
									
										58
									
								
								fastapi_users/db/tortoise.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								fastapi_users/db/tortoise.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					from typing import List, Optional, Type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from tortoise import Model, fields
 | 
				
			||||||
 | 
					from tortoise.exceptions import DoesNotExist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fastapi_users.db import BaseUserDatabase
 | 
				
			||||||
 | 
					from fastapi_users.models import BaseUserDB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseUserModel:
 | 
				
			||||||
 | 
					    id = fields.TextField(pk=True, generated=False)
 | 
				
			||||||
 | 
					    email = fields.CharField(index=True, unique=True, null=False, max_length=255)
 | 
				
			||||||
 | 
					    hashed_password = fields.CharField(null=False, max_length=255)
 | 
				
			||||||
 | 
					    is_active = fields.BooleanField(default=True, null=False)
 | 
				
			||||||
 | 
					    is_superuser = fields.BooleanField(default=False, null=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        table = "user"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TortoiseUserDatabase(BaseUserDatabase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    model: Type[Model]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, model: Type[Model]):
 | 
				
			||||||
 | 
					        self.model = model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def list(self) -> List[BaseUserDB]:
 | 
				
			||||||
 | 
					        users = await self.model.all()
 | 
				
			||||||
 | 
					        return [BaseUserDB.from_orm(user) for user in users]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def get(self, id: str) -> Optional[BaseUserDB]:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            user = await self.model.get(id=id)
 | 
				
			||||||
 | 
					            return BaseUserDB.from_orm(user)
 | 
				
			||||||
 | 
					        except DoesNotExist:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def get_by_email(self, email: str) -> Optional[BaseUserDB]:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            user = await self.model.get(email=email)
 | 
				
			||||||
 | 
					            return BaseUserDB.from_orm(user)
 | 
				
			||||||
 | 
					        except DoesNotExist:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def create(self, user: BaseUserDB) -> BaseUserDB:
 | 
				
			||||||
 | 
					        model = self.model(**user.dict())
 | 
				
			||||||
 | 
					        await model.save()
 | 
				
			||||||
 | 
					        return user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def update(self, user: BaseUserDB) -> BaseUserDB:
 | 
				
			||||||
 | 
					        user_dict = user.dict()
 | 
				
			||||||
 | 
					        user_dict.pop("id")  # Tortoise complains if we pass the PK again
 | 
				
			||||||
 | 
					        await self.model.filter(id=user.id).update(**user_dict)
 | 
				
			||||||
 | 
					        return user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def delete(self, user: BaseUserDB) -> None:
 | 
				
			||||||
 | 
					        await self.model.filter(id=user.id).delete()
 | 
				
			||||||
@ -25,6 +25,9 @@ class BaseUser(BaseModel):
 | 
				
			|||||||
    def create_update_dict_superuser(self):
 | 
					    def create_update_dict_superuser(self):
 | 
				
			||||||
        return self.dict(exclude_unset=True, exclude={"id"})
 | 
					        return self.dict(exclude_unset=True, exclude={"id"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Config:
 | 
				
			||||||
 | 
					        orm_mode = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseUserCreate(BaseUser):
 | 
					class BaseUserCreate(BaseUser):
 | 
				
			||||||
    email: EmailStr
 | 
					    email: EmailStr
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,7 @@ nav:
 | 
				
			|||||||
    - Databases:
 | 
					    - Databases:
 | 
				
			||||||
      - configuration/databases/sqlalchemy.md
 | 
					      - configuration/databases/sqlalchemy.md
 | 
				
			||||||
      - configuration/databases/mongodb.md
 | 
					      - configuration/databases/mongodb.md
 | 
				
			||||||
 | 
					      - configuration/databases/tortoise.md
 | 
				
			||||||
    - Authentication:
 | 
					    - Authentication:
 | 
				
			||||||
      - configuration/authentication/index.md
 | 
					      - configuration/authentication/index.md
 | 
				
			||||||
      - configuration/authentication/jwt.md
 | 
					      - configuration/authentication/jwt.md
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,9 @@ sqlalchemy = [
 | 
				
			|||||||
mongodb = [
 | 
					mongodb = [
 | 
				
			||||||
    "motor ==2.0.0",
 | 
					    "motor ==2.0.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					tortoise-orm = [
 | 
				
			||||||
 | 
					    "tortoise-orm ==0.15.1"
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.flit.metadata.urls]
 | 
					[tool.flit.metadata.urls]
 | 
				
			||||||
Documentation = "https://frankie567.github.io/fastapi-users/"
 | 
					Documentation = "https://frankie567.github.io/fastapi-users/"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										82
									
								
								tests/test_db_tortoise.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								tests/test_db_tortoise.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					from typing import AsyncGenerator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					from tortoise import Model
 | 
				
			||||||
 | 
					from tortoise.exceptions import IntegrityError
 | 
				
			||||||
 | 
					from tortoise import Tortoise
 | 
				
			||||||
 | 
					from fastapi_users.db.tortoise import TortoiseUserDatabase, BaseUserModel
 | 
				
			||||||
 | 
					from fastapi_users.models import BaseUserDB
 | 
				
			||||||
 | 
					from fastapi_users.password import get_password_hash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class User(BaseUserModel, Model):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					async def tortoise_user_db() -> AsyncGenerator[TortoiseUserDatabase, None]:
 | 
				
			||||||
 | 
					    DATABASE_URL = "sqlite://./test.db"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await Tortoise.init(
 | 
				
			||||||
 | 
					        db_url=DATABASE_URL, modules={"models": ["tests.test_db_tortoise"]}
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    await Tortoise.generate_schemas()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    yield TortoiseUserDatabase(User)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await User.all().delete()
 | 
				
			||||||
 | 
					    await Tortoise.close_connections()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.asyncio
 | 
				
			||||||
 | 
					@pytest.mark.db
 | 
				
			||||||
 | 
					async def test_queries(tortoise_user_db: TortoiseUserDatabase):
 | 
				
			||||||
 | 
					    user = BaseUserDB(
 | 
				
			||||||
 | 
					        id="111",
 | 
				
			||||||
 | 
					        email="lancelot@camelot.bt",
 | 
				
			||||||
 | 
					        hashed_password=get_password_hash("guinevere"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Create
 | 
				
			||||||
 | 
					    user_db = await tortoise_user_db.create(user)
 | 
				
			||||||
 | 
					    assert user_db.id is not None
 | 
				
			||||||
 | 
					    assert user_db.is_active is True
 | 
				
			||||||
 | 
					    assert user_db.is_superuser is False
 | 
				
			||||||
 | 
					    assert user_db.email == user.email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Update
 | 
				
			||||||
 | 
					    user_db.is_superuser = True
 | 
				
			||||||
 | 
					    await tortoise_user_db.update(user_db)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Get by id
 | 
				
			||||||
 | 
					    id_user = await tortoise_user_db.get(user.id)
 | 
				
			||||||
 | 
					    assert id_user.id == user_db.id
 | 
				
			||||||
 | 
					    assert id_user.is_superuser is True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Get by email
 | 
				
			||||||
 | 
					    email_user = await tortoise_user_db.get_by_email(user.email)
 | 
				
			||||||
 | 
					    assert email_user.id == user_db.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # List
 | 
				
			||||||
 | 
					    users = await tortoise_user_db.list()
 | 
				
			||||||
 | 
					    assert len(users) == 1
 | 
				
			||||||
 | 
					    first_user = users[0]
 | 
				
			||||||
 | 
					    assert first_user.id == user_db.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Exception when inserting existing email
 | 
				
			||||||
 | 
					    with pytest.raises(IntegrityError):
 | 
				
			||||||
 | 
					        await tortoise_user_db.create(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Exception when inserting non-nullable fields
 | 
				
			||||||
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
 | 
					        wrong_user = BaseUserDB(id="222", hashed_password="aaa")
 | 
				
			||||||
 | 
					        await tortoise_user_db.create(wrong_user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Unknown user
 | 
				
			||||||
 | 
					    unknown_user = await tortoise_user_db.get_by_email("galahad@camelot.bt")
 | 
				
			||||||
 | 
					    assert unknown_user is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Delete user
 | 
				
			||||||
 | 
					    await tortoise_user_db.delete(user)
 | 
				
			||||||
 | 
					    deleted_user = await tortoise_user_db.get(user.id)
 | 
				
			||||||
 | 
					    assert deleted_user is None
 | 
				
			||||||
		Reference in New Issue
	
	Block a user