craftbeerpi4-pione/venv/lib/python3.8/site-packages/aiohttp_session/__init__.py

278 lines
8.1 KiB
Python

"""User sessions for aiohttp.web."""
import abc
import json
import time
from collections.abc import MutableMapping
from aiohttp import web
__version__ = '2.9.0'
class Session(MutableMapping):
"""Session dict-like object."""
def __init__(self, identity, *, data, new, max_age=None):
self._changed = False
self._mapping = {}
self._identity = identity if data != {} else None
self._new = new
self._new = new if data != {} else True
self._max_age = max_age
created = data.get('created', None) if data else None
session_data = data.get('session', None) if data else None
now = int(time.time())
age = now - created if created else now
if max_age is not None and age > max_age:
session_data = None
if self._new or created is None:
self._created = now
else:
self._created = created
if session_data is not None:
self._mapping.update(session_data)
def __repr__(self):
return '<{} [new:{}, changed:{}, created:{}] {!r}>'.format(
self.__class__.__name__, self.new, self._changed,
self.created, self._mapping)
@property
def new(self):
return self._new
@property
def identity(self):
return self._identity
@property
def created(self):
return self._created
@property
def empty(self):
return not bool(self._mapping)
@property
def max_age(self):
return self._max_age
@max_age.setter
def max_age(self, value):
self._max_age = value
def changed(self):
self._changed = True
def invalidate(self):
self._changed = True
self._mapping = {}
def set_new_identity(self, identity):
if not self._new:
raise RuntimeError(
"Can't change identity for a session which is not new")
self._identity = identity
def __len__(self):
return len(self._mapping)
def __iter__(self):
return iter(self._mapping)
def __contains__(self, key):
return key in self._mapping
def __getitem__(self, key):
return self._mapping[key]
def __setitem__(self, key, value):
self._mapping[key] = value
self._changed = True
def __delitem__(self, key):
del self._mapping[key]
self._changed = True
SESSION_KEY = 'aiohttp_session'
STORAGE_KEY = 'aiohttp_session_storage'
async def get_session(request):
session = request.get(SESSION_KEY)
if session is None:
storage = request.get(STORAGE_KEY)
if storage is None:
raise RuntimeError(
"Install aiohttp_session middleware "
"in your aiohttp.web.Application")
else:
session = await storage.load_session(request)
if not isinstance(session, Session):
raise RuntimeError(
"Installed {!r} storage should return session instance "
"on .load_session() call, got {!r}.".format(storage,
session))
request[SESSION_KEY] = session
return session
async def new_session(request):
storage = request.get(STORAGE_KEY)
if storage is None:
raise RuntimeError(
"Install aiohttp_session middleware "
"in your aiohttp.web.Application")
else:
session = await storage.new_session()
if not isinstance(session, Session):
raise RuntimeError(
"Installed {!r} storage should return session instance "
"on .load_session() call, got {!r}.".format(storage, session))
request[SESSION_KEY] = session
return session
def session_middleware(storage):
if not isinstance(storage, AbstractStorage):
raise RuntimeError("Expected AbstractStorage got {}".format(storage))
@web.middleware
async def factory(request, handler):
request[STORAGE_KEY] = storage
raise_response = False
try:
response = await handler(request)
except web.HTTPException as exc:
response = exc
raise_response = True
if not isinstance(response, web.StreamResponse):
raise RuntimeError(
"Expect response, not {!r}".format(type(response)))
if not isinstance(response, web.Response):
# likely got websocket or streaming
return response
if response.prepared:
raise RuntimeError(
"Cannot save session data into prepared response")
session = request.get(SESSION_KEY)
if session is not None:
if session._changed:
await storage.save_session(request, response, session)
if raise_response:
raise response
return response
return factory
def setup(app, storage):
"""Setup the library in aiohttp fashion."""
app.middlewares.append(session_middleware(storage))
class AbstractStorage(metaclass=abc.ABCMeta):
def __init__(self, *, cookie_name="AIOHTTP_SESSION",
domain=None, max_age=None, path='/',
secure=None, httponly=True,
encoder=json.dumps, decoder=json.loads):
self._cookie_name = cookie_name
self._cookie_params = dict(domain=domain,
max_age=max_age,
path=path,
secure=secure,
httponly=httponly)
self._max_age = max_age
self._encoder = encoder
self._decoder = decoder
@property
def cookie_name(self):
return self._cookie_name
@property
def max_age(self):
return self._max_age
@property
def cookie_params(self):
return self._cookie_params
def _get_session_data(self, session):
if not session.empty:
data = {
'created': session.created,
'session': session._mapping
}
else:
data = {}
return data
async def new_session(self):
return Session(None, data=None, new=True, max_age=self.max_age)
@abc.abstractmethod
async def load_session(self, request):
pass
@abc.abstractmethod
async def save_session(self, request, response, session):
pass
def load_cookie(self, request):
cookie = request.cookies.get(self._cookie_name)
return cookie
def save_cookie(self, response, cookie_data, *, max_age=None):
params = dict(self._cookie_params)
if max_age is not None:
params['max_age'] = max_age
params['expires'] = time.strftime(
"%a, %d-%b-%Y %T GMT",
time.gmtime(time.time() + max_age))
if not cookie_data:
response.del_cookie(
self._cookie_name,
domain=params["domain"],
path=params["path"],
)
else:
response.set_cookie(self._cookie_name, cookie_data, **params)
class SimpleCookieStorage(AbstractStorage):
"""Simple JSON storage.
Doesn't any encryption/validation, use it for tests only"""
def __init__(self, *, cookie_name="AIOHTTP_SESSION",
domain=None, max_age=None, path='/',
secure=None, httponly=True,
encoder=json.dumps, decoder=json.loads):
super().__init__(cookie_name=cookie_name, domain=domain,
max_age=max_age, path=path, secure=secure,
httponly=httponly,
encoder=encoder, decoder=decoder)
async def load_session(self, request):
cookie = self.load_cookie(request)
if cookie is None:
return Session(None, data=None, new=True, max_age=self.max_age)
else:
data = self._decoder(cookie)
return Session(None, data=data, new=False, max_age=self.max_age)
async def save_session(self, request, response, session):
cookie_data = self._encoder(self._get_session_data(session))
self.save_cookie(response, cookie_data, max_age=session.max_age)