navigator-session 0.8.0__py3-none-any.whl → 0.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
navigator_session/data.py CHANGED
@@ -56,9 +56,23 @@ if PydanticBaseModel:
56
56
 
57
57
  class SessionData(MutableMapping[str, Any]):
58
58
  """Session dict-like object.
59
+
60
+ Supports both serializable data (stored in _data and persisted) and
61
+ in-memory objects (stored in _objects, not persisted).
62
+
63
+ Non-serializable objects (class instances, etc.) are automatically
64
+ stored in _objects when assigned via session.key = value or session['key'] = value.
59
65
  """
60
66
 
61
67
  _data: Union[str, Any] = {}
68
+ _objects: dict[str, Any] = {}
69
+
70
+ # Internal attributes that should not be stored in _data or _objects
71
+ _internal_attrs = frozenset({
72
+ '_data', '_objects', '_changed', '_id_', '_identity', '_new',
73
+ '_max_age', '_now', '__created__', '_created', '_dow', '_doy',
74
+ '_time', 'args'
75
+ })
62
76
 
63
77
  def __init__(
64
78
  self,
@@ -69,8 +83,10 @@ class SessionData(MutableMapping[str, Any]):
69
83
  identity: Optional[Any] = None,
70
84
  max_age: Optional[int] = None
71
85
  ) -> None:
72
- self._changed = False
73
- self._data = {}
86
+ # Initialize internal storage first (before any attribute access)
87
+ object.__setattr__(self, '_data', {})
88
+ object.__setattr__(self, '_objects', {})
89
+ object.__setattr__(self, '_changed', False)
74
90
  # Unique ID:
75
91
  self._id_ = (data.get(SESSION_ID, None) if data else id) or uuid.uuid4().hex
76
92
  # Session Identity
@@ -99,7 +115,86 @@ class SessionData(MutableMapping[str, Any]):
99
115
  self.args = args
100
116
 
101
117
  def __repr__(self) -> str:
102
- return f'<NAV-Session [new:{self.new}, created:{self.created}] {self._data!r}>'
118
+ return (
119
+ f'<NAV-Session [new:{self.new}, created:{self.created}] '
120
+ f'data={self._data!r}, objects={list(self._objects.keys())}>'
121
+ )
122
+
123
+ # --- Serialization helpers ---
124
+
125
+ def _is_serializable(self, value: Any) -> bool:
126
+ """Check if a value can be reliably serialized and restored with jsonpickle.
127
+
128
+ Returns True for primitive types, dicts, lists, and known serializable models.
129
+ Returns False for arbitrary class instances that may not restore properly
130
+ in a different process/context.
131
+ """
132
+ # Primitive types are always serializable
133
+ if value is None or isinstance(value, (bool, int, float, str, bytes)):
134
+ return True
135
+
136
+ # Dicts and lists need recursive check
137
+ if isinstance(value, dict):
138
+ return all(self._is_serializable(v) for v in value.values())
139
+ if isinstance(value, (list, tuple, set, frozenset)):
140
+ return all(self._is_serializable(v) for v in value)
141
+
142
+ # BaseModel (datamodel) instances are handled by ModelHandler
143
+ if isinstance(value, BaseModel):
144
+ return True
145
+
146
+ # Pydantic models are handled by PydanticHandler
147
+ if PydanticBaseModel and isinstance(value, PydanticBaseModel):
148
+ return True
149
+
150
+ # datetime types are serializable
151
+ if isinstance(value, (datetime,)):
152
+ return True
153
+
154
+ # Any other type (class instances, functions, etc.) is NOT reliably serializable
155
+ # These should be stored in-memory only
156
+ return False
157
+
158
+ def _get_value(self, key: str) -> Any:
159
+ """Unified getter that checks both _objects and _data."""
160
+ if key in self._objects:
161
+ return self._objects[key]
162
+ if key in self._data:
163
+ return self._data[key]
164
+ raise KeyError(key)
165
+
166
+ def _set_value(self, key: str, value: Any) -> None:
167
+ """Unified setter that routes to _objects or _data based on serializability."""
168
+ if self._is_serializable(value):
169
+ # Remove from _objects if it was there before
170
+ self._objects.pop(key, None)
171
+ self._data[key] = value
172
+ self._changed = True
173
+ else:
174
+ # Store in _objects (in-memory only, not persisted)
175
+ # Remove from _data if it was there before
176
+ self._data.pop(key, None)
177
+ self._objects[key] = value
178
+ # Note: _objects changes don't set _changed since they're not persisted
179
+
180
+ def _del_value(self, key: str) -> None:
181
+ """Unified delete that removes from both _objects and _data."""
182
+ deleted = False
183
+ if key in self._objects:
184
+ del self._objects[key]
185
+ deleted = True
186
+ if key in self._data:
187
+ del self._data[key]
188
+ self._changed = True
189
+ deleted = True
190
+ if not deleted:
191
+ raise KeyError(key)
192
+
193
+ def _has_value(self, key: str) -> bool:
194
+ """Check if key exists in either _objects or _data."""
195
+ return key in self._objects or key in self._data
196
+
197
+ # --- Properties ---
103
198
 
104
199
  @property
105
200
  def new(self) -> bool:
@@ -131,7 +226,7 @@ class SessionData(MutableMapping[str, Any]):
131
226
 
132
227
  @property
133
228
  def empty(self) -> bool:
134
- return not bool(self._data)
229
+ return not bool(self._data) and not bool(self._objects)
135
230
 
136
231
  @property
137
232
  def max_age(self) -> Optional[int]:
@@ -153,36 +248,61 @@ class SessionData(MutableMapping[str, Any]):
153
248
  self._changed = True
154
249
 
155
250
  def session_data(self) -> dict:
251
+ """Return only serializable data (for persistence)."""
156
252
  return self._data
157
253
 
254
+ def session_objects(self) -> dict:
255
+ """Return in-memory objects (not persisted)."""
256
+ return self._objects
257
+
158
258
  def invalidate(self) -> None:
259
+ """Clear all session data and in-memory objects."""
159
260
  self._changed = True
160
261
  self._data = {}
262
+ self._objects = {}
263
+
264
+ # --- Magic Methods ---
161
265
 
162
- # Magic Methods
163
266
  def __len__(self) -> int:
164
- return len(self._data)
267
+ return len(self._data) + len(self._objects)
165
268
 
166
269
  def __iter__(self) -> Iterator[str]:
167
- return iter(self._data)
270
+ # Iterate over both _data and _objects keys
271
+ seen = set()
272
+ for key in self._data:
273
+ seen.add(key)
274
+ yield key
275
+ for key in self._objects:
276
+ if key not in seen:
277
+ yield key
168
278
 
169
279
  def __contains__(self, key: object) -> bool:
170
- return key in self._data
280
+ return self._has_value(str(key))
171
281
 
172
282
  def __getitem__(self, key: str) -> Any:
173
- return self._data[key]
283
+ return self._get_value(key)
174
284
 
175
285
  def __setitem__(self, key: str, value: Any) -> None:
176
- self._data[key] = value
177
- self._changed = True
178
- # TODO: also, saved into redis automatically
286
+ self._set_value(key, value)
179
287
 
180
288
  def __delitem__(self, key: str) -> None:
181
- del self._data[key]
182
- self._changed = True
289
+ self._del_value(key)
183
290
 
184
291
  def __getattr__(self, key: str) -> Any:
185
- return self._data[key]
292
+ # Avoid infinite recursion for internal attributes
293
+ if key.startswith('_'):
294
+ raise AttributeError(key)
295
+ try:
296
+ return self._get_value(key)
297
+ except KeyError:
298
+ raise AttributeError(key) from None
299
+
300
+ def __setattr__(self, key: str, value: Any) -> None:
301
+ # Handle internal attributes normally
302
+ if key in self._internal_attrs or key.startswith('_'):
303
+ object.__setattr__(self, key, value)
304
+ else:
305
+ self._set_value(key, value)
186
306
 
187
307
  def encode(self, obj: Any) -> str:
188
308
  """encode
@@ -6,8 +6,9 @@ __description__ = (
6
6
  'Navigator Session allows us to store user-specific data '
7
7
  'into session object.'
8
8
  )
9
- __version__ = '0.8.0'
9
+ __version__ = '0.8.1'
10
10
  __copyright__ = 'Copyright (c) 2023 Jesus Lara'
11
11
  __author__ = 'Jesus Lara'
12
12
  __author_email__ = 'jesuslarag@gmail.com'
13
13
  __license__ = 'Apache-2.0'
14
+ __url__ = 'https://github.com/phenobarbital/navigator-session'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: navigator-session
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: Navigator Session allows us to store user-specific data into session object.
5
5
  Author-email: Jesus Lara Gimenez <jesuslarag@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -1,15 +1,15 @@
1
1
  navigator_session/__init__.py,sha256=U1MtM30ccs5N6c_lzUYFRS5NURz6kheDC8jZmRL_2yc,3056
2
2
  navigator_session/conf.py,sha256=Pe8qsukmt5cXXPc3hwFphSWF5xCIpZfeFQ8JhICEnow,1695
3
- navigator_session/data.py,sha256=Mcsu95sXLTDMJbJ_QmI-fp8gBkFJqIfg-bpCKL_UMkw,6711
3
+ navigator_session/data.py,sha256=z6W4aCWgcEkSvDynYn4ZeRnjp7gGA0LPWjCe7EyLz-w,11430
4
4
  navigator_session/middleware.py,sha256=_bQKfD81BxNZTGuyGTc4yh7y1Rb95I-y2hM1ihE4wCk,1944
5
5
  navigator_session/session.py,sha256=CG-TjsmQqjuk6N7GCYNgQTCkrXptgq-swHDiemgHmKI,2398
6
- navigator_session/version.py,sha256=sG88AbAgbH8l9aRo5I849Lg-2dutyjDExTSNa_nSCQY,430
6
+ navigator_session/version.py,sha256=DIb5BlRf9IBvfNYoZbYmO-bU4pxDnrZIiij_g8hzUZE,492
7
7
  navigator_session/storages/__init__.py,sha256=kTcHfqps5LmnDpFAktxevPuSIwRbnrcMmB7Uygq4axQ,34
8
8
  navigator_session/storages/abstract.py,sha256=wTv0yWMVXTwrzA_bjexcwI7c6_W3tzosXlMOIZIQrBM,6503
9
9
  navigator_session/storages/cookie.py,sha256=VILzVCatKlDhMoQDoE2y0u6s7lp4wbdep5Oc0NfIJcg,2474
10
10
  navigator_session/storages/redis.py,sha256=mqSRyb0YOgV8gvNiAuPhYCLA0fCOgZsQlqOUWE85Gss,11528
11
- navigator_session-0.8.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
- navigator_session-0.8.0.dist-info/METADATA,sha256=83Y_us-KaI51tgkmJvMx80kEEEd1FEXfgwCFjabmLzk,2360
13
- navigator_session-0.8.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- navigator_session-0.8.0.dist-info/top_level.txt,sha256=ZpOEy3wLKGsxG2rc0nHqcqJCV3HIOG_XCfE6mtsYYYY,18
15
- navigator_session-0.8.0.dist-info/RECORD,,
11
+ navigator_session-0.8.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
+ navigator_session-0.8.1.dist-info/METADATA,sha256=48VS8qLDfWasNb0m8qxSZi4pl6Gu035Sbu7sKtpMU6g,2360
13
+ navigator_session-0.8.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
+ navigator_session-0.8.1.dist-info/top_level.txt,sha256=ZpOEy3wLKGsxG2rc0nHqcqJCV3HIOG_XCfE6mtsYYYY,18
15
+ navigator_session-0.8.1.dist-info/RECORD,,