|
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 | + |
2 | 9 | from typing import Generic, TypeVar, cast |
3 | | -from uuid import uuid4 |
4 | 10 |
|
5 | 11 | from pydantic import BaseModel |
6 | 12 |
|
7 | 13 | from ...config import SessionBackend |
8 | 14 | 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 |
9 | 19 |
|
10 | 20 | 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__) |
44 | 22 |
|
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"] |
133 | 24 |
|
134 | 25 |
|
135 | 26 | class SessionStorage(AbstractSessionStorage[T], Generic[T]): |
@@ -159,16 +50,10 @@ def get_session_storage(backend: str, model_type: type[BaseModel], **kwargs) -> |
159 | 50 | An initialized storage backend |
160 | 51 | """ |
161 | 52 | if backend == SessionBackend.REDIS.value: |
162 | | - from .backends.redis import RedisSessionStorage |
163 | | - |
164 | 53 | return RedisSessionStorage(**kwargs) |
165 | 54 | elif backend == SessionBackend.MEMCACHED.value: |
166 | | - from .backends.memcached import MemcachedSessionStorage |
167 | | - |
168 | 55 | return MemcachedSessionStorage(**kwargs) |
169 | 56 | elif backend == SessionBackend.MEMORY.value: |
170 | | - from .backends.memory import MemorySessionStorage |
171 | | - |
172 | 57 | return MemorySessionStorage(**kwargs) |
173 | 58 | else: |
174 | 59 | raise ValueError(f"Unknown backend: {backend}") |
0 commit comments