Skip to content

Commit d9e5559

Browse files
committed
some code quality fixes plus CI
1 parent 7bafd24 commit d9e5559

22 files changed

Lines changed: 214 additions & 251 deletions

File tree

.github/workflows/linting.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
# (backend's dev extra holds ruff itself).
2222
run: uv sync --all-packages --all-extras
2323

24-
- name: Ruff check (backend + cli)
24+
- name: Ruff check (backend + cli + tests)
2525
# --no-sync prevents uv run's implicit re-sync from dropping member
2626
# extras (it defaults to no extras and would remove ruff).
27-
run: uv run --no-sync ruff check backend/src cli/src
27+
run: uv run --no-sync ruff check backend/src cli/src backend/tests

backend/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ line-length = 128
129129

130130
[tool.ruff.lint]
131131
select = ["E", "F", "I", "UP"]
132-
extend-select = ["UP006", "UP007", "UP035", "UP039"]
132+
extend-select = ["UP006", "UP007", "UP035", "UP039", "PLC0415"]
133133

134134
[tool.ruff.lint.isort]
135135
known-first-party = ["src"]

backend/src/infrastructure/auth/session/backends/memcached.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from ....config.settings import get_settings
1616
from ....logging import get_logger
17-
from ..storage import AbstractSessionStorage
17+
from ..base import AbstractSessionStorage
1818

1919
T = TypeVar("T", bound=BaseModel)
2020
settings = get_settings()

backend/src/infrastructure/auth/session/backends/memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pydantic import BaseModel
88

99
from ....logging import get_logger
10-
from ..storage import AbstractSessionStorage
10+
from ..base import AbstractSessionStorage
1111

1212
T = TypeVar("T", bound=BaseModel)
1313
logger = get_logger()

backend/src/infrastructure/auth/session/backends/redis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from ....config.settings import get_settings
1616
from ....logging import get_logger
17-
from ..storage import AbstractSessionStorage
17+
from ..base import AbstractSessionStorage
1818

1919
T = TypeVar("T", bound=BaseModel)
2020
settings = get_settings()
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Abstract base for session storage backends.
2+
3+
Lives in its own module so that the concrete backend implementations
4+
under ``backends/`` can subclass it without participating in the
5+
``storage.py`` factory's import cycle. The factory (``storage.py``)
6+
imports both this base and the concrete backends; concrete backends
7+
only import this base.
8+
"""
9+
10+
from abc import ABC, abstractmethod
11+
from typing import Generic, TypeVar
12+
from uuid import uuid4
13+
14+
from pydantic import BaseModel
15+
16+
T = TypeVar("T", bound=BaseModel)
17+
18+
19+
class AbstractSessionStorage(Generic[T], ABC):
20+
"""Abstract base class for session storage implementations."""
21+
22+
def __init__(
23+
self,
24+
prefix: str = "session:",
25+
expiration: int = 1800,
26+
):
27+
"""Initialize the session storage.
28+
29+
Args:
30+
prefix: Prefix for all session keys
31+
expiration: Default session expiration in seconds
32+
"""
33+
self.prefix = prefix
34+
self.expiration = expiration
35+
36+
def generate_session_id(self) -> str:
37+
"""Generate a unique session ID.
38+
39+
Returns:
40+
A unique session ID string
41+
"""
42+
return str(uuid4())
43+
44+
def get_key(self, session_id: str) -> str:
45+
"""Generate the full key for a session ID.
46+
47+
Args:
48+
session_id: The session ID
49+
50+
Returns:
51+
The full storage key
52+
"""
53+
return f"{self.prefix}{session_id}"
54+
55+
@abstractmethod
56+
async def create(self, data: T, session_id: str | None = None, expiration: int | None = None) -> str:
57+
"""Create a new session.
58+
59+
Args:
60+
data: Session data (must be a Pydantic model)
61+
session_id: Optional session ID. If not provided, one will be generated
62+
expiration: Optional custom expiration in seconds
63+
64+
Returns:
65+
The session ID
66+
"""
67+
pass
68+
69+
@abstractmethod
70+
async def get(self, session_id: str, model_class: type[T]) -> T | None:
71+
"""Get session data.
72+
73+
Args:
74+
session_id: The session ID
75+
model_class: The Pydantic model class to decode the data into
76+
77+
Returns:
78+
The session data or None if session doesn't exist
79+
"""
80+
pass
81+
82+
@abstractmethod
83+
async def update(self, session_id: str, data: T, reset_expiration: bool = True, expiration: int | None = None) -> bool:
84+
"""Update session data.
85+
86+
Args:
87+
session_id: The session ID
88+
data: New session data
89+
reset_expiration: Whether to reset the expiration
90+
expiration: Optional custom expiration in seconds
91+
92+
Returns:
93+
True if the session was updated, False if it didn't exist
94+
"""
95+
pass
96+
97+
@abstractmethod
98+
async def delete(self, session_id: str) -> bool:
99+
"""Delete a session.
100+
101+
Args:
102+
session_id: The session ID
103+
104+
Returns:
105+
True if the session was deleted, False if it didn't exist
106+
"""
107+
pass
108+
109+
@abstractmethod
110+
async def extend(self, session_id: str, expiration: int | None = None) -> bool:
111+
"""Extend the expiration of a session.
112+
113+
Args:
114+
session_id: The session ID
115+
expiration: Optional custom expiration in seconds
116+
117+
Returns:
118+
True if the session was extended, False if it didn't exist
119+
"""
120+
pass
121+
122+
@abstractmethod
123+
async def exists(self, session_id: str) -> bool:
124+
"""Check if a session exists.
125+
126+
Args:
127+
session_id: The session ID
128+
129+
Returns:
130+
True if the session exists, False otherwise
131+
"""
132+
pass
133+
134+
@abstractmethod
135+
async def close(self) -> None:
136+
"""Close the storage connection."""
137+
pass
Lines changed: 14 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,26 @@
1-
from abc import ABC, abstractmethod
1+
"""Session storage factory + backwards-compatible re-export of the abstract base.
2+
3+
The abstract base (``AbstractSessionStorage``) lives in ``base.py`` so
4+
that the concrete ``backends/`` implementations don't form a cycle with
5+
this module. Existing callers that import ``AbstractSessionStorage``
6+
from ``.storage`` keep working via the re-export below.
7+
"""
8+
29
from typing import Generic, TypeVar, cast
3-
from uuid import uuid4
410

511
from pydantic import BaseModel
612

713
from ...config import SessionBackend
814
from ...logging import get_logger
15+
from .backends.memcached import MemcachedSessionStorage
16+
from .backends.memory import MemorySessionStorage
17+
from .backends.redis import RedisSessionStorage
18+
from .base import AbstractSessionStorage
919

1020
T = TypeVar("T", bound=BaseModel)
11-
logger = get_logger()
12-
13-
14-
class AbstractSessionStorage(Generic[T], ABC):
15-
"""Abstract base class for session storage implementations."""
16-
17-
def __init__(
18-
self,
19-
prefix: str = "session:",
20-
expiration: int = 1800,
21-
):
22-
"""Initialize the session storage.
23-
24-
Args:
25-
prefix: Prefix for all session keys
26-
expiration: Default session expiration in seconds
27-
"""
28-
self.prefix = prefix
29-
self.expiration = expiration
30-
31-
def generate_session_id(self) -> str:
32-
"""Generate a unique session ID.
33-
34-
Returns:
35-
A unique session ID string
36-
"""
37-
return str(uuid4())
38-
39-
def get_key(self, session_id: str) -> str:
40-
"""Generate the full key for a session ID.
41-
42-
Args:
43-
session_id: The session ID
21+
logger = get_logger(__name__)
4422

45-
Returns:
46-
The full storage key
47-
"""
48-
return f"{self.prefix}{session_id}"
49-
50-
@abstractmethod
51-
async def create(self, data: T, session_id: str | None = None, expiration: int | None = None) -> str:
52-
"""Create a new session.
53-
54-
Args:
55-
data: Session data (must be a Pydantic model)
56-
session_id: Optional session ID. If not provided, one will be generated
57-
expiration: Optional custom expiration in seconds
58-
59-
Returns:
60-
The session ID
61-
"""
62-
pass
63-
64-
@abstractmethod
65-
async def get(self, session_id: str, model_class: type[T]) -> T | None:
66-
"""Get session data.
67-
68-
Args:
69-
session_id: The session ID
70-
model_class: The Pydantic model class to decode the data into
71-
72-
Returns:
73-
The session data or None if session doesn't exist
74-
"""
75-
pass
76-
77-
@abstractmethod
78-
async def update(self, session_id: str, data: T, reset_expiration: bool = True, expiration: int | None = None) -> bool:
79-
"""Update session data.
80-
81-
Args:
82-
session_id: The session ID
83-
data: New session data
84-
reset_expiration: Whether to reset the expiration
85-
expiration: Optional custom expiration in seconds
86-
87-
Returns:
88-
True if the session was updated, False if it didn't exist
89-
"""
90-
pass
91-
92-
@abstractmethod
93-
async def delete(self, session_id: str) -> bool:
94-
"""Delete a session.
95-
96-
Args:
97-
session_id: The session ID
98-
99-
Returns:
100-
True if the session was deleted, False if it didn't exist
101-
"""
102-
pass
103-
104-
@abstractmethod
105-
async def extend(self, session_id: str, expiration: int | None = None) -> bool:
106-
"""Extend the expiration of a session.
107-
108-
Args:
109-
session_id: The session ID
110-
expiration: Optional custom expiration in seconds
111-
112-
Returns:
113-
True if the session was extended, False if it didn't exist
114-
"""
115-
pass
116-
117-
@abstractmethod
118-
async def exists(self, session_id: str) -> bool:
119-
"""Check if a session exists.
120-
121-
Args:
122-
session_id: The session ID
123-
124-
Returns:
125-
True if the session exists, False otherwise
126-
"""
127-
pass
128-
129-
@abstractmethod
130-
async def close(self) -> None:
131-
"""Close the storage connection."""
132-
pass
23+
__all__ = ["AbstractSessionStorage", "SessionStorage", "get_session_storage"]
13324

13425

13526
class SessionStorage(AbstractSessionStorage[T], Generic[T]):
@@ -159,16 +50,10 @@ def get_session_storage(backend: str, model_type: type[BaseModel], **kwargs) ->
15950
An initialized storage backend
16051
"""
16152
if backend == SessionBackend.REDIS.value:
162-
from .backends.redis import RedisSessionStorage
163-
16453
return RedisSessionStorage(**kwargs)
16554
elif backend == SessionBackend.MEMCACHED.value:
166-
from .backends.memcached import MemcachedSessionStorage
167-
16855
return MemcachedSessionStorage(**kwargs)
16956
elif backend == SessionBackend.MEMORY.value:
170-
from .backends.memory import MemorySessionStorage
171-
17257
return MemorySessionStorage(**kwargs)
17358
else:
17459
raise ValueError(f"Unknown backend: {backend}")

backend/src/infrastructure/logging/config.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
"""
1313

1414
import contextvars
15+
import inspect
1516
import logging
1617
import logging.config
18+
import threading
19+
import uuid
1720

1821
from ..config import LogFormat
1922
from ..config.settings import EnvironmentOption, get_settings
@@ -221,8 +224,6 @@ def add_correlation_id_filter() -> None:
221224
all log records automatically. Adds the filter to the root logger
222225
so all child loggers inherit the correlation ID functionality.
223226
"""
224-
import logging
225-
226227
root_logger = logging.getLogger()
227228
correlation_filter = CorrelationIdFilter()
228229
root_logger.addFilter(correlation_filter)
@@ -268,8 +269,6 @@ def _get_correlation_id(self) -> str | None:
268269
Returns:
269270
Correlation ID string or None if not found
270271
"""
271-
import threading
272-
273272
try:
274273
correlation_id = correlation_id_var.get()
275274
if correlation_id:
@@ -285,8 +284,6 @@ def _get_correlation_id(self) -> str | None:
285284
pass
286285

287286
try:
288-
import inspect
289-
290287
frame = inspect.currentframe()
291288
while frame:
292289
if "request" in frame.f_locals:
@@ -334,8 +331,6 @@ def generate_correlation_id() -> str:
334331
Returns:
335332
New UUID-based correlation ID
336333
"""
337-
import uuid
338-
339334
return str(uuid.uuid4())
340335

341336

0 commit comments

Comments
 (0)