diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6a2f0c5e..d1625fe3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 diff --git a/CHANGES.rst b/CHANGES.rst index b53e0cd0..88b7583b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,16 @@ Flask-Security Changelog Here you can see the full list of changes between each Flask-Security release. +Version 5.3.2 +------------- + +Released xxx + +Fixes +++++++ + +- (:issue:`859`) Update Quickstart to show how to properly handle SQLAlchemy connections + Version 5.3.1 ------------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 86f0e265..f1278ccb 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -141,8 +141,8 @@ SQLAlchemy Install requirements $ . pymyenv/bin/activate $ pip install flask-security-too[common] sqlalchemy -SQLAlchemy Application -~~~~~~~~~~~~~~~~~~~~~~ +SQLAlchemy Application (w/o Flask-SQLAlchemy) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following code sample illustrates how to get started as quickly as possible using `SQLAlchemy in a declarative way @@ -173,6 +173,9 @@ and models.py. # Don't worry if email has findable domain app.config["SECURITY_EMAIL_VALIDATOR_ARGS"] = {"check_deliverability": False} + # manage sessions per request - make sure connections are closed and returned + app.teardown_appcontext(lambda exc: db_session.close()) + # Setup Flask-Security user_datastore = SQLAlchemySessionUserDatastore(db_session, User, Role) app.security = Security(app, user_datastore) diff --git a/tests/conftest.py b/tests/conftest.py index bb612547..ebeceae7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -467,7 +467,7 @@ def sqlalchemy_session_datastore(request, app, tmpdir, realdburl): return sqlalchemy_session_setup(request, app, tmpdir, realdburl) -def sqlalchemy_session_setup(request, app, tmpdir, realdburl): +def sqlalchemy_session_setup(request, app, tmpdir, realdburl, **engine_kwargs): """ Note that we test having a different user id column name here. """ @@ -501,10 +501,11 @@ def sqlalchemy_session_setup(request, app, tmpdir, realdburl): app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + path - engine = create_engine(app.config["SQLALCHEMY_DATABASE_URI"]) + engine = create_engine(app.config["SQLALCHEMY_DATABASE_URI"], **engine_kwargs) db_session = scoped_session( sessionmaker(autocommit=False, autoflush=False, bind=engine) ) + app.teardown_appcontext(lambda exc: db_session.close()) Base = declarative_base() Base.query = db_session.query_property() diff --git a/tests/test_misc.py b/tests/test_misc.py index fa501338..ff6603b3 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -14,7 +14,6 @@ from unittest import mock import re import os.path -import sys import time import typing as t @@ -809,7 +808,6 @@ def __init__(self, email): """ -@pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3 or higher") @pytest.mark.settings(password_check_breached="strict") def test_breached(app, sqlalchemy_datastore): # partial response from: https://api.pwnedpasswords.com/range/07003 @@ -833,7 +831,6 @@ def test_breached(app, sqlalchemy_datastore): assert app.config["SECURITY_MSG_PASSWORD_BREACHED"][0] in pbad[0] -@pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3 or higher") @pytest.mark.settings( password_check_breached="strict", password_breached_count=16, @@ -1444,3 +1441,20 @@ def test_login_email_whatever(app, client, get_message): ) assert response.status_code == 302 assert "/" in response.location + + +@pytest.mark.skip +def test_sqlalchemy_session_conn(request, app, tmpdir, realdburl): + # test harness for checking connection mgmt by logging all connections + from .conftest import sqlalchemy_session_setup + + ds = sqlalchemy_session_setup( + request, app, tmpdir, realdburl, echo_pool="debug", echo="debug" + ) + init_app_with_options(app, ds) + client = app.test_client() + + client.post("/login", data=dict(email="matt@lp.com", password="password")) + + client.post("/login", json=dict(noemail="matt@lp.com", password="password")) + time.sleep(5)