navigator-session 0.4.0__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.
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/Makefile +2 -2
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/PKG-INFO +4 -1
- navigator-session-0.6.0rc1/docs/requirements-dev.txt +21 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/__init__.py +4 -3
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/conf.py +7 -3
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/data.py +25 -8
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/middleware.py +5 -3
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/session.py +11 -9
- navigator-session-0.6.0rc1/navigator_session/storages/__init__.py +1 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/storages/abstract.py +53 -32
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/storages/cookie.py +4 -3
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/storages/redis.py +104 -65
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/version.py +1 -1
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session.egg-info/PKG-INFO +4 -1
- navigator-session-0.6.0rc1/navigator_session.egg-info/requires.txt +7 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/pyproject.toml +5 -2
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/setup.py +14 -11
- navigator-session-0.4.0/docs/requirements-dev.txt +0 -19
- navigator-session-0.4.0/navigator_session/storages/__init__.py +0 -6
- navigator-session-0.4.0/navigator_session.egg-info/requires.txt +0 -9
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/CHANGES.rst +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/LICENSE +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/MANIFEST.in +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/README.md +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/docs/Makefile +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/docs/api.rst +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/docs/authors.rst +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/docs/conf.py +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/docs/examples.rst +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/docs/index.rst +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/docs/make.bat +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/docs/requirements.txt +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session.egg-info/SOURCES.txt +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session.egg-info/dependency_links.txt +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session.egg-info/top_level.txt +0 -0
- {navigator-session-0.4.0 → navigator-session-0.6.0rc1}/setup.cfg +0 -0
- {navigator-session-0.4.0 → 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.
|
|
6
|
+
pip install wheel==0.42.0
|
|
7
7
|
pip install -e .
|
|
8
8
|
|
|
9
9
|
develop:
|
|
10
|
-
pip install wheel==0.
|
|
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.
|
|
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
|
|
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
|
|
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[
|
|
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 =
|
|
26
|
-
|
|
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')
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
import time
|
|
3
3
|
from typing import Union, Optional, Any
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
from collections.abc import Iterator, Mapping, MutableMapping
|
|
5
6
|
import pendulum
|
|
6
7
|
import jsonpickle
|
|
7
8
|
from jsonpickle.unpickler import loadclass
|
|
8
9
|
from aiohttp import web
|
|
9
10
|
from datamodel import BaseModel
|
|
10
|
-
from
|
|
11
|
-
|
|
11
|
+
from .conf import (
|
|
12
|
+
SESSION_KEY,
|
|
13
|
+
SESSION_ID,
|
|
14
|
+
SESSION_STORAGE
|
|
15
|
+
)
|
|
12
16
|
|
|
13
17
|
class ModelHandler(jsonpickle.handlers.BaseHandler):
|
|
14
18
|
"""ModelHandler.
|
|
@@ -42,20 +46,27 @@ class SessionData(MutableMapping[str, Any]):
|
|
|
42
46
|
*args,
|
|
43
47
|
data: Optional[Mapping[str, Any]] = None,
|
|
44
48
|
new: bool = False,
|
|
49
|
+
id: Optional[str] = None,
|
|
45
50
|
identity: Optional[Any] = None,
|
|
46
51
|
max_age: Optional[int] = None
|
|
47
52
|
) -> None:
|
|
48
53
|
self._changed = False
|
|
49
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
|
|
50
60
|
self._identity = data.get(SESSION_KEY, None) if data else identity
|
|
51
61
|
if not self._identity:
|
|
52
|
-
self._identity =
|
|
62
|
+
self._identity = self._id_
|
|
53
63
|
self._new = new if data != {} else True
|
|
54
64
|
self._max_age = max_age if max_age else None
|
|
55
65
|
created = data.get('created', None) if data else None
|
|
56
|
-
self._now = pendulum.now('UTC')
|
|
66
|
+
self._now = pendulum.now('UTC') # time for this instance creation
|
|
67
|
+
self.__created__ = self._now
|
|
57
68
|
now = int(self._now.int_timestamp)
|
|
58
|
-
self._now = now
|
|
69
|
+
self._now = now # time for this instance creation
|
|
59
70
|
age = now - created if created else now
|
|
60
71
|
if max_age is not None and age > max_age:
|
|
61
72
|
data = None
|
|
@@ -73,14 +84,20 @@ class SessionData(MutableMapping[str, Any]):
|
|
|
73
84
|
self.args = args
|
|
74
85
|
|
|
75
86
|
def __repr__(self) -> str:
|
|
76
|
-
return '<
|
|
77
|
-
'NAV-Session ', self.new, self.created, self._data
|
|
78
|
-
)
|
|
87
|
+
return f'<NAV-Session [new:{self.new}, created:{self.created}] {self._data!r}>'
|
|
79
88
|
|
|
80
89
|
@property
|
|
81
90
|
def new(self) -> bool:
|
|
82
91
|
return self._new
|
|
83
92
|
|
|
93
|
+
@property
|
|
94
|
+
def logon_time(self) -> datetime:
|
|
95
|
+
return self.__created__
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def session_id(self) -> str:
|
|
99
|
+
return self._id_
|
|
100
|
+
|
|
84
101
|
@property
|
|
85
102
|
def identity(self) -> Optional[Any]: # type: ignore[misc]
|
|
86
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
|
|
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,
|
|
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
|
|
25
|
+
self.app = app # register the app into the Extension
|
|
23
26
|
else:
|
|
24
|
-
self.app = app.get_app()
|
|
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
|
-
|
|
40
|
+
self.logger.debug(':::: Session Handler Loaded ::::')
|
|
38
41
|
# register the Auth extension into the app
|
|
39
|
-
self.app[
|
|
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
|
-
|
|
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
|
-
|
|
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."""
|
{navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session/storages/abstract.py
RENAMED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
"""Base Class for all Session Storages."""
|
|
2
2
|
import sys
|
|
3
|
-
import
|
|
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
|
|
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
|
|
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=
|
|
39
|
+
class AbstractStorage(metaclass=ABCMeta):
|
|
33
40
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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 =
|
|
64
|
-
self._decoder =
|
|
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
|
-
@
|
|
84
|
+
@abstractmethod
|
|
74
85
|
async def on_startup(self, app: web.Application):
|
|
75
86
|
pass
|
|
76
87
|
|
|
77
|
-
@
|
|
88
|
+
@abstractmethod
|
|
78
89
|
async def on_cleanup(self, app: web.Application):
|
|
79
90
|
pass
|
|
80
91
|
|
|
81
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
@
|
|
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
|
-
"""
|
|
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[
|
|
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:
|
|
132
|
-
|
|
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.
|
|
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.
|
|
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
|
|
148
|
-
if self.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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:
|
|
47
|
-
|
|
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
|
|
53
|
-
except Exception as ex:
|
|
54
|
-
|
|
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(
|
|
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:
|
|
60
|
-
|
|
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[
|
|
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(
|
|
84
|
+
session_id = request.get(SESSION_ID, None)
|
|
78
85
|
if session_id:
|
|
79
|
-
|
|
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.
|
|
86
|
-
session.invalidate()
|
|
87
|
-
except Exception as err:
|
|
88
|
-
|
|
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
|
-
#
|
|
117
|
+
# first: for security, check if cookie csrf_secure exists
|
|
110
118
|
session_id = None
|
|
111
|
-
if ignore_cookie is False and
|
|
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
|
-
|
|
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(
|
|
136
|
+
session_id = request.get(SESSION_ID, None)
|
|
129
137
|
if not session_id:
|
|
130
|
-
session_id = userdata.get(
|
|
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
|
-
|
|
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(
|
|
138
|
-
except Exception as err:
|
|
139
|
-
|
|
140
|
-
f'Redis Storage: Error Getting Session
|
|
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
|
-
|
|
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:
|
|
158
|
-
|
|
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
|
-
|
|
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[
|
|
171
|
-
if self.
|
|
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(
|
|
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(
|
|
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.
|
|
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(
|
|
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
|
-
|
|
199
|
-
|
|
222
|
+
_id_ = f"user:{session_id}"
|
|
223
|
+
await conn.set(
|
|
224
|
+
_id_, data, expire
|
|
200
225
|
)
|
|
201
|
-
except Exception as err:
|
|
202
|
-
|
|
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
|
-
|
|
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
|
|
241
|
+
session_id = data.get(SESSION_ID)
|
|
217
242
|
except KeyError:
|
|
218
243
|
session_id = self.id_factory()
|
|
219
|
-
|
|
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[
|
|
229
|
-
data[
|
|
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
|
-
|
|
260
|
+
_id_, dt, self.max_age
|
|
261
|
+
)
|
|
262
|
+
self._logger.debug(
|
|
263
|
+
f'Session Creation: {result}'
|
|
233
264
|
)
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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] =
|
|
296
|
+
session[SESSION_KEY] = session_identity
|
|
259
297
|
request[SESSION_OBJECT] = session
|
|
260
|
-
request[SESSION_KEY] =
|
|
261
|
-
request[
|
|
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.
|
|
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.
|
|
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
|
+
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
[build-system]
|
|
2
2
|
requires = [
|
|
3
|
-
'setuptools==67.
|
|
4
|
-
'
|
|
3
|
+
'setuptools==67.6.1',
|
|
4
|
+
'Cython==3.0.9',
|
|
5
|
+
'wheel==0.42.0'
|
|
5
6
|
]
|
|
6
7
|
|
|
7
8
|
build-backend = "setuptools.build_meta"
|
|
@@ -19,10 +20,12 @@ classifiers=[
|
|
|
19
20
|
"Programming Language :: Python :: 3.9",
|
|
20
21
|
"Programming Language :: Python :: 3.10",
|
|
21
22
|
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
22
24
|
"Programming Language :: Python",
|
|
23
25
|
"Typing :: Typed",
|
|
24
26
|
"Environment :: Web Environment",
|
|
25
27
|
"Framework :: AsyncIO",
|
|
28
|
+
"Framework :: AIOHTTP",
|
|
26
29
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
27
30
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
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,
|
|
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__',
|
|
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,19 +81,18 @@ setup(
|
|
|
77
81
|
packages=find_packages(exclude=["contrib", "docs", "tests"]),
|
|
78
82
|
license=__license__,
|
|
79
83
|
setup_requires=[
|
|
80
|
-
"wheel==0.
|
|
84
|
+
"wheel==0.42.0",
|
|
85
|
+
"Cython==3.0.9",
|
|
81
86
|
"asyncio==3.4.3"
|
|
82
87
|
],
|
|
83
88
|
install_requires=[
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"uvloop==0.17.0",
|
|
89
|
+
"aiohttp>=3.9.5",
|
|
90
|
+
"uvloop==0.19.0",
|
|
87
91
|
"asyncio==3.4.3",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"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",
|
|
93
96
|
],
|
|
94
97
|
project_urls={ # Optional
|
|
95
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{navigator-session-0.4.0 → navigator-session-0.6.0rc1}/navigator_session.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|