diff --git a/sanic_session/__init__.py b/sanic_session/__init__.py index 1341bea..32aef0a 100644 --- a/sanic_session/__init__.py +++ b/sanic_session/__init__.py @@ -3,11 +3,13 @@ from .memory import InMemorySessionInterface from .mongodb import MongoDBSessionInterface from .redis import RedisSessionInterface +from .multimemory import MultiMemorySessionInterface __all__ = ( "MemcacheSessionInterface", "RedisSessionInterface", "InMemorySessionInterface", + "MultiMemorySessionInterface", "MongoDBSessionInterface", "AIORedisSessionInterface", "Session", @@ -44,3 +46,4 @@ async def save_session(request, response): app.request_middleware.appendleft(add_session_to_request) app.response_middleware.append(save_session) + self.interface._attach_app(app) diff --git a/sanic_session/base.py b/sanic_session/base.py index 274960e..a76203b 100644 --- a/sanic_session/base.py +++ b/sanic_session/base.py @@ -4,7 +4,7 @@ import uuid import ujson - +from sanic import Sanic from sanic_session.utils import CallbackDict @@ -107,6 +107,9 @@ async def _set_value(self, key: str, data: SessionDict): """Set value for datastore""" raise NotImplementedError + def _attach_app(self, app: Sanic): + ... + async def open(self, request) -> SessionDict: """ Opens a session onto the request. Restores the client's session diff --git a/sanic_session/memory.py b/sanic_session/memory.py index d0f971d..e0e6b5a 100644 --- a/sanic_session/memory.py +++ b/sanic_session/memory.py @@ -1,3 +1,4 @@ +from typing import Optional from sanic_session.base import BaseSessionInterface from sanic_session.utils import ExpiringDict @@ -5,13 +6,13 @@ class InMemorySessionInterface(BaseSessionInterface): def __init__( self, - domain: str = None, + domain: Optional[str] = None, expiry: int = 2592000, httponly: bool = True, cookie_name: str = "session", prefix: str = "session:", sessioncookie: bool = False, - samesite: str = None, + samesite: Optional[str] = None, session_name="session", secure: bool = False, ): diff --git a/sanic_session/multimemory.py b/sanic_session/multimemory.py new file mode 100644 index 0000000..a0c96a1 --- /dev/null +++ b/sanic_session/multimemory.py @@ -0,0 +1,78 @@ +import time +from typing import Optional +from sanic_session.base import BaseSessionInterface + +from sanic import Sanic, Request +from multiprocessing import Manager + + +class MultiMemorySessionInterface(BaseSessionInterface): + def __init__( + self, + domain: Optional[str] = None, + expiry: int = 2592000, + httponly: bool = True, + cookie_name: str = "session", + prefix: str = "session:", + sessioncookie: bool = False, + samesite: Optional[str] = None, + session_name="session", + secure: bool = False, + ): + + super().__init__( + expiry=expiry, + prefix=prefix, + cookie_name=cookie_name, + domain=domain, + httponly=httponly, + sessioncookie=sessioncookie, + samesite=samesite, + session_name=session_name, + secure=secure, + ) + + async def _get_value(self, prefix, sid): + key = self.prefix + sid + current = Request.get_current() + data = current.app.shared_ctx.session_info.get(key) + + if not data: + return None + + if time.time() > current.app.shared_ctx.expiry_info[key]: + with current.app.shared_ctx.session_lock: + await self._delete_key(key) + return None + + return data + + async def _delete_key(self, key): + current = Request.get_current() + current.app.shared_ctx.session_info.pop(key, None) + current.app.shared_ctx.expiry_info.pop(key, None) + + async def _set_value(self, key, data): + current = Request.get_current() + with current.app.shared_ctx.session_lock: + current.app.shared_ctx.session_info[key] = data + current.app.shared_ctx.expiry_info[key] = time.time() + self.expiry + + def _attach_app(self, app: Sanic): + app.main_process_start(self.main_process_start) + app.main_process_ready(self.main_process_ready) + app.main_process_stop(self.main_process_stop) + + async def main_process_start(self, _): + self.manager = Manager() + self.session_info = self.manager.dict() + self.expiry_info = self.manager.dict() + self.lock = self.manager.Lock() + + async def main_process_ready(self, app: Sanic): + app.shared_ctx.session_info = self.session_info + app.shared_ctx.expiry_info = self.expiry_info + app.shared_ctx.session_lock = self.lock + + async def main_process_stop(self, _): + self.manager.shutdown()