From 8fac612ec0de7da6dc6f5153833f4430a80d2f4c Mon Sep 17 00:00:00 2001 From: Augustin Trancart Date: Sat, 12 Jan 2019 10:46:01 -0500 Subject: [PATCH] Add standalone orm.close_all method and deprecate SessionMaker.close_all Added a new function :func:`.close_all_sessions` which takes over the task of the :meth:`.Session.close_all` method, which is now deprecated as this is confusing as a classmethod. Pull request courtesy Augustin Trancart. Fixes: #4412 Closes: #4438 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/4438 Pull-request-sha: 7833d12a9898c82d50716427144bf3276c22ab3f Change-Id: Ib35eaa520ae886f3f8f550f9712fc3b139e00b60 --- doc/build/changelog/unreleased_13/4412.rst | 8 ++++ doc/build/orm/session_api.rst | 2 + lib/sqlalchemy/orm/__init__.py | 1 + lib/sqlalchemy/orm/session.py | 27 ++++++++++++- lib/sqlalchemy/testing/fixtures.py | 4 +- test/ext/declarative/test_basic.py | 3 +- test/ext/declarative/test_inheritance.py | 3 +- test/ext/declarative/test_mixin.py | 3 +- test/orm/test_eager_relations.py | 3 +- test/orm/test_session.py | 46 ++++++++++++++++++++++ test/orm/test_subquery_relations.py | 3 +- 11 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 doc/build/changelog/unreleased_13/4412.rst diff --git a/doc/build/changelog/unreleased_13/4412.rst b/doc/build/changelog/unreleased_13/4412.rst new file mode 100644 index 0000000000..3c868ff82e --- /dev/null +++ b/doc/build/changelog/unreleased_13/4412.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: change, orm + :tickets: 4412 + + Added a new function :func:`.close_all_sessions` which takes + over the task of the :meth:`.Session.close_all` method, which + is now deprecated as this is confusing as a classmethod. + Pull request courtesy Augustin Trancart. diff --git a/doc/build/orm/session_api.rst b/doc/build/orm/session_api.rst index 5523422e01..683719945f 100644 --- a/doc/build/orm/session_api.rst +++ b/doc/build/orm/session_api.rst @@ -20,6 +20,8 @@ Session and sessionmaker() Session Utilities ----------------- +.. autofunction:: close_all_sessions + .. autofunction:: make_transient .. autofunction:: make_transient_to_detached diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 5c286a71bc..1666104d98 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -40,6 +40,7 @@ from .relationships import foreign # noqa from .relationships import RelationshipProperty # noqa from .relationships import remote # noqa from .scoping import scoped_session # noqa +from .session import close_all_sessions # noqa from .session import make_transient # noqa from .session import make_transient_to_detached # noqa from .session import object_session # noqa diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index fbc268e01e..360e734667 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -59,11 +59,16 @@ class _SessionClassMethods(object): """Class-level methods for :class:`.Session`, :class:`.sessionmaker`.""" @classmethod + @util.deprecated( + "1.3", + "The :meth:`.Session.close_all` method is deprecated and will be " + "removed in a future release. Please refer to " + ":func:`.session.close_all_sessions`.", + ) def close_all(cls): """Close *all* sessions in memory.""" - for sess in _sessions.values(): - sess.close() + close_all_sessions() @classmethod @util.dependencies("sqlalchemy.orm.util") @@ -3204,6 +3209,24 @@ class sessionmaker(_SessionClassMethods): ) +def close_all_sessions(): + """Close all sessions in memory. + + This function consults a global registry of all :class:`.Session` objects + and calls :meth:`.Session.close` on them, which resets them to a clean + state. + + This function is not for general use but may be useful for test suites + within the teardown scheme. + + .. versionadded:: 1.3 + + """ + + for sess in _sessions.values(): + sess.close() + + def make_transient(instance): """Alter the state of the given instance so that it is :term:`transient`. diff --git a/lib/sqlalchemy/testing/fixtures.py b/lib/sqlalchemy/testing/fixtures.py index 64d9328d72..953b229f29 100644 --- a/lib/sqlalchemy/testing/fixtures.py +++ b/lib/sqlalchemy/testing/fixtures.py @@ -246,7 +246,7 @@ class RemovesEvents(object): class _ORMTest(object): @classmethod def teardown_class(cls): - sa.orm.session.Session.close_all() + sa.orm.session.close_all_sessions() sa.orm.clear_mappers() @@ -287,7 +287,7 @@ class MappedTest(_ORMTest, TablesTest, assertions.AssertsExecutionResults): self._setup_each_inserts() def teardown(self): - sa.orm.session.Session.close_all() + sa.orm.session.close_all_sessions() self._teardown_each_mappers() self._teardown_each_classes() self._teardown_each_tables() diff --git a/test/ext/declarative/test_basic.py b/test/ext/declarative/test_basic.py index 990262f2ca..0f00350190 100644 --- a/test/ext/declarative/test_basic.py +++ b/test/ext/declarative/test_basic.py @@ -19,6 +19,7 @@ from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import backref from sqlalchemy.orm import class_mapper from sqlalchemy.orm import clear_mappers +from sqlalchemy.orm import close_all_sessions from sqlalchemy.orm import column_property from sqlalchemy.orm import composite from sqlalchemy.orm import configure_mappers @@ -60,7 +61,7 @@ class DeclarativeTestBase( Base = decl.declarative_base(testing.db) def teardown(self): - Session.close_all() + close_all_sessions() clear_mappers() Base.metadata.drop_all() diff --git a/test/ext/declarative/test_inheritance.py b/test/ext/declarative/test_inheritance.py index 97c7dcd7c8..17b915da07 100644 --- a/test/ext/declarative/test_inheritance.py +++ b/test/ext/declarative/test_inheritance.py @@ -10,6 +10,7 @@ from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import has_inherited_table from sqlalchemy.orm import class_mapper from sqlalchemy.orm import clear_mappers +from sqlalchemy.orm import close_all_sessions from sqlalchemy.orm import configure_mappers from sqlalchemy.orm import create_session from sqlalchemy.orm import deferred @@ -39,7 +40,7 @@ class DeclarativeTestBase(fixtures.TestBase, testing.AssertsExecutionResults): Base = decl.declarative_base(testing.db) def teardown(self): - Session.close_all() + close_all_sessions() clear_mappers() Base.metadata.drop_all() diff --git a/test/ext/declarative/test_mixin.py b/test/ext/declarative/test_mixin.py index 7fec772708..ef9bbd354d 100644 --- a/test/ext/declarative/test_mixin.py +++ b/test/ext/declarative/test_mixin.py @@ -11,6 +11,7 @@ from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import base as orm_base from sqlalchemy.orm import class_mapper from sqlalchemy.orm import clear_mappers +from sqlalchemy.orm import close_all_sessions from sqlalchemy.orm import column_property from sqlalchemy.orm import configure_mappers from sqlalchemy.orm import create_session @@ -41,7 +42,7 @@ class DeclarativeTestBase(fixtures.TestBase, testing.AssertsExecutionResults): Base = decl.declarative_base(testing.db) def teardown(self): - Session.close_all() + close_all_sessions() clear_mappers() Base.metadata.drop_all() diff --git a/test/orm/test_eager_relations.py b/test/orm/test_eager_relations.py index 2b53e5ae81..ea8ae764d9 100644 --- a/test/orm/test_eager_relations.py +++ b/test/orm/test_eager_relations.py @@ -14,6 +14,7 @@ from sqlalchemy import testing from sqlalchemy import text from sqlalchemy.orm import aliased from sqlalchemy.orm import backref +from sqlalchemy.orm import close_all_sessions from sqlalchemy.orm import column_property from sqlalchemy.orm import contains_eager from sqlalchemy.orm import create_session @@ -5054,7 +5055,7 @@ class CyclicalInheritingEagerTestTwo( session.add_all([rscott, alien, brunner]) session.commit() - session.close_all() + close_all_sessions() self.d = session.query(Director).options(joinedload("*")).first() assert len(list(session)) == 3 diff --git a/test/orm/test_session.py b/test/orm/test_session.py index 1f36578df9..03b18df6aa 100644 --- a/test/orm/test_session.py +++ b/test/orm/test_session.py @@ -7,6 +7,7 @@ from sqlalchemy import String from sqlalchemy import testing from sqlalchemy.orm import attributes from sqlalchemy.orm import backref +from sqlalchemy.orm import close_all_sessions from sqlalchemy.orm import create_session from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import joinedload @@ -148,6 +149,51 @@ class TransScopingTest(_fixtures.FixtureTest): class SessionUtilTest(_fixtures.FixtureTest): run_inserts = None + def test_close_all_sessions(self): + users, User = self.tables.users, self.classes.User + + mapper(User, users) + + s1 = Session() + u1 = User() + s1.add(u1) + + s2 = Session() + u2 = User() + s2.add(u2) + + assert u1 in s1 + assert u2 in s2 + + close_all_sessions() + + assert u1 not in s1 + assert u2 not in s2 + + def test_session_close_all_deprecated(self): + users, User = self.tables.users, self.classes.User + + mapper(User, users) + + s1 = Session() + u1 = User() + s1.add(u1) + + s2 = Session() + u2 = User() + s2.add(u2) + + assert u1 in s1 + assert u2 in s2 + + with assertions.expect_deprecated( + r"The Session.close_all\(\) method is deprecated and will " + "be removed in a future release. "): + Session.close_all() + + assert u1 not in s1 + assert u2 not in s2 + def test_object_session_raises(self): User = self.classes.User diff --git a/test/orm/test_subquery_relations.py b/test/orm/test_subquery_relations.py index 1374d55d8d..a4ee2d804b 100644 --- a/test/orm/test_subquery_relations.py +++ b/test/orm/test_subquery_relations.py @@ -7,6 +7,7 @@ from sqlalchemy import String from sqlalchemy import testing from sqlalchemy.orm import aliased from sqlalchemy.orm import clear_mappers +from sqlalchemy.orm import close_all_sessions from sqlalchemy.orm import create_session from sqlalchemy.orm import deferred from sqlalchemy.orm import joinedload @@ -2666,7 +2667,7 @@ class CyclicalInheritingEagerTestTwo( session.add_all([rscott, alien, brunner]) session.commit() - session.close_all() + close_all_sessions() d = session.query(Director).options(subqueryload("*")).first() assert len(list(session)) == 3 -- 2.47.2