navigator-session 0.4.2__tar.gz → 0.6.0rc1__tar.gz

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.
Files changed (37) hide show
  1. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/Makefile +2 -2
  2. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/PKG-INFO +4 -1
  3. navigator-session-0.6.0rc1/docs/requirements-dev.txt +21 -0
  4. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/__init__.py +4 -3
  5. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/conf.py +7 -3
  6. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/data.py +19 -8
  7. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/middleware.py +5 -3
  8. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/session.py +11 -9
  9. navigator-session-0.6.0rc1/navigator_session/storages/__init__.py +1 -0
  10. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/storages/abstract.py +53 -32
  11. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/storages/cookie.py +4 -3
  12. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/storages/redis.py +104 -65
  13. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session/version.py +1 -1
  14. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session.egg-info/PKG-INFO +4 -1
  15. navigator-session-0.6.0rc1/navigator_session.egg-info/requires.txt +7 -0
  16. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/pyproject.toml +5 -3
  17. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/setup.py +14 -12
  18. navigator-session-0.4.2/docs/requirements-dev.txt +0 -19
  19. navigator-session-0.4.2/navigator_session/storages/__init__.py +0 -6
  20. navigator-session-0.4.2/navigator_session.egg-info/requires.txt +0 -9
  21. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/CHANGES.rst +0 -0
  22. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/LICENSE +0 -0
  23. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/MANIFEST.in +0 -0
  24. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/README.md +0 -0
  25. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/docs/Makefile +0 -0
  26. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/docs/api.rst +0 -0
  27. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/docs/authors.rst +0 -0
  28. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/docs/conf.py +0 -0
  29. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/docs/examples.rst +0 -0
  30. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/docs/index.rst +0 -0
  31. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/docs/make.bat +0 -0
  32. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/docs/requirements.txt +0 -0
  33. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session.egg-info/SOURCES.txt +0 -0
  34. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session.egg-info/dependency_links.txt +0 -0
  35. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/navigator_session.egg-info/top_level.txt +0 -0
  36. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/setup.cfg +0 -0
  37. {navigator-session-0.4.2 → navigator-session-0.6.0rc1}/tests/__init__.py +0 -0
@@ -3,11 +3,11 @@ venv:
3
3
  echo 'run `source .venv/bin/activate` to start develop Navigator-Session'
4
4
 
5
5
  setup:
6
- pip install wheel==0.38.4
6
+ pip install wheel==0.42.0
7
7
  pip install -e .
8
8
 
9
9
  develop:
10
- pip install wheel==0.38.4
10
+ pip install wheel==0.42.0
11
11
  pip install -e .
12
12
  pip install -Ur docs/requirements-dev.txt
13
13
  flit install --symlink
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: navigator-session
3
- Version: 0.4.2
3
+ Version: 0.6.0rc1
4
4
  Summary: Navigator Session allows us to store user-specific data into session object.
5
5
  Home-page: https://github.com/phenobarbital/navigator-session
6
6
  Author: Jesus Lara
@@ -21,6 +21,7 @@ Classifier: Topic :: Database :: Front-Ends
21
21
  Classifier: Programming Language :: Python :: 3.9
22
22
  Classifier: Programming Language :: Python :: 3.10
23
23
  Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
24
25
  Classifier: Framework :: AsyncIO
25
26
  Classifier: Framework :: aiohttp
26
27
  Requires-Python: >=3.9.16
@@ -54,3 +55,5 @@ Navigator_Session Allows us to store User-based data into a session Object, this
54
55
  ### License ###
55
56
 
56
57
  Navigator_Session is copyright of Jesus Lara (https://phenobarbital.info) and under Apache 2 license. I am providing code in this repository under an open source license, remember, this is my personal repository; the license that you receive is from me and not from my employeer.
58
+
59
+
@@ -0,0 +1,21 @@
1
+ aiounittest==1.4.2
2
+ black==24.3.0
3
+ build==1.0.3
4
+ coverage[toml]==7.2.7
5
+ flit==3.9.0
6
+ hypothesis==6.91.0
7
+ ipython==8.14.0
8
+ lint==1.2.1
9
+ mypy==1.7.1
10
+ pylint==2.16.1
11
+ pyperf==2.6.2
12
+ pytest>=6.0.0
13
+ pytest-asyncio==0.23.2
14
+ pytest-cov==4.0.0
15
+ pytest-cython==0.2.0
16
+ pytest-xdist==3.5.0
17
+ pytest-assume==2.4.3
18
+ sdist==0.0.0
19
+ sphinx==6.1.3
20
+ tox==4.6.4
21
+ twine==4.0.2
@@ -12,6 +12,7 @@ from .conf import (
12
12
  AUTH_SESSION_OBJECT,
13
13
  SESSION_TIMEOUT,
14
14
  SESSION_KEY,
15
+ SESSION_REQUEST_KEY,
15
16
  SESSION_URL,
16
17
  SESSION_PREFIX,
17
18
  SESSION_USER_PROPERTY
@@ -37,7 +38,7 @@ async def new_session(request: web.Request, userdata: dict = None) -> SessionDat
37
38
  storage = request.get(SESSION_STORAGE)
38
39
  if storage is None:
39
40
  raise RuntimeError(
40
- "Missing Configuration for Session Middleware, please install on Aiohttp Middlewares"
41
+ "Missing Configuration for NAV Session Middleware."
41
42
  )
42
43
  session = await storage.new_session(request, userdata)
43
44
  if not isinstance(session, SessionData):
@@ -75,7 +76,7 @@ async def get_session(
75
76
  storage = request.get(SESSION_STORAGE)
76
77
  if storage is None:
77
78
  raise RuntimeError(
78
- "Missing Configuration for Session Storage, please install Session Middleware."
79
+ "Missing Configuration of Session Storage, please install it."
79
80
  )
80
81
  # using the storage session for Load an existing Session
81
82
  try:
@@ -90,7 +91,7 @@ async def get_session(
90
91
  f"Error Loading user Session: {err!s}"
91
92
  ) from err
92
93
  request[SESSION_OBJECT] = session
93
- request['session'] = session
94
+ request[SESSION_REQUEST_KEY] = session
94
95
  if new is True and not isinstance(session, SessionData):
95
96
  raise RuntimeError(
96
97
  f"Installed {session!r} storage should return session instance "
@@ -22,9 +22,12 @@ SESSION_NAME = f"{APP_TITLE}_SESSION"
22
22
  JWT_ALGORITHM = config.get("JWT_ALGORITHM", fallback="HS256")
23
23
  SESSION_PREFIX = f'{SESSION_PREFIX}_session'
24
24
  SESSION_TIMEOUT = config.getint('SESSION_TIMEOUT', fallback=360000)
25
- SESSION_STORAGE = 'NAVIGATOR_SESSION_STORAGE'
26
- SESSION_OBJECT = 'NAV_SESSION'
27
-
25
+ SESSION_STORAGE = config.get(
26
+ 'NAV_SESSION_STORAGE',
27
+ fallback='NAVIGATOR_SESSION_STORAGE'
28
+ )
29
+ SESSION_OBJECT = config.get('SESSION_OBJECT', fallback='NAV_SESSION')
30
+ SESSION_REQUEST_KEY = config.get('SESSION_REQUEST_KEY', fallback='session')
28
31
 
29
32
  # SESSION BACKEND:
30
33
  SESSION_BACKEND = config.get('SESSION_BACKEND', fallback='redis')
@@ -38,4 +41,5 @@ SESSION_URL = f"{SESSION_BACKEND}://{REDIS_HOST}:{REDIS_PORT}/{REDIS_SESSION_DB}
38
41
  # User Attributes:
39
42
  SESSION_USER_PROPERTY = config.get('SESSION_USER_PROPERTY', fallback='user')
40
43
  SESSION_KEY = config.get('SESSION_KEY', fallback='id')
44
+ SESSION_ID = config.get('SESSION_ID', fallback='session_id')
41
45
  SESSION_COOKIE_SECURE = config.get('SESSION_COOKIE_SECURE', fallback='csrf_secure')
@@ -8,8 +8,11 @@ import jsonpickle
8
8
  from jsonpickle.unpickler import loadclass
9
9
  from aiohttp import web
10
10
  from datamodel import BaseModel
11
- from navigator_session.conf import SESSION_KEY, SESSION_STORAGE
12
-
11
+ from .conf import (
12
+ SESSION_KEY,
13
+ SESSION_ID,
14
+ SESSION_STORAGE
15
+ )
13
16
 
14
17
  class ModelHandler(jsonpickle.handlers.BaseHandler):
15
18
  """ModelHandler.
@@ -43,21 +46,27 @@ class SessionData(MutableMapping[str, Any]):
43
46
  *args,
44
47
  data: Optional[Mapping[str, Any]] = None,
45
48
  new: bool = False,
49
+ id: Optional[str] = None,
46
50
  identity: Optional[Any] = None,
47
51
  max_age: Optional[int] = None
48
52
  ) -> None:
49
53
  self._changed = False
50
54
  self._data = {}
55
+ # Unique ID:
56
+ self._id_ = data.get(SESSION_ID, None) if data else id
57
+ if not self._id_:
58
+ self._id_ = uuid.uuid4().hex
59
+ # Session Identity
51
60
  self._identity = data.get(SESSION_KEY, None) if data else identity
52
61
  if not self._identity:
53
- self._identity = uuid.uuid4().hex
62
+ self._identity = self._id_
54
63
  self._new = new if data != {} else True
55
64
  self._max_age = max_age if max_age else None
56
65
  created = data.get('created', None) if data else None
57
- self._now = pendulum.now('UTC') # time for this instance creation
66
+ self._now = pendulum.now('UTC') # time for this instance creation
58
67
  self.__created__ = self._now
59
68
  now = int(self._now.int_timestamp)
60
- self._now = now # time for this instance creation
69
+ self._now = now # time for this instance creation
61
70
  age = now - created if created else now
62
71
  if max_age is not None and age > max_age:
63
72
  data = None
@@ -75,9 +84,7 @@ class SessionData(MutableMapping[str, Any]):
75
84
  self.args = args
76
85
 
77
86
  def __repr__(self) -> str:
78
- return '<{} [new:{}, created:{}] {!r}>'.format( # pylint: disable=C0209
79
- 'NAV-Session ', self.new, self.created, self._data
80
- )
87
+ return f'<NAV-Session [new:{self.new}, created:{self.created}] {self._data!r}>'
81
88
 
82
89
  @property
83
90
  def new(self) -> bool:
@@ -87,6 +94,10 @@ class SessionData(MutableMapping[str, Any]):
87
94
  def logon_time(self) -> datetime:
88
95
  return self.__created__
89
96
 
97
+ @property
98
+ def session_id(self) -> str:
99
+ return self._id_
100
+
90
101
  @property
91
102
  def identity(self) -> Optional[Any]: # type: ignore[misc]
92
103
  return self._identity
@@ -3,25 +3,27 @@ from collections.abc import Callable, Awaitable
3
3
  from aiohttp import web
4
4
  from aiohttp.web_middlewares import Handler
5
5
  from navconfig.logging import logging
6
- from navigator_session.conf import (
6
+ from .conf import (
7
7
  SESSION_OBJECT,
8
8
  SESSION_STORAGE
9
9
  )
10
10
  from .storages.abstract import AbstractStorage
11
11
  from .data import SessionData
12
12
 
13
+
13
14
  Middleware = Callable[[web.Request, Handler], Awaitable[web.StreamResponse]]
14
15
 
16
+
15
17
  ### Basic Middleware for Session System
16
18
  def session_middleware(
17
- app: web.Application, # pylint: disable=W0613
19
+ app: web.Application, # pylint: disable=W0613
18
20
  storage: AbstractStorage
19
21
  ) -> Middleware:
20
22
  """Middleware to attach Session Storage to every Request."""
21
23
  if not isinstance(storage, AbstractStorage):
22
24
  raise RuntimeError(
23
25
  f"Expected an AbstractStorage got {storage!s}"
24
- )
26
+ )
25
27
 
26
28
  @web.middleware
27
29
  async def middleware(
@@ -8,10 +8,13 @@ class SessionHandler:
8
8
  """Authentication Backend for Navigator."""
9
9
  storage: Callable = None
10
10
 
11
- def __init__(self,storage: str = 'redis', **kwargs) -> None:
11
+ def __init__(self, storage: str = 'redis', **kwargs) -> None:
12
12
  # TODO: Session Support with parametrization (other storages):
13
+ self._session_object: str = kwargs.get('session_object', 'nav_session')
14
+ # Logging Object:
15
+ self.logger = logging.getLogger(self.__class__.__name__)
13
16
  if storage == 'redis':
14
- self.storage = RedisStorage(**kwargs)
17
+ self.storage = RedisStorage(logger=self.logger, **kwargs)
15
18
  else:
16
19
  raise NotImplementedError(
17
20
  f"Cannot load a Session Storage {storage}"
@@ -19,9 +22,9 @@ class SessionHandler:
19
22
 
20
23
  def setup(self, app: web.Application) -> None:
21
24
  if isinstance(app, web.Application):
22
- self.app = app # register the app into the Extension
25
+ self.app = app # register the app into the Extension
23
26
  else:
24
- self.app = app.get_app() # Nav Application
27
+ self.app = app.get_app() # Nav Application
25
28
  ## Configure the Middleware for NAV Session.
26
29
  self.app.middlewares.append(
27
30
  session_middleware(app, self.storage)
@@ -34,10 +37,9 @@ class SessionHandler:
34
37
  self.app.on_cleanup.append(
35
38
  self.session_cleanup
36
39
  )
37
- logging.debug(':::: Session Handler Loaded ::::')
40
+ self.logger.debug(':::: Session Handler Loaded ::::')
38
41
  # register the Auth extension into the app
39
- self.app['nav_session'] = self
40
-
42
+ self.app[self._session_object] = self
41
43
 
42
44
  async def session_startup(self, app: web.Application):
43
45
  """
@@ -46,7 +48,7 @@ class SessionHandler:
46
48
  try:
47
49
  await self.storage.on_startup(app)
48
50
  except Exception as ex:
49
- logging.exception(f'{ex}')
51
+ self.logger.exception(f'{ex}')
50
52
  raise RuntimeError(
51
53
  f"Session Storage Error: cannot start Storage Backend {ex}"
52
54
  ) from ex
@@ -58,7 +60,7 @@ class SessionHandler:
58
60
  try:
59
61
  await self.storage.on_cleanup(app)
60
62
  except Exception as ex:
61
- logging.exception(f'{ex}')
63
+ self.logger.exception(f'{ex}')
62
64
  raise RuntimeError(
63
65
  f"Session Storage Error: cannot start Storage Backend {ex}"
64
66
  ) from ex
@@ -0,0 +1 @@
1
+ """NAV Session Storage Module."""
@@ -1,19 +1,26 @@
1
1
  """Base Class for all Session Storages."""
2
2
  import sys
3
- import abc
3
+ from abc import ABCMeta, abstractmethod
4
4
  import uuid
5
5
  import time
6
6
  import logging
7
7
  from typing import Optional
8
8
  from aiohttp import web
9
9
  from datamodel.parsers.encoders import DefaultEncoder
10
- from navigator_session.conf import (
10
+ from datamodel.parsers.json import ( # pylint: disable=C0411
11
+ json_encoder,
12
+ json_decoder
13
+ )
14
+ from ..conf import (
11
15
  SESSION_TIMEOUT,
12
16
  SESSION_KEY,
17
+ SESSION_ID,
13
18
  SESSION_OBJECT,
19
+ SESSION_REQUEST_KEY,
14
20
  SESSION_COOKIE_SECURE
15
21
  )
16
- from navigator_session.data import SessionData
22
+ from ..data import SessionData
23
+
17
24
 
18
25
  if sys.version_info >= (3, 8):
19
26
  from typing import TypedDict
@@ -29,13 +36,14 @@ class _CookieParams(TypedDict, total=False):
29
36
  samesite: Optional[str]
30
37
  expires: str
31
38
 
32
- class AbstractStorage(metaclass=abc.ABCMeta):
39
+ class AbstractStorage(metaclass=ABCMeta):
33
40
 
34
- use_cookie: bool = False
41
+ _use_cookies: bool = False
35
42
 
36
43
  def __init__(
37
44
  self,
38
45
  *,
46
+ logger: Optional[logging.Logger] = None,
39
47
  max_age: int = None,
40
48
  secure: bool = True,
41
49
  domain: Optional[str] = None,
@@ -43,14 +51,17 @@ class AbstractStorage(metaclass=abc.ABCMeta):
43
51
  httponly: bool = True,
44
52
  samesite: Optional[str] = 'Lax',
45
53
  **kwargs
46
- ) -> None:
54
+ ) -> None:
55
+ if logger is not None:
56
+ self._logger = logger
57
+ else:
58
+ self._logger = logging.getLogger('Nav_Session.Storage')
47
59
  if not max_age:
48
60
  self.max_age = SESSION_TIMEOUT
49
61
  else:
50
62
  self.max_age = max_age
51
- if 'use_cookie' in kwargs:
52
- self.use_cookie = kwargs['use_cookie']
53
- del kwargs['use_cookie']
63
+ # Using session cookies:
64
+ self._use_cookies = kwargs.get('use_cookies', False)
54
65
  # Storage Name
55
66
  self.__name__: str = SESSION_COOKIE_SECURE
56
67
  self._domain: Optional[str] = domain
@@ -60,8 +71,8 @@ class AbstractStorage(metaclass=abc.ABCMeta):
60
71
  self._httponly = httponly
61
72
  self._samesite = samesite
62
73
  self._objencoder = DefaultEncoder()
63
- self._encoder = self._objencoder.dumps
64
- self._decoder = self._objencoder.loads
74
+ self._encoder = json_encoder
75
+ self._decoder = json_decoder
65
76
 
66
77
  def id_factory(self) -> str:
67
78
  return uuid.uuid4().hex
@@ -70,15 +81,15 @@ class AbstractStorage(metaclass=abc.ABCMeta):
70
81
  def cookie_name(self) -> str:
71
82
  return self.__name__
72
83
 
73
- @abc.abstractmethod
84
+ @abstractmethod
74
85
  async def on_startup(self, app: web.Application):
75
86
  pass
76
87
 
77
- @abc.abstractmethod
88
+ @abstractmethod
78
89
  async def on_cleanup(self, app: web.Application):
79
90
  pass
80
91
 
81
- @abc.abstractmethod
92
+ @abstractmethod
82
93
  async def new_session(
83
94
  self,
84
95
  request: web.Request,
@@ -86,7 +97,7 @@ class AbstractStorage(metaclass=abc.ABCMeta):
86
97
  ) -> SessionData:
87
98
  pass
88
99
 
89
- @abc.abstractmethod
100
+ @abstractmethod
90
101
  async def load_session(
91
102
  self,
92
103
  request: web.Request,
@@ -97,55 +108,66 @@ class AbstractStorage(metaclass=abc.ABCMeta):
97
108
  ) -> SessionData:
98
109
  pass
99
110
 
100
- @abc.abstractmethod
111
+ @abstractmethod
101
112
  async def get_session(self, request: web.Request) -> SessionData:
102
113
  pass
103
114
 
104
115
  def empty_session(self) -> SessionData:
105
- return SessionData(None, data=None, new=True, max_age=self.max_age)
106
-
107
- @abc.abstractmethod
108
- async def save_session(self,
116
+ return SessionData(
117
+ None,
118
+ data=None,
119
+ new=True,
120
+ max_age=self.max_age
121
+ )
122
+
123
+ @abstractmethod
124
+ async def save_session(
125
+ self,
109
126
  request: web.Request,
110
127
  response: web.StreamResponse,
111
128
  session: SessionData
112
129
  ) -> None:
113
130
  pass
114
131
 
115
- @abc.abstractmethod
132
+ @abstractmethod
116
133
  async def invalidate(
117
134
  self,
118
135
  request: web.Request,
119
136
  session: SessionData
120
137
  ) -> None:
121
138
  """Try to Invalidate the Session in the Storage."""
139
+ pass
122
140
 
123
141
  async def forgot(self, request: web.Request, response: web.StreamResponse = None):
124
- """Delete a User Session."""
142
+ """forgot.
143
+
144
+ Forgot (delete) a user session.
145
+ """
125
146
  session = await self.get_session(request)
126
147
  await self.invalidate(request, session)
127
- request["session"] = None
148
+ request[SESSION_REQUEST_KEY] = None
128
149
  try:
129
150
  del request[SESSION_KEY]
151
+ del request[SESSION_ID]
130
152
  del request[SESSION_OBJECT]
131
- except Exception as err: # pylint: disable=W0703
132
- logging.warning(
153
+ except Exception as err: # pylint: disable=W0703
154
+ self._logger.warning(
133
155
  f'Session: Error on Forgot Method: {err}'
134
156
  )
135
157
  if response is not None:
136
158
  # also, forgot the secure Cookie:
137
- self.forgot_cooke(response)
159
+ self.forgot_cookie(response)
138
160
 
139
161
  def load_cookie(self, request: web.Request) -> str:
140
162
  """Getting Cookie from User (if needed)"""
141
- if self.use_cookie is True:
163
+ if self._use_cookies is True:
142
164
  cookie = request.cookies.get(self.__name__, None)
143
165
  if cookie:
144
166
  return self._decoder(cookie)
145
167
  return None
146
168
 
147
- def forgot_cooke(self, response: web.StreamResponse) -> None:
148
- if self.use_cookie is True:
169
+ def forgot_cookie(self, response: web.StreamResponse) -> None:
170
+ if self._use_cookies is True:
149
171
  response.del_cookie(
150
172
  self.__name__, domain=self._domain, path=self._path
151
173
  )
@@ -157,7 +179,7 @@ class AbstractStorage(metaclass=abc.ABCMeta):
157
179
  *,
158
180
  max_age: Optional[int] = None,
159
181
  ) -> None:
160
- if self.use_cookie is True:
182
+ if self._use_cookies is True:
161
183
  expires = None
162
184
  if max_age is not None:
163
185
  t = time.gmtime(time.time() + max_age)
@@ -180,7 +202,6 @@ class AbstractStorage(metaclass=abc.ABCMeta):
180
202
  else:
181
203
  response.set_cookie(self.__name__, cookie_data, **params)
182
204
 
183
-
184
205
  def session_info(self, session: SessionData, request: web.Request) -> SessionData:
185
206
  """session_info.
186
207
  Session Helper for adding more information extracted from Request.
@@ -198,6 +219,6 @@ class AbstractStorage(metaclass=abc.ABCMeta):
198
219
  session.headers = request.headers
199
220
  session.rel_url = request.rel_url
200
221
  except (TypeError, AttributeError, ValueError) as ex:
201
- logging.warning(f'Unable to read Request info: {ex}')
222
+ self._logger.warning(f'Unable to read Request info: {ex}')
202
223
  ### modified Session Object:
203
224
  return session
@@ -1,4 +1,4 @@
1
- """Encrypted JSON Cookie Storage."""
1
+ """TODO: Encrypted JSON Cookie Storage."""
2
2
  import base64
3
3
  from typing import (
4
4
  Optional,
@@ -24,7 +24,7 @@ class CookieStorage(AbstractStorage):
24
24
  path: str = "/",
25
25
  secret_key: Union[str, bytes, bytearray, fernet.Fernet] = None,
26
26
  **kwargs
27
- ) -> None:
27
+ ) -> None:
28
28
  super(
29
29
  CookieStorage, self
30
30
  ).__init__(
@@ -75,7 +75,8 @@ class CookieStorage(AbstractStorage):
75
75
  async def get_session(self, request: web.Request) -> SessionData:
76
76
  pass
77
77
 
78
- async def save_session(self,
78
+ async def save_session(
79
+ self,
79
80
  request: web.Request,
80
81
  response: web.StreamResponse,
81
82
  session: SessionData
@@ -1,14 +1,15 @@
1
1
  """Using Redis for Saving Session Storage."""
2
2
  import time
3
- import logging
4
3
  from typing import Optional
5
4
  from collections.abc import Callable
6
5
  from aiohttp import web
7
- import aioredis
8
- from navigator_session.conf import (
6
+ from redis import asyncio as aioredis
7
+ from ..conf import (
9
8
  SESSION_URL,
10
9
  SESSION_KEY,
10
+ SESSION_ID,
11
11
  SESSION_OBJECT,
12
+ SESSION_REQUEST_KEY,
12
13
  SESSION_STORAGE
13
14
  )
14
15
  from .abstract import AbstractStorage, SessionData
@@ -24,7 +25,7 @@ class RedisStorage(AbstractStorage):
24
25
  domain: Optional[str] = None,
25
26
  path: str = "/",
26
27
  **kwargs
27
- ) -> None:
28
+ ) -> None:
28
29
  self._redis: Callable = None
29
30
  super(
30
31
  RedisStorage, self
@@ -43,21 +44,27 @@ class RedisStorage(AbstractStorage):
43
44
  decode_responses=True,
44
45
  encoding='utf-8'
45
46
  )
46
- except Exception as err: # pylint: disable=W0703
47
- logging.exception(err, stack_info=True)
47
+ except Exception as err: # pylint: disable=W0703
48
+ self._logger.exception(err, stack_info=True)
48
49
  return False
49
50
 
50
51
  async def on_cleanup(self, app: web.Application):
51
52
  try:
52
- await self._redis.disconnect(inuse_connections = True)
53
- except Exception as ex: # pylint: disable=W0703
54
- logging.warning(ex)
53
+ await self._redis.disconnect(inuse_connections=True)
54
+ except Exception as ex: # pylint: disable=W0703
55
+ self._logger.warning(ex)
55
56
 
56
- async def get_session(self, request: web.Request, userdata: dict = None) -> SessionData:
57
+ async def get_session(
58
+ self,
59
+ request: web.Request,
60
+ userdata: dict = None
61
+ ) -> SessionData:
57
62
  try:
58
63
  session = request.get(SESSION_OBJECT)
59
- except Exception as err: # pylint: disable=W0703
60
- logging.debug(f'Redis Storage: Error on get Session: {err!s}')
64
+ except Exception as err: # pylint: disable=W0703
65
+ self._logger.debug(
66
+ f'Redis Storage: Error on get Session: {err!s}'
67
+ )
61
68
  session = None
62
69
  if session is None:
63
70
  storage = request.get(SESSION_STORAGE)
@@ -67,25 +74,26 @@ class RedisStorage(AbstractStorage):
67
74
  )
68
75
  session = await self.load_session(request, userdata)
69
76
  request[SESSION_OBJECT] = session
70
- request["session"] = session
77
+ request[SESSION_REQUEST_KEY] = session
71
78
  return session
72
79
 
73
80
  async def invalidate(self, request: web.Request, session: SessionData) -> None:
74
81
  conn = aioredis.Redis(connection_pool=self._redis)
75
82
  if not session:
76
83
  data = None
77
- session_id = request.get(SESSION_KEY, None)
84
+ session_id = request.get(SESSION_ID, None)
78
85
  if session_id:
79
- data = await conn.get(session_id)
86
+ _id_ = f"user:{session_id}"
87
+ data = await conn.get(_id_)
80
88
  if data is None:
81
89
  # nothing to forgot
82
90
  return True
83
91
  try:
84
92
  # delete the ID of the session
85
- await conn.delete(session.identity)
86
- session.invalidate() # invalidate this session object
87
- except Exception as err: # pylint: disable=W0703
88
- logging.error(err)
93
+ await conn.delete(f"user:{session.session_id}")
94
+ session.invalidate() # invalidate this session object
95
+ except Exception as err: # pylint: disable=W0703
96
+ self._logger.error(err)
89
97
  return False
90
98
  return True
91
99
 
@@ -106,9 +114,9 @@ class RedisStorage(AbstractStorage):
106
114
  ---
107
115
  new: if False, new session is not created.
108
116
  """
109
- # TODO: first: for security, check if cookie csrf_secure exists
117
+ # first: for security, check if cookie csrf_secure exists
110
118
  session_id = None
111
- if ignore_cookie is False and self.use_cookie is True:
119
+ if ignore_cookie is False and self._use_cookies is True:
112
120
  cookie = self.load_cookie(request)
113
121
  try:
114
122
  session_id = cookie['session_id']
@@ -118,26 +126,32 @@ class RedisStorage(AbstractStorage):
118
126
  try:
119
127
  conn = aioredis.Redis(connection_pool=self._redis)
120
128
  except Exception as err:
121
- logging.exception(
129
+ self._logger.exception(
122
130
  f'Redis Storage: Error loading Redis Session: {err!s}'
123
131
  )
124
132
  raise RuntimeError(
125
133
  f'Redis Storage: Error loading Redis Session: {err!s}'
126
134
  ) from err
127
135
  if session_id is None:
128
- session_id = request.get(SESSION_KEY, None)
136
+ session_id = request.get(SESSION_ID, None)
129
137
  if not session_id:
130
- session_id = userdata.get(SESSION_KEY, None) if userdata else None
131
- # TODO: getting from cookie
138
+ session_id = userdata.get(SESSION_ID, None) if userdata else None
132
139
  if session_id is None and new is False:
140
+ # No Session was found, returning false:
133
141
  return False
134
142
  # we need to load session data from redis
135
- logging.debug(f':::::: LOAD SESSION FOR {session_id} ::::: ')
143
+ self._logger.debug(
144
+ f':::::: LOAD SESSION FOR {session_id} ::::: '
145
+ )
146
+ _id_ = f"user:{session_id}"
147
+ session_identity = request.get(SESSION_KEY, None)
148
+ if not session_identity:
149
+ session_identity = userdata.get(SESSION_KEY, None) if userdata else None
136
150
  try:
137
- data = await conn.get(session_id)
138
- except Exception as err: # pylint: disable=W0703
139
- logging.error(
140
- f'Redis Storage: Error Getting Session data: {err!s}'
151
+ data = await conn.get(_id_)
152
+ except Exception as err: # pylint: disable=W0703
153
+ self._logger.error(
154
+ f'Redis Storage: Error Getting Session: {err!s}'
141
155
  )
142
156
  data = None
143
157
  if data is None:
@@ -145,46 +159,57 @@ class RedisStorage(AbstractStorage):
145
159
  # create a new session if not exists:
146
160
  return await self.new_session(request, userdata)
147
161
  else:
162
+ # No Session Was Found
148
163
  return False
149
164
  try:
150
165
  data = self._decoder(data)
151
166
  session = SessionData(
152
- identity=session_id,
167
+ id=session_id,
168
+ identity=session_identity,
153
169
  data=data,
154
170
  new=False,
155
171
  max_age=self.max_age
156
172
  )
157
- except Exception as err: # pylint: disable=W0703
158
- logging.warning(err)
173
+ except Exception as err: # pylint: disable=W0703
174
+ self._logger.warning(
175
+ f"Error creating Session Data: {err}"
176
+ )
159
177
  session = SessionData(
160
- identity=None,
178
+ id=session_id,
179
+ identity=session_identity,
161
180
  data=None,
162
181
  new=True,
163
182
  max_age=self.max_age
164
183
  )
165
184
  ## add other options to session:
166
185
  self.session_info(session, request)
167
- request[SESSION_KEY] = session_id
168
186
  session[SESSION_KEY] = session_id
187
+ request[SESSION_KEY] = session_identity
188
+ request[SESSION_ID] = session_id
169
189
  request[SESSION_OBJECT] = session
170
- request["session"] = session
171
- if self.use_cookie is True and response is not None:
190
+ request[SESSION_REQUEST_KEY] = session
191
+ if self._use_cookies is True and response is not None:
172
192
  cookie_data = {
173
193
  "session_id": session_id
174
194
  }
175
195
  cookie_data = self._encoder(cookie_data)
176
- self.save_cookie(response, cookie_data=cookie_data, max_age=self.max_age)
196
+ self.save_cookie(
197
+ response,
198
+ cookie_data=cookie_data,
199
+ max_age=self.max_age
200
+ )
177
201
  return session
178
202
 
179
- async def save_session(self,
203
+ async def save_session(
204
+ self,
180
205
  request: web.Request,
181
206
  response: web.StreamResponse,
182
207
  session: SessionData
183
208
  ) -> None:
184
209
  """Save the whole session in the backend Storage."""
185
- session_id = session.identity
210
+ session_id = session.session_id if session else request.get(SESSION_ID, None)
186
211
  if not session_id:
187
- session_id = session.get(SESSION_KEY, None)
212
+ session_id = session.get(SESSION_ID, None)
188
213
  if not session_id:
189
214
  session_id = self.id_factory()
190
215
  if session.empty:
@@ -192,15 +217,14 @@ class RedisStorage(AbstractStorage):
192
217
  data = self._encoder(session.session_data())
193
218
  max_age = session.max_age
194
219
  expire = max_age if max_age is not None else 0
195
- # TODO: add expiration on redis to value
196
220
  try:
197
221
  conn = aioredis.Redis(connection_pool=self._redis)
198
- result = await conn.set(
199
- session_id, data, expire
222
+ _id_ = f"user:{session_id}"
223
+ await conn.set(
224
+ _id_, data, expire
200
225
  )
201
- except Exception as err: # pylint: disable=W0703
202
- print('Error Saving Session: ', err)
203
- logging.exception(err, stack_info=True)
226
+ except Exception as err: # pylint: disable=W0703
227
+ self._logger.exception(err, stack_info=True)
204
228
  return False
205
229
 
206
230
  async def new_session(
@@ -210,13 +234,16 @@ class RedisStorage(AbstractStorage):
210
234
  response: web.StreamResponse = None
211
235
  ) -> SessionData:
212
236
  """Create a New Session Object for this User."""
213
- session_id = request.get(SESSION_KEY, None)
237
+ session_identity = request.get(SESSION_KEY, None)
238
+ session_id = request.get(SESSION_ID, None)
214
239
  if not session_id:
215
240
  try:
216
- session_id = data[SESSION_KEY]
241
+ session_id = data.get(SESSION_ID)
217
242
  except KeyError:
218
243
  session_id = self.id_factory()
219
- logging.debug(f':::::: START CREATING A NEW SESSION FOR {session_id} ::::: ')
244
+ self._logger.debug(
245
+ f':::::: START CREATING A NEW SESSION FOR {session_id} ::::: '
246
+ )
220
247
  if not data:
221
248
  data = {}
222
249
  # saving this new session on DB
@@ -225,38 +252,50 @@ class RedisStorage(AbstractStorage):
225
252
  t = time.time()
226
253
  data['created'] = t
227
254
  data['last_visit'] = t
228
- data["last_visited"] = f"Last visited: {t!s}"
229
- data[SESSION_KEY] = session_id
255
+ data[SESSION_KEY] = session_identity
256
+ data[SESSION_ID] = session_id
230
257
  dt = self._encoder(data)
258
+ _id_ = f'user:{session_id}'
231
259
  result = await conn.set(
232
- session_id, dt, self.max_age
260
+ _id_, dt, self.max_age
261
+ )
262
+ self._logger.debug(
263
+ f'Session Creation: {result}'
233
264
  )
234
- logging.info(f'Creation of New Session: {result}')
235
- except Exception as err: # pylint: disable=W0703
236
- logging.exception(err)
265
+ except Exception as err: # pylint: disable=W0703
266
+ self._logger.exception(err)
237
267
  return False
238
268
  try:
239
269
  session = SessionData(
240
- identity=session_id,
270
+ id=session_id,
271
+ identity=session_identity,
241
272
  data=data,
242
273
  new=True,
243
274
  max_age=self.max_age
244
275
  )
245
- if self.use_cookie is True and response is not None:
276
+ if self._use_cookies is True and response is not None:
246
277
  cookie_data = {
247
278
  "last_visit": t,
248
279
  "session_id": session_id
249
280
  }
281
+ # TODO: adding crypt
250
282
  cookie_data = self._encoder(cookie_data)
251
- self.save_cookie(response, cookie_data=cookie_data, max_age=self.max_age)
252
- except Exception as err: # pylint: disable=W0703
253
- print(err)
254
- logging.exception(f'Error creating Session Data: {err!s}')
283
+ self.save_cookie(
284
+ response,
285
+ cookie_data=cookie_data,
286
+ max_age=self.max_age
287
+ )
288
+ except Exception as err: # pylint: disable=W0703
289
+ self._logger.exception(
290
+ f'Error creating Session Data: {err!s}'
291
+ )
292
+ return False
255
293
  # Saving Session Object:
256
294
  ## add other options to session:
257
295
  self.session_info(session, request)
258
- session[SESSION_KEY] = session_id
296
+ session[SESSION_KEY] = session_identity
259
297
  request[SESSION_OBJECT] = session
260
- request[SESSION_KEY] = session_id
261
- request["session"] = session
298
+ request[SESSION_KEY] = session_identity
299
+ request[SESSION_ID] = session_id
300
+ request[SESSION_REQUEST_KEY] = session
262
301
  return session
@@ -5,7 +5,7 @@
5
5
  __title__ = 'navigator_session'
6
6
  __description__ = ('Navigator Session allows us to store user-specific data '
7
7
  'into session object.')
8
- __version__ = '0.4.2'
8
+ __version__ = '0.6.0rc1'
9
9
  __author__ = 'Jesus Lara'
10
10
  __author_email__ = 'jesuslarag@gmail.com'
11
11
  __license__ = 'Apache-2.0'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: navigator-session
3
- Version: 0.4.2
3
+ Version: 0.6.0rc1
4
4
  Summary: Navigator Session allows us to store user-specific data into session object.
5
5
  Home-page: https://github.com/phenobarbital/navigator-session
6
6
  Author: Jesus Lara
@@ -21,6 +21,7 @@ Classifier: Topic :: Database :: Front-Ends
21
21
  Classifier: Programming Language :: Python :: 3.9
22
22
  Classifier: Programming Language :: Python :: 3.10
23
23
  Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
24
25
  Classifier: Framework :: AsyncIO
25
26
  Classifier: Framework :: aiohttp
26
27
  Requires-Python: >=3.9.16
@@ -54,3 +55,5 @@ Navigator_Session Allows us to store User-based data into a session Object, this
54
55
  ### License ###
55
56
 
56
57
  Navigator_Session is copyright of Jesus Lara (https://phenobarbital.info) and under Apache 2 license. I am providing code in this repository under an open source license, remember, this is my personal repository; the license that you receive is from me and not from my employeer.
58
+
59
+
@@ -0,0 +1,7 @@
1
+ aiohttp>=3.9.5
2
+ asyncio==3.4.3
3
+ jsonpickle>=3.0.2
4
+ navconfig[default]>=1.6.3
5
+ python_datamodel>=0.2.1
6
+ redis>=5.0.4
7
+ uvloop==0.19.0
@@ -1,8 +1,8 @@
1
1
  [build-system]
2
2
  requires = [
3
- 'setuptools==67.5.1',
4
- 'Cython==0.29.33',
5
- 'wheel==0.38.4'
3
+ 'setuptools==67.6.1',
4
+ 'Cython==3.0.9',
5
+ 'wheel==0.42.0'
6
6
  ]
7
7
 
8
8
  build-backend = "setuptools.build_meta"
@@ -20,10 +20,12 @@ classifiers=[
20
20
  "Programming Language :: Python :: 3.9",
21
21
  "Programming Language :: Python :: 3.10",
22
22
  "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
23
24
  "Programming Language :: Python",
24
25
  "Typing :: Typed",
25
26
  "Environment :: Web Environment",
26
27
  "Framework :: AsyncIO",
28
+ "Framework :: AIOHTTP",
27
29
  "Topic :: Software Development :: Libraries :: Application Frameworks",
28
30
  "Topic :: Software Development :: Libraries :: Python Modules",
29
31
  "Topic :: Software Development :: Build Tools",
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env python
2
2
  """Navigator-Session.
3
3
 
4
- Asynchronous library for managing user-specific data into a session object, used by Navigator.
4
+ Asynchronous library for managing user-specific data into a session object,
5
+ used by Navigator.
5
6
  See:
6
7
  https://github.com/phenobarbital/navigator-session
7
8
  """
@@ -32,7 +33,9 @@ with open(version, 'r', encoding='utf-8') as meta:
32
33
  '__title__',
33
34
  '__description__',
34
35
  '__author__',
35
- '__license__', '__author_email__'):
36
+ '__license__',
37
+ '__author_email__'
38
+ ):
36
39
  v = node.value
37
40
  if name.id == '__version__':
38
41
  __version__ = v.s
@@ -69,6 +72,7 @@ setup(
69
72
  "Programming Language :: Python :: 3.9",
70
73
  "Programming Language :: Python :: 3.10",
71
74
  "Programming Language :: Python :: 3.11",
75
+ "Programming Language :: Python :: 3.12",
72
76
  "Framework :: AsyncIO",
73
77
  "Framework :: aiohttp",
74
78
  ],
@@ -77,20 +81,18 @@ setup(
77
81
  packages=find_packages(exclude=["contrib", "docs", "tests"]),
78
82
  license=__license__,
79
83
  setup_requires=[
80
- "wheel==0.38.4",
81
- "Cython==0.29.33",
84
+ "wheel==0.42.0",
85
+ "Cython==3.0.9",
82
86
  "asyncio==3.4.3"
83
87
  ],
84
88
  install_requires=[
85
- "PyNaCl==1.5.0",
86
- "aiohttp==3.8.4",
87
- "uvloop==0.17.0",
89
+ "aiohttp>=3.9.5",
90
+ "uvloop==0.19.0",
88
91
  "asyncio==3.4.3",
89
- "navconfig[default]>=1.0.15",
90
- "jsonpickle==3.0.1",
91
- "aioredis==2.0.1",
92
- "pendulum==2.1.2",
93
- "python_datamodel>=0.2.1"
92
+ "jsonpickle>=3.0.2",
93
+ "redis>=5.0.4",
94
+ "python_datamodel>=0.2.1",
95
+ "navconfig[default]>=1.6.3",
94
96
  ],
95
97
  project_urls={ # Optional
96
98
  "Source": "https://github.com/phenobarbital/navigator-session",
@@ -1,19 +0,0 @@
1
- aiounittest==1.4.2
2
- black==23.1.0
3
- codecov==2.1.12
4
- coverage[toml]==7.2.1
5
- flit==3.8.0
6
- hypothesis==6.68.2
7
- ipython==8.11.0
8
- lint==1.2.1
9
- mypy==1.1.1
10
- pylint==2.17.0
11
- sphinx==6.1.3
12
- pytest>=6.0.0
13
- pytest-asyncio==0.20.3
14
- pytest-cov==4.0.0
15
- pytest-cython==0.2.0
16
- pytest-xdist==3.2.0
17
- pytest-assume==2.4.3
18
- sphinx==6.1.3
19
- tox==4.4.6
@@ -1,6 +0,0 @@
1
- """NAV Session Storage Module."""
2
-
3
- # from .redis import RedisStorage
4
- from .abstract import SessionData
5
-
6
- __all__ = ('SessionData', )
@@ -1,9 +0,0 @@
1
- PyNaCl==1.5.0
2
- aiohttp==3.8.4
3
- uvloop==0.17.0
4
- asyncio==3.4.3
5
- navconfig[default]>=1.0.15
6
- jsonpickle==3.0.1
7
- aioredis==2.0.1
8
- pendulum==2.1.2
9
- python_datamodel>=0.2.1