Skip to content

Commit ed757cc

Browse files
author
Eugene Shershen
committed
update workflows and pre-commit to use Python 3.12
1 parent cacdca5 commit ed757cc

File tree

7 files changed

+83
-106
lines changed

7 files changed

+83
-106
lines changed

.flake8

Lines changed: 0 additions & 4 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ jobs:
5555
file: ./coverage.xml
5656
token: ${{ secrets.CODECOV_TOKEN }}
5757

58-
lint:
59-
name: lint
58+
ruff:
59+
name: ruff
6060
runs-on: ubuntu-latest
6161
steps:
6262
- name: Checkout repository
@@ -68,30 +68,10 @@ jobs:
6868
python-version: 3.12
6969

7070
- name: Install dependencies
71-
run: pip install flake8
71+
run: pip install ruff
7272

73-
- name: Run flake8
74-
run: flake8 --count .
73+
- name: Run ruff linter
74+
run: ruff check .
7575

76-
format:
77-
name: format
78-
runs-on: ubuntu-latest
79-
steps:
80-
- name: Checkout repository
81-
uses: actions/checkout@v4
82-
83-
- name: Set up Python
84-
uses: actions/setup-python@v5
85-
with:
86-
python-version: 3.12
87-
88-
- name: Install dependencies
89-
# isort needs all of the packages to be installed so it can
90-
# tell which are third party and which are first party
91-
run: pip install -r requirements.txt
92-
93-
- name: Check formatting of imports
94-
run: isort --check-only --diff --verbose
95-
96-
- name: Check formatting of code
97-
run: black . --check --diff
76+
- name: Run ruff formatter
77+
run: ruff format --check .

.pre-commit-config.yaml

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,20 @@ default_language_version:
55

66
repos:
77
- repo: https://github.com/pre-commit/pre-commit-hooks
8-
rev: v4.4.0
8+
rev: v5.0.0
99
hooks:
1010
- id: end-of-file-fixer
1111
- id: trailing-whitespace
12-
- repo: https://github.com/asottile/pyupgrade
13-
rev: v2.28.0
14-
hooks:
15-
- id: pyupgrade
16-
args:
17-
- --py37-plus
18-
- repo: https://github.com/myint/autoflake
19-
rev: v1.4
20-
hooks:
21-
- id: autoflake
22-
args:
23-
- --in-place
24-
- --remove-all-unused-imports
25-
- --expand-star-imports
26-
- --remove-duplicate-keys
27-
- --remove-unused-variables
28-
- repo: https://github.com/PyCQA/isort
29-
rev: 5.12.0
30-
hooks:
31-
- id: isort
32-
- repo: https://github.com/psf/black
33-
rev: 22.3.0
34-
hooks:
35-
- id: black
36-
- repo: https://github.com/PyCQA/flake8
37-
rev: 5.0.4
12+
13+
- repo: https://github.com/astral-sh/ruff-pre-commit
14+
rev: v0.12.4
3815
hooks:
39-
- id: flake8
40-
args:
41-
- --max-line-length=100
42-
- --ignore=E203, E501, W503
16+
- id: ruff
17+
args: [--fix, --unsafe-fixes]
18+
- id: ruff-format
19+
4320
- repo: https://github.com/pre-commit/mirrors-mypy
44-
rev: v0.982
21+
rev: v1.17.0
4522
hooks:
4623
- id: mypy
4724
additional_dependencies:

fastapi_async_sqlalchemy/middleware.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
11
import asyncio
22
from contextvars import ContextVar
3-
from typing import Dict, Optional, Union
3+
from typing import Dict, Optional, Type, Union
44

5-
from sqlalchemy.engine import Engine
65
from sqlalchemy.engine.url import URL
7-
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
6+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
87
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
98
from starlette.requests import Request
109
from starlette.types import ASGIApp
1110

12-
from fastapi_async_sqlalchemy.exceptions import MissingSessionError, SessionNotInitialisedError
11+
from fastapi_async_sqlalchemy.exceptions import (
12+
MissingSessionError,
13+
SessionNotInitialisedError,
14+
)
1315

1416
try:
15-
from sqlalchemy.ext.asyncio import async_sessionmaker # noqa: F811
17+
from sqlalchemy.ext.asyncio import async_sessionmaker
1618
except ImportError:
17-
from sqlalchemy.orm import sessionmaker as async_sessionmaker
19+
from sqlalchemy.orm import sessionmaker as async_sessionmaker # type: ignore
1820

1921
# Try to import SQLModel's AsyncSession which has the .exec() method
2022
try:
2123
from sqlmodel.ext.asyncio.session import AsyncSession as SQLModelAsyncSession
2224

23-
DefaultAsyncSession = SQLModelAsyncSession
25+
DefaultAsyncSession: Type[AsyncSession] = SQLModelAsyncSession # type: ignore
2426
except ImportError:
25-
DefaultAsyncSession = AsyncSession
27+
DefaultAsyncSession: Type[AsyncSession] = AsyncSession # type: ignore
2628

2729

28-
def create_middleware_and_session_proxy():
30+
def create_middleware_and_session_proxy() -> tuple:
2931
_Session: Optional[async_sessionmaker] = None
30-
_session: ContextVar[Optional[DefaultAsyncSession]] = ContextVar("_session", default=None)
32+
_session: ContextVar[Optional[AsyncSession]] = ContextVar("_session", default=None)
3133
_multi_sessions_ctx: ContextVar[bool] = ContextVar("_multi_sessions_context", default=False)
3234
_commit_on_exit_ctx: ContextVar[bool] = ContextVar("_commit_on_exit_ctx", default=False)
3335
# Usage of context vars inside closures is not recommended, since they are not properly
@@ -39,9 +41,9 @@ def __init__(
3941
self,
4042
app: ASGIApp,
4143
db_url: Optional[Union[str, URL]] = None,
42-
custom_engine: Optional[Engine] = None,
43-
engine_args: Dict = None,
44-
session_args: Dict = None,
44+
custom_engine: Optional[AsyncEngine] = None,
45+
engine_args: Optional[Dict] = None,
46+
session_args: Optional[Dict] = None,
4547
commit_on_exit: bool = False,
4648
):
4749
super().__init__(app)
@@ -52,13 +54,18 @@ def __init__(
5254
if not custom_engine and not db_url:
5355
raise ValueError("You need to pass a db_url or a custom_engine parameter.")
5456
if not custom_engine:
57+
if db_url is None:
58+
raise ValueError("db_url cannot be None when custom_engine is not provided")
5559
engine = create_async_engine(db_url, **engine_args)
5660
else:
5761
engine = custom_engine
5862

5963
nonlocal _Session
6064
_Session = async_sessionmaker(
61-
engine, class_=DefaultAsyncSession, expire_on_commit=False, **session_args
65+
engine,
66+
class_=DefaultAsyncSession,
67+
expire_on_commit=False,
68+
**session_args,
6269
)
6370

6471
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
@@ -67,7 +74,7 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
6774

6875
class DBSessionMeta(type):
6976
@property
70-
def session(self) -> DefaultAsyncSession:
77+
def session(self) -> AsyncSession:
7178
"""Return an instance of Session local to the current async context."""
7279
if _Session is None:
7380
raise SessionNotInitialisedError
@@ -123,7 +130,7 @@ async def cleanup():
123130
class DBSession(metaclass=DBSessionMeta):
124131
def __init__(
125132
self,
126-
session_args: Dict = None,
133+
session_args: Optional[Dict] = None,
127134
commit_on_exit: bool = False,
128135
multi_sessions: bool = False,
129136
):

pyproject.toml

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
1-
[tool.black]
1+
[tool.ruff]
22
line-length = 100
3-
target-version = ['py37']
4-
include = '\.pyi?$'
5-
exclude = '''
6-
(
7-
| .git
8-
| .venv
9-
| build
10-
| dist
11-
)
12-
'''
3+
target-version = "py37"
4+
exclude = [
5+
".git",
6+
".venv",
7+
"build",
8+
"dist",
9+
]
1310

14-
[tool.isort]
15-
multi_line_output = 3
16-
include_trailing_comma = true
17-
force_grid_wrap = 0
18-
use_parentheses = true
19-
line_length = 100
11+
[tool.ruff.lint]
12+
select = [
13+
"E", # pycodestyle errors
14+
"W", # pycodestyle warnings
15+
"F", # pyflakes
16+
"I", # isort
17+
"B", # flake8-bugbear
18+
"C4", # flake8-comprehensions
19+
"UP", # pyupgrade
20+
]
21+
ignore = [
22+
"E203", # whitespace before ':'
23+
]
24+
25+
[tool.ruff.format]
26+
quote-style = "double"
27+
indent-style = "space"
28+
skip-magic-trailing-comma = false
29+
line-ending = "auto"
30+
31+
[tool.ruff.lint.isort]
32+
combine-as-imports = true
33+
split-on-trailing-comma = true

tests/test_session.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
77
from starlette.middleware.base import BaseHTTPMiddleware
88

9-
from fastapi_async_sqlalchemy.exceptions import MissingSessionError, SessionNotInitialisedError
9+
from fastapi_async_sqlalchemy.exceptions import (
10+
MissingSessionError,
11+
SessionNotInitialisedError,
12+
)
1013

1114
db_url = "sqlite+aiosqlite://"
1215

@@ -72,7 +75,7 @@ async def test_inside_route_without_middleware_fails(app, client, db):
7275
@app.get("/")
7376
def test_get():
7477
with pytest.raises(SessionNotInitialisedError):
75-
db.session
78+
_ = db.session
7679

7780
client.get("/")
7881

@@ -88,7 +91,7 @@ async def test_outside_of_route(app, db, SQLAlchemyMiddleware):
8891
@pytest.mark.asyncio
8992
async def test_outside_of_route_without_middleware_fails(db):
9093
with pytest.raises(SessionNotInitialisedError):
91-
db.session
94+
_ = db.session
9295

9396
with pytest.raises(SessionNotInitialisedError):
9497
async with db():
@@ -100,7 +103,7 @@ async def test_outside_of_route_without_context_fails(app, db, SQLAlchemyMiddlew
100103
app.add_middleware(SQLAlchemyMiddleware, db_url=db_url)
101104

102105
with pytest.raises(MissingSessionError):
103-
db.session
106+
_ = db.session
104107

105108

106109
@pytest.mark.asyncio
@@ -131,9 +134,9 @@ async def test_rollback(app, db, SQLAlchemyMiddleware):
131134
# if we could demonstrate somehow that db.session.rollback() was called e.g. once
132135
app.add_middleware(SQLAlchemyMiddleware, db_url=db_url)
133136

134-
with pytest.raises(Exception):
137+
with pytest.raises(RuntimeError):
135138
async with db():
136-
raise Exception
139+
raise RuntimeError("Test exception")
137140

138141
db.session.rollback.assert_called_once()
139142

@@ -150,7 +153,7 @@ async def test_db_context_session_args(app, db, SQLAlchemyMiddleware, commit_on_
150153

151154
session_args = {"expire_on_commit": False}
152155
async with db(session_args=session_args):
153-
db.session
156+
_ = db.session
154157

155158

156159
@pytest.mark.asyncio

tests/test_sqlmodel.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ async def test_sqlmodel_exec_method_exists(app, db, SQLAlchemyMiddleware):
5353
async with db():
5454
# Test that exec method exists
5555
assert hasattr(db.session, "exec")
56-
assert callable(getattr(db.session, "exec"))
56+
assert callable(db.session.exec)
5757

5858

5959
@pytest.mark.skipif(not SQLMODEL_AVAILABLE, reason="SQLModel not available")
@@ -272,8 +272,8 @@ async def test_sqlmodel_session_has_both_exec_and_execute(app, db, SQLAlchemyMid
272272
# Should have both methods
273273
assert hasattr(db.session, "exec")
274274
assert hasattr(db.session, "execute")
275-
assert callable(getattr(db.session, "exec"))
276-
assert callable(getattr(db.session, "execute"))
275+
assert callable(db.session.exec)
276+
assert callable(db.session.execute)
277277

278278
# Both should work
279279
result1 = await db.session.execute(text("SELECT 42 as answer"))

0 commit comments

Comments
 (0)