Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5076112a1
|
||
|
|
ce173e43c7
|
||
|
|
b3105423f8
|
||
|
|
249a7abd89 | ||
|
|
093d28fe62 | ||
|
|
7e43c76ad0 | ||
|
|
c25a62412d | ||
|
|
9186fae9e7 | ||
|
|
31a56ddd47 | ||
|
|
6ad2a3789d | ||
|
|
d08156f193 | ||
|
|
39e5fc5c13 | ||
|
|
237f2354ad | ||
|
|
5119f60434 | ||
|
|
2f53caa654 | ||
|
|
cc99a72901 | ||
|
|
5301b4418f | ||
|
|
3e6b102b34 | ||
|
|
65b44db7df | ||
|
|
b9f2dc0067 | ||
|
|
a19f227d88 | ||
|
|
53a82fda3f | ||
|
|
a82799d358 | ||
|
|
80f39ec79b | ||
|
|
9d02429aee | ||
|
|
13fcc653c6 | ||
|
|
16fb57d62a | ||
|
|
43956f045f | ||
|
|
cdd237b5f9 |
@@ -0,0 +1,28 @@
|
|||||||
|
name: Build & Publish Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: uv build
|
||||||
|
|
||||||
|
- name: Publish to Gitea
|
||||||
|
run: |
|
||||||
|
uv publish \
|
||||||
|
--publish-url "${{ vars.REGISTRY_URL }}/api/packages/${{ github.repository_owner }}/pypi" \
|
||||||
|
--username "${{ github.repository_owner }}" \
|
||||||
|
--password "${{ secrets.ACTION_ACCESS_TOKEN }}"
|
||||||
+24
-1
@@ -1,7 +1,30 @@
|
|||||||
|
### 3.0.2
|
||||||
|
|
||||||
|
- Add package metadata for Gitea PyPI registry publishing.
|
||||||
|
- Add Gitea workflow for tagged package releases.
|
||||||
|
- Add explicit redis-trio extra for the bundled Trio Redis client.
|
||||||
|
|
||||||
|
### 2.1.0
|
||||||
|
|
||||||
|
- `session_cookie_name` fix
|
||||||
|
|
||||||
|
### 2.0.0
|
||||||
|
|
||||||
|
- Move from aioredis to redis
|
||||||
|
|
||||||
|
### 1.0.7
|
||||||
|
|
||||||
|
- Updated memcached support (removed asyncio.loop, removed coroutine decorator)
|
||||||
|
|
||||||
|
### 1.0.6
|
||||||
|
|
||||||
|
- MongoDB support
|
||||||
|
- Transfer copyright to Kroket Ltd.
|
||||||
|
|
||||||
### 1.0.3 2021-08-31
|
### 1.0.3 2021-08-31
|
||||||
|
|
||||||
- Migrated to aioredis 2
|
- Migrated to aioredis 2
|
||||||
- SameSite support https://github.com/sanderfoobar/quart-session/commit/8daae3a6734e8f7da13954d5a1a5da8f5fc5a49a
|
- SameSite support https://github.com/kroketio/quart-session/commit/8daae3a6734e8f7da13954d5a1a5da8f5fc5a49a
|
||||||
- Memcached stuff https://github.com/filak/quart-session/commit/004871c495a069784e57e604b69f65af1b7e645a
|
- Memcached stuff https://github.com/filak/quart-session/commit/004871c495a069784e57e604b69f65af1b7e645a
|
||||||
|
|
||||||
### 1.0.0 2020-01-15
|
### 1.0.0 2020-01-15
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Copyright (c) 2014 by Shipeng Feng.
|
Copyright (c) 2014 by Shipeng Feng.
|
||||||
Copyright (c) 2020 by Sander.
|
Copyright (c) 2020 by Kroket Ltd.
|
||||||
|
|
||||||
Some rights reserved.
|
Some rights reserved.
|
||||||
|
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ recursive-include quart_session *.md
|
|||||||
exclude .gitlab-ci.yml
|
exclude .gitlab-ci.yml
|
||||||
exclude examples
|
exclude examples
|
||||||
exclude docs
|
exclude docs
|
||||||
|
exclude venv
|
||||||
@@ -16,7 +16,7 @@ $ pipenv install quart-session
|
|||||||
$ pip install quart-session
|
$ pip install quart-session
|
||||||
```
|
```
|
||||||
|
|
||||||
and requires Python 3.7.0 or higher. A fairly minimal Quart-Session example is,
|
and requires Python 3.7.0 or higher. A minimal Quart-Session example is:
|
||||||
|
|
||||||
```python3
|
```python3
|
||||||
from quart import Quart, session
|
from quart import Quart, session
|
||||||
@@ -29,7 +29,11 @@ Session(app)
|
|||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def hello():
|
async def hello():
|
||||||
session["foo"] = "bar"
|
session["foo"] = "bar"
|
||||||
return 'hello'
|
return "session key 'foo' set"
|
||||||
|
|
||||||
|
@app.route('/foo')
|
||||||
|
async def foo():
|
||||||
|
return session.get("foo", "session key 'foo' not found")
|
||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
```
|
```
|
||||||
@@ -37,9 +41,9 @@ app.run()
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
||||||
### Redis support
|
### Redis
|
||||||
|
|
||||||
via `aioredis`.
|
via `redis>=4.4.0`.
|
||||||
|
|
||||||
```python3
|
```python3
|
||||||
app = Quart(__name__)
|
app = Quart(__name__)
|
||||||
@@ -47,8 +51,16 @@ app.config['SESSION_TYPE'] = 'redis'
|
|||||||
Session(app)
|
Session(app)
|
||||||
```
|
```
|
||||||
|
|
||||||
If you already have a `aioredis.Client` instance and you'd like to share
|
By default, Quart-session connects to Redis at `127.0.0.1:6379`. If you
|
||||||
it with the session interface,
|
have a different location, use `SESSION_URI`
|
||||||
|
|
||||||
|
```python3
|
||||||
|
app = Quart(__name__)
|
||||||
|
app.config['SESSION_TYPE'] = 'redis'
|
||||||
|
app.config['SESSION_URI'] = 'redis://:password@localhost:6379'
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, for extra control, you may provide your own `aioredis.Client` instance altogether.
|
||||||
|
|
||||||
```python3
|
```python3
|
||||||
app = Quart(__name__)
|
app = Quart(__name__)
|
||||||
@@ -56,15 +68,17 @@ app.config['SESSION_TYPE'] = 'redis'
|
|||||||
|
|
||||||
@app.before_serving
|
@app.before_serving
|
||||||
async def setup():
|
async def setup():
|
||||||
cache = await aioredis.create_redis_pool(...)
|
cache = await aioredis.Redis(
|
||||||
|
host="foobar.com",
|
||||||
|
port=6379,
|
||||||
|
password="foobar"
|
||||||
|
)
|
||||||
|
|
||||||
app.config['SESSION_REDIS'] = cache
|
app.config['SESSION_REDIS'] = cache
|
||||||
Session(app)
|
Session(app)
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, Quart-session creates a single connection to Redis, while
|
#### Trio
|
||||||
the example above sets up a connection pool.
|
|
||||||
|
|
||||||
#### Trio support
|
|
||||||
|
|
||||||
Quart-Session comes with [an (experimental) Redis client](quart_session/redis_trio) for use with the [Trio](https://trio.readthedocs.io/en/stable/) eventloop.
|
Quart-Session comes with [an (experimental) Redis client](quart_session/redis_trio) for use with the [Trio](https://trio.readthedocs.io/en/stable/) eventloop.
|
||||||
|
|
||||||
@@ -77,7 +91,7 @@ app.config['SESSION_TYPE'] = 'redis'
|
|||||||
Session(app)
|
Session(app)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Memcached support
|
### Memcached
|
||||||
|
|
||||||
via `aiomcache`.
|
via `aiomcache`.
|
||||||
|
|
||||||
@@ -87,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`
|
||||||
@@ -118,9 +144,11 @@ app.session_interface.serialize = pickle
|
|||||||
At any point you may interface with the session back-end directly:
|
At any point you may interface with the session back-end directly:
|
||||||
|
|
||||||
```python3
|
```python3
|
||||||
|
from quart_session.sessions import SessionInterface
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
async def hello():
|
async def hello():
|
||||||
cache = app.session_interface
|
cache: SessionInterface = app.session_interface
|
||||||
await cache.set("random_key", "val", expiry=3600)
|
await cache.set("random_key", "val", expiry=3600)
|
||||||
data = await cache.get("random_key")
|
data = await cache.get("random_key")
|
||||||
```
|
```
|
||||||
@@ -160,7 +188,6 @@ by explicitly setting `SESSION_REVERSE_PROXY` to `True`.
|
|||||||
|
|
||||||
## Future development
|
## Future development
|
||||||
|
|
||||||
- `MongoDBSessionInterface`
|
|
||||||
- `FileSystemSessionInterface`
|
- `FileSystemSessionInterface`
|
||||||
- `GoogleCloudDatastoreSessionInterface`
|
- `GoogleCloudDatastoreSessionInterface`
|
||||||
- Pytest
|
- Pytest
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
Quart-Session demo.
|
Quart-Session demo.
|
||||||
|
|
||||||
:copyright: (c) 2020 by Sander.
|
:copyright: (c) 2020 by Kroket Ltd.
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
from quart import Quart, session
|
from quart import Quart, session
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
[project]
|
||||||
|
name = "quart-session"
|
||||||
|
version = "3.0.2"
|
||||||
|
description = "Server-side sessions for Quart"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
license = "BSD-3-Clause"
|
||||||
|
license-files = ["LICENSE"]
|
||||||
|
authors = [{ name = "Kroket Ltd.", email = "code@kroket.io" }]
|
||||||
|
dependencies = [
|
||||||
|
"Quart>=0.19.0",
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Environment :: Web Environment",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://git.yiprawr.dev/daniel156161/quart-session"
|
||||||
|
Repository = "https://git.yiprawr.dev/daniel156161/quart-session"
|
||||||
|
Issues = "https://git.yiprawr.dev/daniel156161/quart-session/issues"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dotenv = ["python-dotenv"]
|
||||||
|
mongodb = ["motor>=2.5.1"]
|
||||||
|
redis = ["redis>=4.4.0"]
|
||||||
|
redis-trio = ["trio>=0.22"]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"build>=1.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=80", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["quart_session*"]
|
||||||
@@ -6,17 +6,23 @@
|
|||||||
Adds server session support to your application.
|
Adds server session support to your application.
|
||||||
|
|
||||||
:copyright: (c) 2014 by Shipeng Feng.
|
:copyright: (c) 2014 by Shipeng Feng.
|
||||||
:copyright: (c) 2020 by Sander.
|
:copyright: (c) 2020 by Kroket Ltd.
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '1.0.3'
|
__version__ = '3.0.0'
|
||||||
|
|
||||||
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(
|
||||||
|
|||||||
+114
-25
@@ -6,13 +6,14 @@
|
|||||||
Server-side Sessions and SessionInterfaces.
|
Server-side Sessions and SessionInterfaces.
|
||||||
|
|
||||||
:copyright: (c) 2014 by Shipeng Feng.
|
:copyright: (c) 2014 by Shipeng Feng.
|
||||||
:copyright: (c) 2020 by Sander.
|
:copyright: (c) 2020 by Kroket Ltd.
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
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
|
||||||
@@ -30,7 +31,7 @@ class ServerSideSession(SecureCookieSession):
|
|||||||
"""Baseclass for server-side based sessions."""
|
"""Baseclass for server-side based sessions."""
|
||||||
|
|
||||||
def __init__(self, initial=None, sid=None, permanent=None, addr=None):
|
def __init__(self, initial=None, sid=None, permanent=None, addr=None):
|
||||||
super(ServerSideSession, self).__init__(**initial or {})
|
super(ServerSideSession, self).__init__(initial or {})
|
||||||
self.sid = sid
|
self.sid = sid
|
||||||
if permanent:
|
if permanent:
|
||||||
self.permanent = permanent
|
self.permanent = permanent
|
||||||
@@ -60,6 +61,10 @@ class MemcachedSession(ServerSideSession):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBSession(ServerSideSession):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NullSession(ServerSideSession):
|
class NullSession(ServerSideSession):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -87,7 +92,8 @@ class SessionInterface(QuartSessionInterface):
|
|||||||
app: Quart,
|
app: Quart,
|
||||||
request: BaseRequestWebsocket
|
request: BaseRequestWebsocket
|
||||||
) -> Optional[SecureCookieSession]:
|
) -> Optional[SecureCookieSession]:
|
||||||
sid = request.cookies.get(app.session_cookie_name)
|
cname = app.config.get('SESSION_COOKIE_NAME', 'session')
|
||||||
|
sid = request.cookies.get(cname)
|
||||||
if self._config['SESSION_REVERSE_PROXY'] is True:
|
if self._config['SESSION_REVERSE_PROXY'] is True:
|
||||||
# and no, you cannot define your own incoming
|
# and no, you cannot define your own incoming
|
||||||
# header, stick to standards :-)
|
# header, stick to standards :-)
|
||||||
@@ -120,14 +126,17 @@ class SessionInterface(QuartSessionInterface):
|
|||||||
options['sid'] = self._generate_sid()
|
options['sid'] = self._generate_sid()
|
||||||
return self.session_class(**options)
|
return self.session_class(**options)
|
||||||
|
|
||||||
try:
|
if self.serializer is None:
|
||||||
data = self.serializer.loads(val)
|
data = val
|
||||||
except:
|
else:
|
||||||
app.logger.warning(f"Failed to deserialize session "
|
try:
|
||||||
f"data for sid: {sid}. Generating new sid.")
|
data = self.serializer.loads(val)
|
||||||
app.logger.debug(f"data: {val}")
|
except:
|
||||||
options['sid'] = self._generate_sid()
|
app.logger.warning(f"Failed to deserialize session "
|
||||||
return self.session_class(**options)
|
f"data for sid: {sid}. Generating new sid.")
|
||||||
|
app.logger.debug(f"data: {val}")
|
||||||
|
options['sid'] = self._generate_sid()
|
||||||
|
return self.session_class(**options)
|
||||||
|
|
||||||
protection = self._config['SESSION_PROTECTION']
|
protection = self._config['SESSION_PROTECTION']
|
||||||
if protection is True and addr is not None and \
|
if protection is True and addr is not None and \
|
||||||
@@ -155,13 +164,14 @@ class SessionInterface(QuartSessionInterface):
|
|||||||
isinstance(response.response, FileBody):
|
isinstance(response.response, FileBody):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
cname = app.config.get('SESSION_COOKIE_NAME', 'session')
|
||||||
session_key = self.key_prefix + session.sid
|
session_key = self.key_prefix + session.sid
|
||||||
domain = self.get_cookie_domain(app)
|
domain = self.get_cookie_domain(app)
|
||||||
path = self.get_cookie_path(app)
|
path = self.get_cookie_path(app)
|
||||||
if not session:
|
if not session:
|
||||||
if session.modified:
|
if session.modified:
|
||||||
await self.delete(key=session_key, app=app)
|
await self.delete(key=session_key, app=app)
|
||||||
response.delete_cookie(app.session_cookie_name,
|
response.delete_cookie(cname,
|
||||||
domain=domain, path=path)
|
domain=domain, path=path)
|
||||||
return
|
return
|
||||||
httponly = self.get_cookie_httponly(app)
|
httponly = self.get_cookie_httponly(app)
|
||||||
@@ -169,20 +179,24 @@ 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)
|
||||||
|
|
||||||
val = self.serializer.dumps(dict(session))
|
if self.serializer is None:
|
||||||
|
val = dict(session)
|
||||||
|
else:
|
||||||
|
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))
|
||||||
else:
|
else:
|
||||||
session_id = session.sid
|
session_id = session.sid
|
||||||
response.set_cookie(app.session_cookie_name, session_id,
|
response.set_cookie(cname, session_id.decode('utf-8'),
|
||||||
expires=expires, httponly=httponly,
|
expires=expires, httponly=httponly,
|
||||||
domain=domain, path=path, secure=secure, samesite=samesite)
|
domain=domain, path=path, secure=secure, samesite=samesite)
|
||||||
|
|
||||||
async def create(self, app: Quart):
|
async def create(self, app: Quart):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def get(self, app: Quart, key: str):
|
async def get(self, key: str, app: Quart = None):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def set(self, key: str, value, expiry: int = None,
|
async def set(self, key: str, value, expiry: int = None,
|
||||||
@@ -224,12 +238,14 @@ class RedisSessionInterface(SessionInterface):
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Creates a single Redis connection, you might prefer
|
Creates a single Redis connection, you might prefer
|
||||||
pooling instead (see ``aioredis.Redis.create_redis_pool``)
|
pooling instead (see the `aioredis` documentation
|
||||||
|
for connection pool examples).
|
||||||
"""
|
"""
|
||||||
if self.backend is None:
|
if self.backend is None:
|
||||||
import aioredis
|
from redis import asyncio as aioredis
|
||||||
|
uri = self._config.get('SESSION_URI', 'redis://localhost')
|
||||||
self.backend = await aioredis.from_url(
|
self.backend = await aioredis.from_url(
|
||||||
"redis://localhost", encoding="utf-8", decode_responses=True
|
uri, encoding="utf-8", decode_responses=True
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get(self, key: str, app: Quart = None):
|
async def get(self, key: str, app: Quart = None):
|
||||||
@@ -313,15 +329,12 @@ class MemcachedSessionInterface(SessionInterface):
|
|||||||
permanent=permanent, **kwargs)
|
permanent=permanent, **kwargs)
|
||||||
self.backend = memcached
|
self.backend = memcached
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def create(self, app: Quart) -> None:
|
||||||
def create(self, app: Quart) -> None:
|
|
||||||
if self.backend is None:
|
if self.backend is None:
|
||||||
import aiomcache
|
import aiomcache
|
||||||
loop = asyncio.get_running_loop()
|
# self.backend = aiomcache.Client("127.0.0.1", 11211)
|
||||||
#self.backend = aiomcache.Client("127.0.0.1", 11211, loop=loop)
|
|
||||||
self.backend = aiomcache.Client(self._config.get('SESSION_MEMCACHED_HOST', '127.0.0.1'),
|
self.backend = aiomcache.Client(self._config.get('SESSION_MEMCACHED_HOST', '127.0.0.1'),
|
||||||
self._config.get('SESSION_MEMCACHED_PORT', 11211),
|
self._config.get('SESSION_MEMCACHED_PORT', 11211))
|
||||||
loop=loop)
|
|
||||||
|
|
||||||
def _get_memcache_timeout(self, timeout):
|
def _get_memcache_timeout(self, timeout):
|
||||||
"""
|
"""
|
||||||
@@ -358,6 +371,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
|
||||||
|
|||||||
@@ -1,56 +1,3 @@
|
|||||||
"""
|
|
||||||
Quart-Session
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Quart-Session is an extension for Quart that adds support for
|
|
||||||
Server-side Session to your application.
|
|
||||||
|
|
||||||
Links
|
|
||||||
`````
|
|
||||||
|
|
||||||
* `Github
|
|
||||||
<https://github.com/sferdi0/quart-session>`_
|
|
||||||
|
|
||||||
"""
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
with open('README.md') as f:
|
setup()
|
||||||
long_description = f.read()
|
|
||||||
|
|
||||||
|
|
||||||
INSTALL_REQUIRES = [
|
|
||||||
"Quart>=0.10.0"
|
|
||||||
]
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name='Quart-Session',
|
|
||||||
version='1.0.3',
|
|
||||||
url='https://github.com/sferdi0/quart-session',
|
|
||||||
license='BSD',
|
|
||||||
author='Sander',
|
|
||||||
author_email='sander@sanderf.nl',
|
|
||||||
description='Adds server-side session support to your Quart application',
|
|
||||||
long_description=long_description,
|
|
||||||
long_description_content_type='text/markdown',
|
|
||||||
packages=['quart_session'],
|
|
||||||
zip_safe=False,
|
|
||||||
include_package_data=True,
|
|
||||||
platforms='any',
|
|
||||||
install_requires=INSTALL_REQUIRES,
|
|
||||||
tests_require=INSTALL_REQUIRES + ["asynctest", "hypothesis", "pytest", "pytest-asyncio"],
|
|
||||||
extras_require={
|
|
||||||
"dotenv": ["python-dotenv"],
|
|
||||||
"redis": ["aioredis>=2.0.0"]
|
|
||||||
},
|
|
||||||
classifiers=[
|
|
||||||
'Environment :: Web Environment',
|
|
||||||
'Intended Audience :: Developers',
|
|
||||||
'License :: OSI Approved :: BSD License',
|
|
||||||
'Operating System :: OS Independent',
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user