Skip to content

Commit 1085e2d

Browse files
authored
Merge pull request #83 from luca-medeiros/feat/ruff-linter
Add ruff linter
2 parents ecf206d + 1f9ad65 commit 1085e2d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+429
-471
lines changed

pyproject.toml

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ description = "A fully Async FastAPI boilerplate using SQLAlchemy and Pydantic 2
55
authors = ["Igor Magalhaes <igor.magalhaes.r@gmail.com>"]
66
license = "MIT"
77
readme = "README.md"
8-
packages = [{include = "fastapi_boilerplate"}]
8+
packages = [{ include = "fastapi_boilerplate" }]
99

1010
[tool.poetry.dependencies]
1111
python = "^3.11"
1212
python-dotenv = "^1.0.0"
13-
pydantic = {extras = ["email"], version = "^2.4.1"}
13+
pydantic = { extras = ["email"], version = "^2.4.1" }
1414
fastapi = "^0.103.1"
1515
uvicorn = "^0.23.2"
1616
uvloop = "^0.17.0"
@@ -35,3 +35,42 @@ bcrypt = "^4.1.1"
3535
[build-system]
3636
requires = ["poetry-core"]
3737
build-backend = "poetry.core.masonry.api"
38+
39+
[tool.ruff]
40+
target-version = "py311"
41+
line-length = 120
42+
fix = true
43+
select = [
44+
# https://docs.astral.sh/ruff/rules/#pyflakes-f
45+
"F", # Pyflakes
46+
# https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
47+
"E", # pycodestyle
48+
"W", # Warning
49+
# https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4
50+
# https://docs.astral.sh/ruff/rules/#mccabe-c90
51+
"C", # Complexity (mccabe+) & comprehensions
52+
# https://docs.astral.sh/ruff/rules/#pyupgrade-up
53+
"UP", # pyupgrade
54+
# https://docs.astral.sh/ruff/rules/#isort-i
55+
"I", # isort
56+
]
57+
ignore = [
58+
# https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
59+
"E402", # module level import not at top of file
60+
# https://docs.astral.sh/ruff/rules/#pyupgrade-up
61+
"UP006", # use-pep585-annotation
62+
"UP007", # use-pep604-annotation
63+
"E741", # Ambiguous variable name
64+
# "UP035", # deprecated-assertion
65+
]
66+
[tool.ruff.per-file-ignores]
67+
"__init__.py" = [
68+
"F401", # unused import
69+
"F403", # star imports
70+
]
71+
72+
[tool.ruff.mccabe]
73+
max-complexity = 24
74+
75+
[tool.ruff.pydocstyle]
76+
convention = "numpy"

src/app/api/dependencies.py

Lines changed: 21 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1-
from typing import Annotated, Union, Any
1+
from typing import Annotated, Any, Union
22

3+
from fastapi import Depends, HTTPException, Request
34
from sqlalchemy.ext.asyncio import AsyncSession
4-
from fastapi import (
5-
Depends,
6-
HTTPException,
7-
Request
8-
)
95

10-
from ..core.security import oauth2_scheme
116
from ..core.config import settings
12-
from ..core.exceptions.http_exceptions import UnauthorizedException, ForbiddenException, RateLimitException
137
from ..core.db.database import async_get_db
8+
from ..core.exceptions.http_exceptions import ForbiddenException, RateLimitException, UnauthorizedException
149
from ..core.logger import logging
10+
from ..core.security import oauth2_scheme, verify_token
1511
from ..core.utils.rate_limit import is_rate_limited
16-
from ..core.security import verify_token
1712
from ..crud.crud_rate_limit import crud_rate_limits
1813
from ..crud.crud_tier import crud_tiers
1914
from ..crud.crud_users import crud_users
@@ -25,49 +20,46 @@
2520
DEFAULT_LIMIT = settings.DEFAULT_RATE_LIMIT_LIMIT
2621
DEFAULT_PERIOD = settings.DEFAULT_RATE_LIMIT_PERIOD
2722

23+
2824
async def get_current_user(
29-
token: Annotated[str, Depends(oauth2_scheme)],
30-
db: Annotated[AsyncSession, Depends(async_get_db)]
25+
token: Annotated[str, Depends(oauth2_scheme)], db: Annotated[AsyncSession, Depends(async_get_db)]
3126
) -> Union[dict[str, Any], None]:
3227
token_data = await verify_token(token, db)
3328
if token_data is None:
3429
raise UnauthorizedException("User not authenticated.")
3530

3631
if "@" in token_data.username_or_email:
3732
user: dict | None = await crud_users.get(db=db, email=token_data.username_or_email, is_deleted=False)
38-
else:
33+
else:
3934
user = await crud_users.get(db=db, username=token_data.username_or_email, is_deleted=False)
40-
35+
4136
if user:
4237
return user
4338

4439
raise UnauthorizedException("User not authenticated.")
4540

4641

47-
async def get_optional_user(
48-
request: Request,
49-
db: AsyncSession = Depends(async_get_db)
50-
) -> dict | None:
42+
async def get_optional_user(request: Request, db: AsyncSession = Depends(async_get_db)) -> dict | None:
5143
token = request.headers.get("Authorization")
5244
if not token:
5345
return None
5446

5547
try:
56-
token_type, _, token_value = token.partition(' ')
57-
if token_type.lower() != 'bearer' or not token_value:
48+
token_type, _, token_value = token.partition(" ")
49+
if token_type.lower() != "bearer" or not token_value:
5850
return None
5951

6052
token_data = await verify_token(token_value, db)
6153
if token_data is None:
6254
return None
6355

6456
return await get_current_user(token_value, db=db)
65-
57+
6658
except HTTPException as http_exc:
6759
if http_exc.status_code != 401:
6860
logger.error(f"Unexpected HTTPException in get_optional_user: {http_exc.detail}")
6961
return None
70-
62+
7163
except Exception as exc:
7264
logger.error(f"Unexpected error in get_optional_user: {exc}")
7365
return None
@@ -76,29 +68,26 @@ async def get_optional_user(
7668
async def get_current_superuser(current_user: Annotated[dict, Depends(get_current_user)]) -> dict:
7769
if not current_user["is_superuser"]:
7870
raise ForbiddenException("You do not have enough privileges.")
79-
71+
8072
return current_user
8173

8274

8375
async def rate_limiter(
84-
request: Request,
85-
db: Annotated[AsyncSession, Depends(async_get_db)],
86-
user: User | None = Depends(get_optional_user)
76+
request: Request, db: Annotated[AsyncSession, Depends(async_get_db)], user: User | None = Depends(get_optional_user)
8777
) -> None:
8878
path = sanitize_path(request.url.path)
8979
if user:
9080
user_id = user["id"]
9181
tier = await crud_tiers.get(db, id=user["tier_id"])
9282
if tier:
93-
rate_limit = await crud_rate_limits.get(
94-
db=db,
95-
tier_id=tier["id"],
96-
path=path
97-
)
83+
rate_limit = await crud_rate_limits.get(db=db, tier_id=tier["id"], path=path)
9884
if rate_limit:
9985
limit, period = rate_limit["limit"], rate_limit["period"]
10086
else:
101-
logger.warning(f"User {user_id} with tier '{tier['name']}' has no specific rate limit for path '{path}'. Applying default rate limit.")
87+
logger.warning(
88+
f"User {user_id} with tier '{tier['name']}' has no specific rate limit for path '{path}'. \
89+
Applying default rate limit."
90+
)
10291
limit, period = DEFAULT_LIMIT, DEFAULT_PERIOD
10392
else:
10493
logger.warning(f"User {user_id} has no assigned tier. Applying default rate limit.")
@@ -107,12 +96,6 @@ async def rate_limiter(
10796
user_id = request.client.host
10897
limit, period = DEFAULT_LIMIT, DEFAULT_PERIOD
10998

110-
is_limited = await is_rate_limited(
111-
db=db,
112-
user_id=user_id,
113-
path=path,
114-
limit=limit,
115-
period=period
116-
)
99+
is_limited = await is_rate_limited(db=db, user_id=user_id, path=path, limit=limit, period=period)
117100
if is_limited:
118101
raise RateLimitException("Rate limit exceeded.")

src/app/api/paginated.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TypeVar, Generic, List, Dict, Any
1+
from typing import Any, Dict, Generic, List, TypeVar
22

33
from pydantic import BaseModel
44

@@ -76,4 +76,4 @@ def compute_offset(page: int, items_per_page: int) -> int:
7676
>>> offset(3, 10)
7777
20
7878
"""
79-
return (page - 1) * items_per_page
79+
return (page - 1) * items_per_page

src/app/api/v1/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
from .login import router as login_router
44
from .logout import router as logout_router
5-
from .users import router as users_router
65
from .posts import router as posts_router
6+
from .rate_limits import router as rate_limits_router
77
from .tasks import router as tasks_router
88
from .tiers import router as tiers_router
9-
from .rate_limits import router as rate_limits_router
9+
from .users import router as users_router
1010

1111
router = APIRouter(prefix="/v1")
1212
router.include_router(login_router)

src/app/api/v1/login.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
from typing import Annotated, Dict
21
from datetime import timedelta
2+
from typing import Annotated, Dict
33

4-
from fastapi import Response, Request, Depends
4+
import fastapi
5+
from fastapi import Depends, Request, Response
56
from fastapi.security import OAuth2PasswordRequestForm
67
from sqlalchemy.ext.asyncio import AsyncSession
7-
import fastapi
88

99
from ...core.config import settings
1010
from ...core.db.database import async_get_db
1111
from ...core.exceptions.http_exceptions import UnauthorizedException
1212
from ...core.schemas import Token
1313
from ...core.security import (
14-
ACCESS_TOKEN_EXPIRE_MINUTES,
15-
create_access_token,
16-
authenticate_user,
14+
ACCESS_TOKEN_EXPIRE_MINUTES,
15+
authenticate_user,
16+
create_access_token,
1717
create_refresh_token,
18-
verify_token
18+
verify_token,
1919
)
2020

2121
router = fastapi.APIRouter(tags=["login"])

src/app/api/v1/logout.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from typing import Dict
22

3-
from fastapi import APIRouter, Response, Depends
4-
from sqlalchemy.ext.asyncio import AsyncSession
3+
from fastapi import APIRouter, Depends, Response
54
from jose import JWTError
5+
from sqlalchemy.ext.asyncio import AsyncSession
66

7-
from ...core.security import oauth2_scheme, blacklist_token
87
from ...core.db.database import async_get_db
98
from ...core.exceptions.http_exceptions import UnauthorizedException
9+
from ...core.security import blacklist_token, oauth2_scheme
1010

1111
router = APIRouter(tags=["login"])
1212

0 commit comments

Comments
 (0)