diff --git a/docs/config_guide.rst b/docs/config_guide.rst new file mode 100644 index 00000000..f80d9a47 --- /dev/null +++ b/docs/config_guide.rst @@ -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 diff --git a/docs/config.rst b/docs/config_reference.rst similarity index 95% rename from docs/config.rst rename to docs/config_reference.rst index e38f8b05..e3ace977 100644 --- a/docs/config.rst +++ b/docs/config_reference.rst @@ -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 ---------------------------------- @@ -79,8 +68,8 @@ These are specific to Flask-Session. ``SESSION_ID_LENGTH`` -Storage configuration ---------------------- +Storage configuration values +---------------------------- Redis diff --git a/docs/config_serialization.rst b/docs/config_serialization.rst index 7f3049fc..fc3f5325 100644 --- a/docs/config_serialization.rst +++ b/docs/config_serialization.rst @@ -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 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 `_. + +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. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 462f0ae9..244be633 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,7 +7,8 @@ Table of Contents introduction installation usage - config + config_guide + config_reference security api contributing diff --git a/src/flask_session/base.py b/src/flask_session/base.py index 04c6473c..2399b1a3 100644 --- a/src/flask_session/base.py +++ b/src/flask_session/base.py @@ -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() @@ -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): diff --git a/src/flask_session/dynamodb/dynamodb.py b/src/flask_session/dynamodb/dynamodb.py index fbd7678c..a9c448ec 100644 --- a/src/flask_session/dynamodb/dynamodb.py +++ b/src/flask_session/dynamodb/dynamodb.py @@ -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: @@ -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={ diff --git a/src/flask_session/memcached/memcached.py b/src/flask_session/memcached/memcached.py index 2a192d81..46930146 100644 --- a/src/flask_session/memcached/memcached.py +++ b/src/flask_session/memcached/memcached.py @@ -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: @@ -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( diff --git a/src/flask_session/mongodb/mongodb.py b/src/flask_session/mongodb/mongodb.py index 60639fda..ea58079a 100644 --- a/src/flask_session/mongodb/mongodb.py +++ b/src/flask_session/mongodb/mongodb.py @@ -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: @@ -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: diff --git a/src/flask_session/redis/redis.py b/src/flask_session/redis/redis.py index 320c6566..abed80df 100644 --- a/src/flask_session/redis/redis.py +++ b/src/flask_session/redis/redis.py @@ -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: @@ -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( diff --git a/src/flask_session/sqlalchemy/sqlalchemy.py b/src/flask_session/sqlalchemy/sqlalchemy.py index 4841c84b..ebd5075b 100644 --- a/src/flask_session/sqlalchemy/sqlalchemy.py +++ b/src/flask_session/sqlalchemy/sqlalchemy.py @@ -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() @@ -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: