Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for table creation to be optional to allow for table/schema management by other tooling #239

Open
wants to merge 6 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [kim-sondrup](https://github.com/kim-sondrup)
- [bnjmn](https://github.com/bnjmn)
- [christopherpickering](https://github.com/christopherpickering)
- [jlgoolsbee](https://github.com/jlgoolsbee)

## Original Author

Expand Down
6 changes: 6 additions & 0 deletions docs/config_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ SqlAlchemy

Default: ``'sessions'``

.. py:data:: SESSION_SQLALCHEMY_TABLE_EXISTS

Whether (or not) the table for storing session data is managed by libraries (e.g. Flask-Migrate) or other means outside of Flask-Session. When set to ``True``, Flask-Session will not try to create the session table.

Default: ``False``

.. py:data:: SESSION_SQLALCHEMY_SEQUENCE

The name of the sequence you want to use for the primary key.
Expand Down
4 changes: 4 additions & 0 deletions src/flask_session/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ def _get_interface(self, app):
SESSION_SQLALCHEMY_TABLE = config.get(
"SESSION_SQLALCHEMY_TABLE", Defaults.SESSION_SQLALCHEMY_TABLE
)
SESSION_SQLALCHEMY_TABLE_EXISTS = config.get(
"SESSION_SQLALCHEMY_TABLE_EXISTS", Defaults.SESSION_SQLALCHEMY_TABLE_EXISTS
)
SESSION_SQLALCHEMY_SEQUENCE = config.get(
"SESSION_SQLALCHEMY_SEQUENCE", Defaults.SESSION_SQLALCHEMY_SEQUENCE
)
Expand Down Expand Up @@ -182,6 +185,7 @@ def _get_interface(self, app):
**common_params,
client=SESSION_SQLALCHEMY,
table=SESSION_SQLALCHEMY_TABLE,
table_exists=SESSION_SQLALCHEMY_TABLE_EXISTS,
sequence=SESSION_SQLALCHEMY_SEQUENCE,
schema=SESSION_SQLALCHEMY_SCHEMA,
bind_key=SESSION_SQLALCHEMY_BIND_KEY,
Expand Down
1 change: 1 addition & 0 deletions src/flask_session/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Defaults:
SESSION_SQLALCHEMY_SEQUENCE = None
SESSION_SQLALCHEMY_SCHEMA = None
SESSION_SQLALCHEMY_BIND_KEY = None
SESSION_SQLALCHEMY_TABLE_EXISTS = False

# DynamoDB settings
SESSION_DYNAMODB = None
Expand Down
30 changes: 23 additions & 7 deletions src/flask_session/sqlalchemy/sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Provides a Session Interface to SQLAlchemy"""

import warnings
from datetime import datetime
from datetime import timedelta as TimeDelta
Expand Down Expand Up @@ -46,6 +48,13 @@ def __repr__(self):
class SqlAlchemySessionInterface(ServerSideSessionInterface):
"""Uses the Flask-SQLAlchemy from a flask app as session storage.

By default (``table_exists=False``) Flask-Session itself will create the table for session storage according to the
model defined by the ``create_session_model`` method. If ``table_exists`` is set to True, you're responsible—either
manually or via other tooling (e.g., Flask-Migrate)—for creating a table that matches the model, taking into account
the values (or defaults) provided via configuration parameters to the SQLAlchemy session interface (specifically
``SESSION_SQLALCHEMY_TABLE``, ``SESSION_SQLALCHEMY_SCHEMA``, ``SESSION_SQLALCHEMY_BIND_KEY``, and
``SESSION_SQLALCHEMY_SEQUENCE``).

:param app: A Flask app instance.
:param client: A Flask-SQLAlchemy instance.
:param key_prefix: A prefix that is added to all storage keys.
Expand All @@ -54,11 +63,15 @@ class SqlAlchemySessionInterface(ServerSideSessionInterface):
: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 table: The table name you want to use.
:param table_exists: Whether the session table is created/managed outside of Flask-Session (default=False).
: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.9
The `table_exists` parameter was added.

.. versionadded:: 0.7
db changed to client to be standard on all session interfaces.
The `cleanup_n_request` parameter was added.
Expand Down Expand Up @@ -86,6 +99,7 @@ def __init__(
sequence: Optional[str] = Defaults.SESSION_SQLALCHEMY_SEQUENCE,
schema: Optional[str] = Defaults.SESSION_SQLALCHEMY_SCHEMA,
bind_key: Optional[str] = Defaults.SESSION_SQLALCHEMY_BIND_KEY,
table_exists: bool = Defaults.SESSION_SQLALCHEMY_TABLE_EXISTS,
cleanup_n_requests: Optional[int] = Defaults.SESSION_CLEANUP_N_REQUESTS,
):
self.app = app
Expand All @@ -103,13 +117,15 @@ def __init__(
self.sql_session_model = create_session_model(
client, table, schema, bind_key, sequence
)
# Create the table if it does not exist
with app.app_context():
if bind_key:
engine = self.client.get_engine(app, bind=bind_key)
else:
engine = self.client.engine
self.sql_session_model.__table__.create(bind=engine, checkfirst=True)

# Optionally create the table if it does not exist
if not table_exists:
with app.app_context():
if bind_key:
engine = self.client.get_engine(app, bind=bind_key)
else:
engine = self.client.engine
self.sql_session_model.__table__.create(bind=engine, checkfirst=True)

super().__init__(
app,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_memcached.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TestMemcachedSession:

@contextmanager
def setup_memcached(self):
self.mc = memcache.Client(("127.0.0.1:11211"))
self.mc = memcache.Client("127.0.0.1:11211")
try:
self.mc.flush_all()
yield
Expand Down
19 changes: 19 additions & 0 deletions tests/test_sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,22 @@ def test_use_signer(self, app_utils):
json.loads(byte_string.decode("utf-8")) if byte_string else {}
)
assert stored_session.get("value") == "44"

@pytest.mark.filterwarnings("ignore:No valid SQLAlchemy instance provided")
def test_database_not_created_automatically(self, app_utils):
app = app_utils.create_app(
{
"SESSION_TYPE": "sqlalchemy",
"SQLALCHEMY_DATABASE_URI": "sqlite:///",
"SESSION_SQLALCHEMY_TABLE_EXISTS": True,
}
)
with app.app_context() and self.setup_sqlalchemy(
app
) and app.test_request_context():
assert isinstance(
flask.session,
SqlAlchemySession,
)
with pytest.raises(AssertionError):
app_utils.test_session(app)
Loading