Skip to content

Commit

Permalink
Merge branch 'serializer-override' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
Lxstr committed Apr 18, 2024
2 parents 302039b + 0c22ec6 commit d487b57
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:
"""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 d487b57

Please sign in to comment.