Skip to content

Commit

Permalink
Drop Python 3.8 support and upgrade tooling
Browse files Browse the repository at this point in the history
  • Loading branch information
frankie567 committed Jan 4, 2025
1 parent f0078b0 commit 00c21ee
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 46 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python_version: [3.8, 3.9, '3.10', '3.11']
python_version: [3.9, '3.10', '3.11', '3.12', '3.13']
database_url:
[
"sqlite+aiosqlite:///./test-fastapiusers.db",
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,6 @@ ENV/
# mypy
.mypy_cache/

# .vscode
.vscode/

# OS files
.DS_Store

Expand Down
21 changes: 21 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true,
"python.terminal.activateEnvironment": true,
"python.terminal.activateEnvInCurrentTerminal": true,
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"editor.rulers": [88],
"python.defaultInterpreterPath": "${workspaceFolder}/.hatch/fastapi-users-db-sqlalchemy/bin/python",
"python.testing.pytestPath": "${workspaceFolder}/.hatch/fastapi-users-db-sqlalchemy/bin/pytest",
"python.testing.cwd": "${workspaceFolder}",
"python.testing.pytestArgs": ["--no-cov"],
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
"editor.defaultFormatter": "charliermarsh.ruff"
}
}
19 changes: 10 additions & 9 deletions fastapi_users_db_sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""FastAPI Users database adapter for SQLAlchemy."""

import uuid
from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, Type
from typing import TYPE_CHECKING, Any, Generic, Optional

from fastapi_users.db.base import BaseUserDatabase
from fastapi_users.models import ID, OAP, UP
Expand Down Expand Up @@ -103,14 +104,14 @@ class SQLAlchemyUserDatabase(Generic[UP, ID], BaseUserDatabase[UP, ID]):
"""

session: AsyncSession
user_table: Type[UP]
oauth_account_table: Optional[Type[SQLAlchemyBaseOAuthAccountTable]]
user_table: type[UP]
oauth_account_table: Optional[type[SQLAlchemyBaseOAuthAccountTable]]

def __init__(
self,
session: AsyncSession,
user_table: Type[UP],
oauth_account_table: Optional[Type[SQLAlchemyBaseOAuthAccountTable]] = None,
user_table: type[UP],
oauth_account_table: Optional[type[SQLAlchemyBaseOAuthAccountTable]] = None,
):
self.session = session
self.user_table = user_table
Expand Down Expand Up @@ -138,14 +139,14 @@ async def get_by_oauth_account(self, oauth: str, account_id: str) -> Optional[UP
)
return await self._get_user(statement)

async def create(self, create_dict: Dict[str, Any]) -> UP:
async def create(self, create_dict: dict[str, Any]) -> UP:
user = self.user_table(**create_dict)
self.session.add(user)
await self.session.commit()
await self.session.refresh(user)
return user

async def update(self, user: UP, update_dict: Dict[str, Any]) -> UP:
async def update(self, user: UP, update_dict: dict[str, Any]) -> UP:
for key, value in update_dict.items():
setattr(user, key, value)
self.session.add(user)
Expand All @@ -157,7 +158,7 @@ async def delete(self, user: UP) -> None:
await self.session.delete(user)
await self.session.commit()

async def add_oauth_account(self, user: UP, create_dict: Dict[str, Any]) -> UP:
async def add_oauth_account(self, user: UP, create_dict: dict[str, Any]) -> UP:
if self.oauth_account_table is None:
raise NotImplementedError()

Expand All @@ -172,7 +173,7 @@ async def add_oauth_account(self, user: UP, create_dict: Dict[str, Any]) -> UP:
return user

async def update_oauth_account(
self, user: UP, oauth_account: OAP, update_dict: Dict[str, Any]
self, user: UP, oauth_account: OAP, update_dict: dict[str, Any]
) -> UP:
if self.oauth_account_table is None:
raise NotImplementedError()
Expand Down
8 changes: 4 additions & 4 deletions fastapi_users_db_sqlalchemy/access_token.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import uuid
from datetime import datetime
from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, Type
from typing import TYPE_CHECKING, Any, Generic, Optional

from fastapi_users.authentication.strategy.db import AP, AccessTokenDatabase
from fastapi_users.models import ID
Expand Down Expand Up @@ -50,7 +50,7 @@ class SQLAlchemyAccessTokenDatabase(Generic[AP], AccessTokenDatabase[AP]):
def __init__(
self,
session: AsyncSession,
access_token_table: Type[AP],
access_token_table: type[AP],
):
self.session = session
self.access_token_table = access_token_table
Expand All @@ -69,14 +69,14 @@ async def get_by_token(
results = await self.session.execute(statement)
return results.scalar_one_or_none()

async def create(self, create_dict: Dict[str, Any]) -> AP:
async def create(self, create_dict: dict[str, Any]) -> AP:
access_token = self.access_token_table(**create_dict)
self.session.add(access_token)
await self.session.commit()
await self.session.refresh(access_token)
return access_token

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:
for key, value in update_dict.items():
setattr(access_token, key, value)
self.session.add(access_token)
Expand Down
21 changes: 13 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
plugins = "sqlalchemy.ext.mypy.plugin"

[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_mode = "strict"
asyncio_default_fixture_loop_scope = "function"
addopts = "--ignore=test_build.py"

[tool.ruff]
extend-select = ["I"]

[tool.ruff.lint]
extend-select = ["I", "UP"]

[tool.hatch]

Expand All @@ -19,6 +22,7 @@ commit_extra_args = ["-e"]
path = "fastapi_users_db_sqlalchemy/__init__.py"

[tool.hatch.envs.default]
installer = "uv"
dependencies = [
"aiosqlite",
"asyncpg",
Expand All @@ -40,13 +44,13 @@ dependencies = [
test = "pytest --cov=fastapi_users_db_sqlalchemy/ --cov-report=term-missing --cov-fail-under=100"
test-cov-xml = "pytest --cov=fastapi_users_db_sqlalchemy/ --cov-report=xml --cov-fail-under=100"
lint = [
"black . ",
"ruff --fix .",
"ruff format . ",
"ruff check --fix .",
"mypy fastapi_users_db_sqlalchemy/",
]
lint-check = [
"black --check .",
"ruff .",
"ruff format --check .",
"ruff check .",
"mypy fastapi_users_db_sqlalchemy/",
]

Expand All @@ -71,14 +75,15 @@ 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-users >= 10.0.0",
"sqlalchemy[asyncio] >=2.0.0,<2.1.0",
Expand Down
15 changes: 3 additions & 12 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import asyncio
import os
from typing import Any, Dict, Optional
from typing import Any, Optional

import pytest
from fastapi_users import schemas
Expand All @@ -26,16 +25,8 @@ class UserOAuth(User, schemas.BaseOAuthAccountMixin):
pass


@pytest.fixture(scope="session")
def event_loop():
"""Force the pytest-asyncio loop to be the main one."""
loop = asyncio.new_event_loop()
yield loop
loop.close()


@pytest.fixture
def oauth_account1() -> Dict[str, Any]:
def oauth_account1() -> dict[str, Any]:
return {
"oauth_name": "service1",
"access_token": "TOKEN",
Expand All @@ -46,7 +37,7 @@ def oauth_account1() -> Dict[str, Any]:


@pytest.fixture
def oauth_account2() -> Dict[str, Any]:
def oauth_account2() -> dict[str, Any]:
return {
"oauth_name": "service2",
"access_token": "TOKEN",
Expand Down
5 changes: 3 additions & 2 deletions tests/test_access_token.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import uuid
from collections.abc import AsyncGenerator
from datetime import datetime, timedelta, timezone
from typing import AsyncGenerator

import pytest
import pytest_asyncio
from pydantic import UUID4
from sqlalchemy import exc
from sqlalchemy.ext.asyncio import (
Expand Down Expand Up @@ -42,7 +43,7 @@ def user_id() -> UUID4:
return uuid.uuid4()


@pytest.fixture
@pytest_asyncio.fixture
async def sqlalchemy_access_token_db(
user_id: UUID4,
) -> AsyncGenerator[SQLAlchemyAccessTokenDatabase[AccessToken], None]:
Expand Down
14 changes: 8 additions & 6 deletions tests/test_users.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Any, AsyncGenerator, Dict, List
from collections.abc import AsyncGenerator
from typing import Any

import pytest
import pytest_asyncio
from sqlalchemy import String, exc
from sqlalchemy.ext.asyncio import (
AsyncEngine,
Expand Down Expand Up @@ -45,12 +47,12 @@ class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, OAuthBase):

class UserOAuth(SQLAlchemyBaseUserTableUUID, OAuthBase):
first_name: Mapped[str] = mapped_column(String(255), nullable=True)
oauth_accounts: Mapped[List[OAuthAccount]] = relationship(
oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
"OAuthAccount", lazy="joined"
)


@pytest.fixture
@pytest_asyncio.fixture
async def sqlalchemy_user_db() -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
engine = create_async_engine(DATABASE_URL)
sessionmaker = create_async_session_maker(engine)
Expand All @@ -65,7 +67,7 @@ async def sqlalchemy_user_db() -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
await connection.run_sync(Base.metadata.drop_all)


@pytest.fixture
@pytest_asyncio.fixture
async def sqlalchemy_user_db_oauth() -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
engine = create_async_engine(DATABASE_URL)
sessionmaker = create_async_session_maker(engine)
Expand Down Expand Up @@ -168,8 +170,8 @@ async def test_queries_custom_fields(
@pytest.mark.asyncio
async def test_queries_oauth(
sqlalchemy_user_db_oauth: SQLAlchemyUserDatabase[UserOAuth, UUID_ID],
oauth_account1: Dict[str, Any],
oauth_account2: Dict[str, Any],
oauth_account1: dict[str, Any],
oauth_account2: dict[str, Any],
):
user_create = {
"email": "[email protected]",
Expand Down

0 comments on commit 00c21ee

Please sign in to comment.