mirror of
https://github.com/fastapi-users/fastapi-users.git
synced 2026-03-13 07:49:55 +08:00
Inject every models variations and DB model in DB adapters (#84)
* Inject every model variations in router and DB model in DB adapters * Update documentation and import Tortoise in db module * Use path operation decorator dependencies for superuser routes
This commit is contained in:
@@ -31,6 +31,7 @@ Add quickly a registration and authentication system to your [FastAPI](https://f
|
||||
* [X] Customizable database backend
|
||||
* [X] SQLAlchemy async backend included thanks to [encode/databases](https://www.encode.io/databases/)
|
||||
* [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] Multiple customizable authentication backends
|
||||
* [X] JWT authentication backend included
|
||||
* [X] Cookie authentication backend included
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
Let's create a MongoDB connection and instantiate a collection.
|
||||
|
||||
```py hl_lines="5 6 7 8"
|
||||
```py hl_lines="23 24 25 26"
|
||||
{!./src/db_mongodb.py!}
|
||||
```
|
||||
|
||||
@@ -16,10 +16,12 @@ You can choose any name for the database and the collection.
|
||||
|
||||
The database adapter of **FastAPI Users** makes the link between your database configuration and the users logic. Create it like this.
|
||||
|
||||
```py hl_lines="14"
|
||||
```py hl_lines="32"
|
||||
{!./src/db_mongodb.py!}
|
||||
```
|
||||
|
||||
Notice that we pass a reference to your [`UserDB` model](../model.md).
|
||||
|
||||
!!! info
|
||||
The database adapter will automatically create a [unique index](https://docs.mongodb.com/manual/core/index-unique/) on `id` and `email`.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ For the sake of this tutorial from now on, we'll use a simple SQLite databse.
|
||||
|
||||
Let's create a `metadata` object and declare our User table.
|
||||
|
||||
```py hl_lines="4 14 15"
|
||||
```py hl_lines="5 32 33"
|
||||
{!./src/db_sqlalchemy.py!}
|
||||
```
|
||||
|
||||
@@ -34,7 +34,7 @@ As you can see, **FastAPI Users** provides a mixin that will include base fields
|
||||
|
||||
We'll now create an SQLAlchemy enigne and ask it to create all the defined tables.
|
||||
|
||||
```py hl_lines="18 19 20 21 22"
|
||||
```py hl_lines="36 37 38 39 40"
|
||||
{!./src/db_sqlalchemy.py!}
|
||||
```
|
||||
|
||||
@@ -45,11 +45,15 @@ We'll now create an SQLAlchemy enigne and ask it to create all the defined table
|
||||
|
||||
The database adapter of **FastAPI Users** makes the link between your database configuration and the users logic. Create it like this.
|
||||
|
||||
```py hl_lines="24 25"
|
||||
```py hl_lines="42 43"
|
||||
{!./src/db_sqlalchemy.py!}
|
||||
```
|
||||
|
||||
Notice that we declare the `users` variable, which is the actual SQLAlchemy table behind the table class. We also use our `database` instance, which allows us to do asynchronous request to the database.
|
||||
Notice that we pass it three things:
|
||||
|
||||
* A reference to your [`UserDB` model](../model.md).
|
||||
* The `users` variable, which is the actual SQLAlchemy table behind the table class.
|
||||
* A `database` instance, which allows us to do asynchronous request to the database.
|
||||
|
||||
## Next steps
|
||||
|
||||
|
||||
@@ -22,29 +22,31 @@ 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.
|
||||
Let's declare our User ORM model.
|
||||
|
||||
```py hl_lines="9 10"
|
||||
```py hl_lines="26 27"
|
||||
{!./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!
|
||||
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. Create it like this.
|
||||
|
||||
```py hl_lines="13"
|
||||
```py hl_lines="30"
|
||||
{!./src/db_tortoise.py!}
|
||||
```
|
||||
|
||||
Notice that we pass a reference to your [`UserDB` model](../model.md).
|
||||
|
||||
## 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"
|
||||
```py hl_lines="33"
|
||||
{!./src/db_tortoise.py!}
|
||||
```
|
||||
|
||||
|
||||
@@ -7,15 +7,34 @@
|
||||
* `is_active` (`bool`) – Whether or not the user is active. If not, login and forgot password requests will be denied. Default to `True`.
|
||||
* `is_active` (`bool`) – Whether or not the user is a superuser. Useful to implement administration logic. Default to `False`.
|
||||
|
||||
## Use the model
|
||||
## Define your models
|
||||
|
||||
The model is exposed as a Pydantic model mixin.
|
||||
There are four Pydantic models variations provided as mixins:
|
||||
|
||||
* `BaseUser`, which provides the basic fields and validation ;
|
||||
* `BaseCreateUser`, dedicated to user registration, which makes the `email` compulsory and adds a compulsory `password` field ;
|
||||
* `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 BaseUser
|
||||
from fastapi_users import models
|
||||
|
||||
|
||||
class User(BaseUser):
|
||||
class User(models.BaseUser):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreate(User, models.BaseUserCreate):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(User, models.BaseUserUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class UserDB(User, models.BaseUserDB):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Router
|
||||
|
||||
We're almost there! The last step is to configure the `FastAPIUsers` object that will wire the database adapter, the authentication class and the user model to expose the FastAPI router.
|
||||
We're almost there! The last step is to configure the `FastAPIUsers` object that will wire the database adapter, the authentication class and the user models to expose the FastAPI router.
|
||||
|
||||
## Configure `FastAPIUsers`
|
||||
|
||||
@@ -9,6 +9,9 @@ Configure `FastAPIUsers` object with all the elements we defined before. More pr
|
||||
* `db`: Database adapter instance.
|
||||
* `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.
|
||||
* `reset_password_token_secret`: Secret to encode reset password token.
|
||||
* `reset_password_token_lifetime_seconds`: Lifetime of reset password token in seconds. Default to one hour.
|
||||
|
||||
@@ -19,6 +22,9 @@ fastapi_users = FastAPIUsers(
|
||||
user_db,
|
||||
auth_backends,
|
||||
User,
|
||||
UserCreate,
|
||||
UserUpdate,
|
||||
UserDB,
|
||||
SECRET,
|
||||
)
|
||||
```
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
import motor.motor_asyncio
|
||||
from fastapi import FastAPI
|
||||
from fastapi_users import models
|
||||
from fastapi_users.db import MongoDBUserDatabase
|
||||
|
||||
|
||||
class User(models.BaseUser):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreate(User, models.BaseUserCreate):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(User, models.BaseUserUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class UserDB(User, models.BaseUserDB):
|
||||
pass
|
||||
|
||||
|
||||
DATABASE_URL = "mongodb://localhost:27017"
|
||||
client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URL)
|
||||
db = client["database_name"]
|
||||
@@ -11,4 +29,4 @@ collection = db["users"]
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
user_db = MongoDBUserDatabase(collection)
|
||||
user_db = MongoDBUserDatabase(UserDB, collection)
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
from fastapi import FastAPI
|
||||
from fastapi_users import models
|
||||
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
|
||||
|
||||
|
||||
class User(models.BaseUser):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreate(User, models.BaseUserCreate):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(User, models.BaseUserUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class UserDB(User, models.BaseUserDB):
|
||||
pass
|
||||
|
||||
|
||||
DATABASE_URL = "sqlite:///./test.db"
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
@@ -22,7 +40,7 @@ engine = sqlalchemy.create_engine(
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
users = UserTable.__table__
|
||||
user_db = SQLAlchemyUserDatabase(database, users)
|
||||
user_db = SQLAlchemyUserDatabase(UserDB, database, users)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi_users.db.tortoise import BaseUserModel, TortoiseUserDatabase
|
||||
from tortoise import Model
|
||||
from fastapi_users import models
|
||||
from fastapi_users.db import TortoiseBaseUserModel, TortoiseUserDatabase
|
||||
from tortoise.contrib.starlette import register_tortoise
|
||||
|
||||
|
||||
class User(models.BaseUser):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreate(User, models.BaseUserCreate):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(User, models.BaseUserUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class UserDB(User, models.BaseUserDB):
|
||||
pass
|
||||
|
||||
|
||||
DATABASE_URL = "sqlite://./test.db"
|
||||
|
||||
|
||||
class UserModel(BaseUserModel, Model):
|
||||
class UserModel(TortoiseBaseUserModel):
|
||||
pass
|
||||
|
||||
|
||||
user_db = TortoiseUserDatabase(UserModel)
|
||||
user_db = TortoiseUserDatabase(UserDB, UserModel)
|
||||
app = FastAPI()
|
||||
|
||||
register_tortoise(app, modules={"models": ["path_to_your_package"]})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import motor.motor_asyncio
|
||||
from fastapi import FastAPI
|
||||
from fastapi_users import BaseUser, FastAPIUsers
|
||||
from fastapi_users import FastAPIUsers, models
|
||||
from fastapi_users.authentication import JWTAuthentication
|
||||
from fastapi_users.db import MongoDBUserDatabase
|
||||
|
||||
@@ -8,24 +8,35 @@ DATABASE_URL = "mongodb://localhost:27017"
|
||||
SECRET = "SECRET"
|
||||
|
||||
|
||||
class User(models.BaseUser):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreate(User, models.BaseUserCreate):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(User, models.BaseUserUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class UserDB(User, models.BaseUserDB):
|
||||
pass
|
||||
|
||||
|
||||
client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URL)
|
||||
db = client["database_name"]
|
||||
collection = db["users"]
|
||||
|
||||
|
||||
user_db = MongoDBUserDatabase(collection)
|
||||
|
||||
|
||||
class User(BaseUser):
|
||||
pass
|
||||
|
||||
user_db = MongoDBUserDatabase(UserDB, collection)
|
||||
|
||||
auth_backends = [
|
||||
JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
|
||||
]
|
||||
|
||||
app = FastAPI()
|
||||
fastapi_users = FastAPIUsers(user_db, auth_backends, User, SECRET)
|
||||
fastapi_users = FastAPIUsers(
|
||||
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
|
||||
)
|
||||
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
from fastapi import FastAPI
|
||||
from fastapi_users import BaseUser, FastAPIUsers
|
||||
from fastapi_users import FastAPIUsers, models
|
||||
from fastapi_users.authentication import JWTAuthentication
|
||||
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
|
||||
@@ -10,8 +10,23 @@ DATABASE_URL = "sqlite:///./test.db"
|
||||
SECRET = "SECRET"
|
||||
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
class User(models.BaseUser):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreate(User, models.BaseUserCreate):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(User, models.BaseUserUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class UserDB(User, models.BaseUserDB):
|
||||
pass
|
||||
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
Base: DeclarativeMeta = declarative_base()
|
||||
|
||||
|
||||
@@ -22,15 +37,10 @@ class UserTable(Base, SQLAlchemyBaseUserTable):
|
||||
engine = sqlalchemy.create_engine(
|
||||
DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
)
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
users = UserTable.__table__
|
||||
user_db = SQLAlchemyUserDatabase(database, users)
|
||||
|
||||
|
||||
class User(BaseUser):
|
||||
pass
|
||||
user_db = SQLAlchemyUserDatabase(UserDB, database, users)
|
||||
|
||||
|
||||
auth_backends = [
|
||||
@@ -38,7 +48,9 @@ auth_backends = [
|
||||
]
|
||||
|
||||
app = FastAPI()
|
||||
fastapi_users = FastAPIUsers(user_db, auth_backends, User, SECRET)
|
||||
fastapi_users = FastAPIUsers(
|
||||
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
|
||||
)
|
||||
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
|
||||
|
||||
|
||||
|
||||
@@ -1,28 +1,44 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi_users import BaseUser, FastAPIUsers
|
||||
from fastapi_users import FastAPIUsers, models
|
||||
from fastapi_users.authentication import JWTAuthentication
|
||||
from fastapi_users.db.tortoise import BaseUserModel, TortoiseUserDatabase
|
||||
from tortoise import Model
|
||||
from fastapi_users.db import TortoiseBaseUserModel, TortoiseUserDatabase
|
||||
from tortoise.contrib.starlette import register_tortoise
|
||||
|
||||
DATABASE_URL = "sqlite://./test.db"
|
||||
SECRET = "SECRET"
|
||||
|
||||
|
||||
class UserModel(BaseUserModel, Model):
|
||||
class User(models.BaseUser):
|
||||
pass
|
||||
|
||||
|
||||
class User(BaseUser):
|
||||
class UserCreate(User, models.BaseUserCreate):
|
||||
pass
|
||||
|
||||
|
||||
auth = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
|
||||
user_db = TortoiseUserDatabase(UserModel)
|
||||
class UserUpdate(User, models.BaseUserUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class UserDB(User, models.BaseUserDB):
|
||||
pass
|
||||
|
||||
|
||||
class UserModel(TortoiseBaseUserModel):
|
||||
pass
|
||||
|
||||
|
||||
user_db = TortoiseUserDatabase(UserDB, UserModel)
|
||||
app = FastAPI()
|
||||
|
||||
register_tortoise(app, db_url=DATABASE_URL, modules={"models": ["test"]})
|
||||
fastapi_users = FastAPIUsers(user_db, auth, User, SECRET)
|
||||
|
||||
auth_backends = [
|
||||
JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
|
||||
]
|
||||
|
||||
fastapi_users = FastAPIUsers(
|
||||
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
|
||||
)
|
||||
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
|
||||
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
__version__ = "0.4.1"
|
||||
|
||||
from fastapi_users import models # noqa: F401
|
||||
from fastapi_users.fastapi_users import FastAPIUsers # noqa: F401
|
||||
from fastapi_users.models import BaseUser # noqa: F401
|
||||
|
||||
@@ -12,3 +12,11 @@ try:
|
||||
)
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
try:
|
||||
from fastapi_users.db.tortoise import ( # noqa: F401
|
||||
TortoiseBaseUserModel,
|
||||
TortoiseUserDatabase,
|
||||
)
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
from typing import List, Optional
|
||||
from typing import Generic, List, Optional, Type
|
||||
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
from fastapi_users import password
|
||||
from fastapi_users.models import BaseUserDB
|
||||
from fastapi_users.models import UD
|
||||
|
||||
|
||||
class BaseUserDatabase:
|
||||
"""Base adapter for retrieving, creating and updating users from a database."""
|
||||
class BaseUserDatabase(Generic[UD]):
|
||||
"""
|
||||
Base adapter for retrieving, creating and updating users from a database.
|
||||
|
||||
async def list(self) -> List[BaseUserDB]:
|
||||
:param user_db_model: Pydantic model of a DB representation of a user.
|
||||
"""
|
||||
|
||||
user_db_model: Type[UD]
|
||||
|
||||
def __init__(self, user_db_model: Type[UD]):
|
||||
self.user_db_model = user_db_model
|
||||
|
||||
async def list(self) -> List[UD]:
|
||||
"""List all users."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def get(self, id: str) -> Optional[BaseUserDB]:
|
||||
async def get(self, id: str) -> Optional[UD]:
|
||||
"""Get a single user by id."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def get_by_email(self, email: str) -> Optional[BaseUserDB]:
|
||||
async def get_by_email(self, email: str) -> Optional[UD]:
|
||||
"""Get a single user by email."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def create(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def create(self, user: UD) -> UD:
|
||||
"""Create a user."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def update(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def update(self, user: UD) -> UD:
|
||||
"""Update a user."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def delete(self, user: BaseUserDB) -> None:
|
||||
async def delete(self, user: UD) -> None:
|
||||
"""Delete a user."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def authenticate(
|
||||
self, credentials: OAuth2PasswordRequestForm
|
||||
) -> Optional[BaseUserDB]:
|
||||
) -> Optional[UD]:
|
||||
"""
|
||||
Authenticate and return a user following an email and a password.
|
||||
|
||||
|
||||
@@ -1,43 +1,45 @@
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Type
|
||||
|
||||
from motor.motor_asyncio import AsyncIOMotorCollection
|
||||
|
||||
from fastapi_users.db.base import BaseUserDatabase
|
||||
from fastapi_users.models import BaseUserDB
|
||||
from fastapi_users.models import UD
|
||||
|
||||
|
||||
class MongoDBUserDatabase(BaseUserDatabase):
|
||||
class MongoDBUserDatabase(BaseUserDatabase[UD]):
|
||||
"""
|
||||
Database adapter for MongoDB.
|
||||
|
||||
:param user_db_model: Pydantic model of a DB representation of a user.
|
||||
:param collection: Collection instance from `motor`.
|
||||
"""
|
||||
|
||||
collection: AsyncIOMotorCollection
|
||||
|
||||
def __init__(self, collection: AsyncIOMotorCollection):
|
||||
def __init__(self, user_db_model: Type[UD], collection: AsyncIOMotorCollection):
|
||||
super().__init__(user_db_model)
|
||||
self.collection = collection
|
||||
self.collection.create_index("id", unique=True)
|
||||
self.collection.create_index("email", unique=True)
|
||||
|
||||
async def list(self) -> List[BaseUserDB]:
|
||||
return [BaseUserDB(**user) async for user in self.collection.find()]
|
||||
async def list(self) -> List[UD]:
|
||||
return [self.user_db_model(**user) async for user in self.collection.find()]
|
||||
|
||||
async def get(self, id: str) -> Optional[BaseUserDB]:
|
||||
async def get(self, id: str) -> Optional[UD]:
|
||||
user = await self.collection.find_one({"id": id})
|
||||
return BaseUserDB(**user) if user else None
|
||||
return self.user_db_model(**user) if user else None
|
||||
|
||||
async def get_by_email(self, email: str) -> Optional[BaseUserDB]:
|
||||
async def get_by_email(self, email: str) -> Optional[UD]:
|
||||
user = await self.collection.find_one({"email": email})
|
||||
return BaseUserDB(**user) if user else None
|
||||
return self.user_db_model(**user) if user else None
|
||||
|
||||
async def create(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def create(self, user: UD) -> UD:
|
||||
await self.collection.insert_one(user.dict())
|
||||
return user
|
||||
|
||||
async def update(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def update(self, user: UD) -> UD:
|
||||
await self.collection.replace_one({"id": user.id}, user.dict())
|
||||
return user
|
||||
|
||||
async def delete(self, user: BaseUserDB) -> None:
|
||||
async def delete(self, user: UD) -> None:
|
||||
await self.collection.delete_one({"id": user.id})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Type
|
||||
|
||||
from databases import Database
|
||||
from sqlalchemy import Boolean, Column, String, Table
|
||||
|
||||
from fastapi_users.db.base import BaseUserDatabase
|
||||
from fastapi_users.models import BaseUserDB
|
||||
from fastapi_users.models import UD
|
||||
|
||||
|
||||
class SQLAlchemyBaseUserTable:
|
||||
@@ -19,10 +19,11 @@ class SQLAlchemyBaseUserTable:
|
||||
is_superuser = Column(Boolean, default=False, nullable=False)
|
||||
|
||||
|
||||
class SQLAlchemyUserDatabase(BaseUserDatabase):
|
||||
class SQLAlchemyUserDatabase(BaseUserDatabase[UD]):
|
||||
"""
|
||||
Database adapter for SQLAlchemy.
|
||||
|
||||
:param user_db_model: Pydantic model of a DB representation of a user.
|
||||
:param database: `Database` instance from `encode/databases`.
|
||||
:param users: SQLAlchemy users table instance.
|
||||
"""
|
||||
@@ -30,37 +31,38 @@ class SQLAlchemyUserDatabase(BaseUserDatabase):
|
||||
database: Database
|
||||
users: Table
|
||||
|
||||
def __init__(self, database: Database, users: Table):
|
||||
def __init__(self, user_db_model: Type[UD], database: Database, users: Table):
|
||||
super().__init__(user_db_model)
|
||||
self.database = database
|
||||
self.users = users
|
||||
|
||||
async def list(self) -> List[BaseUserDB]:
|
||||
async def list(self) -> List[UD]:
|
||||
query = self.users.select()
|
||||
users = await self.database.fetch_all(query)
|
||||
return [BaseUserDB(**user) for user in users]
|
||||
return [self.user_db_model(**user) for user in users]
|
||||
|
||||
async def get(self, id: str) -> Optional[BaseUserDB]:
|
||||
async def get(self, id: str) -> Optional[UD]:
|
||||
query = self.users.select().where(self.users.c.id == id)
|
||||
user = await self.database.fetch_one(query)
|
||||
return BaseUserDB(**user) if user else None
|
||||
return self.user_db_model(**user) if user else None
|
||||
|
||||
async def get_by_email(self, email: str) -> Optional[BaseUserDB]:
|
||||
async def get_by_email(self, email: str) -> Optional[UD]:
|
||||
query = self.users.select().where(self.users.c.email == email)
|
||||
user = await self.database.fetch_one(query)
|
||||
return BaseUserDB(**user) if user else None
|
||||
return self.user_db_model(**user) if user else None
|
||||
|
||||
async def create(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def create(self, user: UD) -> UD:
|
||||
query = self.users.insert().values(**user.dict())
|
||||
await self.database.execute(query)
|
||||
return user
|
||||
|
||||
async def update(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def update(self, user: UD) -> UD:
|
||||
query = (
|
||||
self.users.update().where(self.users.c.id == user.id).values(**user.dict())
|
||||
)
|
||||
await self.database.execute(query)
|
||||
return user
|
||||
|
||||
async def delete(self, user: BaseUserDB) -> None:
|
||||
async def delete(self, user: UD) -> None:
|
||||
query = self.users.delete().where(self.users.c.id == user.id)
|
||||
await self.database.execute(query)
|
||||
|
||||
@@ -3,11 +3,11 @@ 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
|
||||
from fastapi_users.db.base import BaseUserDatabase
|
||||
from fastapi_users.models import UD
|
||||
|
||||
|
||||
class BaseUserModel:
|
||||
class TortoiseBaseUserModel(Model):
|
||||
id = fields.CharField(pk=True, generated=False, max_length=255)
|
||||
email = fields.CharField(index=True, unique=True, null=False, max_length=255)
|
||||
hashed_password = fields.CharField(null=False, max_length=255)
|
||||
@@ -15,44 +15,51 @@ class BaseUserModel:
|
||||
is_superuser = fields.BooleanField(default=False, null=False)
|
||||
|
||||
class Meta:
|
||||
table = "user"
|
||||
abstract = True
|
||||
|
||||
|
||||
class TortoiseUserDatabase(BaseUserDatabase):
|
||||
class TortoiseUserDatabase(BaseUserDatabase[UD]):
|
||||
"""
|
||||
Database adapter for Tortoise ORM.
|
||||
|
||||
model: Type[Model]
|
||||
:param user_db_model: Pydantic model of a DB representation of a user.
|
||||
:param model: Tortoise ORM model.
|
||||
"""
|
||||
|
||||
def __init__(self, model: Type[Model]):
|
||||
model: Type[TortoiseBaseUserModel]
|
||||
|
||||
def __init__(self, user_db_model: Type[UD], model: Type[TortoiseBaseUserModel]):
|
||||
super().__init__(user_db_model)
|
||||
self.model = model
|
||||
|
||||
async def list(self) -> List[BaseUserDB]:
|
||||
async def list(self) -> List[UD]:
|
||||
users = await self.model.all()
|
||||
return [BaseUserDB.from_orm(user) for user in users]
|
||||
return [self.user_db_model.from_orm(user) for user in users]
|
||||
|
||||
async def get(self, id: str) -> Optional[BaseUserDB]:
|
||||
async def get(self, id: str) -> Optional[UD]:
|
||||
try:
|
||||
user = await self.model.get(id=id)
|
||||
return BaseUserDB.from_orm(user)
|
||||
return self.user_db_model.from_orm(user)
|
||||
except DoesNotExist:
|
||||
return None
|
||||
|
||||
async def get_by_email(self, email: str) -> Optional[BaseUserDB]:
|
||||
async def get_by_email(self, email: str) -> Optional[UD]:
|
||||
try:
|
||||
user = await self.model.get(email=email)
|
||||
return BaseUserDB.from_orm(user)
|
||||
return self.user_db_model.from_orm(user)
|
||||
except DoesNotExist:
|
||||
return None
|
||||
|
||||
async def create(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def create(self, user: UD) -> UD:
|
||||
model = self.model(**user.dict())
|
||||
await model.save()
|
||||
return user
|
||||
|
||||
async def update(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def update(self, user: UD) -> UD:
|
||||
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:
|
||||
async def delete(self, user: UD) -> None:
|
||||
await self.model.filter(id=user.id).delete()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Callable, Sequence, Type
|
||||
|
||||
from fastapi_users import models
|
||||
from fastapi_users.authentication import Authenticator, BaseAuthentication
|
||||
from fastapi_users.db import BaseUserDatabase
|
||||
from fastapi_users.models import BaseUser
|
||||
from fastapi_users.router import Event, UserRouter, get_user_router
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ class FastAPIUsers:
|
||||
:param db: Database adapter instance.
|
||||
:param auth_backends: List of authentication backends.
|
||||
:param user_model: Pydantic model of a user.
|
||||
:param user_create_model: Pydantic model for creating a user.
|
||||
:param user_update_model: Pydantic model for updating a user.
|
||||
:param user_db_model: Pydantic model of a DB representation of a user.
|
||||
:param reset_password_token_secret: Secret to encode reset password token.
|
||||
:param reset_password_token_lifetime_seconds: Lifetime of reset password token.
|
||||
|
||||
@@ -28,7 +31,10 @@ class FastAPIUsers:
|
||||
self,
|
||||
db: BaseUserDatabase,
|
||||
auth_backends: Sequence[BaseAuthentication],
|
||||
user_model: Type[BaseUser],
|
||||
user_model: Type[models.BaseUser],
|
||||
user_create_model: Type[models.BaseUserCreate],
|
||||
user_update_model: Type[models.BaseUserUpdate],
|
||||
user_db_model: Type[models.BaseUserDB],
|
||||
reset_password_token_secret: str,
|
||||
reset_password_token_lifetime_seconds: int = 3600,
|
||||
):
|
||||
@@ -37,6 +43,9 @@ class FastAPIUsers:
|
||||
self.router = get_user_router(
|
||||
self.db,
|
||||
user_model,
|
||||
user_create_model,
|
||||
user_update_model,
|
||||
user_db_model,
|
||||
self.authenticator,
|
||||
reset_password_token_secret,
|
||||
reset_password_token_lifetime_seconds,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import uuid
|
||||
from typing import Optional, Type
|
||||
from typing import Optional, TypeVar
|
||||
|
||||
import pydantic
|
||||
from pydantic import BaseModel, EmailStr
|
||||
@@ -25,9 +25,6 @@ class BaseUser(BaseModel):
|
||||
def create_update_dict_superuser(self):
|
||||
return self.dict(exclude_unset=True, exclude={"id"})
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class BaseUserCreate(BaseUser):
|
||||
email: EmailStr
|
||||
@@ -39,23 +36,11 @@ class BaseUserUpdate(BaseUser):
|
||||
|
||||
|
||||
class BaseUserDB(BaseUser):
|
||||
id: str
|
||||
hashed_password: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Models:
|
||||
"""Generate models inheriting from the custom User model."""
|
||||
|
||||
def __init__(self, user_model: Type[BaseUser]):
|
||||
class UserCreate(user_model, BaseUserCreate): # type: ignore
|
||||
pass
|
||||
|
||||
class UserUpdate(user_model, BaseUserUpdate): # type: ignore
|
||||
pass
|
||||
|
||||
class UserDB(user_model, BaseUserDB): # type: ignore
|
||||
pass
|
||||
|
||||
self.User = user_model
|
||||
self.UserCreate = UserCreate
|
||||
self.UserUpdate = UserUpdate
|
||||
self.UserDB = UserDB
|
||||
UD = TypeVar("UD", bound=BaseUserDB)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import typing
|
||||
from collections import defaultdict
|
||||
from enum import Enum, auto
|
||||
from typing import Any, Callable, DefaultDict, Dict, List, Type, cast
|
||||
|
||||
import jwt
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
@@ -10,9 +10,9 @@ from pydantic import EmailStr
|
||||
from starlette import status
|
||||
from starlette.responses import Response
|
||||
|
||||
from fastapi_users import models
|
||||
from fastapi_users.authentication import Authenticator, BaseAuthentication
|
||||
from fastapi_users.db import BaseUserDatabase
|
||||
from fastapi_users.models import BaseUser, Models
|
||||
from fastapi_users.password import get_password_hash
|
||||
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
|
||||
|
||||
@@ -29,13 +29,13 @@ class Event(Enum):
|
||||
|
||||
|
||||
class UserRouter(APIRouter):
|
||||
event_handlers: typing.DefaultDict[Event, typing.List[typing.Callable]]
|
||||
event_handlers: DefaultDict[Event, List[Callable]]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.event_handlers = defaultdict(list)
|
||||
|
||||
def add_event_handler(self, event_type: Event, func: typing.Callable) -> None:
|
||||
def add_event_handler(self, event_type: Event, func: Callable) -> None:
|
||||
self.event_handlers[event_type].append(func)
|
||||
|
||||
async def run_handlers(self, event_type: Event, *args, **kwargs) -> None:
|
||||
@@ -65,30 +65,30 @@ def _add_login_route(
|
||||
|
||||
|
||||
def get_user_router(
|
||||
user_db: BaseUserDatabase,
|
||||
user_model: typing.Type[BaseUser],
|
||||
user_db: BaseUserDatabase[models.BaseUserDB],
|
||||
user_model: Type[models.BaseUser],
|
||||
user_create_model: Type[models.BaseUserCreate],
|
||||
user_update_model: Type[models.BaseUserUpdate],
|
||||
user_db_model: Type[models.BaseUserDB],
|
||||
authenticator: Authenticator,
|
||||
reset_password_token_secret: str,
|
||||
reset_password_token_lifetime_seconds: int = 3600,
|
||||
) -> UserRouter:
|
||||
"""Generate a router with the authentication routes."""
|
||||
router = UserRouter()
|
||||
models = Models(user_model)
|
||||
|
||||
reset_password_token_audience = "fastapi-users:reset"
|
||||
|
||||
get_current_active_user = authenticator.get_current_active_user
|
||||
get_current_superuser = authenticator.get_current_superuser
|
||||
|
||||
async def _get_or_404(id: str) -> models.UserDB: # type: ignore
|
||||
async def _get_or_404(id: str) -> models.BaseUserDB:
|
||||
user = await user_db.get(id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
return user
|
||||
|
||||
async def _update_user(
|
||||
user: models.UserDB, update_dict: typing.Dict[str, typing.Any] # type: ignore
|
||||
):
|
||||
async def _update_user(user: models.BaseUserDB, update_dict: Dict[str, Any]):
|
||||
for field in update_dict:
|
||||
if field == "password":
|
||||
hashed_password = get_password_hash(update_dict[field])
|
||||
@@ -101,9 +101,10 @@ def get_user_router(
|
||||
_add_login_route(router, user_db, auth_backend)
|
||||
|
||||
@router.post(
|
||||
"/register", response_model=models.User, status_code=status.HTTP_201_CREATED
|
||||
"/register", response_model=user_model, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def register(user: models.UserCreate): # type: ignore
|
||||
async def register(user: user_create_model): # type: ignore
|
||||
user = cast(models.BaseUserCreate, user) # Prevent mypy complain
|
||||
existing_user = await user_db.get_by_email(user.email)
|
||||
|
||||
if existing_user is not None:
|
||||
@@ -113,7 +114,7 @@ def get_user_router(
|
||||
)
|
||||
|
||||
hashed_password = get_password_hash(user.password)
|
||||
db_user = models.UserDB(
|
||||
db_user = user_db_model(
|
||||
**user.create_update_dict(), hashed_password=hashed_password
|
||||
)
|
||||
created_user = await user_db.create(db_user)
|
||||
@@ -168,48 +169,60 @@ def get_user_router(
|
||||
detail=ErrorCode.RESET_PASSWORD_BAD_TOKEN,
|
||||
)
|
||||
|
||||
@router.get("/me", response_model=models.User)
|
||||
@router.get("/me", response_model=user_model)
|
||||
async def me(
|
||||
user: models.UserDB = Depends(get_current_active_user), # type: ignore
|
||||
user: user_db_model = Depends(get_current_active_user), # type: ignore
|
||||
):
|
||||
return user
|
||||
|
||||
@router.patch("/me", response_model=models.User)
|
||||
@router.patch("/me", response_model=user_model)
|
||||
async def update_me(
|
||||
updated_user: models.UserUpdate, # type: ignore
|
||||
user: models.UserDB = Depends(get_current_active_user), # type: ignore
|
||||
updated_user: user_update_model, # type: ignore
|
||||
user: user_db_model = Depends(get_current_active_user), # type: ignore
|
||||
):
|
||||
updated_user = cast(
|
||||
models.BaseUserUpdate, updated_user,
|
||||
) # Prevent mypy complain
|
||||
updated_user_data = updated_user.create_update_dict()
|
||||
return await _update_user(user, updated_user_data)
|
||||
|
||||
@router.get("/", response_model=typing.List[models.User]) # type: ignore
|
||||
async def list_users(
|
||||
superuser: models.UserDB = Depends(get_current_superuser), # type: ignore
|
||||
):
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=List[user_model], # type: ignore
|
||||
dependencies=[Depends(get_current_superuser)],
|
||||
)
|
||||
async def list_users():
|
||||
return await user_db.list()
|
||||
|
||||
@router.get("/{id}", response_model=models.User)
|
||||
async def get_user(
|
||||
id: str,
|
||||
superuser: models.UserDB = Depends(get_current_superuser), # type: ignore
|
||||
):
|
||||
@router.get(
|
||||
"/{id}",
|
||||
response_model=user_model,
|
||||
dependencies=[Depends(get_current_superuser)],
|
||||
)
|
||||
async def get_user(id: str,):
|
||||
return await _get_or_404(id)
|
||||
|
||||
@router.patch("/{id}", response_model=models.User)
|
||||
@router.patch(
|
||||
"/{id}",
|
||||
response_model=user_model,
|
||||
dependencies=[Depends(get_current_superuser)],
|
||||
)
|
||||
async def update_user(
|
||||
id: str,
|
||||
updated_user: models.UserUpdate, # type: ignore
|
||||
superuser: models.UserDB = Depends(get_current_superuser), # type: ignore
|
||||
id: str, updated_user: user_update_model, # type: ignore
|
||||
):
|
||||
updated_user = cast(
|
||||
models.BaseUserUpdate, updated_user,
|
||||
) # Prevent mypy complain
|
||||
user = await _get_or_404(id)
|
||||
updated_user_data = updated_user.create_update_dict_superuser()
|
||||
return await _update_user(user, updated_user_data)
|
||||
|
||||
@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_user(
|
||||
id: str,
|
||||
superuser: models.UserDB = Depends(get_current_superuser), # type: ignore
|
||||
):
|
||||
@router.delete(
|
||||
"/{id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(get_current_superuser)],
|
||||
)
|
||||
async def delete_user(id: str):
|
||||
user = await _get_or_404(id)
|
||||
await user_db.delete(user)
|
||||
return None
|
||||
|
||||
@@ -8,6 +8,7 @@ from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from fastapi_users import models
|
||||
from fastapi_users.authentication import Authenticator, BaseAuthentication
|
||||
from fastapi_users.db import BaseUserDatabase
|
||||
from fastapi_users.models import BaseUserDB
|
||||
@@ -18,9 +19,25 @@ angharad_password_hash = get_password_hash("angharad")
|
||||
viviane_password_hash = get_password_hash("viviane")
|
||||
|
||||
|
||||
class User(models.BaseUser):
|
||||
first_name: Optional[str]
|
||||
|
||||
|
||||
class UserCreate(User, models.BaseUserCreate):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(User, models.BaseUserUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class UserDB(User, models.BaseUserDB):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user() -> BaseUserDB:
|
||||
return BaseUserDB(
|
||||
def user() -> UserDB:
|
||||
return UserDB(
|
||||
id="aaa",
|
||||
email="king.arthur@camelot.bt",
|
||||
hashed_password=guinevere_password_hash,
|
||||
@@ -28,8 +45,8 @@ def user() -> BaseUserDB:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inactive_user() -> BaseUserDB:
|
||||
return BaseUserDB(
|
||||
def inactive_user() -> UserDB:
|
||||
return UserDB(
|
||||
id="bbb",
|
||||
email="percival@camelot.bt",
|
||||
hashed_password=angharad_password_hash,
|
||||
@@ -38,8 +55,8 @@ def inactive_user() -> BaseUserDB:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def superuser() -> BaseUserDB:
|
||||
return BaseUserDB(
|
||||
def superuser() -> UserDB:
|
||||
return UserDB(
|
||||
id="ccc",
|
||||
email="merlin@camelot.bt",
|
||||
hashed_password=viviane_password_hash,
|
||||
@@ -49,11 +66,11 @@ def superuser() -> BaseUserDB:
|
||||
|
||||
@pytest.fixture
|
||||
def mock_user_db(user, inactive_user, superuser) -> BaseUserDatabase:
|
||||
class MockUserDatabase(BaseUserDatabase):
|
||||
async def list(self) -> List[BaseUserDB]:
|
||||
class MockUserDatabase(BaseUserDatabase[UserDB]):
|
||||
async def list(self) -> List[UserDB]:
|
||||
return [user, inactive_user, superuser]
|
||||
|
||||
async def get(self, id: str) -> Optional[BaseUserDB]:
|
||||
async def get(self, id: str) -> Optional[UserDB]:
|
||||
if id == user.id:
|
||||
return user
|
||||
if id == inactive_user.id:
|
||||
@@ -62,7 +79,7 @@ def mock_user_db(user, inactive_user, superuser) -> BaseUserDatabase:
|
||||
return superuser
|
||||
return None
|
||||
|
||||
async def get_by_email(self, email: str) -> Optional[BaseUserDB]:
|
||||
async def get_by_email(self, email: str) -> Optional[UserDB]:
|
||||
if email == user.email:
|
||||
return user
|
||||
if email == inactive_user.email:
|
||||
@@ -71,16 +88,16 @@ def mock_user_db(user, inactive_user, superuser) -> BaseUserDatabase:
|
||||
return superuser
|
||||
return None
|
||||
|
||||
async def create(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def create(self, user: UserDB) -> UserDB:
|
||||
return user
|
||||
|
||||
async def update(self, user: BaseUserDB) -> BaseUserDB:
|
||||
async def update(self, user: UserDB) -> UserDB:
|
||||
return user
|
||||
|
||||
async def delete(self, user: BaseUserDB) -> None:
|
||||
async def delete(self, user: UserDB) -> None:
|
||||
pass
|
||||
|
||||
return MockUserDatabase()
|
||||
return MockUserDatabase(UserDB)
|
||||
|
||||
|
||||
class MockAuthentication(BaseAuthentication):
|
||||
@@ -139,20 +156,18 @@ def get_test_auth_client(mock_user_db):
|
||||
authenticator = Authenticator(backends, mock_user_db)
|
||||
|
||||
@app.get("/test-current-user")
|
||||
def test_current_user(
|
||||
user: BaseUserDB = Depends(authenticator.get_current_user),
|
||||
):
|
||||
def test_current_user(user: UserDB = Depends(authenticator.get_current_user),):
|
||||
return user
|
||||
|
||||
@app.get("/test-current-active-user")
|
||||
def test_current_active_user(
|
||||
user: BaseUserDB = Depends(authenticator.get_current_active_user),
|
||||
user: UserDB = Depends(authenticator.get_current_active_user),
|
||||
):
|
||||
return user
|
||||
|
||||
@app.get("/test-current-superuser")
|
||||
def test_current_superuser(
|
||||
user: BaseUserDB = Depends(authenticator.get_current_superuser),
|
||||
user: UserDB = Depends(authenticator.get_current_superuser),
|
||||
):
|
||||
return user
|
||||
|
||||
|
||||
@@ -84,9 +84,7 @@ async def test_get_login_response(cookie_authentication, user):
|
||||
# so that FastAPI can terminate it properly
|
||||
assert login_response is None
|
||||
|
||||
cookies = [
|
||||
header for header in response.raw_headers if header[0] == b"set-cookie"
|
||||
]
|
||||
cookies = [header for header in response.raw_headers if header[0] == b"set-cookie"]
|
||||
assert len(cookies) == 1
|
||||
|
||||
cookie = cookies[0][1].decode("latin-1")
|
||||
|
||||
@@ -2,6 +2,7 @@ import pytest
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
from fastapi_users.db import BaseUserDatabase
|
||||
from tests.conftest import UserDB
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -15,7 +16,7 @@ def create_oauth2_password_request_form():
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.db
|
||||
async def test_not_implemented_methods(user):
|
||||
base_user_db = BaseUserDatabase()
|
||||
base_user_db = BaseUserDatabase(UserDB)
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
await base_user_db.list()
|
||||
|
||||
@@ -5,8 +5,8 @@ import pytest
|
||||
import pymongo.errors
|
||||
|
||||
from fastapi_users.db.mongodb import MongoDBUserDatabase
|
||||
from fastapi_users.models import BaseUserDB
|
||||
from fastapi_users.password import get_password_hash
|
||||
from tests.conftest import UserDB
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -24,15 +24,15 @@ async def mongodb_user_db() -> AsyncGenerator[MongoDBUserDatabase, None]:
|
||||
db = client["test_database"]
|
||||
collection = db["users"]
|
||||
|
||||
yield MongoDBUserDatabase(collection)
|
||||
yield MongoDBUserDatabase(UserDB, collection)
|
||||
|
||||
await collection.drop()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.db
|
||||
async def test_queries(mongodb_user_db):
|
||||
user = BaseUserDB(
|
||||
async def test_queries(mongodb_user_db: MongoDBUserDatabase[UserDB]):
|
||||
user = UserDB(
|
||||
id="111",
|
||||
email="lancelot@camelot.bt",
|
||||
hashed_password=get_password_hash("guinevere"),
|
||||
@@ -51,11 +51,13 @@ async def test_queries(mongodb_user_db):
|
||||
|
||||
# Get by id
|
||||
id_user = await mongodb_user_db.get(user.id)
|
||||
assert id_user is not None
|
||||
assert id_user.id == user_db.id
|
||||
assert id_user.is_superuser is True
|
||||
|
||||
# Get by email
|
||||
email_user = await mongodb_user_db.get_by_email(user.email)
|
||||
email_user = await mongodb_user_db.get_by_email(str(user.email))
|
||||
assert email_user is not None
|
||||
assert email_user.id == user_db.id
|
||||
|
||||
# List
|
||||
@@ -76,3 +78,21 @@ async def test_queries(mongodb_user_db):
|
||||
await mongodb_user_db.delete(user)
|
||||
deleted_user = await mongodb_user_db.get(user.id)
|
||||
assert deleted_user is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.db
|
||||
async def test_queries_custom_fields(mongodb_user_db: MongoDBUserDatabase[UserDB]):
|
||||
"""It should output custom fields in query result."""
|
||||
user = UserDB(
|
||||
id="111",
|
||||
email="lancelot@camelot.bt",
|
||||
hashed_password=get_password_hash("guinevere"),
|
||||
first_name="Lancelot",
|
||||
)
|
||||
await mongodb_user_db.create(user)
|
||||
|
||||
id_user = await mongodb_user_db.get(user.id)
|
||||
assert id_user is not None
|
||||
assert id_user.id == user.id
|
||||
assert id_user.first_name == user.first_name
|
||||
|
||||
@@ -4,11 +4,12 @@ from typing import AsyncGenerator
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from databases import Database
|
||||
from sqlalchemy import Column, String
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
|
||||
|
||||
from fastapi_users.db.sqlalchemy import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
|
||||
from fastapi_users.models import BaseUserDB
|
||||
from fastapi_users.password import get_password_hash
|
||||
from tests.conftest import UserDB
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -16,7 +17,7 @@ async def sqlalchemy_user_db() -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
|
||||
Base: DeclarativeMeta = declarative_base()
|
||||
|
||||
class User(SQLAlchemyBaseUserTable, Base):
|
||||
pass
|
||||
first_name = Column(String, nullable=True)
|
||||
|
||||
DATABASE_URL = "sqlite:///./test.db"
|
||||
database = Database(DATABASE_URL)
|
||||
@@ -28,15 +29,15 @@ async def sqlalchemy_user_db() -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
|
||||
|
||||
await database.connect()
|
||||
|
||||
yield SQLAlchemyUserDatabase(database, User.__table__)
|
||||
yield SQLAlchemyUserDatabase(UserDB, database, User.__table__)
|
||||
|
||||
Base.metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.db
|
||||
async def test_queries(sqlalchemy_user_db):
|
||||
user = BaseUserDB(
|
||||
async def test_queries(sqlalchemy_user_db: SQLAlchemyUserDatabase[UserDB]):
|
||||
user = UserDB(
|
||||
id="111",
|
||||
email="lancelot@camelot.bt",
|
||||
hashed_password=get_password_hash("guinevere"),
|
||||
@@ -55,11 +56,13 @@ async def test_queries(sqlalchemy_user_db):
|
||||
|
||||
# Get by id
|
||||
id_user = await sqlalchemy_user_db.get(user.id)
|
||||
assert id_user is not None
|
||||
assert id_user.id == user_db.id
|
||||
assert id_user.is_superuser is True
|
||||
|
||||
# Get by email
|
||||
email_user = await sqlalchemy_user_db.get_by_email(user.email)
|
||||
email_user = await sqlalchemy_user_db.get_by_email(str(user.email))
|
||||
assert email_user is not None
|
||||
assert email_user.id == user_db.id
|
||||
|
||||
# List
|
||||
@@ -74,7 +77,7 @@ async def test_queries(sqlalchemy_user_db):
|
||||
|
||||
# Exception when inserting non-nullable fields
|
||||
with pytest.raises(sqlite3.IntegrityError):
|
||||
wrong_user = BaseUserDB(id="222", hashed_password="aaa")
|
||||
wrong_user = UserDB(id="222", hashed_password="aaa")
|
||||
await sqlalchemy_user_db.create(wrong_user)
|
||||
|
||||
# Unknown user
|
||||
@@ -85,3 +88,23 @@ async def test_queries(sqlalchemy_user_db):
|
||||
await sqlalchemy_user_db.delete(user)
|
||||
deleted_user = await sqlalchemy_user_db.get(user.id)
|
||||
assert deleted_user is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.db
|
||||
async def test_queries_custom_fields(
|
||||
sqlalchemy_user_db: SQLAlchemyUserDatabase[UserDB],
|
||||
):
|
||||
"""It should output custom fields in query result."""
|
||||
user = UserDB(
|
||||
id="111",
|
||||
email="lancelot@camelot.bt",
|
||||
hashed_password=get_password_hash("guinevere"),
|
||||
first_name="Lancelot",
|
||||
)
|
||||
await sqlalchemy_user_db.create(user)
|
||||
|
||||
id_user = await sqlalchemy_user_db.get(user.id)
|
||||
assert id_user is not None
|
||||
assert id_user.id == user.id
|
||||
assert id_user.first_name == user.first_name
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
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 tortoise import Tortoise, fields
|
||||
|
||||
from fastapi_users.db.tortoise import TortoiseUserDatabase, TortoiseBaseUserModel
|
||||
from fastapi_users.password import get_password_hash
|
||||
from tests.conftest import UserDB
|
||||
|
||||
|
||||
class User(BaseUserModel, Model):
|
||||
pass
|
||||
class User(TortoiseBaseUserModel):
|
||||
first_name = fields.CharField(null=True, max_length=255)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -22,7 +22,7 @@ async def tortoise_user_db() -> AsyncGenerator[TortoiseUserDatabase, None]:
|
||||
)
|
||||
await Tortoise.generate_schemas()
|
||||
|
||||
yield TortoiseUserDatabase(User)
|
||||
yield TortoiseUserDatabase(UserDB, User)
|
||||
|
||||
await User.all().delete()
|
||||
await Tortoise.close_connections()
|
||||
@@ -30,8 +30,8 @@ async def tortoise_user_db() -> AsyncGenerator[TortoiseUserDatabase, None]:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.db
|
||||
async def test_queries(tortoise_user_db: TortoiseUserDatabase):
|
||||
user = BaseUserDB(
|
||||
async def test_queries(tortoise_user_db: TortoiseUserDatabase[UserDB]):
|
||||
user = UserDB(
|
||||
id="111",
|
||||
email="lancelot@camelot.bt",
|
||||
hashed_password=get_password_hash("guinevere"),
|
||||
@@ -50,11 +50,13 @@ async def test_queries(tortoise_user_db: TortoiseUserDatabase):
|
||||
|
||||
# Get by id
|
||||
id_user = await tortoise_user_db.get(user.id)
|
||||
assert id_user is not None
|
||||
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)
|
||||
email_user = await tortoise_user_db.get_by_email(str(user.email))
|
||||
assert email_user is not None
|
||||
assert email_user.id == user_db.id
|
||||
|
||||
# List
|
||||
@@ -69,7 +71,7 @@ async def test_queries(tortoise_user_db: TortoiseUserDatabase):
|
||||
|
||||
# Exception when inserting non-nullable fields
|
||||
with pytest.raises(ValueError):
|
||||
wrong_user = BaseUserDB(id="222", hashed_password="aaa")
|
||||
wrong_user = UserDB(id="222", hashed_password="aaa")
|
||||
await tortoise_user_db.create(wrong_user)
|
||||
|
||||
# Unknown user
|
||||
@@ -80,3 +82,21 @@ async def test_queries(tortoise_user_db: TortoiseUserDatabase):
|
||||
await tortoise_user_db.delete(user)
|
||||
deleted_user = await tortoise_user_db.get(user.id)
|
||||
assert deleted_user is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.db
|
||||
async def test_queries_custom_fields(tortoise_user_db: TortoiseUserDatabase[UserDB]):
|
||||
"""It should output custom fields in query result."""
|
||||
user = UserDB(
|
||||
id="111",
|
||||
email="lancelot@camelot.bt",
|
||||
hashed_password=get_password_hash("guinevere"),
|
||||
first_name="Lancelot",
|
||||
)
|
||||
await tortoise_user_db.create(user)
|
||||
|
||||
id_user = await tortoise_user_db.get(user.id)
|
||||
assert id_user is not None
|
||||
assert id_user.id == user.id
|
||||
assert id_user.first_name == user.first_name
|
||||
|
||||
@@ -4,8 +4,8 @@ from starlette import status
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from fastapi_users import FastAPIUsers
|
||||
from fastapi_users.models import BaseUser, BaseUserDB
|
||||
from fastapi_users.router import Event
|
||||
from tests.conftest import User, UserCreate, UserUpdate, UserDB
|
||||
|
||||
|
||||
def sync_event_handler():
|
||||
@@ -18,10 +18,15 @@ async def async_event_handler():
|
||||
|
||||
@pytest.fixture(params=[sync_event_handler, async_event_handler])
|
||||
def fastapi_users(request, mock_user_db, mock_authentication) -> FastAPIUsers:
|
||||
class User(BaseUser):
|
||||
pass
|
||||
|
||||
fastapi_users = FastAPIUsers(mock_user_db, [mock_authentication], User, "SECRET")
|
||||
fastapi_users = FastAPIUsers(
|
||||
mock_user_db,
|
||||
[mock_authentication],
|
||||
User,
|
||||
UserCreate,
|
||||
UserUpdate,
|
||||
UserDB,
|
||||
"SECRET",
|
||||
)
|
||||
|
||||
@fastapi_users.on_after_register()
|
||||
def on_after_register():
|
||||
@@ -100,7 +105,7 @@ class TestGetCurrentUser:
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_valid_token(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_valid_token(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/current-user", headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
@@ -120,7 +125,7 @@ class TestGetCurrentActiveUser:
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_valid_token_inactive_user(
|
||||
self, test_app_client: TestClient, inactive_user: BaseUserDB
|
||||
self, test_app_client: TestClient, inactive_user: UserDB
|
||||
):
|
||||
response = test_app_client.get(
|
||||
"/current-active-user",
|
||||
@@ -128,7 +133,7 @@ class TestGetCurrentActiveUser:
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_valid_token(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_valid_token(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/current-active-user", headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
@@ -147,16 +152,14 @@ class TestGetCurrentSuperuser:
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_valid_token_regular_user(
|
||||
self, test_app_client: TestClient, user: BaseUserDB
|
||||
):
|
||||
def test_valid_token_regular_user(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/current-superuser", headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_valid_token_superuser(
|
||||
self, test_app_client: TestClient, superuser: BaseUserDB
|
||||
self, test_app_client: TestClient, superuser: UserDB
|
||||
):
|
||||
response = test_app_client.get(
|
||||
"/current-superuser", headers={"Authorization": f"Bearer {superuser.id}"}
|
||||
|
||||
@@ -8,10 +8,9 @@ from starlette import status
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from fastapi_users.authentication import Authenticator
|
||||
from fastapi_users.models import BaseUser, BaseUserDB
|
||||
from fastapi_users.router import ErrorCode, Event, get_user_router
|
||||
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
|
||||
from tests.conftest import MockAuthentication
|
||||
from tests.conftest import MockAuthentication, User, UserCreate, UserUpdate, UserDB
|
||||
|
||||
SECRET = "SECRET"
|
||||
LIFETIME = 3600
|
||||
@@ -43,15 +42,21 @@ def event_handler(request):
|
||||
|
||||
@pytest.fixture()
|
||||
def test_app_client(mock_user_db, mock_authentication, event_handler) -> TestClient:
|
||||
class User(BaseUser):
|
||||
pass
|
||||
|
||||
mock_authentication_bis = MockAuthentication(name="mock-bis")
|
||||
authenticator = Authenticator(
|
||||
[mock_authentication, mock_authentication_bis], mock_user_db
|
||||
)
|
||||
|
||||
userRouter = get_user_router(mock_user_db, User, authenticator, SECRET, LIFETIME)
|
||||
userRouter = get_user_router(
|
||||
mock_user_db,
|
||||
User,
|
||||
UserCreate,
|
||||
UserUpdate,
|
||||
UserDB,
|
||||
authenticator,
|
||||
SECRET,
|
||||
LIFETIME,
|
||||
)
|
||||
|
||||
userRouter.add_event_handler(Event.ON_AFTER_REGISTER, event_handler)
|
||||
userRouter.add_event_handler(Event.ON_AFTER_FORGOT_PASSWORD, event_handler)
|
||||
@@ -158,9 +163,7 @@ class TestLogin:
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
|
||||
|
||||
def test_valid_credentials(
|
||||
self, path, test_app_client: TestClient, user: BaseUserDB
|
||||
):
|
||||
def test_valid_credentials(self, path, test_app_client: TestClient, user: UserDB):
|
||||
data = {"username": "king.arthur@camelot.bt", "password": "guinevere"}
|
||||
response = test_app_client.post(path, data=data)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
@@ -249,7 +252,7 @@ class TestResetPassword:
|
||||
mock_user_db,
|
||||
test_app_client: TestClient,
|
||||
forgot_password_token,
|
||||
inactive_user: BaseUserDB,
|
||||
inactive_user: UserDB,
|
||||
):
|
||||
mocker.spy(mock_user_db, "update")
|
||||
|
||||
@@ -268,7 +271,7 @@ class TestResetPassword:
|
||||
mock_user_db,
|
||||
test_app_client: TestClient,
|
||||
forgot_password_token,
|
||||
user: BaseUserDB,
|
||||
user: UserDB,
|
||||
):
|
||||
mocker.spy(mock_user_db, "update")
|
||||
current_hashed_passord = user.hashed_password
|
||||
@@ -288,15 +291,13 @@ class TestMe:
|
||||
response = test_app_client.get("/me")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_inactive_user(
|
||||
self, test_app_client: TestClient, inactive_user: BaseUserDB
|
||||
):
|
||||
def test_inactive_user(self, test_app_client: TestClient, inactive_user: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/me", headers={"Authorization": f"Bearer {inactive_user.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_active_user(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_active_user(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/me", headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
@@ -313,15 +314,13 @@ class TestUpdateMe:
|
||||
response = test_app_client.patch("/me")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_inactive_user(
|
||||
self, test_app_client: TestClient, inactive_user: BaseUserDB
|
||||
):
|
||||
def test_inactive_user(self, test_app_client: TestClient, inactive_user: UserDB):
|
||||
response = test_app_client.patch(
|
||||
"/me", headers={"Authorization": f"Bearer {inactive_user.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_empty_body(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_empty_body(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.patch(
|
||||
"/me", json={}, headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
@@ -330,7 +329,7 @@ class TestUpdateMe:
|
||||
response_json = response.json()
|
||||
assert response_json["email"] == user.email
|
||||
|
||||
def test_valid_body(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_valid_body(self, test_app_client: TestClient, user: UserDB):
|
||||
json = {"email": "king.arthur@tintagel.bt"}
|
||||
response = test_app_client.patch(
|
||||
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
||||
@@ -340,9 +339,7 @@ class TestUpdateMe:
|
||||
response_json = response.json()
|
||||
assert response_json["email"] == "king.arthur@tintagel.bt"
|
||||
|
||||
def test_valid_body_is_superuser(
|
||||
self, test_app_client: TestClient, user: BaseUserDB
|
||||
):
|
||||
def test_valid_body_is_superuser(self, test_app_client: TestClient, user: UserDB):
|
||||
json = {"is_superuser": True}
|
||||
response = test_app_client.patch(
|
||||
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
||||
@@ -352,7 +349,7 @@ class TestUpdateMe:
|
||||
response_json = response.json()
|
||||
assert response_json["is_superuser"] is False
|
||||
|
||||
def test_valid_body_is_active(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_valid_body_is_active(self, test_app_client: TestClient, user: UserDB):
|
||||
json = {"is_active": False}
|
||||
response = test_app_client.patch(
|
||||
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
||||
@@ -363,7 +360,7 @@ class TestUpdateMe:
|
||||
assert response_json["is_active"] is True
|
||||
|
||||
def test_valid_body_password(
|
||||
self, mocker, mock_user_db, test_app_client: TestClient, user: BaseUserDB
|
||||
self, mocker, mock_user_db, test_app_client: TestClient, user: UserDB
|
||||
):
|
||||
mocker.spy(mock_user_db, "update")
|
||||
current_hashed_passord = user.hashed_password
|
||||
@@ -385,13 +382,13 @@ class TestListUsers:
|
||||
response = test_app_client.get("/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_regular_user(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_regular_user(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/", headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_superuser(self, test_app_client: TestClient, superuser: BaseUserDB):
|
||||
def test_superuser(self, test_app_client: TestClient, superuser: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/", headers={"Authorization": f"Bearer {superuser.id}"}
|
||||
)
|
||||
@@ -410,22 +407,20 @@ class TestGetUser:
|
||||
response = test_app_client.get("/000")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_regular_user(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_regular_user(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/000", headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_not_existing_user(
|
||||
self, test_app_client: TestClient, superuser: BaseUserDB
|
||||
):
|
||||
def test_not_existing_user(self, test_app_client: TestClient, superuser: UserDB):
|
||||
response = test_app_client.get(
|
||||
"/000", headers={"Authorization": f"Bearer {superuser.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
def test_superuser(
|
||||
self, test_app_client: TestClient, user: BaseUserDB, superuser: BaseUserDB
|
||||
self, test_app_client: TestClient, user: UserDB, superuser: UserDB
|
||||
):
|
||||
response = test_app_client.get(
|
||||
f"/{user.id}", headers={"Authorization": f"Bearer {superuser.id}"}
|
||||
@@ -443,22 +438,20 @@ class TestUpdateUser:
|
||||
response = test_app_client.patch("/000")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_regular_user(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_regular_user(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.patch(
|
||||
"/000", headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_not_existing_user(
|
||||
self, test_app_client: TestClient, superuser: BaseUserDB
|
||||
):
|
||||
def test_not_existing_user(self, test_app_client: TestClient, superuser: UserDB):
|
||||
response = test_app_client.patch(
|
||||
"/000", json={}, headers={"Authorization": f"Bearer {superuser.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
def test_empty_body(
|
||||
self, test_app_client: TestClient, user: BaseUserDB, superuser: BaseUserDB
|
||||
self, test_app_client: TestClient, user: UserDB, superuser: UserDB
|
||||
):
|
||||
response = test_app_client.patch(
|
||||
f"/{user.id}", json={}, headers={"Authorization": f"Bearer {superuser.id}"}
|
||||
@@ -469,7 +462,7 @@ class TestUpdateUser:
|
||||
assert response_json["email"] == user.email
|
||||
|
||||
def test_valid_body(
|
||||
self, test_app_client: TestClient, user: BaseUserDB, superuser: BaseUserDB
|
||||
self, test_app_client: TestClient, user: UserDB, superuser: UserDB
|
||||
):
|
||||
json = {"email": "king.arthur@tintagel.bt"}
|
||||
response = test_app_client.patch(
|
||||
@@ -483,7 +476,7 @@ class TestUpdateUser:
|
||||
assert response_json["email"] == "king.arthur@tintagel.bt"
|
||||
|
||||
def test_valid_body_is_superuser(
|
||||
self, test_app_client: TestClient, user: BaseUserDB, superuser: BaseUserDB
|
||||
self, test_app_client: TestClient, user: UserDB, superuser: UserDB
|
||||
):
|
||||
json = {"is_superuser": True}
|
||||
response = test_app_client.patch(
|
||||
@@ -497,7 +490,7 @@ class TestUpdateUser:
|
||||
assert response_json["is_superuser"] is True
|
||||
|
||||
def test_valid_body_is_active(
|
||||
self, test_app_client: TestClient, user: BaseUserDB, superuser: BaseUserDB
|
||||
self, test_app_client: TestClient, user: UserDB, superuser: UserDB
|
||||
):
|
||||
json = {"is_active": False}
|
||||
response = test_app_client.patch(
|
||||
@@ -515,8 +508,8 @@ class TestUpdateUser:
|
||||
mocker,
|
||||
mock_user_db,
|
||||
test_app_client: TestClient,
|
||||
user: BaseUserDB,
|
||||
superuser: BaseUserDB,
|
||||
user: UserDB,
|
||||
superuser: UserDB,
|
||||
):
|
||||
mocker.spy(mock_user_db, "update")
|
||||
current_hashed_passord = user.hashed_password
|
||||
@@ -540,15 +533,13 @@ class TestDeleteUser:
|
||||
response = test_app_client.delete("/000")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_regular_user(self, test_app_client: TestClient, user: BaseUserDB):
|
||||
def test_regular_user(self, test_app_client: TestClient, user: UserDB):
|
||||
response = test_app_client.delete(
|
||||
"/000", headers={"Authorization": f"Bearer {user.id}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_not_existing_user(
|
||||
self, test_app_client: TestClient, superuser: BaseUserDB
|
||||
):
|
||||
def test_not_existing_user(self, test_app_client: TestClient, superuser: UserDB):
|
||||
response = test_app_client.delete(
|
||||
"/000", headers={"Authorization": f"Bearer {superuser.id}"}
|
||||
)
|
||||
@@ -559,8 +550,8 @@ class TestDeleteUser:
|
||||
mocker,
|
||||
mock_user_db,
|
||||
test_app_client: TestClient,
|
||||
user: BaseUserDB,
|
||||
superuser: BaseUserDB,
|
||||
user: UserDB,
|
||||
superuser: UserDB,
|
||||
):
|
||||
mocker.spy(mock_user_db, "delete")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user