diff --git a/.all-contributorsrc b/.all-contributorsrc index 003e1a5d..920228b3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -741,6 +741,24 @@ "contributions": [ "doc" ] + }, + { + "login": "sashkent3", + "name": "Alexander Zinov", + "avatar_url": "https://avatars.githubusercontent.com/u/33320473?v=4", + "profile": "https://github.com/sashkent3", + "contributions": [ + "code" + ] + }, + { + "login": "nimaxin", + "name": "nimaxin", + "avatar_url": "https://avatars.githubusercontent.com/u/97331299?v=4", + "profile": "https://github.com/nimaxin", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7e04fad1..b10baaf6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,7 @@ updates: open-pull-requests-limit: 10 reviewers: - frankie567 +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98d7c83c..b660cfd4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,12 +8,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python_version: [3.8, 3.9, '3.10', '3.11', '3.12'] + python_version: [3.9, '3.10', '3.11', '3.12', '3.13'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} - name: Install dependencies @@ -28,12 +28,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python_version: [3.8, 3.9, '3.10', '3.11', '3.12'] + python_version: [3.9, '3.10', '3.11', '3.12', '3.13'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} - name: Install dependencies @@ -43,7 +43,7 @@ jobs: - name: Test run: | hatch run test:test-cov-xml - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true @@ -60,11 +60,11 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies shell: bash run: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0f71fea1..8e2bbfbd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,11 +38,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -69,4 +69,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 65adb395..58e83afd 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies shell: bash run: | diff --git a/README.md b/README.md index 460e7007..f1608701 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![PyPI version](https://badge.fury.io/py/fastapi-users.svg)](https://badge.fury.io/py/fastapi-users) [![Downloads](https://pepy.tech/badge/fastapi-users)](https://pepy.tech/project/fastapi-users) -[![All Contributors](https://img.shields.io/badge/all_contributors-79-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-81-orange.svg?style=flat-square)](#contributors-)

@@ -53,14 +53,13 @@ Add quickly a registration and authentication system to your [FastAPI](https://f ## In a hurry? Discover Fief, the open-source authentication platform

- Fief + Fief

-Fief +Fief **Implementing registration, login, social auth is hard and painful. We know it. With our highly secure and open-source users management platform, you can focus on your app while staying in control of your users data.** -* Based on **FastAPI Users**! * **Open-source**: self-host it for free * **Pre-built login and registration pages**: clean and fast authentication so you don't have to do it yourself * **Official Python client** with built-in **FastAPI integration** @@ -68,9 +67,9 @@ Add quickly a registration and authentication system to your [FastAPI](https://f

- +

-

It's free!

+

It's free and open-source

## Contributors and sponsors âœ¨â˜•ī¸ @@ -183,6 +182,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d raindata5
raindata5

📖 Mark Donnelly
Mark Donnelly

📖 + Alexander Zinov
Alexander Zinov

đŸ’ģ + nimaxin
nimaxin

📖 diff --git a/docs/configuration/authentication/strategies/database.md b/docs/configuration/authentication/strategies/database.md index 40594e21..ec82f2c7 100644 --- a/docs/configuration/authentication/strategies/database.md +++ b/docs/configuration/authentication/strategies/database.md @@ -1,6 +1,6 @@ # Database -The most natural way for storing tokens is of course the very same database you're using for your application. In this strategy, we set up a table (or collection) for storing those tokens with the associated user id. On each request, we try to retrive this token from the database to get the corresponding user id. +The most natural way for storing tokens is of course the very same database you're using for your application. In this strategy, we set up a table (or collection) for storing those tokens with the associated user id. On each request, we try to retrieve this token from the database to get the corresponding user id. ## Configuration diff --git a/docs/configuration/databases/sqlalchemy.md b/docs/configuration/databases/sqlalchemy.md index 77cd0dad..14e4bc7c 100644 --- a/docs/configuration/databases/sqlalchemy.md +++ b/docs/configuration/databases/sqlalchemy.md @@ -11,8 +11,8 @@ To work with your DBMS, you'll need to install the corresponding asyncio driver. Examples of `DB_URL`s are: -* PostgreSQL: `engine = create_engine('postgresql+asyncpg://user:password@host:port/name')` -* SQLite: `engine = create_engine('sqlite+aiosqlite:///name.db')` +* PostgreSQL: `postgresql+asyncpg://user:password@host:port/name` +* SQLite: `sqlite+aiosqlite:///name.db` For the sake of this tutorial from now on, we'll use a simple SQLite database. diff --git a/docs/src/db_beanie_oauth.py b/docs/src/db_beanie_oauth.py index e7179a6d..b1786869 100644 --- a/docs/src/db_beanie_oauth.py +++ b/docs/src/db_beanie_oauth.py @@ -1,5 +1,3 @@ -from typing import List - import motor.motor_asyncio from beanie import Document from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase @@ -17,7 +15,7 @@ class OAuthAccount(BaseOAuthAccount): class User(BeanieBaseUser, Document): - oauth_accounts: List[OAuthAccount] = Field(default_factory=list) + oauth_accounts: list[OAuthAccount] = Field(default_factory=list) async def get_user_db(): diff --git a/docs/src/db_sqlalchemy.py b/docs/src/db_sqlalchemy.py index 1ff831d3..1bff0fd6 100644 --- a/docs/src/db_sqlalchemy.py +++ b/docs/src/db_sqlalchemy.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator from fastapi import Depends from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase diff --git a/docs/src/db_sqlalchemy_access_tokens.py b/docs/src/db_sqlalchemy_access_tokens.py index 97fc20bd..79c75816 100644 --- a/docs/src/db_sqlalchemy_access_tokens.py +++ b/docs/src/db_sqlalchemy_access_tokens.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator from fastapi import Depends from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase diff --git a/docs/src/db_sqlalchemy_oauth.py b/docs/src/db_sqlalchemy_oauth.py index 827dc38a..93a38e11 100644 --- a/docs/src/db_sqlalchemy_oauth.py +++ b/docs/src/db_sqlalchemy_oauth.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator, List +from collections.abc import AsyncGenerator from fastapi import Depends from fastapi_users.db import ( @@ -21,7 +21,7 @@ class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base): class User(SQLAlchemyBaseUserTableUUID, Base): - oauth_accounts: Mapped[List[OAuthAccount]] = relationship( + oauth_accounts: Mapped[list[OAuthAccount]] = relationship( "OAuthAccount", lazy="joined" ) diff --git a/examples/beanie-oauth/app/db.py b/examples/beanie-oauth/app/db.py index e7179a6d..b1786869 100644 --- a/examples/beanie-oauth/app/db.py +++ b/examples/beanie-oauth/app/db.py @@ -1,5 +1,3 @@ -from typing import List - import motor.motor_asyncio from beanie import Document from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase @@ -17,7 +15,7 @@ class OAuthAccount(BaseOAuthAccount): class User(BeanieBaseUser, Document): - oauth_accounts: List[OAuthAccount] = Field(default_factory=list) + oauth_accounts: list[OAuthAccount] = Field(default_factory=list) async def get_user_db(): diff --git a/examples/beanie-oauth/app/users.py b/examples/beanie-oauth/app/users.py index 81ad5ca4..cd7097c4 100644 --- a/examples/beanie-oauth/app/users.py +++ b/examples/beanie-oauth/app/users.py @@ -3,7 +3,7 @@ from beanie import PydanticObjectId from fastapi import Depends, Request -from fastapi_users import BaseUserManager, FastAPIUsers +from fastapi_users import BaseUserManager, FastAPIUsers, models from fastapi_users.authentication import ( AuthenticationBackend, BearerTransport, @@ -47,7 +47,7 @@ async def get_user_manager(user_db: BeanieUserDatabase = Depends(get_user_db)): bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") -def get_jwt_strategy() -> JWTStrategy: +def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]: return JWTStrategy(secret=SECRET, lifetime_seconds=3600) diff --git a/examples/sqlalchemy-oauth/app/db.py b/examples/sqlalchemy-oauth/app/db.py index 827dc38a..93a38e11 100644 --- a/examples/sqlalchemy-oauth/app/db.py +++ b/examples/sqlalchemy-oauth/app/db.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator, List +from collections.abc import AsyncGenerator from fastapi import Depends from fastapi_users.db import ( @@ -21,7 +21,7 @@ class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base): class User(SQLAlchemyBaseUserTableUUID, Base): - oauth_accounts: Mapped[List[OAuthAccount]] = relationship( + oauth_accounts: Mapped[list[OAuthAccount]] = relationship( "OAuthAccount", lazy="joined" ) diff --git a/examples/sqlalchemy-oauth/app/users.py b/examples/sqlalchemy-oauth/app/users.py index 0b61b2a5..a7337e7f 100644 --- a/examples/sqlalchemy-oauth/app/users.py +++ b/examples/sqlalchemy-oauth/app/users.py @@ -3,7 +3,7 @@ from typing import Optional from fastapi import Depends, Request -from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin +from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models from fastapi_users.authentication import ( AuthenticationBackend, BearerTransport, @@ -47,7 +47,7 @@ async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") -def get_jwt_strategy() -> JWTStrategy: +def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]: return JWTStrategy(secret=SECRET, lifetime_seconds=3600) diff --git a/examples/sqlalchemy/app/db.py b/examples/sqlalchemy/app/db.py index 1ff831d3..1bff0fd6 100644 --- a/examples/sqlalchemy/app/db.py +++ b/examples/sqlalchemy/app/db.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator from fastapi import Depends from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase diff --git a/examples/sqlalchemy/app/users.py b/examples/sqlalchemy/app/users.py index 479c49e2..f37f0ac3 100644 --- a/examples/sqlalchemy/app/users.py +++ b/examples/sqlalchemy/app/users.py @@ -2,7 +2,7 @@ from typing import Optional from fastapi import Depends, Request -from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin +from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models from fastapi_users.authentication import ( AuthenticationBackend, BearerTransport, @@ -40,7 +40,7 @@ async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") -def get_jwt_strategy() -> JWTStrategy: +def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]: return JWTStrategy(secret=SECRET, lifetime_seconds=3600) diff --git a/fastapi_users/__init__.py b/fastapi_users/__init__.py index c0aab03c..0c213994 100644 --- a/fastapi_users/__init__.py +++ b/fastapi_users/__init__.py @@ -1,6 +1,6 @@ """Ready-to-use and customizable users management for FastAPI.""" -__version__ = "13.0.0" +__version__ = "14.0.1" from fastapi_users import models, schemas # noqa: F401 from fastapi_users.exceptions import InvalidID, InvalidPasswordException diff --git a/fastapi_users/authentication/authenticator.py b/fastapi_users/authentication/authenticator.py index 7fab4b78..bc30a0bf 100644 --- a/fastapi_users/authentication/authenticator.py +++ b/fastapi_users/authentication/authenticator.py @@ -1,6 +1,7 @@ import re +from collections.abc import Sequence from inspect import Parameter, Signature -from typing import Callable, List, Optional, Sequence, Tuple, cast +from typing import Any, Callable, Generic, Optional, cast from fastapi import Depends, HTTPException, status from makefun import with_signature @@ -31,10 +32,12 @@ class DuplicateBackendNamesError(Exception): pass -EnabledBackendsDependency = DependencyCallable[Sequence[AuthenticationBackend]] +EnabledBackendsDependency = DependencyCallable[ + Sequence[AuthenticationBackend[models.UP, models.ID]] +] -class Authenticator: +class Authenticator(Generic[models.UP, models.ID]): """ Provides dependency callables to retrieve authenticated user. @@ -46,11 +49,11 @@ class Authenticator: :param get_user_manager: User manager dependency callable. """ - backends: Sequence[AuthenticationBackend] + backends: Sequence[AuthenticationBackend[models.UP, models.ID]] def __init__( self, - backends: Sequence[AuthenticationBackend], + backends: Sequence[AuthenticationBackend[models.UP, models.ID]], get_user_manager: UserManagerDependency[models.UP, models.ID], ): self.backends = backends @@ -62,7 +65,9 @@ def current_user_token( active: bool = False, verified: bool = False, superuser: bool = False, - get_enabled_backends: Optional[EnabledBackendsDependency] = None, + get_enabled_backends: Optional[ + EnabledBackendsDependency[models.UP, models.ID] + ] = None, ): """ Return a dependency callable to retrieve currently authenticated user and token. @@ -88,7 +93,7 @@ def current_user_token( signature = self._get_dependency_signature(get_enabled_backends) @with_signature(signature) - async def current_user_token_dependency(*args, **kwargs): + async def current_user_token_dependency(*args: Any, **kwargs: Any): return await self._authenticate( *args, optional=optional, @@ -106,7 +111,9 @@ def current_user( active: bool = False, verified: bool = False, superuser: bool = False, - get_enabled_backends: Optional[EnabledBackendsDependency] = None, + get_enabled_backends: Optional[ + EnabledBackendsDependency[models.UP, models.ID] + ] = None, ): """ Return a dependency callable to retrieve currently authenticated user. @@ -132,7 +139,7 @@ def current_user( signature = self._get_dependency_signature(get_enabled_backends) @with_signature(signature) - async def current_user_dependency(*args, **kwargs): + async def current_user_dependency(*args: Any, **kwargs: Any): user, _ = await self._authenticate( *args, optional=optional, @@ -154,11 +161,11 @@ async def _authenticate( verified: bool = False, superuser: bool = False, **kwargs, - ) -> Tuple[Optional[models.UP], Optional[str]]: + ) -> tuple[Optional[models.UP], Optional[str]]: user: Optional[models.UP] = None token: Optional[str] = None - enabled_backends: Sequence[AuthenticationBackend] = kwargs.get( - "enabled_backends", self.backends + enabled_backends: Sequence[AuthenticationBackend[models.UP, models.ID]] = ( + kwargs.get("enabled_backends", self.backends) ) for backend in self.backends: if backend in enabled_backends: @@ -197,7 +204,7 @@ def _get_dependency_signature( This way, each security schemes are detected by the OpenAPI generator. """ try: - parameters: List[Parameter] = [ + parameters: list[Parameter] = [ Parameter( name="user_manager", kind=Parameter.POSITIONAL_OR_KEYWORD, diff --git a/fastapi_users/authentication/strategy/db/adapter.py b/fastapi_users/authentication/strategy/db/adapter.py index 6ed1a02b..c5b999b2 100644 --- a/fastapi_users/authentication/strategy/db/adapter.py +++ b/fastapi_users/authentication/strategy/db/adapter.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, Generic, Optional, Protocol +from typing import Any, Generic, Optional, Protocol from fastapi_users.authentication.strategy.db.models import AP @@ -13,11 +13,11 @@ async def get_by_token( """Get a single access token by token.""" ... # pragma: no cover - async def create(self, create_dict: Dict[str, Any]) -> AP: + async def create(self, create_dict: dict[str, Any]) -> AP: """Create an access token.""" ... # pragma: no cover - async def update(self, access_token: AP, update_dict: Dict[str, Any]) -> AP: + async def update(self, access_token: AP, update_dict: dict[str, Any]) -> AP: """Update an access token.""" ... # pragma: no cover diff --git a/fastapi_users/authentication/strategy/db/strategy.py b/fastapi_users/authentication/strategy/db/strategy.py index d7c3c7a3..da438438 100644 --- a/fastapi_users/authentication/strategy/db/strategy.py +++ b/fastapi_users/authentication/strategy/db/strategy.py @@ -1,6 +1,6 @@ import secrets from datetime import datetime, timedelta, timezone -from typing import Any, Dict, Generic, Optional +from typing import Any, Generic, Optional from fastapi_users import exceptions, models from fastapi_users.authentication.strategy.base import Strategy @@ -50,6 +50,6 @@ async def destroy_token(self, token: str, user: models.UP) -> None: if access_token is not None: await self.database.delete(access_token) - def _create_access_token_dict(self, user: models.UP) -> Dict[str, Any]: + def _create_access_token_dict(self, user: models.UP) -> dict[str, Any]: token = secrets.token_urlsafe() return {"token": token, "user_id": user.id} diff --git a/fastapi_users/authentication/strategy/jwt.py b/fastapi_users/authentication/strategy/jwt.py index bf3fd903..d790ca79 100644 --- a/fastapi_users/authentication/strategy/jwt.py +++ b/fastapi_users/authentication/strategy/jwt.py @@ -1,4 +1,4 @@ -from typing import Generic, List, Optional +from typing import Generic, Optional import jwt @@ -22,7 +22,7 @@ def __init__( self, secret: SecretType, lifetime_seconds: Optional[int], - token_audience: List[str] = ["fastapi-users:auth"], + token_audience: list[str] = ["fastapi-users:auth"], algorithm: str = "HS256", public_key: Optional[SecretType] = None, ): diff --git a/fastapi_users/db/base.py b/fastapi_users/db/base.py index 5c0b675c..c90ad6b2 100644 --- a/fastapi_users/db/base.py +++ b/fastapi_users/db/base.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Generic, Optional +from typing import Any, Generic, Optional from fastapi_users.models import ID, OAP, UOAP, UP from fastapi_users.types import DependencyCallable @@ -19,11 +19,11 @@ async def get_by_oauth_account(self, oauth: str, account_id: str) -> Optional[UP """Get a single user by OAuth account id.""" raise NotImplementedError() - async def create(self, create_dict: Dict[str, Any]) -> UP: + async def create(self, create_dict: dict[str, Any]) -> UP: """Create a user.""" raise NotImplementedError() - async def update(self, user: UP, update_dict: Dict[str, Any]) -> UP: + async def update(self, user: UP, update_dict: dict[str, Any]) -> UP: """Update a user.""" raise NotImplementedError() @@ -32,7 +32,7 @@ async def delete(self, user: UP) -> None: raise NotImplementedError() async def add_oauth_account( - self: "BaseUserDatabase[UOAP, ID]", user: UOAP, create_dict: Dict[str, Any] + self: "BaseUserDatabase[UOAP, ID]", user: UOAP, create_dict: dict[str, Any] ) -> UOAP: """Create an OAuth account and add it to the user.""" raise NotImplementedError() @@ -41,7 +41,7 @@ async def update_oauth_account( self: "BaseUserDatabase[UOAP, ID]", user: UOAP, oauth_account: OAP, - update_dict: Dict[str, Any], + update_dict: dict[str, Any], ) -> UOAP: """Update an OAuth account on a user.""" raise NotImplementedError() diff --git a/fastapi_users/fastapi_users.py b/fastapi_users/fastapi_users.py index 10ca1465..1161d9f3 100644 --- a/fastapi_users/fastapi_users.py +++ b/fastapi_users/fastapi_users.py @@ -1,4 +1,5 @@ -from typing import Generic, Optional, Sequence, Type +from collections.abc import Sequence +from typing import Generic, Optional from fastapi import APIRouter @@ -20,7 +21,7 @@ from fastapi_users.router import get_oauth_router from fastapi_users.router.oauth import get_oauth_associate_router except ModuleNotFoundError: # pragma: no cover - BaseOAuth2 = Type # type: ignore + BaseOAuth2 = type # type: ignore class FastAPIUsers(Generic[models.UP, models.ID]): @@ -35,19 +36,19 @@ class FastAPIUsers(Generic[models.UP, models.ID]): with a specific set of parameters. """ - authenticator: Authenticator + authenticator: Authenticator[models.UP, models.ID] def __init__( self, get_user_manager: UserManagerDependency[models.UP, models.ID], - auth_backends: Sequence[AuthenticationBackend], + auth_backends: Sequence[AuthenticationBackend[models.UP, models.ID]], ): self.authenticator = Authenticator(auth_backends, get_user_manager) self.get_user_manager = get_user_manager self.current_user = self.authenticator.current_user def get_register_router( - self, user_schema: Type[schemas.U], user_create_schema: Type[schemas.UC] + self, user_schema: type[schemas.U], user_create_schema: type[schemas.UC] ) -> APIRouter: """ Return a router with a register route. @@ -59,7 +60,7 @@ def get_register_router( self.get_user_manager, user_schema, user_create_schema ) - def get_verify_router(self, user_schema: Type[schemas.U]) -> APIRouter: + def get_verify_router(self, user_schema: type[schemas.U]) -> APIRouter: """ Return a router with e-mail verification routes. @@ -72,7 +73,9 @@ def get_reset_password_router(self) -> APIRouter: return get_reset_password_router(self.get_user_manager) def get_auth_router( - self, backend: AuthenticationBackend, requires_verification: bool = False + self, + backend: AuthenticationBackend[models.UP, models.ID], + requires_verification: bool = False, ) -> APIRouter: """ Return an auth router for a given authentication backend. @@ -91,7 +94,7 @@ def get_auth_router( def get_oauth_router( self, oauth_client: BaseOAuth2, - backend: AuthenticationBackend, + backend: AuthenticationBackend[models.UP, models.ID], state_secret: SecretType, redirect_url: Optional[str] = None, associate_by_email: bool = False, @@ -124,7 +127,7 @@ def get_oauth_router( def get_oauth_associate_router( self, oauth_client: BaseOAuth2, - user_schema: Type[schemas.U], + user_schema: type[schemas.U], state_secret: SecretType, redirect_url: Optional[str] = None, requires_verification: bool = False, @@ -152,8 +155,8 @@ def get_oauth_associate_router( def get_users_router( self, - user_schema: Type[schemas.U], - user_update_schema: Type[schemas.UU], + user_schema: type[schemas.U], + user_update_schema: type[schemas.UU], requires_verification: bool = False, ) -> APIRouter: """ diff --git a/fastapi_users/jwt.py b/fastapi_users/jwt.py index 4278ee8f..05214101 100644 --- a/fastapi_users/jwt.py +++ b/fastapi_users/jwt.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta, timezone -from typing import Any, Dict, List, Optional, Union +from typing import Any, Optional, Union import jwt from pydantic import SecretStr @@ -30,9 +30,9 @@ def generate_jwt( def decode_jwt( encoded_jwt: str, secret: SecretType, - audience: List[str], - algorithms: List[str] = [JWT_ALGORITHM], -) -> Dict[str, Any]: + audience: list[str], + algorithms: list[str] = [JWT_ALGORITHM], +) -> dict[str, Any]: return jwt.decode( encoded_jwt, _get_secret_value(secret), diff --git a/fastapi_users/manager.py b/fastapi_users/manager.py index 65da8113..7d783b75 100644 --- a/fastapi_users/manager.py +++ b/fastapi_users/manager.py @@ -1,5 +1,5 @@ import uuid -from typing import Any, Dict, Generic, Optional, Union +from typing import Any, Generic, Optional, Union import jwt from fastapi import Request, Response @@ -514,7 +514,7 @@ async def on_after_register( async def on_after_update( self, user: models.UP, - update_dict: Dict[str, Any], + update_dict: dict[str, Any], request: Optional[Request] = None, ) -> None: """ @@ -662,7 +662,7 @@ async def authenticate( return user - async def _update(self, user: models.UP, update_dict: Dict[str, Any]) -> models.UP: + async def _update(self, user: models.UP, update_dict: dict[str, Any]) -> models.UP: validated_update_dict = {} for field, value in update_dict.items(): if field == "email" and value != user.email: diff --git a/fastapi_users/models.py b/fastapi_users/models.py index b65b64bd..16680b40 100644 --- a/fastapi_users/models.py +++ b/fastapi_users/models.py @@ -1,4 +1,4 @@ -from typing import Generic, List, Optional, Protocol, TypeVar +from typing import Generic, Optional, Protocol, TypeVar ID = TypeVar("ID") @@ -39,7 +39,7 @@ class UserOAuthProtocol(UserProtocol[ID], Generic[ID, OAP]): is_active: bool is_superuser: bool is_verified: bool - oauth_accounts: List[OAP] + oauth_accounts: list[OAP] UOAP = TypeVar("UOAP", bound=UserOAuthProtocol) diff --git a/fastapi_users/openapi.py b/fastapi_users/openapi.py index 98e3c063..68a5d67a 100644 --- a/fastapi_users/openapi.py +++ b/fastapi_users/openapi.py @@ -1,3 +1,3 @@ -from typing import Any, Dict, Union +from typing import Any, Union -OpenAPIResponseType = Dict[Union[int, str], Dict[str, Any]] +OpenAPIResponseType = dict[Union[int, str], dict[str, Any]] diff --git a/fastapi_users/password.py b/fastapi_users/password.py index 4dbb4c95..71cbd2b2 100644 --- a/fastapi_users/password.py +++ b/fastapi_users/password.py @@ -1,5 +1,5 @@ import secrets -from typing import Optional, Protocol, Tuple, Union +from typing import Optional, Protocol, Union from pwdlib import PasswordHash from pwdlib.hashers.argon2 import Argon2Hasher @@ -9,7 +9,7 @@ class PasswordHelperProtocol(Protocol): def verify_and_update( self, plain_password: str, hashed_password: str - ) -> Tuple[bool, Union[str, None]]: ... # pragma: no cover + ) -> tuple[bool, Union[str, None]]: ... # pragma: no cover def hash(self, password: str) -> str: ... # pragma: no cover @@ -30,7 +30,7 @@ def __init__(self, password_hash: Optional[PasswordHash] = None) -> None: def verify_and_update( self, plain_password: str, hashed_password: str - ) -> Tuple[bool, Union[str, None]]: + ) -> tuple[bool, Union[str, None]]: return self.password_hash.verify_and_update(plain_password, hashed_password) def hash(self, password: str) -> str: diff --git a/fastapi_users/router/auth.py b/fastapi_users/router/auth.py index c61770f0..7097b76d 100644 --- a/fastapi_users/router/auth.py +++ b/fastapi_users/router/auth.py @@ -1,5 +1,3 @@ -from typing import Tuple - from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.security import OAuth2PasswordRequestForm @@ -11,9 +9,9 @@ def get_auth_router( - backend: AuthenticationBackend, + backend: AuthenticationBackend[models.UP, models.ID], get_user_manager: UserManagerDependency[models.UP, models.ID], - authenticator: Authenticator, + authenticator: Authenticator[models.UP, models.ID], requires_verification: bool = False, ) -> APIRouter: """Generate a router with login/logout routes for an authentication backend.""" @@ -83,7 +81,7 @@ async def login( "/logout", name=f"auth:{backend.name}.logout", responses=logout_responses ) async def logout( - user_token: Tuple[models.UP, str] = Depends(get_current_user_token), + user_token: tuple[models.UP, str] = Depends(get_current_user_token), strategy: Strategy[models.UP, models.ID] = Depends(backend.get_strategy), ): user, token = user_token diff --git a/fastapi_users/router/common.py b/fastapi_users/router/common.py index 8a329485..51441e85 100644 --- a/fastapi_users/router/common.py +++ b/fastapi_users/router/common.py @@ -1,11 +1,11 @@ from enum import Enum -from typing import Dict, Union +from typing import Union from pydantic import BaseModel class ErrorModel(BaseModel): - detail: Union[str, Dict[str, str]] + detail: Union[str, dict[str, str]] class ErrorCodeReasonModel(BaseModel): diff --git a/fastapi_users/router/oauth.py b/fastapi_users/router/oauth.py index 9300c603..b14aa95f 100644 --- a/fastapi_users/router/oauth.py +++ b/fastapi_users/router/oauth.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Tuple, Type +from typing import Optional import jwt from fastapi import APIRouter, Depends, HTTPException, Query, Request, status @@ -21,7 +21,7 @@ class OAuth2AuthorizeResponse(BaseModel): def generate_state_token( - data: Dict[str, str], secret: SecretType, lifetime_seconds: int = 3600 + data: dict[str, str], secret: SecretType, lifetime_seconds: int = 3600 ) -> str: data["aud"] = STATE_TOKEN_AUDIENCE return generate_jwt(data, secret, lifetime_seconds) @@ -29,7 +29,7 @@ def generate_state_token( def get_oauth_router( oauth_client: BaseOAuth2, - backend: AuthenticationBackend, + backend: AuthenticationBackend[models.UP, models.ID], get_user_manager: UserManagerDependency[models.UP, models.ID], state_secret: SecretType, redirect_url: Optional[str] = None, @@ -57,14 +57,14 @@ def get_oauth_router( response_model=OAuth2AuthorizeResponse, ) async def authorize( - request: Request, scopes: List[str] = Query(None) + request: Request, scopes: list[str] = Query(None) ) -> OAuth2AuthorizeResponse: if redirect_url is not None: authorize_redirect_url = redirect_url else: authorize_redirect_url = str(request.url_for(callback_route_name)) - state_data: Dict[str, str] = {} + state_data: dict[str, str] = {} state = generate_state_token(state_data, state_secret) authorization_url = await oauth_client.get_authorization_url( authorize_redirect_url, @@ -100,7 +100,7 @@ async def authorize( ) async def callback( request: Request, - access_token_state: Tuple[OAuth2Token, str] = Depends( + access_token_state: tuple[OAuth2Token, str] = Depends( oauth2_authorize_callback ), user_manager: BaseUserManager[models.UP, models.ID] = Depends(get_user_manager), @@ -156,9 +156,9 @@ async def callback( def get_oauth_associate_router( oauth_client: BaseOAuth2, - authenticator: Authenticator, + authenticator: Authenticator[models.UP, models.ID], get_user_manager: UserManagerDependency[models.UP, models.ID], - user_schema: Type[schemas.U], + user_schema: type[schemas.U], state_secret: SecretType, redirect_url: Optional[str] = None, requires_verification: bool = False, @@ -190,7 +190,7 @@ def get_oauth_associate_router( ) async def authorize( request: Request, - scopes: List[str] = Query(None), + scopes: list[str] = Query(None), user: models.UP = Depends(get_current_active_user), ) -> OAuth2AuthorizeResponse: if redirect_url is not None: @@ -198,7 +198,7 @@ async def authorize( else: authorize_redirect_url = str(request.url_for(callback_route_name)) - state_data: Dict[str, str] = {"sub": str(user.id)} + state_data: dict[str, str] = {"sub": str(user.id)} state = generate_state_token(state_data, state_secret) authorization_url = await oauth_client.get_authorization_url( authorize_redirect_url, @@ -232,7 +232,7 @@ async def authorize( async def callback( request: Request, user: models.UP = Depends(get_current_active_user), - access_token_state: Tuple[OAuth2Token, str] = Depends( + access_token_state: tuple[OAuth2Token, str] = Depends( oauth2_authorize_callback ), user_manager: BaseUserManager[models.UP, models.ID] = Depends(get_user_manager), diff --git a/fastapi_users/router/register.py b/fastapi_users/router/register.py index 33facd46..7f3c9c7e 100644 --- a/fastapi_users/router/register.py +++ b/fastapi_users/router/register.py @@ -1,5 +1,3 @@ -from typing import Type - from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi_users import exceptions, models, schemas @@ -9,8 +7,8 @@ def get_register_router( get_user_manager: UserManagerDependency[models.UP, models.ID], - user_schema: Type[schemas.U], - user_create_schema: Type[schemas.UC], + user_schema: type[schemas.U], + user_create_schema: type[schemas.UC], ) -> APIRouter: """Generate a router with the register route.""" router = APIRouter() diff --git a/fastapi_users/router/users.py b/fastapi_users/router/users.py index b3cc4351..d6c21e18 100644 --- a/fastapi_users/router/users.py +++ b/fastapi_users/router/users.py @@ -1,5 +1,3 @@ -from typing import Type - from fastapi import APIRouter, Depends, HTTPException, Request, Response, status from fastapi_users import exceptions, models, schemas @@ -10,9 +8,9 @@ def get_users_router( get_user_manager: UserManagerDependency[models.UP, models.ID], - user_schema: Type[schemas.U], - user_update_schema: Type[schemas.UU], - authenticator: Authenticator, + user_schema: type[schemas.U], + user_update_schema: type[schemas.UU], + authenticator: Authenticator[models.UP, models.ID], requires_verification: bool = False, ) -> APIRouter: """Generate a router with the authentication routes.""" diff --git a/fastapi_users/router/verify.py b/fastapi_users/router/verify.py index 299bdc19..33a87853 100644 --- a/fastapi_users/router/verify.py +++ b/fastapi_users/router/verify.py @@ -1,5 +1,3 @@ -from typing import Type - from fastapi import APIRouter, Body, Depends, HTTPException, Request, status from pydantic import EmailStr @@ -10,7 +8,7 @@ def get_verify_router( get_user_manager: UserManagerDependency[models.UP, models.ID], - user_schema: Type[schemas.U], + user_schema: type[schemas.U], ): router = APIRouter() diff --git a/fastapi_users/schemas.py b/fastapi_users/schemas.py index 1a618410..8cc7f1b2 100644 --- a/fastapi_users/schemas.py +++ b/fastapi_users/schemas.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Generic, List, Optional, Type, TypeVar +from typing import Any, Generic, Optional, TypeVar from pydantic import BaseModel, ConfigDict, EmailStr from pydantic.version import VERSION as PYDANTIC_VERSION @@ -11,18 +11,18 @@ if PYDANTIC_V2: # pragma: no cover - def model_dump(model: BaseModel, *args, **kwargs) -> Dict[str, Any]: + def model_dump(model: BaseModel, *args, **kwargs) -> dict[str, Any]: return model.model_dump(*args, **kwargs) # type: ignore - def model_validate(schema: Type[SCHEMA], obj: Any, *args, **kwargs) -> SCHEMA: + def model_validate(schema: type[SCHEMA], obj: Any, *args, **kwargs) -> SCHEMA: return schema.model_validate(obj, *args, **kwargs) # type: ignore else: # pragma: no cover # type: ignore - def model_dump(model: BaseModel, *args, **kwargs) -> Dict[str, Any]: + def model_dump(model: BaseModel, *args, **kwargs) -> dict[str, Any]: return model.dict(*args, **kwargs) # type: ignore - def model_validate(schema: Type[SCHEMA], obj: Any, *args, **kwargs) -> SCHEMA: + def model_validate(schema: type[SCHEMA], obj: Any, *args, **kwargs) -> SCHEMA: return schema.from_orm(obj) # type: ignore @@ -104,4 +104,4 @@ class Config: class BaseOAuthAccountMixin(BaseModel): """Adds OAuth accounts list to a User model.""" - oauth_accounts: List[BaseOAuthAccount] = [] + oauth_accounts: list[BaseOAuthAccount] = [] diff --git a/fastapi_users/types.py b/fastapi_users/types.py index 29c0c5c2..94d3c724 100644 --- a/fastapi_users/types.py +++ b/fastapi_users/types.py @@ -1,12 +1,5 @@ -from typing import ( - AsyncGenerator, - AsyncIterator, - Callable, - Coroutine, - Generator, - TypeVar, - Union, -) +from collections.abc import AsyncGenerator, AsyncIterator, Coroutine, Generator +from typing import Callable, TypeVar, Union RETURN_TYPE = TypeVar("RETURN_TYPE") diff --git a/mkdocs.yml b/mkdocs.yml index 56344532..9e61c6d4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,7 +28,7 @@ theme: - search.highlight - content.code.annotate -repo_name: frankie567/fastapi-users +repo_name: fastapi-users/fastapi-users repo_url: https://github.com/fastapi-users/fastapi-users edit_uri: "" diff --git a/pyproject.toml b/pyproject.toml index 3f17d6a7..d346869f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ ignore_missing_imports = true [tool.pytest.ini_options] asyncio_mode = "auto" addopts = "--ignore=test_build.py" +asyncio_default_fixture_loop_scope = "session" markers = [ "authentication", "db", @@ -32,7 +33,7 @@ markers = [ ] [tool.ruff] -target-version = "py38" +target-version = "py39" [tool.ruff.lint] extend-select = ["UP", "TRY"] @@ -48,6 +49,7 @@ commit_extra_args = ["-e"] path = "fastapi_users/__init__.py" [tool.hatch.envs.default] +installer = "uv" features = [ "sqlalchemy", "beanie", @@ -131,30 +133,30 @@ classifiers = [ "Framework :: FastAPI", "Framework :: AsyncIO", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3 :: Only", "Topic :: Internet :: WWW/HTTP :: Session", ] -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "fastapi >=0.65.2", - "pwdlib[argon2,bcrypt] ==0.2.0", - "email-validator >=1.1.0,<2.2", - "pyjwt[crypto] ==2.8.0", - "python-multipart ==0.0.9", + "pwdlib[argon2,bcrypt] ==0.2.1", + "email-validator >=1.1.0,<2.3", + "pyjwt[crypto] ==2.10.1", + "python-multipart ==0.0.20", "makefun >=1.11.2,<2.0.0", ] [project.optional-dependencies] sqlalchemy = [ - "fastapi-users-db-sqlalchemy >=6.0.0", + "fastapi-users-db-sqlalchemy >=7.0.0", ] beanie = [ - "fastapi-users-db-beanie >=3.0.0", + "fastapi-users-db-beanie >=4.0.0", ] oauth = [ "httpx-oauth >=0.13" diff --git a/tests/conftest.py b/tests/conftest.py index 0cf63a46..d7e47ecf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,17 +1,8 @@ import asyncio import dataclasses import uuid -from typing import ( - Any, - AsyncGenerator, - Callable, - Dict, - Generic, - List, - Optional, - Type, - Union, -) +from collections.abc import AsyncGenerator +from typing import Any, Callable, Generic, Optional, Union from unittest.mock import MagicMock import httpx @@ -66,7 +57,7 @@ class OAuthAccountModel(models.OAuthAccountProtocol[IDType]): @dataclasses.dataclass class UserOAuthModel(UserModel): - oauth_accounts: List[OAuthAccountModel] = dataclasses.field(default_factory=list) + oauth_accounts: list[OAuthAccountModel] = dataclasses.field(default_factory=list) class User(schemas.BaseUser[IDType]): @@ -344,11 +335,11 @@ async def get_by_email(self, email: str) -> Optional[UserModel]: return verified_superuser return None - async def create(self, create_dict: Dict[str, Any]) -> UserModel: + async def create(self, create_dict: dict[str, Any]) -> UserModel: return UserModel(**create_dict) async def update( - self, user: UserModel, update_dict: Dict[str, Any] + self, user: UserModel, update_dict: dict[str, Any] ) -> UserModel: for field, value in update_dict.items(): setattr(user, field, value) @@ -414,11 +405,11 @@ async def get_by_oauth_account( return inactive_user_oauth return None - async def create(self, create_dict: Dict[str, Any]) -> UserOAuthModel: + async def create(self, create_dict: dict[str, Any]) -> UserOAuthModel: return UserOAuthModel(**create_dict) async def update( - self, user: UserOAuthModel, update_dict: Dict[str, Any] + self, user: UserOAuthModel, update_dict: dict[str, Any] ) -> UserOAuthModel: for field, value in update_dict.items(): setattr(user, field, value) @@ -428,7 +419,7 @@ async def delete(self, user: UserOAuthModel) -> None: pass async def add_oauth_account( - self, user: UserOAuthModel, create_dict: Dict[str, Any] + self, user: UserOAuthModel, create_dict: dict[str, Any] ) -> UserOAuthModel: oauth_account = OAuthAccountModel(**create_dict) user.oauth_accounts.append(oauth_account) @@ -438,7 +429,7 @@ async def update_oauth_account( # type: ignore self, user: UserOAuthModel, oauth_account: OAuthAccountModel, - update_dict: Dict[str, Any], + update_dict: dict[str, Any], ) -> UserOAuthModel: for field, value in update_dict.items(): setattr(oauth_account, field, value) @@ -458,7 +449,7 @@ async def update_oauth_account( # type: ignore @pytest.fixture def make_user_manager(mocker: MockerFixture): - def _make_user_manager(user_manager_class: Type[BaseTestUserManager], mock_user_db): + def _make_user_manager(user_manager_class: type[BaseTestUserManager], mock_user_db): user_manager = user_manager_class(mock_user_db) mocker.spy(user_manager, "get_by_email") mocker.spy(user_manager, "request_verify") @@ -555,7 +546,7 @@ def get_test_client(): async def _get_test_client(app: FastAPI) -> AsyncGenerator[httpx.AsyncClient, None]: async with LifespanManager(app): async with httpx.AsyncClient( - app=app, base_url="http://app.io" + transport=httpx.ASGITransport(app), base_url="http://app.io" ) as test_client: yield test_client diff --git a/tests/test_authentication_authenticator.py b/tests/test_authentication_authenticator.py index baec8724..a7b04d22 100644 --- a/tests/test_authentication_authenticator.py +++ b/tests/test_authentication_authenticator.py @@ -1,4 +1,5 @@ -from typing import AsyncGenerator, Generic, List, Optional, Sequence +from collections.abc import AsyncGenerator, Sequence +from typing import Generic, Optional import httpx import pytest @@ -70,7 +71,7 @@ def _get_backend_user(name: str = "user"): @pytest_asyncio.fixture def get_test_auth_client(get_user_manager, get_test_client): async def _get_test_auth_client( - backends: List[AuthenticationBackend], + backends: list[AuthenticationBackend], get_enabled_backends: Optional[ DependencyCallable[Sequence[AuthenticationBackend]] ] = None, diff --git a/tests/test_authentication_backend.py b/tests/test_authentication_backend.py index a4cbc050..973b21c3 100644 --- a/tests/test_authentication_backend.py +++ b/tests/test_authentication_backend.py @@ -1,4 +1,4 @@ -from typing import Callable, Generic, Optional, Type, cast +from typing import Callable, Generic, Optional, cast import pytest from fastapi import Response @@ -34,13 +34,13 @@ async def destroy_token(self, token: str, user: models.UP) -> None: @pytest.fixture(params=[MockTransport, MockTransportLogoutNotSupported]) def transport(request) -> Transport: - transport_class: Type[BearerTransport] = request.param + transport_class: type[BearerTransport] = request.param return transport_class(tokenUrl="/login") @pytest.fixture(params=[MockStrategy, MockStrategyDestroyNotSupported]) def get_strategy(request) -> Callable[..., Strategy]: - strategy_class: Type[Strategy] = request.param + strategy_class: type[Strategy] = request.param return lambda: strategy_class() diff --git a/tests/test_authentication_strategy_db.py b/tests/test_authentication_strategy_db.py index c81a7626..998e0a35 100644 --- a/tests/test_authentication_strategy_db.py +++ b/tests/test_authentication_strategy_db.py @@ -1,7 +1,7 @@ import dataclasses import uuid from datetime import datetime, timezone -from typing import Any, Dict, Optional +from typing import Any, Optional import pytest @@ -24,7 +24,7 @@ class AccessTokenModel(AccessTokenProtocol[IDType]): class AccessTokenDatabaseMock(AccessTokenDatabase[AccessTokenModel]): - store: Dict[str, AccessTokenModel] + store: dict[str, AccessTokenModel] def __init__(self): self.store = {} @@ -41,13 +41,13 @@ async def get_by_token( else: return access_token - async def create(self, create_dict: Dict[str, Any]) -> AccessTokenModel: + async def create(self, create_dict: dict[str, Any]) -> AccessTokenModel: access_token = AccessTokenModel(**create_dict) self.store[access_token.token] = access_token return access_token async def update( - self, access_token: AccessTokenModel, update_dict: Dict[str, Any] + self, access_token: AccessTokenModel, update_dict: dict[str, Any] ) -> AccessTokenModel: for field, value in update_dict.items(): setattr(access_token, field, value) diff --git a/tests/test_authentication_strategy_redis.py b/tests/test_authentication_strategy_redis.py index f7c48635..16cd5fe6 100644 --- a/tests/test_authentication_strategy_redis.py +++ b/tests/test_authentication_strategy_redis.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Dict, Optional, Tuple +from typing import Optional import pytest @@ -8,7 +8,7 @@ class RedisMock: - store: Dict[str, Tuple[str, Optional[int]]] + store: dict[str, tuple[str, Optional[int]]] def __init__(self): self.store = {} diff --git a/tests/test_fastapi_users.py b/tests/test_fastapi_users.py index a42aa78a..32126525 100644 --- a/tests/test_fastapi_users.py +++ b/tests/test_fastapi_users.py @@ -1,4 +1,5 @@ -from typing import AsyncGenerator, Optional +from collections.abc import AsyncGenerator +from typing import Optional import httpx import pytest diff --git a/tests/test_manager.py b/tests/test_manager.py index 30fa5a5e..2eab6c96 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -139,7 +139,7 @@ async def test_regular_user( ): user = UserCreate(email=email, password="guinevere") created_user = await user_manager.create(user) - assert type(created_user) == UserModel + assert isinstance(created_user, UserModel) assert user_manager.on_after_register.called is True @@ -151,7 +151,7 @@ async def test_superuser( email="lancelot@camelot.b", password="guinevere", is_superuser=True ) created_user = await user_manager.create(user, safe) - assert type(created_user) == UserModel + assert isinstance(created_user, UserModel) assert created_user.is_superuser is result assert user_manager.on_after_register.called is True @@ -164,7 +164,7 @@ async def test_is_active( email="lancelot@camelot.b", password="guinevere", is_active=False ) created_user = await user_manager.create(user, safe) - assert type(created_user) == UserModel + assert isinstance(created_user, UserModel) assert created_user.is_active is result assert user_manager.on_after_register.called is True diff --git a/tests/test_router_auth.py b/tests/test_router_auth.py index 8e648b57..139d40e4 100644 --- a/tests/test_router_auth.py +++ b/tests/test_router_auth.py @@ -1,4 +1,5 @@ -from typing import Any, AsyncGenerator, Dict, Tuple, cast +from collections.abc import AsyncGenerator +from typing import Any, cast import httpx import pytest @@ -45,7 +46,7 @@ def _app_factory(requires_verification: bool) -> FastAPI: ) async def test_app_client( request, get_test_client, app_factory -) -> AsyncGenerator[Tuple[httpx.AsyncClient, bool], None]: +) -> AsyncGenerator[tuple[httpx.AsyncClient, bool], None]: requires_verification = request.param app = app_factory(requires_verification) @@ -60,7 +61,7 @@ class TestLogin: async def test_empty_body( self, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user_manager, ): client, _ = test_app_client @@ -71,7 +72,7 @@ async def test_empty_body( async def test_missing_username( self, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user_manager, ): client, _ = test_app_client @@ -83,7 +84,7 @@ async def test_missing_username( async def test_missing_password( self, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user_manager, ): client, _ = test_app_client @@ -95,28 +96,28 @@ async def test_missing_password( async def test_not_existing_user( self, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user_manager, ): client, _ = test_app_client data = {"username": "lancelot@camelot.bt", "password": "guinevere"} response = await client.post(path, data=data) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS assert user_manager.on_after_login.called is False async def test_wrong_password( self, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user_manager, ): client, _ = test_app_client data = {"username": "king.arthur@camelot.bt", "password": "percival"} response = await client.post(path, data=data) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS assert user_manager.on_after_login.called is False @@ -127,7 +128,7 @@ async def test_valid_credentials_unverified( self, path, email, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user_manager, user: UserModel, ): @@ -136,7 +137,7 @@ async def test_valid_credentials_unverified( response = await client.post(path, data=data) if requires_verification: assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.LOGIN_USER_NOT_VERIFIED assert user_manager.on_after_login.called is False else: @@ -152,7 +153,7 @@ async def test_valid_credentials_verified( self, path, email, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user_manager, verified_user: UserModel, ): @@ -172,14 +173,14 @@ async def test_valid_credentials_verified( async def test_inactive_user( self, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user_manager, ): client, _ = test_app_client data = {"username": "percival@camelot.bt", "password": "angharad"} response = await client.post(path, data=data) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS assert user_manager.on_after_login.called is False @@ -191,7 +192,7 @@ class TestLogout: async def test_missing_token( self, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], ): client, _ = test_app_client response = await client.post(path) @@ -201,7 +202,7 @@ async def test_valid_credentials_unverified( self, mocker, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -217,7 +218,7 @@ async def test_valid_credentials_verified( self, mocker, path, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client diff --git a/tests/test_router_oauth.py b/tests/test_router_oauth.py index 50ed3ad1..d67de146 100644 --- a/tests/test_router_oauth.py +++ b/tests/test_router_oauth.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, cast +from typing import Any, cast import httpx import pytest @@ -184,7 +184,7 @@ async def test_already_exists_error( assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.OAUTH_USER_ALREADY_EXISTS assert user_manager_oauth.on_after_login.called is False @@ -214,7 +214,7 @@ async def test_active_user( assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["access_token"] == str(user_oauth.id) assert user_manager_oauth.on_after_login.called is True @@ -278,7 +278,7 @@ async def test_redirect_url_router( "CODE", "http://www.tintagel.bt/callback", None ) - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["access_token"] == str(user_oauth.id) assert user_manager_oauth.on_after_login.called is True @@ -486,7 +486,7 @@ async def test_active_user( assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["id"] == str(user_oauth.id) async def test_redirect_url_router( @@ -521,7 +521,7 @@ async def test_redirect_url_router( "CODE", "http://www.tintagel.bt/callback", None ) - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["id"] == str(user_oauth.id) async def test_not_available_email( diff --git a/tests/test_router_register.py b/tests/test_router_register.py index 50f0f61b..b6bb1c4b 100644 --- a/tests/test_router_register.py +++ b/tests/test_router_register.py @@ -1,4 +1,5 @@ -from typing import Any, AsyncGenerator, Dict, cast +from collections.abc import AsyncGenerator +from typing import Any, cast import httpx import pytest @@ -52,7 +53,7 @@ async def test_invalid_password(self, test_app_client: httpx.AsyncClient): json = {"email": "king.arthur@camelot.bt", "password": "g"} response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == { "code": ErrorCode.REGISTER_INVALID_PASSWORD, "reason": "Password should be at least 3 characters", @@ -65,7 +66,7 @@ async def test_existing_user(self, email, test_app_client: httpx.AsyncClient): json = {"email": email, "password": "guinevere"} response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.REGISTER_USER_ALREADY_EXISTS @pytest.mark.parametrize("email", ["lancelot@camelot.bt", "Lancelot@camelot.bt"]) @@ -74,7 +75,7 @@ async def test_valid_body(self, email, test_app_client: httpx.AsyncClient): response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_201_CREATED - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert "hashed_password" not in data assert "password" not in data assert data["id"] is not None @@ -88,7 +89,7 @@ async def test_valid_body_is_superuser(self, test_app_client: httpx.AsyncClient) response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_201_CREATED - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_superuser"] is False async def test_valid_body_is_active(self, test_app_client: httpx.AsyncClient): @@ -100,7 +101,7 @@ async def test_valid_body_is_active(self, test_app_client: httpx.AsyncClient): response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_201_CREATED - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_active"] is True diff --git a/tests/test_router_reset.py b/tests/test_router_reset.py index 55c8b453..851ba1cb 100644 --- a/tests/test_router_reset.py +++ b/tests/test_router_reset.py @@ -1,4 +1,5 @@ -from typing import Any, AsyncGenerator, Dict, cast +from collections.abc import AsyncGenerator +from typing import Any, cast import httpx import pytest @@ -106,7 +107,7 @@ async def test_invalid_token( json = {"token": "foo", "password": "guinevere"} response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN async def test_inactive_user( @@ -118,7 +119,7 @@ async def test_inactive_user( json = {"token": "foo", "password": "guinevere"} response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN async def test_invalid_password( @@ -132,7 +133,7 @@ async def test_invalid_password( json = {"token": "foo", "password": "guinevere"} response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == { "code": ErrorCode.RESET_PASSWORD_INVALID_PASSWORD, "reason": "Invalid", diff --git a/tests/test_router_users.py b/tests/test_router_users.py index b6427c50..78ae6438 100644 --- a/tests/test_router_users.py +++ b/tests/test_router_users.py @@ -1,4 +1,5 @@ -from typing import Any, AsyncGenerator, Dict, Tuple, cast +from collections.abc import AsyncGenerator +from typing import Any, cast import httpx import pytest @@ -39,7 +40,7 @@ def _app_factory(requires_verification: bool) -> FastAPI: ) async def test_app_client( request, get_test_client, app_factory -) -> AsyncGenerator[Tuple[httpx.AsyncClient, bool], None]: +) -> AsyncGenerator[tuple[httpx.AsyncClient, bool], None]: requires_verification = request.param app = app_factory(requires_verification) @@ -50,14 +51,14 @@ async def test_app_client( @pytest.mark.router @pytest.mark.asyncio class TestMe: - async def test_missing_token(self, test_app_client: Tuple[httpx.AsyncClient, bool]): + async def test_missing_token(self, test_app_client: tuple[httpx.AsyncClient, bool]): client, _ = test_app_client response = await client.get("/me") assert response.status_code == status.HTTP_401_UNAUTHORIZED async def test_inactive_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], inactive_user: UserModel, ): client, _ = test_app_client @@ -68,7 +69,7 @@ async def test_inactive_user( async def test_active_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -79,13 +80,13 @@ async def test_active_user( assert response.status_code == status.HTTP_403_FORBIDDEN else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["id"] == str(user.id) assert data["email"] == user.email async def test_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -93,7 +94,7 @@ async def test_verified_user( "/me", headers={"Authorization": f"Bearer {verified_user.id}"} ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["id"] == str(verified_user.id) assert data["email"] == verified_user.email @@ -106,7 +107,7 @@ async def test_current_user_namespace(self, app_factory): class TestUpdateMe: async def test_missing_token( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], ): client, _ = test_app_client response = await client.patch("/me") @@ -114,7 +115,7 @@ async def test_missing_token( async def test_inactive_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], inactive_user: UserModel, ): client, _ = test_app_client @@ -125,7 +126,7 @@ async def test_inactive_user( async def test_existing_email( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_user: UserModel, ): @@ -139,12 +140,12 @@ async def test_existing_email( assert response.status_code == status.HTTP_403_FORBIDDEN else: assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.UPDATE_USER_EMAIL_ALREADY_EXISTS async def test_invalid_password( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -157,7 +158,7 @@ async def test_invalid_password( assert response.status_code == status.HTTP_403_FORBIDDEN else: assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == { "code": ErrorCode.UPDATE_USER_INVALID_PASSWORD, "reason": "Password should be at least 3 characters", @@ -165,7 +166,7 @@ async def test_invalid_password( async def test_empty_body( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -177,12 +178,12 @@ async def test_empty_body( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["email"] == user.email async def test_valid_body( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -195,12 +196,12 @@ async def test_valid_body( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["email"] == "king.arthur@tintagel.bt" async def test_unverified_after_email_change( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -210,12 +211,12 @@ async def test_unverified_after_email_change( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_verified"] is False async def test_valid_body_is_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -228,12 +229,12 @@ async def test_valid_body_is_superuser( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_superuser"] is False async def test_valid_body_is_active( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -246,12 +247,12 @@ async def test_valid_body_is_active( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_active"] is True async def test_valid_body_is_verified( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -264,14 +265,14 @@ async def test_valid_body_is_verified( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_verified"] is False async def test_valid_body_password( self, mocker, mock_user_db, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -293,7 +294,7 @@ async def test_valid_body_password( async def test_empty_body_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -302,12 +303,12 @@ async def test_empty_body_verified_user( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["email"] == verified_user.email async def test_valid_body_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -317,12 +318,12 @@ async def test_valid_body_verified_user( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["email"] == "king.arthur@tintagel.bt" async def test_valid_body_is_superuser_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -332,12 +333,12 @@ async def test_valid_body_is_superuser_verified_user( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_superuser"] is False async def test_valid_body_is_active_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -347,12 +348,12 @@ async def test_valid_body_is_active_verified_user( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_active"] is True async def test_valid_body_is_verified_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -362,14 +363,14 @@ async def test_valid_body_is_verified_verified_user( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_verified"] is True async def test_valid_body_password_verified_user( self, mocker, mock_user_db, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -390,14 +391,14 @@ async def test_valid_body_password_verified_user( @pytest.mark.router @pytest.mark.asyncio class TestGetUser: - async def test_missing_token(self, test_app_client: Tuple[httpx.AsyncClient, bool]): + async def test_missing_token(self, test_app_client: tuple[httpx.AsyncClient, bool]): client, _ = test_app_client response = await client.get("/d35d213e-f3d8-4f08-954a-7e0d1bea286f") assert response.status_code == status.HTTP_401_UNAUTHORIZED async def test_regular_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -410,7 +411,7 @@ async def test_regular_user( async def test_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -422,7 +423,7 @@ async def test_verified_user( async def test_not_existing_user_unverified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], superuser: UserModel, ): client, requires_verification = test_app_client @@ -437,7 +438,7 @@ async def test_not_existing_user_unverified_superuser( async def test_not_existing_user_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_superuser: UserModel, ): client, _ = test_app_client @@ -449,7 +450,7 @@ async def test_not_existing_user_verified_superuser( async def test_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -462,13 +463,13 @@ async def test_superuser( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["id"] == str(user.id) assert "hashed_password" not in data async def test_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): @@ -478,7 +479,7 @@ async def test_verified_superuser( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["id"] == str(user.id) assert "hashed_password" not in data @@ -489,14 +490,14 @@ async def test_get_user_namespace(self, app_factory, user: UserModel): @pytest.mark.router @pytest.mark.asyncio class TestUpdateUser: - async def test_missing_token(self, test_app_client: Tuple[httpx.AsyncClient, bool]): + async def test_missing_token(self, test_app_client: tuple[httpx.AsyncClient, bool]): client, _ = test_app_client response = await client.patch("/d35d213e-f3d8-4f08-954a-7e0d1bea286f") assert response.status_code == status.HTTP_401_UNAUTHORIZED async def test_regular_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -509,7 +510,7 @@ async def test_regular_user( async def test_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -521,7 +522,7 @@ async def test_verified_user( async def test_not_existing_user_unverified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], superuser: UserModel, ): client, requires_verification = test_app_client @@ -537,7 +538,7 @@ async def test_not_existing_user_unverified_superuser( async def test_not_existing_user_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_superuser: UserModel, ): client, _ = test_app_client @@ -550,7 +551,7 @@ async def test_not_existing_user_verified_superuser( async def test_empty_body_unverified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -563,12 +564,12 @@ async def test_empty_body_unverified_superuser( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["email"] == user.email async def test_empty_body_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): @@ -580,12 +581,12 @@ async def test_empty_body_verified_superuser( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["email"] == user.email async def test_valid_body_unverified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -601,12 +602,12 @@ async def test_valid_body_unverified_superuser( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["email"] == "king.arthur@tintagel.bt" async def test_existing_email_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_user: UserModel, verified_superuser: UserModel, @@ -618,12 +619,12 @@ async def test_existing_email_verified_superuser( headers={"Authorization": f"Bearer {verified_superuser.id}"}, ) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.UPDATE_USER_EMAIL_ALREADY_EXISTS async def test_invalid_password_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): @@ -634,7 +635,7 @@ async def test_invalid_password_verified_superuser( headers={"Authorization": f"Bearer {verified_superuser.id}"}, ) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == { "code": ErrorCode.UPDATE_USER_INVALID_PASSWORD, "reason": "Password should be at least 3 characters", @@ -642,7 +643,7 @@ async def test_invalid_password_verified_superuser( async def test_valid_body_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): @@ -655,12 +656,12 @@ async def test_valid_body_verified_superuser( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["email"] == "king.arthur@tintagel.bt" async def test_valid_body_is_superuser_unverified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -676,12 +677,12 @@ async def test_valid_body_is_superuser_unverified_superuser( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_superuser"] is True async def test_valid_body_is_superuser_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): @@ -694,12 +695,12 @@ async def test_valid_body_is_superuser_verified_superuser( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_superuser"] is True async def test_valid_body_is_active_unverified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -715,12 +716,12 @@ async def test_valid_body_is_active_unverified_superuser( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_active"] is False async def test_valid_body_is_active_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): @@ -733,12 +734,12 @@ async def test_valid_body_is_active_verified_superuser( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_active"] is False async def test_valid_body_is_verified_unverified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -754,12 +755,12 @@ async def test_valid_body_is_verified_unverified_superuser( else: assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_verified"] is True async def test_valid_body_is_verified_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): @@ -772,14 +773,14 @@ async def test_valid_body_is_verified_verified_superuser( ) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["is_verified"] is True async def test_valid_body_password_unverified_superuser( self, mocker, mock_user_db, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -806,7 +807,7 @@ async def test_valid_body_password_verified_superuser( self, mocker, mock_user_db, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): @@ -830,7 +831,7 @@ async def test_valid_body_password_unchanged_unverified_superuser( self, mocker, mock_user_db, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -857,14 +858,14 @@ async def test_valid_body_password_unchanged_unverified_superuser( @pytest.mark.router @pytest.mark.asyncio class TestDeleteUser: - async def test_missing_token(self, test_app_client: Tuple[httpx.AsyncClient, bool]): + async def test_missing_token(self, test_app_client: tuple[httpx.AsyncClient, bool]): client, _ = test_app_client response = await client.delete("/d35d213e-f3d8-4f08-954a-7e0d1bea286f") assert response.status_code == status.HTTP_401_UNAUTHORIZED async def test_regular_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, ): client, requires_verification = test_app_client @@ -877,7 +878,7 @@ async def test_regular_user( async def test_verified_user( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_user: UserModel, ): client, _ = test_app_client @@ -889,7 +890,7 @@ async def test_verified_user( async def test_not_existing_user_unverified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], superuser: UserModel, ): client, requires_verification = test_app_client @@ -904,7 +905,7 @@ async def test_not_existing_user_unverified_superuser( async def test_not_existing_user_verified_superuser( self, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], verified_superuser: UserModel, ): client, _ = test_app_client @@ -918,7 +919,7 @@ async def test_unverified_superuser( self, mocker, mock_user_db, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, superuser: UserModel, ): @@ -942,7 +943,7 @@ async def test_verified_superuser( self, mocker, mock_user_db, - test_app_client: Tuple[httpx.AsyncClient, bool], + test_app_client: tuple[httpx.AsyncClient, bool], user: UserModel, verified_superuser: UserModel, ): diff --git a/tests/test_router_verify.py b/tests/test_router_verify.py index e798e84a..ef8b8034 100644 --- a/tests/test_router_verify.py +++ b/tests/test_router_verify.py @@ -1,4 +1,5 @@ -from typing import Any, AsyncGenerator, Dict, cast +from collections.abc import AsyncGenerator +from typing import Any, cast import httpx import pytest @@ -136,7 +137,7 @@ async def test_invalid_verify_token( response = await test_app_client.post("/verify", json={"token": "foo"}) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.VERIFY_USER_BAD_TOKEN async def test_user_not_exists( @@ -148,7 +149,7 @@ async def test_user_not_exists( response = await test_app_client.post("/verify", json={"token": "foo"}) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.VERIFY_USER_BAD_TOKEN async def test_user_already_verified( @@ -160,7 +161,7 @@ async def test_user_already_verified( response = await test_app_client.post("/verify", json={"token": "foo"}) assert response.status_code == status.HTTP_400_BAD_REQUEST - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["detail"] == ErrorCode.VERIFY_USER_ALREADY_VERIFIED async def test_success( @@ -174,7 +175,7 @@ async def test_success( response = await test_app_client.post("/verify", json={"token": "foo"}) assert response.status_code == status.HTTP_200_OK - data = cast(Dict[str, Any], response.json()) + data = cast(dict[str, Any], response.json()) assert data["id"] == str(user.id) async def test_verify_namespace(