From: Itachi-0xAI <285282994+Itachi-0xAI@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:06:20 +0000 (-0400) Subject: Deprecate SQLite pool selection based on mode=memory X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=abd177230f2c6a8166e07c71010e8e27004b9740;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Deprecate SQLite pool selection based on mode=memory Deprecated the selection of a single-connection pool class, i.e. SingletonThreadPool for pysqlite or StaticPool for aiosqlite, based on the presence of the ``mode=memory`` query string argument in a SQLite URL. Pool selection for SQLite is intended to be based on the database name alone, where only ``:memory:`` or an empty database name indicate a memory database; interpreting the query string additionally requires that assumptions be made regarding whether or not the resulting database can be shared among multiple connections. In a future release, such URLs will make use of QueuePool or AsyncAdaptedQueuePool as would any other URL. This notably includes the shared cache form ``sqlite:///file:mydb?mode=memory&cache=shared&uri=true``, for which a queue pool is in fact the appropriate class, as a shared cache database supports multiple concurrent connections, whereas a single-connection pool causes such connections to share one transaction state. Applications that rely upon the present behavior should indicate the intended pool using the create_engine.poolclass parameter. Added a warning for query string arguments that are passed to a SQLite URL without the ``uri=true`` argument also being present, and which are not accepted by the ``sqlite3`` driver itself. SQLite URI arguments such as ``mode`` or ``cache`` take effect only when URI mode is in use; without it they were previously discarded silently, so that a URL such as ``sqlite:///file:mydb?mode=memory`` would connect to a file on disk named ``file:mydb``. Corrected the SQLite documentation regarding shared cache memory databases, which incorrectly indicated that the named form ``sqlite:///file:mydb?mode=memory&cache=shared&uri=true`` makes use of QueuePool; a single-connection pool is used for this form. Documentation has also been added noting that a shared cache database exists only for as long as at least one connection to it remains open, so that ordinary pool operations such as Engine.dispose() or use of create_engine.pool_recycle will discard its contents. Fixes: #13433 Closes: #13465 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13465 Pull-request-sha: fc50a9837fd50334450ee6c9dd645db1b27b4dad Change-Id: Iac1d334f92fd2ed49f6dafd324f24354d517a041 --- diff --git a/doc/build/changelog/unreleased_21/13433.rst b/doc/build/changelog/unreleased_21/13433.rst new file mode 100644 index 0000000000..429a3ca063 --- /dev/null +++ b/doc/build/changelog/unreleased_21/13433.rst @@ -0,0 +1,49 @@ +.. change:: + :tags: deprecated, sqlite + :tickets: 13433 + + Deprecated the selection of a single-connection pool class, i.e. + :class:`.SingletonThreadPool` for pysqlite or :class:`.StaticPool` for + aiosqlite, based on the presence of the ``mode=memory`` query string + argument in a SQLite URL. Pool selection for SQLite is intended to be + based on the database name alone, where only ``:memory:`` or an empty + database name indicate a memory database; interpreting the query string + additionally requires that assumptions be made regarding whether or not + the resulting database can be shared among multiple connections. In a + future release, such URLs will make use of :class:`.QueuePool` or + :class:`.AsyncAdaptedQueuePool` as would any other URL. This notably + includes the shared cache form + ``sqlite:///file:mydb?mode=memory&cache=shared&uri=true``, for which a + queue pool is in fact the appropriate class, as a shared cache database + supports multiple concurrent connections, whereas a single-connection + pool causes such connections to share one transaction state. + Applications that rely upon the present behavior should indicate the + intended pool using the :paramref:`_sa.create_engine.poolclass` + parameter. Pull request courtesy Itachi-0xAI. + +.. change:: + :tags: bug, sqlite + :tickets: 13433 + + Added a warning for query string arguments that are passed to a SQLite + URL without the ``uri=true`` argument also being present, and which are + not accepted by the ``sqlite3`` driver itself. SQLite URI arguments + such as ``mode`` or ``cache`` take effect only when URI mode is in use; + without it they were previously discarded silently, so that a URL such + as ``sqlite:///file:mydb?mode=memory`` would connect to a file on disk + named ``file:mydb``. Arguments intended for the driver itself may be + passed using the :paramref:`_sa.create_engine.connect_args` parameter. + +.. change:: + :tags: bug, documentation, sqlite + :tickets: 13433 + + Corrected the SQLite documentation regarding shared cache memory + databases, which incorrectly indicated that the named form + ``sqlite:///file:mydb?mode=memory&cache=shared&uri=true`` makes use of + :class:`.QueuePool`; a single-connection pool is used for this form. + Documentation has also been added noting that a shared cache database + exists only for as long as at least one connection to it remains open, + so that ordinary pool operations such as + :meth:`_engine.Engine.dispose` or use of + :paramref:`_sa.create_engine.pool_recycle` will discard its contents. diff --git a/doc/build/errors.rst b/doc/build/errors.rst index 8dce4555b7..239f4dd7de 100644 --- a/doc/build/errors.rst +++ b/doc/build/errors.rst @@ -246,6 +246,95 @@ in the 2.x series of SQLAlchemy when any SQL statements are emitted. When a con that was in progress is now in an invalid state, and must be explicitly rolled back in order to remove it from the :class:`_engine.Connection`. +.. _error_sqmp: + +Selection of the pool class based on the 'mode=memory' query string argument is deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The SQLite dialects select a connection pool class automatically, based on +whether the URL refers to a file database or to a memory database. A memory +database is indicated by the database name ``:memory:``, or by an empty +database name, and receives a single-connection pool class, being +:class:`.SingletonThreadPool` for pysqlite or :class:`.StaticPool` for +aiosqlite. All other database names receive :class:`.QueuePool` or, for +aiosqlite, :class:`.AsyncAdaptedQueuePool`. + +Historically, a URL that includes ``mode=memory`` within the query string +also received a single-connection pool class. This behavior is deprecated +as of SQLAlchemy 2.1 and will be removed in a future release, as it requires +that the dialect make assumptions regarding whether or not the resulting +database can be shared among multiple connections, based on query string +arguments whose meaning is determined by SQLite rather than by SQLAlchemy. + +The behavior is notably incorrect for the shared cache form:: + + # a shared cache database supports multiple concurrent connections, + # however a single-connection pool is presently selected + engine = create_engine("sqlite:///file:mydb?mode=memory&cache=shared&uri=true") + +For this URL, :class:`.QueuePool` is the appropriate pool class, as each +connection to a shared cache database has its own transaction state. A +single-connection pool instead causes all :class:`.Session` or +:class:`_engine.Connection` objects to share one transaction state, so that a +``ROLLBACK`` emitted by one will discard uncommitted work belonging to +another. + +To resolve the warning, indicate the intended pool class explicitly, which +also disables automatic selection entirely:: + + from sqlalchemy.pool import QueuePool + + engine = create_engine( + "sqlite:///file:mydb?mode=memory&cache=shared&uri=true", + poolclass=QueuePool, + ) + +For a single-connection memory database, the plain ``:memory:`` form may be +used instead, which continues to select a single-connection pool:: + + engine = create_engine("sqlite://") + +.. seealso:: + + :ref:`pysqlite_threading_pooling` + + :ref:`pysqlite_uri_shared_cache` + + :ref:`pysqlite_shared_cache_lifespan` + +.. _error_squa: + +Query string argument(s) are not accepted by the pysqlite driver and are being ignored +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SQLite accepts a range of options within the database name itself, using a +`URI filename `_ such as +``file:mydb?mode=ro``. These options are interpreted by SQLite only when URI +filenames are enabled, which for the ``sqlite3`` driver requires that +``uri=true`` be passed to its ``connect()`` function. + +Within a SQLAlchemy URL, query string arguments are delivered to the +``sqlite3`` driver, with those not recognized by the driver being appended to +the SQLite URI filename. This latter step takes place only when ``uri=true`` +is also present; otherwise the database name is treated as an ordinary +filename and the remaining arguments have no effect at all:: + + # 'mode' has no effect; connects to a file named "file:mydb" + engine = create_engine("sqlite:///file:mydb?mode=memory") + + # 'mode' is passed to SQLite as part of the URI filename + engine = create_engine("sqlite:///file:mydb?mode=memory&uri=true") + +The warning indicates arguments that fall into the first case above. To +resolve it, add ``uri=true`` to the URL if the arguments are intended for +SQLite, or remove them if they are not. Arguments intended for the +``sqlite3`` driver itself may alternatively be passed using the +:paramref:`_sa.create_engine.connect_args` parameter. + +.. seealso:: + + :ref:`pysqlite_uri_connections` + .. _error_dbapi: DBAPI Errors diff --git a/lib/sqlalchemy/dialects/sqlite/aiosqlite.py b/lib/sqlalchemy/dialects/sqlite/aiosqlite.py index 2a5f712173..9f4c234999 100644 --- a/lib/sqlalchemy/dialects/sqlite/aiosqlite.py +++ b/lib/sqlalchemy/dialects/sqlite/aiosqlite.py @@ -76,6 +76,11 @@ based on the kind of SQLite database that's requested: may be used by specifying it via the :paramref:`_sa.create_engine.poolclass` parameter. +As with the pysqlite dialect, this selection is made based on the database +name alone, and the ``mode=memory`` query string argument is deprecated as +a means of influencing it; see :ref:`pysqlite_threading_pooling` for +background. + .. _aiosqlite_memory: Using a Memory Database with Multiple Coroutines @@ -101,6 +106,12 @@ Because this URL form is treated as a file-based database by the dialect, :class:`.AsyncAdaptedQueuePool` is used automatically and no additional configuration is needed. +Note that a shared-cache database is discarded once its last connection is +closed, so that operations such as :meth:`_asyncio.AsyncEngine.dispose` or +the use of :paramref:`_sa.create_engine.pool_recycle` will destroy its +contents; see :ref:`pysqlite_shared_cache_lifespan` for background and for +how to hold such a database open. + See the pysqlite documentation at :ref:`pysqlite_uri_shared_cache` for full details on shared-cache memory databases, including how to use named databases to maintain multiple @@ -330,6 +341,9 @@ class SQLiteDialect_aiosqlite(SQLiteDialect_pysqlite): if cls._is_url_file_db(url): return pool.AsyncAdaptedQueuePool else: + cls._warn_memory_mode_pool_selection( + url, pool.StaticPool, pool.AsyncAdaptedQueuePool + ) return pool.StaticPool def is_disconnect( diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlite.py b/lib/sqlalchemy/dialects/sqlite/pysqlite.py index 54fe3b1701..a2c1b7f7b7 100644 --- a/lib/sqlalchemy/dialects/sqlite/pysqlite.py +++ b/lib/sqlalchemy/dialects/sqlite/pysqlite.py @@ -242,6 +242,23 @@ based on the kind of SQLite database that's requested: may be used by specifying it via the :paramref:`_sa.create_engine.poolclass` parameter. +This selection is made based on the database name alone. Where a +particular pool class is desired, it should be stated explicitly using the +:paramref:`_sa.create_engine.poolclass` parameter, in which case no +selection takes place at all. + +.. deprecated:: 2.1 + + A URL that passes ``mode=memory`` in the query string is currently also + given a single-connection pool class. This behavior is deprecated and + will be removed in a future release, at which point such URLs will + receive :class:`.QueuePool` like any other. This affects URLs such as + ``sqlite:///file:mydb?mode=memory&cache=shared&uri=true``, for which + :class:`.QueuePool` is in fact the appropriate class, as a shared cache + database supports multiple concurrent connections; see + :ref:`pysqlite_uri_shared_cache`. Applications relying on the present + behavior should state the pool class explicitly. + Disabling Connection Pooling for File Databases ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -310,23 +327,76 @@ Because this URL form is treated as a file-based database by the dialect, :class:`.QueuePool` is used automatically and ``check_same_thread`` defaults to ``False``, so no additional pool or connect_args configuration is needed. Each checkout from the -pool is a distinct DBAPI connection with its own transaction state, -and the in-memory database persists as long as at least one -connection remains open. +pool is a distinct DBAPI connection with its own transaction state. The shared-cache database is scoped by the filename component of the URI. ``file::memory:`` (empty name) is process-global — all engines in the process that use this URI share the same database. To maintain multiple independent in-memory databases within the -same process, supply a distinct name for each:: +same process, supply a distinct name for each. A named database of +this kind requires ``mode=memory``, which presently causes a +single-connection pool to be selected; :class:`.QueuePool` should +therefore be requested explicitly:: + + from sqlalchemy.pool import QueuePool engine_a = create_engine( - "sqlite:///file:db_a?mode=memory&cache=shared&uri=true" + "sqlite:///file:db_a?mode=memory&cache=shared&uri=true", + poolclass=QueuePool, ) engine_b = create_engine( - "sqlite:///file:db_b?mode=memory&cache=shared&uri=true" + "sqlite:///file:db_b?mode=memory&cache=shared&uri=true", + poolclass=QueuePool, ) +For :func:`_asyncio.create_async_engine`, use +:class:`.AsyncAdaptedQueuePool` in the same way. + +.. deprecated:: 2.1 + + Selection of a single-connection pool class based on ``mode=memory`` + is deprecated; a future release will use :class:`.QueuePool` for these + URLs, at which point stating + :paramref:`_sa.create_engine.poolclass` explicitly will no longer be + necessary. See :ref:`pysqlite_threading_pooling`. + +.. _pysqlite_shared_cache_lifespan: + +Lifespan of a Shared-Cache Memory Database +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A shared-cache in-memory database exists only for as long as at least one +connection to it remains open; when the last connection is closed, the +database and all of its contents are discarded. A subsequent connection +using the same URI then opens a new, empty database, which typically +surfaces as ``no such table`` errors. + +:class:`.QueuePool` retains connections that have been returned to it, so +in a default configuration the database will normally persist once the +first connection has been established. This is a consequence of pool +behavior rather than a guarantee, however, and the database will be +discarded by ordinary pool operations including: + +* :meth:`_engine.Engine.dispose`, which closes all connections currently + in the pool +* :paramref:`_sa.create_engine.pool_recycle`, as a recycled connection is + closed before its replacement is opened +* connection invalidation, including that performed by + :paramref:`_sa.create_engine.pool_pre_ping` +* use of :class:`.NullPool`, which closes each connection as it is + returned + +Where the database must survive independently of pool activity, hold a +single connection open for as long as the database is needed:: + + engine = create_engine("sqlite:///file::memory:?cache=shared&uri=true") + + # keep the database alive for the lifetime of the engine + keepalive = engine.connect() + +The same consideration applies to the :ref:`aiosqlite ` +dialect, using :meth:`_asyncio.AsyncEngine.connect`. + Using StaticPool for Single-Connection Memory Databases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -573,11 +643,40 @@ class SQLiteDialect_pysqlite(SQLiteDialect): else: return False + @classmethod + def _warn_memory_mode_pool_selection( + cls, + url: URL, + current_pool: type[pool.Pool], + future_pool: type[pool.Pool], + ) -> None: + """Warn when the ``mode=memory`` query string argument is what + caused a single-connection pool class to be selected. + + See :ticket:`13433`. + + """ + if url.query.get("mode", None) != "memory": + return + + util.warn_deprecated( + "Selection of the %s pool class based on the 'mode=memory' " + "query string argument is deprecated; a future release will " + "use %s for this URL. Indicate the intended pool class " + "using the create_engine.poolclass parameter." + % (current_pool.__name__, future_pool.__name__), + "2.1", + code="sqmp", + ) + @classmethod def get_pool_class(cls, url: URL) -> type[pool.Pool]: if cls._is_url_file_db(url): return pool.QueuePool else: + cls._warn_memory_mode_pool_selection( + url, pool.SingletonThreadPool, pool.QueuePool + ) return pool.SingletonThreadPool def _get_server_version_info( @@ -701,6 +800,22 @@ class SQLiteDialect_pysqlite(SQLiteDialect): ) ) else: + # without uri=True, the SQLite URI query string is not in + # play at all, so anything left over here is silently + # discarded; warn rather than have it appear to take effect + ignored = sorted( + set(opts).difference(key for key, _ in pysqlite_args) + ) + if ignored: + util.warn( + "Query string argument(s) %s are not accepted by the " + "pysqlite driver and are being ignored; SQLite URI " + "arguments require that 'uri=true' also be present " + "in the URL." + % (", ".join("'%s'" % key for key in ignored),), + code="squa", + ) + filename = url.database or ":memory:" if filename != ":memory:": filename = os.path.abspath(filename) diff --git a/lib/sqlalchemy/exc.py b/lib/sqlalchemy/exc.py index c91930e739..50e4764b37 100644 --- a/lib/sqlalchemy/exc.py +++ b/lib/sqlalchemy/exc.py @@ -888,6 +888,8 @@ class SADeprecationWarning(HasDescriptionCode, DeprecationWarning): deprecated_since: Optional[str] = None "Indicates the version that started raising this deprecation warning" + _what_are_we = "warning" + class Base20DeprecationWarning(SADeprecationWarning): """Issued for usage of APIs specifically deprecated or legacy in diff --git a/test/dialect/sqlite/test_dialect.py b/test/dialect/sqlite/test_dialect.py index 5deb799ea7..4404c699cc 100644 --- a/test/dialect/sqlite/test_dialect.py +++ b/test/dialect/sqlite/test_dialect.py @@ -22,6 +22,7 @@ from sqlalchemy import testing from sqlalchemy import text from sqlalchemy import types as sqltypes from sqlalchemy import UniqueConstraint +from sqlalchemy.dialects.sqlite import aiosqlite as aiosqlite_dialect from sqlalchemy.dialects.sqlite import base as sqlite from sqlalchemy.dialects.sqlite import pysqlite as pysqlite_dialect from sqlalchemy.engine.url import make_url @@ -33,6 +34,7 @@ from sqlalchemy.testing import AssertsExecutionResults from sqlalchemy.testing import combinations from sqlalchemy.testing import engines from sqlalchemy.testing import eq_ +from sqlalchemy.testing import expect_deprecated from sqlalchemy.testing import expect_raises from sqlalchemy.testing import expect_warnings from sqlalchemy.testing import fixtures @@ -331,21 +333,108 @@ class DialectTest( assert "méil" in result.keys() assert "\u6e2c\u8a66" in result.keys() - def test_pool_class(self): - e = create_engine("sqlite+pysqlite://") - assert e.pool.__class__ is pool.SingletonThreadPool + @combinations( + ("sqlite+pysqlite://", pool.SingletonThreadPool), + ("sqlite+pysqlite:///:memory:", pool.SingletonThreadPool), + # changed as of 2.0 #7490 + ("sqlite+pysqlite:///foo.db", pool.QueuePool), + ("sqlite+pysqlite:///file:foo.db?uri=true", pool.QueuePool), + ("sqlite+pysqlite:///file:foo.db?mode=rwc&uri=true", pool.QueuePool), + ( + "sqlite+pysqlite:///file::memory:?cache=shared&uri=true", + pool.QueuePool, + ), + argnames="url, expected", + ) + def test_pool_class(self, url, expected): + e = create_engine(url) + assert e.pool.__class__ is expected - e = create_engine("sqlite+pysqlite:///:memory:") - assert e.pool.__class__ is pool.SingletonThreadPool + @combinations( + ( + "sqlite+pysqlite:///file:foo.db?mode=memory&uri=true", + pysqlite_dialect.dialect, + "SingletonThreadPool", + "QueuePool", + ), + ( + "sqlite+pysqlite:///file:foo.db?" + "mode=memory&cache=shared&uri=true", + pysqlite_dialect.dialect, + "SingletonThreadPool", + "QueuePool", + ), + ( + "sqlite+aiosqlite:///file:foo.db?" + "mode=memory&cache=shared&uri=true", + aiosqlite_dialect.dialect, + "StaticPool", + "AsyncAdaptedQueuePool", + ), + argnames="url, dialect_cls, current_pool, future_pool", + ) + def test_memory_mode_pool_deprecated( + self, url, dialect_cls, current_pool, future_pool + ): + """test #13433 + + the ``mode=memory`` query string argument selecting a + single-connection pool class is deprecated, including for a + shared cache database, where the queue pool is in fact the + appropriate class. + + """ + + with expect_deprecated( + "Selection of the %s pool class based on the 'mode=memory' " + "query string argument is deprecated; a future release will " + "use %s for this URL." % (current_pool, future_pool) + ): + pool_cls = dialect_cls.get_pool_class(make_url(url)) + + eq_(pool_cls.__name__, current_pool) + + def test_memory_mode_pool_deprecated_no_uri(self): + """test #13433 + + without ``uri=true``, the ``mode`` argument is not passed to the + driver at all, so that both warnings are emitted. + + """ + + with ( + expect_warnings( + "Query string argument\\(s\\) 'mode' are not accepted by " + "the pysqlite driver and are being ignored" + ), + expect_deprecated( + "Selection of the SingletonThreadPool pool class based on " + "the 'mode=memory' query string argument is deprecated" + ), + ): + e = create_engine("sqlite+pysqlite:///file:foo.db?mode=memory") - e = create_engine( - "sqlite+pysqlite:///file:foo.db?mode=memory&uri=true" - ) assert e.pool.__class__ is pool.SingletonThreadPool - e = create_engine("sqlite+pysqlite:///foo.db") - # changed as of 2.0 #7490 - assert e.pool.__class__ is pool.QueuePool + @combinations( + ("sqlite:///foo.db?charset=utf8", "'charset'"), + ("sqlite:///foo.db?cache=shared&nolock=1", "'cache', 'nolock'"), + argnames="url, expected", + ) + def test_connect_args_ignored(self, url, expected): + """test #13433 + + query string arguments that are not accepted by the driver are + silently discarded when ``uri=true`` is not present; warn instead. + + """ + + d = pysqlite_dialect.dialect() + with expect_warnings( + "Query string argument\\(s\\) %s are not accepted by the " + "pysqlite driver and are being ignored" % expected + ): + d.create_connect_args(make_url(url)) @combinations( ( diff --git a/test/engine/test_parseconnect.py b/test/engine/test_parseconnect.py index 254ac1ff12..4239d301e7 100644 --- a/test/engine/test_parseconnect.py +++ b/test/engine/test_parseconnect.py @@ -1201,10 +1201,7 @@ class TestRegNewDBAPI(fixtures.TestBase): ], ) - @testing.requires.sqlite def test_plugin_url_registration(self): - from sqlalchemy.dialects import sqlite - global MyEnginePlugin def side_effect(url, kw): @@ -1226,38 +1223,35 @@ class TestRegNewDBAPI(fixtures.TestBase): MyEnginePlugin = Mock(side_effect=side_effect, update_url=update_url) plugins.register("engineplugin", __name__, "MyEnginePlugin") + registry.register("mockdialect", __name__, "MockDialect") e = create_engine( - "sqlite:///?plugin=engineplugin&foo=bar&myplugin_arg=bat", + "mockdialect://?plugin=engineplugin&foo=bar&myplugin_arg=bat", logging_name="foob", ) - eq_(e.dialect.name, "sqlite") eq_(e.logging_name, "bar") # plugin args are removed from URL. eq_(e.url.query, {"foo": "bar"}) - assert isinstance(e.dialect, sqlite.dialect) + assert isinstance(e.dialect, MockDialect) eq_( MyEnginePlugin.mock_calls, [ call( url.make_url( - "sqlite:///?plugin=engineplugin" + "mockdialect://?plugin=engineplugin" "&foo=bar&myplugin_arg=bat" ), {}, ), - call.handle_dialect_kwargs(sqlite.dialect, mock.ANY), + call.handle_dialect_kwargs(MockDialect, mock.ANY), call.handle_pool_kwargs(mock.ANY, {"dialect": e.dialect}), call.engine_created(e), ], ) - @testing.requires.sqlite def test_plugin_multiple_url_registration(self): - from sqlalchemy.dialects import sqlite - global MyEnginePlugin1 global MyEnginePlugin2 @@ -1283,27 +1277,27 @@ class TestRegNewDBAPI(fixtures.TestBase): plugins.register("engineplugin1", __name__, "MyEnginePlugin1") plugins.register("engineplugin2", __name__, "MyEnginePlugin2") + registry.register("mockdialect", __name__, "MockDialect") url_str = ( - "sqlite:///?plugin=engineplugin1&foo=bar&myplugin1_arg=bat" + "mockdialect://?plugin=engineplugin1&foo=bar&myplugin1_arg=bat" "&plugin=engineplugin2&myplugin2_arg=hoho" ) e = create_engine( url_str, logging_name="foob", ) - eq_(e.dialect.name, "sqlite") eq_(e.logging_name, "bar") # plugin args are removed from URL. eq_(e.url.query, {"foo": "bar"}) - assert isinstance(e.dialect, sqlite.dialect) + assert isinstance(e.dialect, MockDialect) eq_( MyEnginePlugin1.mock_calls, [ call(url.make_url(url_str), {}), - call.handle_dialect_kwargs(sqlite.dialect, mock.ANY), + call.handle_dialect_kwargs(MockDialect, mock.ANY), call.handle_pool_kwargs(mock.ANY, {"dialect": e.dialect}), call.engine_created(e), ], @@ -1313,16 +1307,13 @@ class TestRegNewDBAPI(fixtures.TestBase): MyEnginePlugin2.mock_calls, [ call(url.make_url(url_str), {}), - call.handle_dialect_kwargs(sqlite.dialect, mock.ANY), + call.handle_dialect_kwargs(MockDialect, mock.ANY), call.handle_pool_kwargs(mock.ANY, {"dialect": e.dialect}), call.engine_created(e), ], ) - @testing.requires.sqlite def test_plugin_arg_registration(self): - from sqlalchemy.dialects import sqlite - global MyEnginePlugin def side_effect(url, kw): @@ -1344,23 +1335,23 @@ class TestRegNewDBAPI(fixtures.TestBase): MyEnginePlugin = Mock(side_effect=side_effect, update_url=update_url) plugins.register("engineplugin", __name__, "MyEnginePlugin") + registry.register("mockdialect", __name__, "MockDialect") e = create_engine( - "sqlite:///?foo=bar", + "mockdialect://?foo=bar", logging_name="foob", plugins=["engineplugin"], myplugin_arg="bat", ) - eq_(e.dialect.name, "sqlite") eq_(e.logging_name, "bar") - assert isinstance(e.dialect, sqlite.dialect) + assert isinstance(e.dialect, MockDialect) eq_( MyEnginePlugin.mock_calls, [ - call(url.make_url("sqlite:///?foo=bar"), {}), - call.handle_dialect_kwargs(sqlite.dialect, mock.ANY), + call(url.make_url("mockdialect://?foo=bar"), {}), + call.handle_dialect_kwargs(MockDialect, mock.ANY), call.handle_pool_kwargs(mock.ANY, {"dialect": e.dialect}), call.engine_created(e), ],