diff --git a/CHANGES.rst b/CHANGES.rst index 6a29726d..bbb2be8f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,17 +1,20 @@ Version 0.7.0 ------------------ -- Use msgpack for serialization, along with ``SESSION_SERIALIZATION_FORMAT``. +- Use msgpack for serialization, along with ``SESSION_SERIALIZATION_FORMAT`` to choose between ``json`` and ``msgpack``. +- Deprecated pickle. It is still available to read existing sessions, but will be removed in 1.0.0. All sessions will transfer to msgspec upon first interaction with 0.7.0. - Prevent sid reuse on storage miss. - Add time-to-live expiration for MongoDB. +- Add retry for SQL based storage. - Abstraction to improve consistency between backends. -- Enforce PERMANENT_SESSION_LIFETIME as expiration consistently for all backends. +- Enforce ``PERMANENT_SESSION_LIFETIME`` as expiration consistently for all backends. - Add logo and additional documentation. -- Add ``flask session_cleanup`` command and alternatively, ``SESSION_CLEANUP_N_REQUESTS`` for SQLAlchemy -- Use Vary cookie header -- Type hints +- Add ``flask session_cleanup`` command and alternatively, ``SESSION_CLEANUP_N_REQUESTS`` for SQLAlchemy or future non-TTL backends. +- Use Vary cookie header. +- Type hints. - Remove null session in favour of specific exception messages. - Deprecate ``SESSION_USE_SIGNER``. +- Remove backend session interfaces from public API and semver. Version 0.6.0 diff --git a/docs/api.rst b/docs/api.rst index ec55deec..0f3da578 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,20 +1,12 @@ API --- +Anything documented here is part of the public API that Flask-Session provides, unless otherwise indicated. Anything not documented here is considered internal or private and may change at any time. + + .. module:: flask_session .. autoclass:: Session :members: init_app .. autoclass:: flask_session.sessions.ServerSideSession - - .. attribute:: sid - - Session id, internally we use :func:`secrets.token_urlsafe` to generate one - session id. You can access it with ``session.sid``. - -.. autoclass:: RedisSessionInterface -.. autoclass:: MemcachedSessionInterface -.. autoclass:: FileSystemSessionInterface -.. autoclass:: MongoDBSessionInterface -.. autoclass:: SqlAlchemySessionInterface diff --git a/docs/config_backend.rst b/docs/config_storage.rst similarity index 99% rename from docs/config_backend.rst rename to docs/config_storage.rst index d73da5c8..df3083aa 100644 --- a/docs/config_backend.rst +++ b/docs/config_storage.rst @@ -1,4 +1,4 @@ -Backend configuration +Storage configuration --------------------- diff --git a/docs/installation.rst b/docs/installation.rst index bac7af89..4b8266c5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -12,22 +12,22 @@ Flask-Session's only required dependency is msgspec for serialization, which has .. note:: - You need to choose and a backend and install an appropriate client library, unless you are using the FileSystemCache. + You need to choose a storage type and install an appropriate client library, unless you are using the FileSystemCache. -For example, if you want to use Redis as your backend, you will need to install the redis-py client library: +For example, if you want to use Redis as your storage, you will need to install the redis-py client library: .. code-block:: bash $ pip install redis -Supported backends and client libraries: +Supported storage and client libraries: .. list-table:: :header-rows: 1 - * - Backend + * - Storage - Client Library * - Redis - redis-py_ diff --git a/docs/quickstart.rst b/docs/quickstart.rst index ba77df84..447a76e9 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -7,8 +7,11 @@ Quick Start Create your Flask application, load the configuration of choice, and then create the :class:`Session` object by passing it the application. -The ``Session`` instance is not used for direct access, you should always use -:class:`flask.session`. +.. note:: + + You can not use ``Session`` instance directly, what ``Session`` does + is just change the :attr:`~flask.Flask.session_interface` attribute on + your Flask applications. You should always use :class:`flask.session`. .. code-block:: python diff --git a/src/flask_session/__init__.py b/src/flask_session/__init__.py index 89e7d1a8..c031f8ac 100644 --- a/src/flask_session/__init__.py +++ b/src/flask_session/__init__.py @@ -14,30 +14,16 @@ class Session: """This class is used to add Server-side Session to one or more Flask applications. - There are two usage modes. One is initialize the instance with a very - specific Flask application:: + For a typical setup use the following initialization:: app = Flask(__name__) Session(app) - The second possibility is to create the object once and configure the - application later:: - - sess = Session() - - def create_app(): - app = Flask(__name__) - sess.init_app(app) - return app - - By default Flask-Session will use :class:`NullSessionInterface`, you - really should configurate your app to use a different SessionInterface. - .. note:: You can not use ``Session`` instance directly, what ``Session`` does is just change the :attr:`~flask.Flask.session_interface` attribute on - your Flask applications. + your Flask applications. You should always use :class:`flask.session`. """ def __init__(self, app=None): @@ -46,7 +32,14 @@ def __init__(self, app=None): self.init_app(app) def init_app(self, app): - """This is used to set up session for your app object. + """This the the alternate setup method, typically used in an application factory pattern:: + + sess = Session() + + def create_app(): + app = Flask(__name__) + sess.init_app(app) + return app :param app: the Flask app object with proper configuration. """ diff --git a/src/flask_session/sessions.py b/src/flask_session/sessions.py index 0e35f414..8182bed5 100644 --- a/src/flask_session/sessions.py +++ b/src/flask_session/sessions.py @@ -30,7 +30,29 @@ def total_seconds(timedelta): class ServerSideSession(CallbackDict, SessionMixin): - """Baseclass for server-side based sessions.""" + """Baseclass for server-side based sessions. This can be accessed through ``flask.session``. + + .. attribute:: sid + + Session id, internally we use :func:`secrets.token_urlsafe` to generate one + session id. + + .. attribute:: modified + + When data is changed, this is set to ``True``. Only the session dictionary + itself is tracked; if the session contains mutable data (for example a nested + dict) then this must be set to ``True`` manually when modifying that data. The + session cookie will only be written to the response if this is ``True``. + + Default is ``False``. + + .. attribute:: permanent + + This sets and reflects the ``'_permanent'`` key in the dict. + + Default is ``False``. + + """ def __bool__(self) -> bool: return bool(dict(self)) and self.keys() != {"_permanent"} @@ -275,18 +297,23 @@ def _upsert_session( @retry_query() def _delete_expired_sessions(self) -> None: - """Delete expired sessions from the backend storage. Only required for non-TTL databases.""" + """Delete expired sessions from the session storage. Only required for non-TTL databases.""" pass class RedisSessionInterface(ServerSideSessionInterface): - """Uses the Redis key-value store as a session backend. (`redis-py` required) + """Uses the Redis key-value store as a session storage. (`redis-py` required) - :param redis: A ``redis.Redis`` instance. + :param app: A Flask app instance. :param key_prefix: A prefix that is added to all Redis store 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. + :param redis: A ``redis.Redis`` instance. + + .. versionadded:: 0.7 + The `serialization_format` and `app` parameters were added. .. versionadded:: 0.6 The `sid_length` parameter was added. @@ -353,13 +380,18 @@ def _upsert_session( class MemcachedSessionInterface(ServerSideSessionInterface): - """A Session interface that uses memcached as backend. (`pylibmc` or `python-memcached` or `pymemcache` required) + """A Session interface that uses memcached as session storage. (`pylibmc` or `python-memcached` or `pymemcache` required) - :param client: A ``memcache.Client`` instance. + :param app: A Flask app instance. :param key_prefix: A prefix that is added to all Memcached store 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. + :param client: A ``memcache.Client`` instance. + + .. versionadded:: 0.7 + The `serialization_format` and `app` parameters were added. .. versionadded:: 0.6 The `sid_length` parameter was added. @@ -415,7 +447,6 @@ def _get_memcache_timeout(self, timeout: int) -> int: timeout += int(time.time()) return timeout - @retry_query() 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) @@ -429,11 +460,9 @@ def _retrieve_session_data(self, store_id: str) -> Optional[dict]: ) return None - @retry_query() def _delete_session(self, store_id: str) -> None: self.client.delete(store_id) - @retry_query() def _upsert_session( self, session_lifetime: TimeDelta, session: ServerSideSession, store_id: str ) -> None: @@ -451,16 +480,20 @@ def _upsert_session( class FileSystemSessionInterface(ServerSideSessionInterface): - """Uses the :class:`cachelib.file.FileSystemCache` as a session backend. + """Uses the :class:`cachelib.file.FileSystemCache` as a session storage. - :param cache_dir: the directory where session files are stored. - :param threshold: the maximum number of items the session stores before it - starts deleting some. - :param mode: the file mode wanted for the session files, default 0600 + :param app: A Flask app instance. :param key_prefix: A prefix that is added to FileSystemCache store 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. + :param cache_dir: the directory where session files are stored. + :param threshold: the maximum number of items the session stores before it + :param mode: the file mode wanted for the session files, default 0600 + + .. versionadded:: 0.7 + The `serialization_format` and `app` parameters were added. .. versionadded:: 0.6 The `sid_length` parameter was added. @@ -491,16 +524,13 @@ def __init__( app, key_prefix, use_signer, permanent, sid_length, serialization_format ) - @retry_query() def _retrieve_session_data(self, store_id: str) -> Optional[dict]: # Get the saved session (item) from the database return self.cache.get(store_id) - @retry_query() def _delete_session(self, store_id: str) -> None: self.cache.delete(store_id) - @retry_query() def _upsert_session( self, session_lifetime: TimeDelta, session: ServerSideSession, store_id: str ) -> None: @@ -518,15 +548,20 @@ def _upsert_session( class MongoDBSessionInterface(ServerSideSessionInterface): - """A Session interface that uses mongodb as backend. (`pymongo` required) + """A Session interface that uses mongodb as session storage. (`pymongo` required) - :param client: A ``pymongo.MongoClient`` instance. - :param db: The database you want to use. - :param collection: The collection you want to use. + :param app: A Flask app instance. :param key_prefix: A prefix that is added to all MongoDB store 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. + :param client: A ``pymongo.MongoClient`` instance. + :param db: The database you want to use. + :param collection: The collection you want to use. + + .. versionadded:: 0.7 + The `serialization_format` and `app` parameters were added. .. versionadded:: 0.6 The `sid_length` parameter was added. @@ -566,7 +601,6 @@ def __init__( app, key_prefix, use_signer, permanent, sid_length, serialization_format ) - @retry_query() def _retrieve_session_data(self, store_id: str) -> Optional[dict]: # Get the saved session (document) from the database document = self.store.find_one({"id": store_id}) @@ -581,14 +615,12 @@ def _retrieve_session_data(self, store_id: str) -> Optional[dict]: ) return None - @retry_query() def _delete_session(self, store_id: str) -> None: if self.use_deprecated_method: self.store.remove({"id": store_id}) else: self.store.delete_one({"id": store_id}) - @retry_query() def _upsert_session( self, session_lifetime: TimeDelta, session: ServerSideSession, store_id: str ) -> None: @@ -623,22 +655,23 @@ def _upsert_session( class SqlAlchemySessionInterface(ServerSideSessionInterface): - """Uses the Flask-SQLAlchemy from a flask app as a session backend. + """Uses the Flask-SQLAlchemy from a flask app as session storage. :param app: A Flask app instance. - :param db: A Flask-SQLAlchemy instance. - :param table: The table name you want to use. :param key_prefix: A prefix that is added to all store 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. + :param db: A Flask-SQLAlchemy instance. + :param table: The table name you want to use. :param sequence: The sequence to use for the primary key if needed. :param schema: The db schema to use :param bind_key: The db bind key to use :param cleanup_n_requests: Delete expired sessions on average every N requests. .. versionadded:: 0.7 - The `cleanup_n_requests` parameter was added. + The `cleanup_n_requests`, `app`, `cleanup_n_requests` parameters were added. .. versionadded:: 0.6 The `sid_length`, `sequence`, `schema` and `bind_key` parameters were added.