Skip to content

Commit

Permalink
Make it easier to override serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
Lxstr committed Mar 28, 2024
1 parent 1e3a1cb commit 0c22ec6
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 33 deletions.
14 changes: 14 additions & 0 deletions docs/config_guide.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Configuration Guide
===================

.. include:: config_example.rst

.. include:: config_nonpermanent.rst

.. include:: config_cleanup.rst

.. include:: config_exceptions.rst

.. include:: config_serialization.rst

.. include:: config_flask.rst
21 changes: 5 additions & 16 deletions docs/config.rst → docs/config_reference.rst
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
Configuration
=============

.. include:: config_example.rst

.. include:: config_nonpermanent.rst

.. include:: config_cleanup.rst

.. include:: config_exceptions.rst

.. include:: config_serialization.rst
Configuration Reference
=========================

.. include:: config_flask.rst



Flask-Session configuration values
----------------------------------

Expand Down Expand Up @@ -79,8 +68,8 @@ These are specific to Flask-Session.
``SESSION_ID_LENGTH``


Storage configuration
---------------------
Storage configuration values
----------------------------


Redis
Expand Down
35 changes: 34 additions & 1 deletion docs/config_serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,41 @@ Serialization

Flask-session versions below 1.0.0 use pickle serialization (or fallback) for session storage. While not a direct vulnerability, it is a potential security risk. If you are using a version below 1.0.0, it is recommended to upgrade to the latest version as soon as it's available.

From 0.7.0 the serializer is msgspec, which is configurable using ``SESSION_SERIALIZATION_FORMAT``. The default format is ``'msgpack'`` which has 30% storage reduction compared to ``'json'``. The ``'json'`` format may be helpful for debugging, easier viewing or compatibility. Switching between the two should be seamless, even for existing sessions.
From 0.7.0 the serializer is msgspec. The format it uses is configurable with ``SESSION_SERIALIZATION_FORMAT``. The default format is ``'msgpack'`` which has 30% storage reduction compared to ``'json'``. The ``'json'`` format may be helpful for debugging, easier viewing or compatibility. Switching between the two should be seamless, even for existing sessions.

All sessions that are accessed or modified while using 0.7.0 will convert to a msgspec format. Once using 1.0.0, any sessions that are still in pickle format will be cleared upon access.

The msgspec library has speed and memory advantages over other libraries. However, if you want to use a different library (such as pickle or orjson), you can override the :attr:`session_interface.serializer`.

If you encounter a TypeError such as: "Encoding objects of type <type> is unsupported", you may be attempting to serialize an unsupported type. In this case, you can either convert the object to a supported type or use a different serializer.

Casting to a supported type:
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python
session["status"] = str(LazyString('done')))
.. note::

Flask's flash method uses the session to store messages so you must also pass supported types to the flash method.


For a detailed list of supported types by the msgspec serializer, please refer to the official documentation at `msgspec supported types <https://jcristharif.com/msgspec/supported-types.html>`_.

Overriding the serializer:
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python
from flask_session import Session
import orjson
app = Flask(__name__)
Session(app)
# Override the serializer
app.session_interface.serializer = orjson
Any serializer that has a ``dumps`` and ``loads`` method can be used.
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Table of Contents
introduction
installation
usage
config
config_guide
config_reference
security
api
contributing
Expand Down
10 changes: 5 additions & 5 deletions src/flask_session/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ class Serializer(ABC):
"""Baseclass for session serialization."""

@abstractmethod
def decode(self, serialized_data: bytes) -> dict:
def dumps(self, serialized_data: bytes) -> dict:

This comment has been minimized.

Copy link
@anderssonjohan

anderssonjohan Nov 15, 2024

@Lxstr I got totally confused by this change 😅 First after using the code I realized the abstractmethods are mixed up. def dumps should be def loads and vice versa. (they are correct in the implementation on lines 129 and 137)

"""Deserialize the session data."""
raise NotImplementedError()

@abstractmethod
def encode(self, session: ServerSideSession) -> bytes:
def loads(self, session: ServerSideSession) -> bytes:
"""Serialize the session data."""
raise NotImplementedError()

Expand All @@ -126,15 +126,15 @@ def __init__(self, app: Flask, format: str):
else:
raise ValueError(f"Unsupported serialization format: {format}")

def encode(self, session: ServerSideSession) -> bytes:
def dumps(self, data: dict) -> bytes:
"""Serialize the session data."""
try:
return self.encoder.encode(dict(session))
return self.encoder.encode(data)
except Exception as e:
self.app.logger.error(f"Failed to serialize session data: {e}")
raise

def decode(self, serialized_data: bytes) -> dict:
def loads(self, serialized_data: bytes) -> dict:
"""Deserialize the session data."""
# TODO: Remove the pickle fallback in 1.0.0
with suppress(msgspec.DecodeError):
Expand Down
4 changes: 2 additions & 2 deletions src/flask_session/dynamodb/dynamodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def _retrieve_session_data(self, store_id: str) -> Optional[dict]:
document = self.store.get_item(Key={"id": store_id}).get("Item")
if document:
serialized_session_data = want_bytes(document.get("val").value)
return self.serializer.decode(serialized_session_data)
return self.serializer.loads(serialized_session_data)
return None

def _delete_session(self, store_id: str) -> None:
Expand All @@ -112,7 +112,7 @@ def _upsert_session(
) -> None:
storage_expiration_datetime = datetime.utcnow() + session_lifetime
# Serialize the session data
serialized_session_data = self.serializer.encode(session)
serialized_session_data = self.serializer.dumps(dict(session))

self.store.update_item(
Key={
Expand Down
4 changes: 2 additions & 2 deletions src/flask_session/memcached/memcached.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def _retrieve_session_data(self, store_id: str) -> Optional[dict]:
# Get the saved session (item) from the database
serialized_session_data = self.client.get(store_id)
if serialized_session_data:
return self.serializer.decode(serialized_session_data)
return self.serializer.loads(serialized_session_data)
return None

def _delete_session(self, store_id: str) -> None:
Expand All @@ -112,7 +112,7 @@ def _upsert_session(
storage_time_to_live = total_seconds(session_lifetime)

# Serialize the session data
serialized_session_data = self.serializer.encode(session)
serialized_session_data = self.serializer.dumps(dict(session))

# Update existing or create new session in the database
self.client.set(
Expand Down
4 changes: 2 additions & 2 deletions src/flask_session/mongodb/mongodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def _retrieve_session_data(self, store_id: str) -> Optional[dict]:
document = self.store.find_one({"id": store_id})
if document:
serialized_session_data = want_bytes(document["val"])
return self.serializer.decode(serialized_session_data)
return self.serializer.loads(serialized_session_data)
return None

def _delete_session(self, store_id: str) -> None:
Expand All @@ -92,7 +92,7 @@ def _upsert_session(
storage_expiration_datetime = datetime.utcnow() + session_lifetime

# Serialize the session data
serialized_session_data = self.serializer.encode(session)
serialized_session_data = self.serializer.dumps(dict(session))

# Update existing or create new session in the database
if self.use_deprecated_method:
Expand Down
4 changes: 2 additions & 2 deletions src/flask_session/redis/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ 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.decode(serialized_session_data)
return self.serializer.loads(serialized_session_data)
return None

def _delete_session(self, store_id: str) -> None:
Expand All @@ -75,7 +75,7 @@ def _upsert_session(
storage_time_to_live = total_seconds(session_lifetime)

# Serialize the session data
serialized_session_data = self.serializer.encode(session)
serialized_session_data = self.serializer.dumps(dict(session))

# Update existing or create new session in the database
self.client.set(
Expand Down
4 changes: 2 additions & 2 deletions src/flask_session/sqlalchemy/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def _retrieve_session_data(self, store_id: str) -> Optional[dict]:

if record:
serialized_session_data = want_bytes(record.data)
return self.serializer.decode(serialized_session_data)
return self.serializer.loads(serialized_session_data)
return None

@retry_query()
Expand All @@ -168,7 +168,7 @@ def _upsert_session(
storage_expiration_datetime = datetime.utcnow() + session_lifetime

# Serialize session data
serialized_session_data = self.serializer.encode(session)
serialized_session_data = self.serializer.dumps(dict(session))

# Update existing or create new session in the database
try:
Expand Down

0 comments on commit 0c22ec6

Please sign in to comment.