Skip to content

Commit e8fbbfb

Browse files
Merge remote-tracking branch 'origin/main' into hetzner
2 parents b533faa + f63bbdc commit e8fbbfb

7 files changed

Lines changed: 46 additions & 51 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "fastapi-jinja2-postgres-webapp"
3-
version = "0.1.28"
3+
version = "0.1.29"
44
description = "A template webapp with a pure-Python FastAPI backend, frontend templating with Jinja2, and a Postgres database to power user auth"
55
readme = "README.md"
66
package-mode = false

tests/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
)
2727
from main import app
2828
from datetime import datetime, UTC, timedelta
29+
from utils.core.rate_limit import clear_all_rate_limiters
30+
31+
32+
@pytest.fixture(autouse=True)
33+
def reset_rate_limiters() -> Generator[None, None, None]:
34+
clear_all_rate_limiters()
35+
yield
36+
clear_all_rate_limiters()
2937

3038

3139
# Define a custom exception for test setup errors

tests/routers/core/test_account.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import pytest
21
from fastapi.testclient import TestClient
32
from starlette.datastructures import URLPath
43
from sqlmodel import Session, select
@@ -28,28 +27,13 @@
2827
get_password_hash,
2928
)
3029
from utils.core.rate_limit import (
31-
login_ip_limiter,
30+
forgot_password_email_limiter,
31+
forgot_password_ip_limiter,
3232
login_email_limiter,
33+
login_ip_limiter,
3334
register_ip_limiter,
34-
forgot_password_ip_limiter,
35-
forgot_password_email_limiter,
3635
)
3736

38-
39-
@pytest.fixture(autouse=True)
40-
def _reset_rate_limiters():
41-
"""Reset all rate limiter state between tests to avoid cross-test pollution."""
42-
yield
43-
for limiter in (
44-
login_ip_limiter,
45-
login_email_limiter,
46-
register_ip_limiter,
47-
forgot_password_ip_limiter,
48-
forgot_password_email_limiter,
49-
):
50-
limiter._attempts.clear()
51-
52-
5337
# --- API Endpoint Tests ---
5438

5539

tests/test_htmx.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,15 @@
88
- Non-HTMX paths remain unchanged (303 RedirectResponse or full-page error).
99
"""
1010

11-
import pytest
1211
from starlette.requests import Request
1312
from fastapi.templating import Jinja2Templates
1413
from tests.conftest import htmx_headers
1514
from utils.core.htmx import is_htmx_request, toast_response, append_toast
1615
from utils.core.rate_limit import (
17-
login_ip_limiter,
18-
login_email_limiter,
19-
register_ip_limiter,
2016
forgot_password_ip_limiter,
21-
forgot_password_email_limiter,
17+
login_ip_limiter,
2218
)
2319

24-
25-
@pytest.fixture(autouse=True)
26-
def _reset_rate_limiters():
27-
"""Reset all rate limiter state between tests."""
28-
yield
29-
for limiter in (
30-
login_ip_limiter,
31-
login_email_limiter,
32-
register_ip_limiter,
33-
forgot_password_ip_limiter,
34-
forgot_password_email_limiter,
35-
):
36-
limiter._attempts.clear()
37-
38-
3920
# ---------------------------------------------------------------------------
4021
# 1.3 — is_htmx_request helper
4122
# ---------------------------------------------------------------------------

utils/core/organizations.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from typing import Any, cast
2+
13
from sqlmodel import Session, select
2-
from sqlalchemy.orm import selectinload
4+
from sqlalchemy.orm import InstrumentedAttribute, selectinload
35

46
from utils.core.models import Organization, Role, User, Invitation
57

@@ -21,13 +23,15 @@ def load_org_for_members_partial(
2123
select(Organization)
2224
.where(Organization.id == organization_id)
2325
.options(
24-
selectinload(Organization.roles)
25-
.selectinload(Role.users)
26-
.selectinload(User.account),
27-
selectinload(Organization.roles)
28-
.selectinload(Role.users)
29-
.selectinload(User.roles),
30-
selectinload(Organization.roles).selectinload(Role.permissions),
26+
selectinload(cast(InstrumentedAttribute[Any], Organization.roles))
27+
.selectinload(cast(InstrumentedAttribute[Any], Role.users))
28+
.selectinload(cast(InstrumentedAttribute[Any], User.account)),
29+
selectinload(cast(InstrumentedAttribute[Any], Organization.roles))
30+
.selectinload(cast(InstrumentedAttribute[Any], Role.users))
31+
.selectinload(cast(InstrumentedAttribute[Any], User.roles)),
32+
selectinload(
33+
cast(InstrumentedAttribute[Any], Organization.roles)
34+
).selectinload(cast(InstrumentedAttribute[Any], Role.permissions)),
3135
)
3236
).first()
3337
user_permissions = _user_permissions_for_org(user, organization_id)
@@ -43,8 +47,12 @@ def load_org_for_roles_partial(
4347
select(Organization)
4448
.where(Organization.id == organization_id)
4549
.options(
46-
selectinload(Organization.roles).selectinload(Role.users),
47-
selectinload(Organization.roles).selectinload(Role.permissions),
50+
selectinload(
51+
cast(InstrumentedAttribute[Any], Organization.roles)
52+
).selectinload(cast(InstrumentedAttribute[Any], Role.users)),
53+
selectinload(
54+
cast(InstrumentedAttribute[Any], Organization.roles)
55+
).selectinload(cast(InstrumentedAttribute[Any], Role.permissions)),
4856
)
4957
).first()
5058
user_permissions = _user_permissions_for_org(user, organization_id)

utils/core/rate_limit.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,20 @@ def _int_env(name: str, default: int) -> int:
154154
window_seconds=_int_env("FORGOT_PASSWORD_EMAIL_WINDOW_SECONDS", 60),
155155
)
156156

157+
_ALL_LIMITERS = (
158+
login_ip_limiter,
159+
login_email_limiter,
160+
register_ip_limiter,
161+
forgot_password_ip_limiter,
162+
forgot_password_email_limiter,
163+
)
164+
165+
166+
def clear_all_rate_limiters() -> None:
167+
"""Clear all in-memory rate limiter state."""
168+
for limiter in _ALL_LIMITERS:
169+
limiter._attempts.clear()
170+
157171

158172
# --- Dependency helpers ---
159173

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)