diff --git a/docs/api.rst b/docs/api.rst
index faae610d..194b4cb5 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -15,10 +15,11 @@ Anything documented here is part of the public API that Flask-Session provides,
:members: regenerate
.. autoclass:: flask_session.redis.RedisSessionInterface
+.. autoclass:: flask_session.valkey.ValkeySessionInterface
.. autoclass:: flask_session.memcached.MemcachedSessionInterface
.. autoclass:: flask_session.filesystem.FileSystemSessionInterface
.. autoclass:: flask_session.cachelib.CacheLibSessionInterface
.. autoclass:: flask_session.mongodb.MongoDBSessionInterface
.. autoclass:: flask_session.sqlalchemy.SqlAlchemySessionInterface
.. autoclass:: flask_session.dynamodb.DynamoDBSessionInterface
-.. autoclass:: flask_session.postgresql.PostgreSqlSessionInterface
\ No newline at end of file
+.. autoclass:: flask_session.postgresql.PostgreSqlSessionInterface
diff --git a/docs/config_cleanup.rst b/docs/config_cleanup.rst
index ac535f11..f6905dba 100644
--- a/docs/config_cleanup.rst
+++ b/docs/config_cleanup.rst
@@ -15,4 +15,4 @@ Run the the following command regularly with a cron job or scheduler such as Her
Alternatively, set the configuration variable ``SESSION_CLEANUP_N_REQUESTS`` to the average number of requests after which the cleanup should be performed. This is less desirable than using the scheduled app command cleanup as it may slow down some requests but may be useful for small applications or rapid development.
-This is not required for the ``Redis``, ``Memecached``, ``Filesystem``, ``Mongodb`` storage engines, as they support time-to-live for records.
\ No newline at end of file
+This is not required for the ``Redis``, ``Valkey``, ``Memecached``, ``Filesystem``, ``Mongodb`` storage engines, as they support time-to-live for records.
diff --git a/docs/config_reference.rst b/docs/config_reference.rst
index 9fef8fd5..eb3072bd 100644
--- a/docs/config_reference.rst
+++ b/docs/config_reference.rst
@@ -2,7 +2,7 @@ Configuration Reference
=========================
.. include:: config_flask.rst
-
+
Flask-Session configuration values
----------------------------------
@@ -13,6 +13,7 @@ These are specific to Flask-Session.
Specifies which type of session interface to use. Built-in session types:
- **redis**: RedisSessionInterface
+ - **valkey**: ValkeySessionInterface
- **memcached**: MemcachedSessionInterface
- **filesystem**: FileSystemSessionInterface (Deprecated in 0.7.0, will be removed in 1.0.0 in favor of CacheLibSessionInterface)
- **cachelib**: CacheLibSessionInterface
@@ -23,7 +24,7 @@ These are specific to Flask-Session.
.. py:data:: SESSION_PERMANENT
Whether use permanent session or not.
-
+
Default: ``True``
.. py:data:: SESSION_USE_SIGNER
@@ -32,7 +33,7 @@ These are specific to Flask-Session.
.. note::
This feature is historical and generally only relevant if you are using client-side sessions ie. not Flask-Session. SESSION_ID_LENGTH provides the relevant entropy for session identifiers.
-
+
Default: ``False``
.. deprecated:: 0.7.0
@@ -40,21 +41,21 @@ These are specific to Flask-Session.
.. py:data:: SESSION_KEY_PREFIX
A prefix that is added before all session keys. This makes it easier to use the same backend storage server for different apps.
-
+
Default: ``'session:'``
.. py:data:: SESSION_ID_LENGTH
The length of the session identifier in bytes (of entropy).
-
+
Default: ``32``
.. versionadded:: 0.6.0
.. py:data:: SESSION_SERIALIZATION_FORMAT
-
+
The serialization format to use. Can be `'msgpack'`` or `'json'`. Set to `'msgpack'`` for a more efficient serialization format. Set to `'json'`` for a human-readable format.
-
+
Default: ``'msgpack'``
.. versionadded:: 0.7.0
@@ -79,7 +80,17 @@ Redis
.. py:data:: SESSION_REDIS
A ``redis.Redis`` instance.
-
+
+ Default: Instance connected to ``127.0.0.1:6379``
+
+
+Valkey
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. py:data:: SESSION_VALKEY
+
+ A ``valkey.Valkey`` instance.
+
Default: Instance connected to ``127.0.0.1:6379``
@@ -89,7 +100,7 @@ Memcached
.. py:data:: SESSION_MEMCACHED
A ``memcache.Client`` instance.
-
+
Default: Instance connected to ``127.0.0.1:6379``
@@ -99,23 +110,23 @@ FileSystem
.. py:data:: SESSION_FILE_DIR
The directory where session files are stored.
-
+
Default: ``flask_session`` directory under current working directory.
.. deprecated:: 0.7.0
.. py:data:: SESSION_FILE_THRESHOLD
-
+
The maximum number of items the session stores before it starts deleting some.
-
+
Default: ``500``
.. deprecated:: 0.7.0
.. py:data:: SESSION_FILE_MODE
-
+
The file mode wanted for the session files.
-
+
Default: ``0600``
.. deprecated:: 0.7.0
@@ -125,17 +136,17 @@ CacheLib
.. py:data:: SESSION_CACHELIB
Any valid `cachelib backend `_. This allows you maximum flexibility in choosing the cache backend and it's configuration.
-
+
The following would set a cache directory called "flask_session" and a threshold of 500 items before it starts deleting some.
-
+
.. code-block:: python
app.config['SESSION_CACHELIB'] = FileSystemCache(cache_dir='flask_session', threshold=500)
-
+
.. important::
-
+
A ``default_timeout`` set in any of the ``CacheLib`` backends will be overrode by the ``PERMANENT_SESSION_LIFETIME`` when each stored session's expiry is set.
-
+
Default: ``FileSystemCache`` in ``./flask_session`` directory.
MongoDB
@@ -144,19 +155,19 @@ MongoDB
.. py:data:: SESSION_MONGODB
A ``pymongo.MongoClient`` instance.
-
+
Default: Instance connected to ``127.0.0.1:27017``
.. py:data:: SESSION_MONGODB_DB
-
+
The MongoDB database you want to use.
-
+
Default: ``'flask_session'``
.. py:data:: SESSION_MONGODB_COLLECT
-
+
The MongoDB collection you want to use.
-
+
Default: ``'sessions'``
@@ -166,60 +177,60 @@ SqlAlchemy
.. py:data:: SESSION_SQLALCHEMY
A ``flask_sqlalchemy.SQLAlchemy`` instance whose database connection URI is configured using the ``SQLALCHEMY_DATABASE_URI`` parameter.
-
+
Must be set in flask_sqlalchemy version 3.0 or higher.
.. py:data:: SESSION_SQLALCHEMY_TABLE
-
+
The name of the SQL table you want to use.
-
+
Default: ``'sessions'``
.. py:data:: SESSION_SQLALCHEMY_SEQUENCE
-
+
The name of the sequence you want to use for the primary key.
-
+
Default: ``None``
.. py:data:: SESSION_SQLALCHEMY_SCHEMA
-
+
The name of the schema you want to use.
-
+
Default: ``None``
.. py:data:: SESSION_SQLALCHEMY_BIND_KEY
-
+
The name of the bind key you want to use.
-
+
Default: ``None``
.. py:data:: SESSION_CLEANUP_N_REQUESTS
Only applicable to non-TTL backends.
-
+
The average number of requests after which Flask-Session will perform a session cleanup. This involves removing all session data that is older than ``PERMANENT_SESSION_LIFETIME``. Using the app command ``flask session_cleanup`` instead is preferable.
-
+
Default: ``None``
Dynamodb
~~~~~~~~~~~~~~~~~~~~~~~
.. py:data:: SESSION_DYNAMODB
-
+
A ``boto3.resource`` instance.
-
+
Default: Instance connected to ``'localhost:8000'``
.. py:data:: SESSION_DYNAMODB_TABLE_NAME
The name of the table you want to use.
-
+
Default: ``'Sessions'``
.. py:data:: SESSION_DYNAMODB_TABLE_EXISTS
By default it will create a new table with the TTL setting activated unless you set this parameter to ``True``, then it assumes that the table already exists.
-
+
Default: ``False``
.. deprecated:: 0.7.0
diff --git a/docs/installation.rst b/docs/installation.rst
index 99313035..14ff6321 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -10,7 +10,7 @@ Install from PyPI using an installer such as pip:
Flask-Session's only required dependency is msgspec for serialization, which has no sub-dependencies.
-However, you also need to choose a storage type and install an appropriate client library so the app can communicate with storage.
+However, you also need to choose a storage type and install an appropriate client library so the app can communicate with storage.
For example, if you want to use Redis as your storage, you will need to install the redis-py_ library either directly or as an optional dependency like below:
.. code-block:: bash
@@ -48,10 +48,14 @@ Available storage options and their corresponding ```` values ar
- ``redis``
- redis-py_
-
+ * - **Valkey**
+ - ``valkey``
+ - valkey-py_
+ -
* - **Memcached**
- ``memcached``
- pymemcache_
- - pylibmc_, python-memcached_, libmc_
+ - pylibmc_, python-memcached_, libmc_
* - **MongoDB**
- ``mongodb``
- pymongo_
@@ -74,7 +78,7 @@ Other storage backends might be compatible with Flask-Session as long as they ad
Cachelib
--------
-Flask-Session also indirectly supports storage and client libraries via cachelib_, which is a wrapper around various cache libraries.
+Flask-Session also indirectly supports storage and client libraries via cachelib_, which is a wrapper around various cache libraries.
You must also install cachelib_ itselfand the relevant client library to use these.
.. list-table::
@@ -91,6 +95,8 @@ You must also install cachelib_ itselfand the relevant client library to use the
- uwsgi_
* - Redis
- redis-py_
+ * - Valkey
+ - valkey-py_
* - Memcached
- pylibmc_, python-memcached_, libmc_ or `google.appengine.api.memcached`_
* - MongoDB
@@ -105,6 +111,7 @@ You must also install cachelib_ itselfand the relevant client library to use the
.. _redis-py: https://github.com/redis/redis-py
+.. _valkey-py: https://github.com/valkey-io/valkey-py
.. _pylibmc: http://sendapatch.se/projects/pylibmc/
.. _python-memcached: https://github.com/linsomniac/python-memcached
.. _pymemcache: https://github.com/pinterest/pymemcache
diff --git a/pyproject.toml b/pyproject.toml
index da444405..2620e946 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -77,6 +77,7 @@ dev-dependencies = [
"pytest>=7.4.4",
"pytest-cov>=4.1.0",
"redis>=5.0.3",
+ "valkey>=6.0.0",
"python-memcached>=1.62",
"flask-sqlalchemy>=3.0.5",
"pymongo>=4.6.2",
diff --git a/src/flask_session/__init__.py b/src/flask_session/__init__.py
index 5d4caee9..87037ccf 100644
--- a/src/flask_session/__init__.py
+++ b/src/flask_session/__init__.py
@@ -1,6 +1,6 @@
from .defaults import Defaults
-__version__ = "0.8.0"
+__version__ = "0.9.0"
class Session:
@@ -61,6 +61,9 @@ def _get_interface(self, app):
# Redis settings
SESSION_REDIS = config.get("SESSION_REDIS", Defaults.SESSION_REDIS)
+ # Valkey settings
+ SESSION_VALKEY = config.get("SESSION_VALKEY", Defaults.SESSION_VALKEY)
+
# Memcached settings
SESSION_MEMCACHED = config.get("SESSION_MEMCACHED", Defaults.SESSION_MEMCACHED)
@@ -144,6 +147,13 @@ def _get_interface(self, app):
**common_params,
client=SESSION_REDIS,
)
+ elif SESSION_TYPE == "valkey":
+ from .valkey import ValkeySessionInterface
+
+ session_interface = ValkeySessionInterface(
+ **common_params,
+ client=SESSION_VALKEY,
+ )
elif SESSION_TYPE == "memcached":
from .memcached import MemcachedSessionInterface
diff --git a/src/flask_session/defaults.py b/src/flask_session/defaults.py
index f1bc1501..53a0725d 100644
--- a/src/flask_session/defaults.py
+++ b/src/flask_session/defaults.py
@@ -16,6 +16,9 @@ class Defaults:
# Redis settings
SESSION_REDIS = None
+ # Valkey settings
+ SESSION_VALKEY = None
+
# Memcached settings
SESSION_MEMCACHED = None
@@ -48,4 +51,4 @@ class Defaults:
# PostgreSQL settings
SESSION_POSTGRESQL = None
SESSION_POSTGRESQL_TABLE = "flask_sessions"
- SESSION_POSTGRESQL_SCHEMA = "public"
\ No newline at end of file
+ SESSION_POSTGRESQL_SCHEMA = "public"
diff --git a/src/flask_session/valkey/__init__.py b/src/flask_session/valkey/__init__.py
new file mode 100644
index 00000000..213e570a
--- /dev/null
+++ b/src/flask_session/valkey/__init__.py
@@ -0,0 +1 @@
+from .valkey import ValkeySession, ValkeySessionInterface # noqa: F401
diff --git a/src/flask_session/valkey/valkey.py b/src/flask_session/valkey/valkey.py
new file mode 100644
index 00000000..89b95266
--- /dev/null
+++ b/src/flask_session/valkey/valkey.py
@@ -0,0 +1,85 @@
+import warnings
+from datetime import timedelta as TimeDelta
+from typing import Optional
+
+from flask import Flask
+from valkey import Valkey
+
+from .._utils import total_seconds
+from ..base import ServerSideSession, ServerSideSessionInterface
+from ..defaults import Defaults
+
+
+class ValkeySession(ServerSideSession):
+ pass
+
+
+class ValkeySessionInterface(ServerSideSessionInterface):
+ """Uses the Valkey key-value store as a session storage. (`valkey-py` required)
+
+ :param client: A ``valkey.Valkey`` instance.
+ :param key_prefix: A prefix that is added to all storage keys.
+ :param use_signer: Whether to sign the session id cookie or not.
+ :param permanent: Whether to use permanent session or not.
+ :param sid_length: The length of the generated session id in bytes.
+ :param serialization_format: The serialization format to use for the session data.
+
+ .. versionadded:: 0.7
+ The `serialization_format` and `app` parameters were added.
+
+ .. versionadded:: 0.6
+ The `sid_length` parameter was added.
+
+ .. versionadded:: 0.2
+ The `use_signer` parameter was added.
+ """
+
+ session_class = ValkeySession
+ ttl = True
+
+ def __init__(
+ self,
+ app: Flask,
+ client: Optional[Valkey] = Defaults.SESSION_VALKEY,
+ key_prefix: str = Defaults.SESSION_KEY_PREFIX,
+ use_signer: bool = Defaults.SESSION_USE_SIGNER,
+ permanent: bool = Defaults.SESSION_PERMANENT,
+ sid_length: int = Defaults.SESSION_ID_LENGTH,
+ serialization_format: str = Defaults.SESSION_SERIALIZATION_FORMAT,
+ ):
+ if client is None or not isinstance(client, Valkey):
+ warnings.warn(
+ "No valid Valkey instance provided, attempting to create a new instance on localhost with default settings.",
+ RuntimeWarning,
+ stacklevel=1,
+ )
+ client = Valkey()
+ self.client = client
+ super().__init__(
+ app, key_prefix, use_signer, permanent, sid_length, serialization_format
+ )
+
+ def _retrieve_session_data(self, store_id: str) -> Optional[dict]:
+ # Get the saved session (value) from the database
+ serialized_session_data = self.client.get(store_id)
+ if serialized_session_data:
+ return self.serializer.loads(serialized_session_data)
+ return None
+
+ def _delete_session(self, store_id: str) -> None:
+ self.client.delete(store_id)
+
+ def _upsert_session(
+ self, session_lifetime: TimeDelta, session: ServerSideSession, store_id: str
+ ) -> None:
+ storage_time_to_live = total_seconds(session_lifetime)
+
+ # Serialize the session data
+ serialized_session_data = self.serializer.dumps(dict(session))
+
+ # Update existing or create new session in the database
+ self.client.set(
+ name=store_id,
+ value=serialized_session_data,
+ ex=storage_time_to_live,
+ )