add session handler for mongodb: MongoDBSessionInterface
This commit is contained in:
@@ -101,6 +101,18 @@ app.config['SESSION_TYPE'] = 'memcached'
|
|||||||
Session(app)
|
Session(app)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### MongoDB
|
||||||
|
|
||||||
|
via `motor`.
|
||||||
|
|
||||||
|
```python3
|
||||||
|
app = Quart(__name__)
|
||||||
|
app.config['SESSION_TYPE'] = 'mongodb'
|
||||||
|
app.config['SESSION_MONGODB_URI'] = 'mongodb://localhost:27017/my_database'
|
||||||
|
app.config['SESSION_MONGODB_COLLECTION'] = 'sessions'
|
||||||
|
Session(app)
|
||||||
|
```
|
||||||
|
|
||||||
### JSON serializer
|
### JSON serializer
|
||||||
|
|
||||||
[flask-session](https://pypi.org/project/Flask-Session/) uses `pickle`
|
[flask-session](https://pypi.org/project/Flask-Session/) uses `pickle`
|
||||||
@@ -174,7 +186,6 @@ by explicitly setting `SESSION_REVERSE_PROXY` to `True`.
|
|||||||
|
|
||||||
## Future development
|
## Future development
|
||||||
|
|
||||||
- `MongoDBSessionInterface`
|
|
||||||
- `FileSystemSessionInterface`
|
- `FileSystemSessionInterface`
|
||||||
- `GoogleCloudDatastoreSessionInterface`
|
- `GoogleCloudDatastoreSessionInterface`
|
||||||
- Pytest
|
- Pytest
|
||||||
|
|||||||
@@ -10,13 +10,19 @@
|
|||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '1.0.4'
|
__version__ = '1.0.5-dev'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from quart import Quart
|
from quart import Quart
|
||||||
|
|
||||||
from .sessions import RedisSessionInterface, RedisTrioSessionInterface, MemcachedSessionInterface, NullSessionInterface
|
from .sessions import (
|
||||||
|
RedisSessionInterface,
|
||||||
|
RedisTrioSessionInterface,
|
||||||
|
MemcachedSessionInterface,
|
||||||
|
MongoDBSessionInterface,
|
||||||
|
NullSessionInterface
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Session(object):
|
class Session(object):
|
||||||
@@ -133,6 +139,16 @@ class Session(object):
|
|||||||
use_signer=config['SESSION_USE_SIGNER'],
|
use_signer=config['SESSION_USE_SIGNER'],
|
||||||
permanent=config['SESSION_PERMANENT'],
|
permanent=config['SESSION_PERMANENT'],
|
||||||
**config)
|
**config)
|
||||||
|
elif config['SESSION_TYPE'] == 'mongodb':
|
||||||
|
session_interface = MongoDBSessionInterface(
|
||||||
|
mongodb_uri=config['SESSION_MONGODB_URI'],
|
||||||
|
collection=config['SESSION_MONGODB_COLLECTION'],
|
||||||
|
client_kwargs=config.get('SESSION_MONGODB_CLIENT_KWARGS', {}),
|
||||||
|
set_callback=config.get('SESSION_MONGODB_SET_CALLBACK'),
|
||||||
|
key_prefix=config['SESSION_KEY_PREFIX'],
|
||||||
|
use_signer=config['SESSION_USE_SIGNER'],
|
||||||
|
permanent=config['SESSION_PERMANENT'],
|
||||||
|
**config)
|
||||||
elif config['SESSION_TYPE'] == 'null':
|
elif config['SESSION_TYPE'] == 'null':
|
||||||
app.logger.warning(f"{backend_warning}. Currently using: null")
|
app.logger.warning(f"{backend_warning}. Currently using: null")
|
||||||
session_interface = NullSessionInterface(
|
session_interface = NullSessionInterface(
|
||||||
|
|||||||
@@ -11,8 +11,9 @@
|
|||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4, UUID
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import functools
|
||||||
|
|
||||||
from quart import Quart, current_app
|
from quart import Quart, current_app
|
||||||
from quart.wrappers import BaseRequestWebsocket, Response
|
from quart.wrappers import BaseRequestWebsocket, Response
|
||||||
@@ -60,6 +61,10 @@ class MemcachedSession(ServerSideSession):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBSession(ServerSideSession):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NullSession(ServerSideSession):
|
class NullSession(ServerSideSession):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -120,6 +125,9 @@ class SessionInterface(QuartSessionInterface):
|
|||||||
options['sid'] = self._generate_sid()
|
options['sid'] = self._generate_sid()
|
||||||
return self.session_class(**options)
|
return self.session_class(**options)
|
||||||
|
|
||||||
|
if self.serializer is None:
|
||||||
|
data = val
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
data = self.serializer.loads(val)
|
data = self.serializer.loads(val)
|
||||||
except:
|
except:
|
||||||
@@ -169,7 +177,11 @@ class SessionInterface(QuartSessionInterface):
|
|||||||
secure = self.get_cookie_secure(app)
|
secure = self.get_cookie_secure(app)
|
||||||
expires = self.get_expiration_time(app, session)
|
expires = self.get_expiration_time(app, session)
|
||||||
|
|
||||||
|
if self.serializer is None:
|
||||||
|
val = dict(session)
|
||||||
|
else:
|
||||||
val = self.serializer.dumps(dict(session))
|
val = self.serializer.dumps(dict(session))
|
||||||
|
|
||||||
await self.set(key=session_key, value=val, app=app)
|
await self.set(key=session_key, value=val, app=app)
|
||||||
if self.use_signer:
|
if self.use_signer:
|
||||||
session_id = self._get_signer(app).sign(want_bytes(session.sid))
|
session_id = self._get_signer(app).sign(want_bytes(session.sid))
|
||||||
@@ -360,6 +372,82 @@ class MemcachedSessionInterface(SessionInterface):
|
|||||||
return await self.backend.delete(key)
|
return await self.backend.delete(key)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_key_to_uuid(func):
|
||||||
|
"""
|
||||||
|
convert the session UUID to a UUID object for mongodb
|
||||||
|
|
||||||
|
example:
|
||||||
|
"session:b8ebbf02-cc7a-4b0b-824f-22a984c8c0b8" ->
|
||||||
|
UUID("b8ebbf02-cc7a-4b0b-824f-22a984c8c0b8")
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
if 'key' in kwargs:
|
||||||
|
key = kwargs['key']
|
||||||
|
try:
|
||||||
|
if key.startswith('session:'):
|
||||||
|
_, _uuid = tuple(key.split(':'))
|
||||||
|
kwargs['key'] = UUID(_uuid)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.warning(
|
||||||
|
f"session could not be converted to a uuid object: {key}"
|
||||||
|
)
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBSessionInterface(SessionInterface):
|
||||||
|
# mongodb does not a serializer as many object types are properly handled by the connector
|
||||||
|
serializer = None
|
||||||
|
session_class = MongoDBSession
|
||||||
|
|
||||||
|
def __init__(self, mongodb_uri, collection, client_kwargs={}, set_callback=None, **kwargs):
|
||||||
|
from motor.motor_asyncio import AsyncIOMotorClient
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.mongodb_uri = mongodb_uri
|
||||||
|
self.client_kwargs = client_kwargs
|
||||||
|
self.set_callback = set_callback
|
||||||
|
self._collection = collection
|
||||||
|
self._client = AsyncIOMotorClient(self.mongodb_uri, uuidRepresentation='standard', **self.client_kwargs)
|
||||||
|
self._database = self._client.get_database()
|
||||||
|
|
||||||
|
async def create(self, app: Quart) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@_convert_key_to_uuid
|
||||||
|
async def get(self, key, app):
|
||||||
|
value = await self.collection.find_one({'_id': key}, {'data': True})
|
||||||
|
if value:
|
||||||
|
return value.get('data', {})
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@_convert_key_to_uuid
|
||||||
|
async def set(self, key, value, expiry=None, app=None):
|
||||||
|
doc = {
|
||||||
|
'data': value,
|
||||||
|
}
|
||||||
|
|
||||||
|
# allows the document to be modified prior upsert
|
||||||
|
if callable(self.set_callback):
|
||||||
|
self.set_callback(doc)
|
||||||
|
|
||||||
|
await self.collection.update_one({
|
||||||
|
'_id': key
|
||||||
|
}, {
|
||||||
|
'$set': doc
|
||||||
|
},
|
||||||
|
upsert=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def collection(self):
|
||||||
|
return self._database.get_collection(self._collection)
|
||||||
|
|
||||||
|
|
||||||
class NullSessionInterface(SessionInterface):
|
class NullSessionInterface(SessionInterface):
|
||||||
"""This class does absolutely nothing"""
|
"""This class does absolutely nothing"""
|
||||||
session_class = NullSession
|
session_class = NullSession
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ INSTALL_REQUIRES = [
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Quart-Session',
|
name='Quart-Session',
|
||||||
version='1.0.4',
|
version='1.0.5-dev',
|
||||||
url='https://github.com/sferdi0/quart-session',
|
url='https://github.com/sferdi0/quart-session',
|
||||||
license='BSD',
|
license='BSD',
|
||||||
author='Sander',
|
author='Sander',
|
||||||
@@ -40,6 +40,7 @@ setup(
|
|||||||
tests_require=INSTALL_REQUIRES + ["asynctest", "hypothesis", "pytest", "pytest-asyncio"],
|
tests_require=INSTALL_REQUIRES + ["asynctest", "hypothesis", "pytest", "pytest-asyncio"],
|
||||||
extras_require={
|
extras_require={
|
||||||
"dotenv": ["python-dotenv"],
|
"dotenv": ["python-dotenv"],
|
||||||
|
"mongodb": ["motor>=2.5.1"],
|
||||||
"redis": ["aioredis>=2.0.0"]
|
"redis": ["aioredis>=2.0.0"]
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
|||||||
Reference in New Issue
Block a user