Skip to content

Commit 00c21ee

Browse files
committed
Drop Python 3.8 support and upgrade tooling
1 parent f0078b0 commit 00c21ee

File tree

9 files changed

+64
-46
lines changed

9 files changed

+64
-46
lines changed

.github/workflows/build.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
strategy:
3535
fail-fast: false
3636
matrix:
37-
python_version: [3.8, 3.9, '3.10', '3.11']
37+
python_version: [3.9, '3.10', '3.11', '3.12', '3.13']
3838
database_url:
3939
[
4040
"sqlite+aiosqlite:///./test-fastapiusers.db",
@@ -82,7 +82,7 @@ jobs:
8282
- name: Set up Python
8383
uses: actions/setup-python@v4
8484
with:
85-
python-version: 3.8
85+
python-version: 3.9
8686
- name: Install dependencies
8787
run: |
8888
python -m pip install --upgrade pip

.gitignore

-3
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ ENV/
104104
# mypy
105105
.mypy_cache/
106106

107-
# .vscode
108-
.vscode/
109-
110107
# OS files
111108
.DS_Store
112109

.vscode/settings.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"python.analysis.typeCheckingMode": "basic",
3+
"python.analysis.autoImportCompletions": true,
4+
"python.terminal.activateEnvironment": true,
5+
"python.terminal.activateEnvInCurrentTerminal": true,
6+
"python.testing.unittestEnabled": false,
7+
"python.testing.pytestEnabled": true,
8+
"editor.rulers": [88],
9+
"python.defaultInterpreterPath": "${workspaceFolder}/.hatch/fastapi-users-db-sqlalchemy/bin/python",
10+
"python.testing.pytestPath": "${workspaceFolder}/.hatch/fastapi-users-db-sqlalchemy/bin/pytest",
11+
"python.testing.cwd": "${workspaceFolder}",
12+
"python.testing.pytestArgs": ["--no-cov"],
13+
"[python]": {
14+
"editor.formatOnSave": true,
15+
"editor.codeActionsOnSave": {
16+
"source.fixAll": "explicit",
17+
"source.organizeImports": "explicit"
18+
},
19+
"editor.defaultFormatter": "charliermarsh.ruff"
20+
}
21+
}

fastapi_users_db_sqlalchemy/__init__.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""FastAPI Users database adapter for SQLAlchemy."""
2+
23
import uuid
3-
from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, Type
4+
from typing import TYPE_CHECKING, Any, Generic, Optional
45

56
from fastapi_users.db.base import BaseUserDatabase
67
from fastapi_users.models import ID, OAP, UP
@@ -103,14 +104,14 @@ class SQLAlchemyUserDatabase(Generic[UP, ID], BaseUserDatabase[UP, ID]):
103104
"""
104105

105106
session: AsyncSession
106-
user_table: Type[UP]
107-
oauth_account_table: Optional[Type[SQLAlchemyBaseOAuthAccountTable]]
107+
user_table: type[UP]
108+
oauth_account_table: Optional[type[SQLAlchemyBaseOAuthAccountTable]]
108109

109110
def __init__(
110111
self,
111112
session: AsyncSession,
112-
user_table: Type[UP],
113-
oauth_account_table: Optional[Type[SQLAlchemyBaseOAuthAccountTable]] = None,
113+
user_table: type[UP],
114+
oauth_account_table: Optional[type[SQLAlchemyBaseOAuthAccountTable]] = None,
114115
):
115116
self.session = session
116117
self.user_table = user_table
@@ -138,14 +139,14 @@ async def get_by_oauth_account(self, oauth: str, account_id: str) -> Optional[UP
138139
)
139140
return await self._get_user(statement)
140141

141-
async def create(self, create_dict: Dict[str, Any]) -> UP:
142+
async def create(self, create_dict: dict[str, Any]) -> UP:
142143
user = self.user_table(**create_dict)
143144
self.session.add(user)
144145
await self.session.commit()
145146
await self.session.refresh(user)
146147
return user
147148

148-
async def update(self, user: UP, update_dict: Dict[str, Any]) -> UP:
149+
async def update(self, user: UP, update_dict: dict[str, Any]) -> UP:
149150
for key, value in update_dict.items():
150151
setattr(user, key, value)
151152
self.session.add(user)
@@ -157,7 +158,7 @@ async def delete(self, user: UP) -> None:
157158
await self.session.delete(user)
158159
await self.session.commit()
159160

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

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

174175
async def update_oauth_account(
175-
self, user: UP, oauth_account: OAP, update_dict: Dict[str, Any]
176+
self, user: UP, oauth_account: OAP, update_dict: dict[str, Any]
176177
) -> UP:
177178
if self.oauth_account_table is None:
178179
raise NotImplementedError()

fastapi_users_db_sqlalchemy/access_token.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import uuid
22
from datetime import datetime
3-
from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, Type
3+
from typing import TYPE_CHECKING, Any, Generic, Optional
44

55
from fastapi_users.authentication.strategy.db import AP, AccessTokenDatabase
66
from fastapi_users.models import ID
@@ -50,7 +50,7 @@ class SQLAlchemyAccessTokenDatabase(Generic[AP], AccessTokenDatabase[AP]):
5050
def __init__(
5151
self,
5252
session: AsyncSession,
53-
access_token_table: Type[AP],
53+
access_token_table: type[AP],
5454
):
5555
self.session = session
5656
self.access_token_table = access_token_table
@@ -69,14 +69,14 @@ async def get_by_token(
6969
results = await self.session.execute(statement)
7070
return results.scalar_one_or_none()
7171

72-
async def create(self, create_dict: Dict[str, Any]) -> AP:
72+
async def create(self, create_dict: dict[str, Any]) -> AP:
7373
access_token = self.access_token_table(**create_dict)
7474
self.session.add(access_token)
7575
await self.session.commit()
7676
await self.session.refresh(access_token)
7777
return access_token
7878

79-
async def update(self, access_token: AP, update_dict: Dict[str, Any]) -> AP:
79+
async def update(self, access_token: AP, update_dict: dict[str, Any]) -> AP:
8080
for key, value in update_dict.items():
8181
setattr(access_token, key, value)
8282
self.session.add(access_token)

pyproject.toml

+13-8
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
plugins = "sqlalchemy.ext.mypy.plugin"
33

44
[tool.pytest.ini_options]
5-
asyncio_mode = "auto"
5+
asyncio_mode = "strict"
6+
asyncio_default_fixture_loop_scope = "function"
67
addopts = "--ignore=test_build.py"
78

89
[tool.ruff]
9-
extend-select = ["I"]
10+
11+
[tool.ruff.lint]
12+
extend-select = ["I", "UP"]
1013

1114
[tool.hatch]
1215

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

2124
[tool.hatch.envs.default]
25+
installer = "uv"
2226
dependencies = [
2327
"aiosqlite",
2428
"asyncpg",
@@ -40,13 +44,13 @@ dependencies = [
4044
test = "pytest --cov=fastapi_users_db_sqlalchemy/ --cov-report=term-missing --cov-fail-under=100"
4145
test-cov-xml = "pytest --cov=fastapi_users_db_sqlalchemy/ --cov-report=xml --cov-fail-under=100"
4246
lint = [
43-
"black . ",
44-
"ruff --fix .",
47+
"ruff format . ",
48+
"ruff check --fix .",
4549
"mypy fastapi_users_db_sqlalchemy/",
4650
]
4751
lint-check = [
48-
"black --check .",
49-
"ruff .",
52+
"ruff format --check .",
53+
"ruff check .",
5054
"mypy fastapi_users_db_sqlalchemy/",
5155
]
5256

@@ -71,14 +75,15 @@ classifiers = [
7175
"Framework :: FastAPI",
7276
"Framework :: AsyncIO",
7377
"Intended Audience :: Developers",
74-
"Programming Language :: Python :: 3.8",
7578
"Programming Language :: Python :: 3.9",
7679
"Programming Language :: Python :: 3.10",
7780
"Programming Language :: Python :: 3.11",
81+
"Programming Language :: Python :: 3.12",
82+
"Programming Language :: Python :: 3.13",
7883
"Programming Language :: Python :: 3 :: Only",
7984
"Topic :: Internet :: WWW/HTTP :: Session",
8085
]
81-
requires-python = ">=3.8"
86+
requires-python = ">=3.9"
8287
dependencies = [
8388
"fastapi-users >= 10.0.0",
8489
"sqlalchemy[asyncio] >=2.0.0,<2.1.0",

tests/conftest.py

+3-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import asyncio
21
import os
3-
from typing import Any, Dict, Optional
2+
from typing import Any, Optional
43

54
import pytest
65
from fastapi_users import schemas
@@ -26,16 +25,8 @@ class UserOAuth(User, schemas.BaseOAuthAccountMixin):
2625
pass
2726

2827

29-
@pytest.fixture(scope="session")
30-
def event_loop():
31-
"""Force the pytest-asyncio loop to be the main one."""
32-
loop = asyncio.new_event_loop()
33-
yield loop
34-
loop.close()
35-
36-
3728
@pytest.fixture
38-
def oauth_account1() -> Dict[str, Any]:
29+
def oauth_account1() -> dict[str, Any]:
3930
return {
4031
"oauth_name": "service1",
4132
"access_token": "TOKEN",
@@ -46,7 +37,7 @@ def oauth_account1() -> Dict[str, Any]:
4637

4738

4839
@pytest.fixture
49-
def oauth_account2() -> Dict[str, Any]:
40+
def oauth_account2() -> dict[str, Any]:
5041
return {
5142
"oauth_name": "service2",
5243
"access_token": "TOKEN",

tests/test_access_token.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import uuid
2+
from collections.abc import AsyncGenerator
23
from datetime import datetime, timedelta, timezone
3-
from typing import AsyncGenerator
44

55
import pytest
6+
import pytest_asyncio
67
from pydantic import UUID4
78
from sqlalchemy import exc
89
from sqlalchemy.ext.asyncio import (
@@ -42,7 +43,7 @@ def user_id() -> UUID4:
4243
return uuid.uuid4()
4344

4445

45-
@pytest.fixture
46+
@pytest_asyncio.fixture
4647
async def sqlalchemy_access_token_db(
4748
user_id: UUID4,
4849
) -> AsyncGenerator[SQLAlchemyAccessTokenDatabase[AccessToken], None]:

tests/test_users.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from typing import Any, AsyncGenerator, Dict, List
1+
from collections.abc import AsyncGenerator
2+
from typing import Any
23

34
import pytest
5+
import pytest_asyncio
46
from sqlalchemy import String, exc
57
from sqlalchemy.ext.asyncio import (
68
AsyncEngine,
@@ -45,12 +47,12 @@ class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, OAuthBase):
4547

4648
class UserOAuth(SQLAlchemyBaseUserTableUUID, OAuthBase):
4749
first_name: Mapped[str] = mapped_column(String(255), nullable=True)
48-
oauth_accounts: Mapped[List[OAuthAccount]] = relationship(
50+
oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
4951
"OAuthAccount", lazy="joined"
5052
)
5153

5254

53-
@pytest.fixture
55+
@pytest_asyncio.fixture
5456
async def sqlalchemy_user_db() -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
5557
engine = create_async_engine(DATABASE_URL)
5658
sessionmaker = create_async_session_maker(engine)
@@ -65,7 +67,7 @@ async def sqlalchemy_user_db() -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
6567
await connection.run_sync(Base.metadata.drop_all)
6668

6769

68-
@pytest.fixture
70+
@pytest_asyncio.fixture
6971
async def sqlalchemy_user_db_oauth() -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
7072
engine = create_async_engine(DATABASE_URL)
7173
sessionmaker = create_async_session_maker(engine)
@@ -168,8 +170,8 @@ async def test_queries_custom_fields(
168170
@pytest.mark.asyncio
169171
async def test_queries_oauth(
170172
sqlalchemy_user_db_oauth: SQLAlchemyUserDatabase[UserOAuth, UUID_ID],
171-
oauth_account1: Dict[str, Any],
172-
oauth_account2: Dict[str, Any],
173+
oauth_account1: dict[str, Any],
174+
oauth_account2: dict[str, Any],
173175
):
174176
user_create = {
175177
"email": "[email protected]",

0 commit comments

Comments
 (0)