diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/base/test_events.py | 126 | ||||
| -rw-r--r-- | test/base/test_utils.py | 222 | ||||
| -rw-r--r-- | test/ext/asyncio/test_engine_py3k.py | 182 | ||||
| -rw-r--r-- | test/ext/asyncio/test_session_py3k.py | 58 | ||||
| -rw-r--r-- | test/orm/test_scoping.py | 24 | ||||
| -rw-r--r-- | test/orm/test_session.py | 55 |
6 files changed, 500 insertions, 167 deletions
diff --git a/test/base/test_events.py b/test/base/test_events.py index a4ed1000b..19f68e9a3 100644 --- a/test/base/test_events.py +++ b/test/base/test_events.py @@ -15,7 +15,19 @@ from sqlalchemy.testing.mock import Mock from sqlalchemy.testing.util import gc_collect -class EventsTest(fixtures.TestBase): +class TearDownLocalEventsFixture(object): + def tearDown(self): + classes = set() + for entry in event.base._registrars.values(): + for evt_cls in entry: + if evt_cls.__module__ == __name__: + classes.add(evt_cls) + + for evt_cls in classes: + event.base._remove_dispatcher(evt_cls) + + +class EventsTest(TearDownLocalEventsFixture, fixtures.TestBase): """Test class- and instance-level event registration.""" def setUp(self): @@ -34,9 +46,6 @@ class EventsTest(fixtures.TestBase): self.Target = Target - def tearDown(self): - event.base._remove_dispatcher(self.Target.__dict__["dispatch"].events) - def test_register_class(self): def listen(x, y): pass @@ -258,7 +267,60 @@ class EventsTest(fixtures.TestBase): ) -class NamedCallTest(fixtures.TestBase): +class SlotsEventsTest(fixtures.TestBase): + @testing.requires.python3 + def test_no_slots_dispatch(self): + class Target(object): + __slots__ = () + + class TargetEvents(event.Events): + _dispatch_target = Target + + def event_one(self, x, y): + pass + + def event_two(self, x): + pass + + def event_three(self, x): + pass + + t1 = Target() + + with testing.expect_raises_message( + TypeError, + r"target .*Target.* doesn't have __dict__, should it " + "be defining _slots_dispatch", + ): + event.listen(t1, "event_one", Mock()) + + def test_slots_dispatch(self): + class Target(object): + __slots__ = ("_slots_dispatch",) + + class TargetEvents(event.Events): + _dispatch_target = Target + + def event_one(self, x, y): + pass + + def event_two(self, x): + pass + + def event_three(self, x): + pass + + t1 = Target() + + m1 = Mock() + event.listen(t1, "event_one", m1) + + t1.dispatch.event_one(2, 4) + + eq_(m1.mock_calls, [call(2, 4)]) + + +class NamedCallTest(TearDownLocalEventsFixture, fixtures.TestBase): def _fixture(self): class TargetEventsOne(event.Events): def event_one(self, x, y): @@ -373,7 +435,7 @@ class NamedCallTest(fixtures.TestBase): eq_(canary.mock_calls, [call({"x": 4, "y": 5, "z": 8, "q": 5})]) -class LegacySignatureTest(fixtures.TestBase): +class LegacySignatureTest(TearDownLocalEventsFixture, fixtures.TestBase): """test adaption of legacy args""" def setUp(self): @@ -397,11 +459,6 @@ class LegacySignatureTest(fixtures.TestBase): self.TargetOne = TargetOne - def tearDown(self): - event.base._remove_dispatcher( - self.TargetOne.__dict__["dispatch"].events - ) - def test_legacy_accept(self): canary = Mock() @@ -550,12 +607,7 @@ class LegacySignatureTest(fixtures.TestBase): ) -class ClsLevelListenTest(fixtures.TestBase): - def tearDown(self): - event.base._remove_dispatcher( - self.TargetOne.__dict__["dispatch"].events - ) - +class ClsLevelListenTest(TearDownLocalEventsFixture, fixtures.TestBase): def setUp(self): class TargetEventsOne(event.Events): def event_one(self, x, y): @@ -622,7 +674,7 @@ class ClsLevelListenTest(fixtures.TestBase): assert handler2 not in s2.dispatch.event_one -class AcceptTargetsTest(fixtures.TestBase): +class AcceptTargetsTest(TearDownLocalEventsFixture, fixtures.TestBase): """Test default target acceptance.""" def setUp(self): @@ -643,14 +695,6 @@ class AcceptTargetsTest(fixtures.TestBase): self.TargetOne = TargetOne self.TargetTwo = TargetTwo - def tearDown(self): - event.base._remove_dispatcher( - self.TargetOne.__dict__["dispatch"].events - ) - event.base._remove_dispatcher( - self.TargetTwo.__dict__["dispatch"].events - ) - def test_target_accept(self): """Test that events of the same name are routed to the correct collection based on the type of target given. @@ -687,7 +731,7 @@ class AcceptTargetsTest(fixtures.TestBase): eq_(list(t2.dispatch.event_one), [listen_two, listen_four]) -class CustomTargetsTest(fixtures.TestBase): +class CustomTargetsTest(TearDownLocalEventsFixture, fixtures.TestBase): """Test custom target acceptance.""" def setUp(self): @@ -707,9 +751,6 @@ class CustomTargetsTest(fixtures.TestBase): self.Target = Target - def tearDown(self): - event.base._remove_dispatcher(self.Target.__dict__["dispatch"].events) - def test_indirect(self): def listen(x, y): pass @@ -727,7 +768,7 @@ class CustomTargetsTest(fixtures.TestBase): ) -class SubclassGrowthTest(fixtures.TestBase): +class SubclassGrowthTest(TearDownLocalEventsFixture, fixtures.TestBase): """test that ad-hoc subclasses are garbage collected.""" def setUp(self): @@ -752,7 +793,7 @@ class SubclassGrowthTest(fixtures.TestBase): eq_(self.Target.__subclasses__(), []) -class ListenOverrideTest(fixtures.TestBase): +class ListenOverrideTest(TearDownLocalEventsFixture, fixtures.TestBase): """Test custom listen functions which change the listener function signature.""" @@ -778,9 +819,6 @@ class ListenOverrideTest(fixtures.TestBase): self.Target = Target - def tearDown(self): - event.base._remove_dispatcher(self.Target.__dict__["dispatch"].events) - def test_listen_override(self): listen_one = Mock() listen_two = Mock() @@ -816,7 +854,7 @@ class ListenOverrideTest(fixtures.TestBase): eq_(listen_one.mock_calls, [call(12)]) -class PropagateTest(fixtures.TestBase): +class PropagateTest(TearDownLocalEventsFixture, fixtures.TestBase): def setUp(self): class TargetEvents(event.Events): def event_one(self, arg): @@ -850,7 +888,7 @@ class PropagateTest(fixtures.TestBase): eq_(listen_two.mock_calls, []) -class JoinTest(fixtures.TestBase): +class JoinTest(TearDownLocalEventsFixture, fixtures.TestBase): def setUp(self): class TargetEvents(event.Events): def event_one(self, target, arg): @@ -875,11 +913,6 @@ class JoinTest(fixtures.TestBase): self.TargetFactory = TargetFactory self.TargetElement = TargetElement - def tearDown(self): - for cls in (self.TargetElement, self.TargetFactory, self.BaseTarget): - if "dispatch" in cls.__dict__: - event.base._remove_dispatcher(cls.__dict__["dispatch"].events) - def test_neither(self): element = self.TargetFactory().create() element.run_event(1) @@ -1075,7 +1108,7 @@ class JoinTest(fixtures.TestBase): ) -class DisableClsPropagateTest(fixtures.TestBase): +class DisableClsPropagateTest(TearDownLocalEventsFixture, fixtures.TestBase): def setUp(self): class TargetEvents(event.Events): def event_one(self, target, arg): @@ -1093,11 +1126,6 @@ class DisableClsPropagateTest(fixtures.TestBase): self.BaseTarget = BaseTarget self.SubTarget = SubTarget - def tearDown(self): - for cls in (self.SubTarget, self.BaseTarget): - if "dispatch" in cls.__dict__: - event.base._remove_dispatcher(cls.__dict__["dispatch"].events) - def test_listen_invoke_clslevel(self): canary = Mock() @@ -1132,7 +1160,7 @@ class DisableClsPropagateTest(fixtures.TestBase): eq_(canary.mock_calls, []) -class RemovalTest(fixtures.TestBase): +class RemovalTest(TearDownLocalEventsFixture, fixtures.TestBase): def _fixture(self): class TargetEvents(event.Events): def event_one(self, x, y): diff --git a/test/base/test_utils.py b/test/base/test_utils.py index fa347243e..9220720b6 100644 --- a/test/base/test_utils.py +++ b/test/base/test_utils.py @@ -2300,7 +2300,14 @@ class SymbolTest(fixtures.TestBase): class _Py3KFixtures(object): - pass + def _kw_only_fixture(self): + pass + + def _kw_plus_posn_fixture(self): + pass + + def _kw_opt_fixture(self): + pass if util.py3k: @@ -2321,185 +2328,208 @@ def _kw_opt_fixture(self, a, *, b, c="c"): for k in _locals: setattr(_Py3KFixtures, k, _locals[k]) +py3k_fixtures = _Py3KFixtures() -class TestFormatArgspec(_Py3KFixtures, fixtures.TestBase): - def _test_format_argspec_plus(self, fn, wanted, grouped=None): - - # test direct function - if grouped is None: - parsed = util.format_argspec_plus(fn) - else: - parsed = util.format_argspec_plus(fn, grouped=grouped) - eq_(parsed, wanted) - - # test sending fullargspec - spec = compat.inspect_getfullargspec(fn) - if grouped is None: - parsed = util.format_argspec_plus(spec) - else: - parsed = util.format_argspec_plus(spec, grouped=grouped) - eq_(parsed, wanted) - def test_specs(self): - self._test_format_argspec_plus( +class TestFormatArgspec(_Py3KFixtures, fixtures.TestBase): + @testing.combinations( + ( lambda: None, { "args": "()", "self_arg": None, "apply_kw": "()", "apply_pos": "()", + "apply_pos_proxied": "()", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda: None, - {"args": "", "self_arg": None, "apply_kw": "", "apply_pos": ""}, - grouped=False, - ) - - self._test_format_argspec_plus( + { + "args": "", + "self_arg": None, + "apply_kw": "", + "apply_pos": "", + "apply_pos_proxied": "", + }, + False, + ), + ( lambda self: None, { "args": "(self)", "self_arg": "self", "apply_kw": "(self)", "apply_pos": "(self)", + "apply_pos_proxied": "()", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda self: None, { "args": "self", "self_arg": "self", "apply_kw": "self", "apply_pos": "self", + "apply_pos_proxied": "", }, - grouped=False, - ) - - self._test_format_argspec_plus( + False, + ), + ( lambda *a: None, { "args": "(*a)", "self_arg": "a[0]", "apply_kw": "(*a)", "apply_pos": "(*a)", + "apply_pos_proxied": "(*a)", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda **kw: None, { "args": "(**kw)", "self_arg": None, "apply_kw": "(**kw)", "apply_pos": "(**kw)", + "apply_pos_proxied": "(**kw)", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda *a, **kw: None, { "args": "(*a, **kw)", "self_arg": "a[0]", "apply_kw": "(*a, **kw)", "apply_pos": "(*a, **kw)", + "apply_pos_proxied": "(*a, **kw)", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda a, *b: None, { "args": "(a, *b)", "self_arg": "a", "apply_kw": "(a, *b)", "apply_pos": "(a, *b)", + "apply_pos_proxied": "(*b)", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda a, **b: None, { "args": "(a, **b)", "self_arg": "a", "apply_kw": "(a, **b)", "apply_pos": "(a, **b)", + "apply_pos_proxied": "(**b)", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda a, *b, **c: None, { "args": "(a, *b, **c)", "self_arg": "a", "apply_kw": "(a, *b, **c)", "apply_pos": "(a, *b, **c)", + "apply_pos_proxied": "(*b, **c)", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda a, b=1, **c: None, { "args": "(a, b=1, **c)", "self_arg": "a", "apply_kw": "(a, b=b, **c)", "apply_pos": "(a, b, **c)", + "apply_pos_proxied": "(b, **c)", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda a=1, b=2: None, { "args": "(a=1, b=2)", "self_arg": "a", "apply_kw": "(a=a, b=b)", "apply_pos": "(a, b)", + "apply_pos_proxied": "(b)", }, - ) - - self._test_format_argspec_plus( + True, + ), + ( lambda a=1, b=2: None, { "args": "a=1, b=2", "self_arg": "a", "apply_kw": "a=a, b=b", "apply_pos": "a, b", + "apply_pos_proxied": "b", }, - grouped=False, - ) - - if util.py3k: - self._test_format_argspec_plus( - self._kw_only_fixture, - { - "args": "self, a, *, b, c", - "self_arg": "self", - "apply_pos": "self, a, *, b, c", - "apply_kw": "self, a, b=b, c=c", - }, - grouped=False, - ) - self._test_format_argspec_plus( - self._kw_plus_posn_fixture, - { - "args": "self, a, *args, b, c", - "self_arg": "self", - "apply_pos": "self, a, *args, b, c", - "apply_kw": "self, a, b=b, c=c, *args", - }, - grouped=False, - ) - self._test_format_argspec_plus( - self._kw_opt_fixture, - { - "args": "self, a, *, b, c='c'", - "self_arg": "self", - "apply_pos": "self, a, *, b, c", - "apply_kw": "self, a, b=b, c=c", - }, - grouped=False, - ) + False, + ), + ( + py3k_fixtures._kw_only_fixture, + { + "args": "self, a, *, b, c", + "self_arg": "self", + "apply_pos": "self, a, *, b, c", + "apply_kw": "self, a, b=b, c=c", + "apply_pos_proxied": "a, *, b, c", + }, + False, + testing.requires.python3, + ), + ( + py3k_fixtures._kw_plus_posn_fixture, + { + "args": "self, a, *args, b, c", + "self_arg": "self", + "apply_pos": "self, a, *args, b, c", + "apply_kw": "self, a, b=b, c=c, *args", + "apply_pos_proxied": "a, *args, b, c", + }, + False, + testing.requires.python3, + ), + ( + py3k_fixtures._kw_opt_fixture, + { + "args": "self, a, *, b, c='c'", + "self_arg": "self", + "apply_pos": "self, a, *, b, c", + "apply_kw": "self, a, b=b, c=c", + "apply_pos_proxied": "a, *, b, c", + }, + False, + testing.requires.python3, + ), + argnames="fn,wanted,grouped", + ) + def test_specs(self, fn, wanted, grouped): + + # test direct function + if grouped is None: + parsed = util.format_argspec_plus(fn) + else: + parsed = util.format_argspec_plus(fn, grouped=grouped) + eq_(parsed, wanted) + + # test sending fullargspec + spec = compat.inspect_getfullargspec(fn) + if grouped is None: + parsed = util.format_argspec_plus(spec) + else: + parsed = util.format_argspec_plus(spec, grouped=grouped) + eq_(parsed, wanted) @testing.requires.cpython def test_init_grouped(self): @@ -2508,17 +2538,20 @@ class TestFormatArgspec(_Py3KFixtures, fixtures.TestBase): "self_arg": "self", "apply_pos": "(self)", "apply_kw": "(self)", + "apply_pos_proxied": "()", } wrapper_spec = { "args": "(self, *args, **kwargs)", "self_arg": "self", "apply_pos": "(self, *args, **kwargs)", "apply_kw": "(self, *args, **kwargs)", + "apply_pos_proxied": "(*args, **kwargs)", } custom_spec = { "args": "(slef, a=123)", "self_arg": "slef", # yes, slef "apply_pos": "(slef, a)", + "apply_pos_proxied": "(a)", "apply_kw": "(slef, a=a)", } @@ -2532,18 +2565,21 @@ class TestFormatArgspec(_Py3KFixtures, fixtures.TestBase): "self_arg": "self", "apply_pos": "self", "apply_kw": "self", + "apply_pos_proxied": "", } wrapper_spec = { "args": "self, *args, **kwargs", "self_arg": "self", "apply_pos": "self, *args, **kwargs", "apply_kw": "self, *args, **kwargs", + "apply_pos_proxied": "*args, **kwargs", } custom_spec = { "args": "slef, a=123", "self_arg": "slef", # yes, slef "apply_pos": "slef, a", "apply_kw": "slef, a=a", + "apply_pos_proxied": "a", } self._test_init(False, object_spec, wrapper_spec, custom_spec) diff --git a/test/ext/asyncio/test_engine_py3k.py b/test/ext/asyncio/test_engine_py3k.py index 7c7d90e21..83987b06f 100644 --- a/test/ext/asyncio/test_engine_py3k.py +++ b/test/ext/asyncio/test_engine_py3k.py @@ -2,6 +2,7 @@ import asyncio from sqlalchemy import Column from sqlalchemy import delete +from sqlalchemy import event from sqlalchemy import exc from sqlalchemy import func from sqlalchemy import Integer @@ -9,13 +10,19 @@ from sqlalchemy import select from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import testing +from sqlalchemy import text from sqlalchemy import union_all from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.ext.asyncio import engine as _async_engine from sqlalchemy.ext.asyncio import exc as asyncio_exc from sqlalchemy.testing import async_test from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures +from sqlalchemy.testing import is_ +from sqlalchemy.testing import is_not +from sqlalchemy.testing import mock from sqlalchemy.testing.asyncio import assert_raises_message_async +from sqlalchemy.util.concurrency import greenlet_spawn class EngineFixture(fixtures.TablesTest): @@ -50,6 +57,117 @@ class EngineFixture(fixtures.TablesTest): class AsyncEngineTest(EngineFixture): __backend__ = True + def test_proxied_attrs_engine(self, async_engine): + sync_engine = async_engine.sync_engine + + is_(async_engine.url, sync_engine.url) + is_(async_engine.pool, sync_engine.pool) + is_(async_engine.dialect, sync_engine.dialect) + eq_(async_engine.name, sync_engine.name) + eq_(async_engine.driver, sync_engine.driver) + eq_(async_engine.echo, sync_engine.echo) + + def test_clear_compiled_cache(self, async_engine): + async_engine.sync_engine._compiled_cache["foo"] = "bar" + eq_(async_engine.sync_engine._compiled_cache["foo"], "bar") + async_engine.clear_compiled_cache() + assert "foo" not in async_engine.sync_engine._compiled_cache + + def test_execution_options(self, async_engine): + a2 = async_engine.execution_options(foo="bar") + assert isinstance(a2, _async_engine.AsyncEngine) + eq_(a2.sync_engine._execution_options, {"foo": "bar"}) + eq_(async_engine.sync_engine._execution_options, {}) + + """ + + attr uri, pool, dialect, engine, name, driver, echo + methods clear_compiled_cache, update_execution_options, + execution_options, get_execution_options, dispose + + """ + + @async_test + async def test_proxied_attrs_connection(self, async_engine): + conn = await async_engine.connect() + + sync_conn = conn.sync_connection + + is_(conn.engine, async_engine) + is_(conn.closed, sync_conn.closed) + is_(conn.dialect, async_engine.sync_engine.dialect) + eq_(conn.default_isolation_level, sync_conn.default_isolation_level) + + @async_test + async def test_invalidate(self, async_engine): + conn = await async_engine.connect() + + is_(conn.invalidated, False) + + connection_fairy = await conn.get_raw_connection() + is_(connection_fairy.is_valid, True) + dbapi_connection = connection_fairy.connection + + await conn.invalidate() + assert dbapi_connection._connection.is_closed() + + new_fairy = await conn.get_raw_connection() + is_not(new_fairy.connection, dbapi_connection) + is_not(new_fairy, connection_fairy) + is_(new_fairy.is_valid, True) + is_(connection_fairy.is_valid, False) + + @async_test + async def test_get_dbapi_connection_raise(self, async_engine): + + conn = await async_engine.connect() + + with testing.expect_raises_message( + exc.InvalidRequestError, + "AsyncConnection.connection accessor is not " + "implemented as the attribute", + ): + conn.connection + + @async_test + async def test_get_raw_connection(self, async_engine): + + conn = await async_engine.connect() + + pooled = await conn.get_raw_connection() + is_(pooled, conn.sync_connection.connection) + + @async_test + async def test_isolation_level(self, async_engine): + conn = await async_engine.connect() + + sync_isolation_level = await greenlet_spawn( + conn.sync_connection.get_isolation_level + ) + isolation_level = await conn.get_isolation_level() + + eq_(isolation_level, sync_isolation_level) + + await conn.execution_options(isolation_level="SERIALIZABLE") + isolation_level = await conn.get_isolation_level() + + eq_(isolation_level, "SERIALIZABLE") + + @async_test + async def test_dispose(self, async_engine): + c1 = await async_engine.connect() + c2 = await async_engine.connect() + + await c1.close() + await c2.close() + + p1 = async_engine.pool + eq_(async_engine.pool.checkedin(), 2) + + await async_engine.dispose() + eq_(async_engine.pool.checkedin(), 0) + is_not(p1, async_engine.pool) + @async_test async def test_init_once_concurrency(self, async_engine): c1 = async_engine.connect() @@ -169,6 +287,70 @@ class AsyncEngineTest(EngineFixture): ) +class AsyncEventTest(EngineFixture): + """The engine events all run in their normal synchronous context. + + we do not provide an asyncio event interface at this time. + + """ + + __backend__ = True + + @async_test + async def test_no_async_listeners(self, async_engine): + with testing.expect_raises_message( + NotImplementedError, + "asynchronous events are not implemented " + "at this time. Apply synchronous listeners to the " + "AsyncEngine.sync_engine or " + "AsyncConnection.sync_connection attributes.", + ): + event.listen(async_engine, "before_cursor_execute", mock.Mock()) + + conn = await async_engine.connect() + + with testing.expect_raises_message( + NotImplementedError, + "asynchronous events are not implemented " + "at this time. Apply synchronous listeners to the " + "AsyncEngine.sync_engine or " + "AsyncConnection.sync_connection attributes.", + ): + event.listen(conn, "before_cursor_execute", mock.Mock()) + + @async_test + async def test_sync_before_cursor_execute_engine(self, async_engine): + canary = mock.Mock() + + event.listen(async_engine.sync_engine, "before_cursor_execute", canary) + + async with async_engine.connect() as conn: + sync_conn = conn.sync_connection + await conn.execute(text("select 1")) + + eq_( + canary.mock_calls, + [mock.call(sync_conn, mock.ANY, "select 1", (), mock.ANY, False)], + ) + + @async_test + async def test_sync_before_cursor_execute_connection(self, async_engine): + canary = mock.Mock() + + async with async_engine.connect() as conn: + sync_conn = conn.sync_connection + + event.listen( + async_engine.sync_engine, "before_cursor_execute", canary + ) + await conn.execute(text("select 1")) + + eq_( + canary.mock_calls, + [mock.call(sync_conn, mock.ANY, "select 1", (), mock.ANY, False)], + ) + + class AsyncResultTest(EngineFixture): @testing.combinations( (None,), ("scalars",), ("mappings",), argnames="filter_" diff --git a/test/ext/asyncio/test_session_py3k.py b/test/ext/asyncio/test_session_py3k.py index e8caaca3e..a3b8add67 100644 --- a/test/ext/asyncio/test_session_py3k.py +++ b/test/ext/asyncio/test_session_py3k.py @@ -1,3 +1,4 @@ +from sqlalchemy import event from sqlalchemy import exc from sqlalchemy import func from sqlalchemy import select @@ -9,6 +10,7 @@ from sqlalchemy.orm import selectinload from sqlalchemy.testing import async_test from sqlalchemy.testing import eq_ from sqlalchemy.testing import is_ +from sqlalchemy.testing import mock from ...orm import _fixtures @@ -140,6 +142,27 @@ class AsyncSessionTransactionTest(AsyncFixture): eq_(await outer_conn.scalar(select(func.count(User.id))), 1) @async_test + async def test_delete(self, async_session): + User = self.classes.User + + async with async_session.begin(): + u1 = User(name="u1") + + async_session.add(u1) + + await async_session.flush() + + conn = await async_session.connection() + + eq_(await conn.scalar(select(func.count(User.id))), 1) + + async_session.delete(u1) + + await async_session.flush() + + eq_(await conn.scalar(select(func.count(User.id))), 0) + + @async_test async def test_flush(self, async_session): User = self.classes.User @@ -198,3 +221,38 @@ class AsyncSessionTransactionTest(AsyncFixture): is_(new_u_merged, u1) eq_(u1.name, "new u1") + + +class AsyncEventTest(AsyncFixture): + """The engine events all run in their normal synchronous context. + + we do not provide an asyncio event interface at this time. + + """ + + __backend__ = True + + @async_test + async def test_no_async_listeners(self, async_session): + with testing.expect_raises( + NotImplementedError, + "NotImplementedError: asynchronous events are not implemented " + "at this time. Apply synchronous listeners to the " + "AsyncEngine.sync_engine or " + "AsyncConnection.sync_connection attributes.", + ): + event.listen(async_session, "before_flush", mock.Mock()) + + @async_test + async def test_sync_before_commit(self, async_session): + canary = mock.Mock() + + event.listen(async_session.sync_session, "before_commit", canary) + + async with async_session.begin(): + pass + + eq_( + canary.mock_calls, + [mock.call(async_session.sync_session)], + ) diff --git a/test/orm/test_scoping.py b/test/orm/test_scoping.py index 6b7feaea7..d1ed9acc1 100644 --- a/test/orm/test_scoping.py +++ b/test/orm/test_scoping.py @@ -10,6 +10,7 @@ from sqlalchemy.orm import scoped_session from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures +from sqlalchemy.testing import mock from sqlalchemy.testing.mock import Mock from sqlalchemy.testing.schema import Column from sqlalchemy.testing.schema import Table @@ -127,3 +128,26 @@ class ScopedSessionTest(fixtures.MappedTest): mock_scope_func.return_value = 1 s2 = Session(autocommit=True) assert s2.autocommit == True + + def test_methods_etc(self): + mock_session = Mock() + mock_session.bind = "the bind" + + sess = scoped_session(lambda: mock_session) + + sess.add("add") + sess.delete("delete") + + eq_(sess.bind, "the bind") + + eq_( + mock_session.mock_calls, + [mock.call.add("add", True), mock.call.delete("delete")], + ) + + with mock.patch( + "sqlalchemy.orm.session.object_session" + ) as mock_object_session: + sess.object_session("foo") + + eq_(mock_object_session.mock_calls, [mock.call("foo")]) diff --git a/test/orm/test_session.py b/test/orm/test_session.py index 9bc6c6f7c..4562df44a 100644 --- a/test/orm/test_session.py +++ b/test/orm/test_session.py @@ -1,3 +1,5 @@ +import inspect as _py_inspect + import sqlalchemy as sa from sqlalchemy import event from sqlalchemy import ForeignKey @@ -1820,22 +1822,28 @@ class DisposedStates(fixtures.MappedTest): class SessionInterface(fixtures.TestBase): """Bogus args to Session methods produce actionable exceptions.""" - # TODO: expand with message body assertions. - _class_methods = set(("connection", "execute", "get_bind", "scalar")) def _public_session_methods(self): Session = sa.orm.session.Session - blacklist = set(("begin", "query")) - + blacklist = {"begin", "query", "bind_mapper", "get", "bind_table"} + specials = {"__iter__", "__contains__"} ok = set() - for meth in Session.public_methods: - if meth in blacklist: - continue - spec = inspect_getfullargspec(getattr(Session, meth)) - if len(spec[0]) > 1 or spec[1]: - ok.add(meth) + for name in dir(Session): + if ( + name in Session.__dict__ + and (not name.startswith("_") or name in specials) + and ( + _py_inspect.ismethod(getattr(Session, name)) + or _py_inspect.isfunction(getattr(Session, name)) + ) + ): + if name in blacklist: + continue + spec = inspect_getfullargspec(getattr(Session, name)) + if len(spec[0]) > 1 or spec[1]: + ok.add(name) return ok def _map_it(self, cls): @@ -1866,18 +1874,21 @@ class SessionInterface(fixtures.TestBase): def raises_(method, *args, **kw): x_raises_(create_session(), method, *args, **kw) - raises_("__contains__", user_arg) - - raises_("add", user_arg) + for name in [ + "__contains__", + "is_modified", + "merge", + "refresh", + "add", + "delete", + "expire", + "expunge", + "enable_relationship_loading", + ]: + raises_(name, user_arg) raises_("add_all", (user_arg,)) - raises_("delete", user_arg) - - raises_("expire", user_arg) - - raises_("expunge", user_arg) - # flush will no-op without something in the unit of work def _(): class OK(object): @@ -1891,12 +1902,6 @@ class SessionInterface(fixtures.TestBase): _() - raises_("is_modified", user_arg) - - raises_("merge", user_arg) - - raises_("refresh", user_arg) - instance_methods = ( self._public_session_methods() - self._class_methods |
