summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_14/sqlite_autocommit.rst6
-rw-r--r--doc/build/errors.rst28
-rw-r--r--doc/build/orm/extensions/asyncio.rst2
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py10
-rw-r--r--lib/sqlalchemy/dialects/sqlite/pysqlite.py6
-rw-r--r--lib/sqlalchemy/ext/asyncio/base.py5
-rw-r--r--lib/sqlalchemy/ext/asyncio/engine.py23
-rw-r--r--lib/sqlalchemy/testing/requirements.py4
-rw-r--r--test/dialect/test_sqlite.py17
-rw-r--r--test/ext/asyncio/test_engine_py3k.py35
-rw-r--r--test/ext/asyncio/test_session_py3k.py8
11 files changed, 137 insertions, 7 deletions
diff --git a/doc/build/changelog/unreleased_14/sqlite_autocommit.rst b/doc/build/changelog/unreleased_14/sqlite_autocommit.rst
new file mode 100644
index 000000000..183e0eeed
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/sqlite_autocommit.rst
@@ -0,0 +1,6 @@
+.. change::
+ :tags: bug, sqlite
+
+ Fixed bug where the error message for SQLite invalid isolation level on the
+ pysqlite driver would fail to indicate that "AUTOCOMMIT" is one of the
+ valid isolation levels.
diff --git a/doc/build/errors.rst b/doc/build/errors.rst
index 5081928dd..4058b06ea 100644
--- a/doc/build/errors.rst
+++ b/doc/build/errors.rst
@@ -1265,6 +1265,34 @@ attempt, which is unsupported when using SQLAlchemy with AsyncIO dialects.
:ref:`asyncio_orm_avoid_lazyloads` - covers most ORM scenarios where
this problem can occur and how to mitigate.
+.. _error_xd3s:
+
+No Inspection Avaliable
+-----------------------
+
+Using the :func:`_sa.inspect` function directly on an
+:class:`_asyncio.AsyncConnection` or :class:`_asyncio.AsyncEngine` object is
+not currently supported, as there is not yet an awaitable form of the
+:class:`_reflection.Inspector` object available. Instead, the object
+is used by acquiring it using the
+:func:`_sa.inspect` function in such a way that it refers to the underlying
+:attr:`_asyncio.AsyncConnection.sync_connection` attribute of the
+:class:`_asyncio.AsyncConnection` object; the :class:`_engine.Inspector` is
+then used in a "synchronous" calling style by using the
+:meth:`_asyncio.AsyncConnection.run_sync` method along with a custom function
+that performs the desired operations::
+
+ async def async_main():
+ async with engine.connect() as conn:
+ tables = await conn.run_sync(
+ lambda sync_conn: inspect(sync_conn).get_table_names()
+ )
+
+.. seealso::
+
+ :ref:`asyncio_inspector` - additional examples of using :func:`_sa.inspect`
+ with the asyncio extension.
+
Core Exception Classes
======================
diff --git a/doc/build/orm/extensions/asyncio.rst b/doc/build/orm/extensions/asyncio.rst
index 940c19a7e..f3e89c647 100644
--- a/doc/build/orm/extensions/asyncio.rst
+++ b/doc/build/orm/extensions/asyncio.rst
@@ -498,6 +498,8 @@ the usual ``await`` keywords are necessary, including for the
.. currentmodule:: sqlalchemy.ext.asyncio
+.. _asyncio_inspector:
+
Using the Inspector to inspect schema objects
---------------------------------------------------
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index dc5ebc3f0..c4a6bf8e9 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -1915,7 +1915,9 @@ class SQLiteDialect(default.DefaultDialect):
14,
)
- _isolation_lookup = {"READ UNCOMMITTED": 1, "SERIALIZABLE": 0}
+ _isolation_lookup = util.immutabledict(
+ {"READ UNCOMMITTED": 1, "SERIALIZABLE": 0}
+ )
def set_isolation_level(self, connection, level):
try:
@@ -1925,7 +1927,11 @@ class SQLiteDialect(default.DefaultDialect):
exc.ArgumentError(
"Invalid value '%s' for isolation_level. "
"Valid isolation levels for %s are %s"
- % (level, self.name, ", ".join(self._isolation_lookup))
+ % (
+ level,
+ self.name,
+ ", ".join(self._isolation_lookup),
+ )
),
replace_context=err,
)
diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlite.py b/lib/sqlalchemy/dialects/sqlite/pysqlite.py
index 96a5351da..0f96e8830 100644
--- a/lib/sqlalchemy/dialects/sqlite/pysqlite.py
+++ b/lib/sqlalchemy/dialects/sqlite/pysqlite.py
@@ -492,6 +492,12 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
def _get_server_version_info(self, connection):
return self.dbapi.sqlite_version_info
+ _isolation_lookup = SQLiteDialect._isolation_lookup.union(
+ {
+ "AUTOCOMMIT": None,
+ }
+ )
+
def set_isolation_level(self, connection, level):
if hasattr(connection, "connection"):
dbapi_connection = connection.connection
diff --git a/lib/sqlalchemy/ext/asyncio/base.py b/lib/sqlalchemy/ext/asyncio/base.py
index 3f2c084f4..3f77f5500 100644
--- a/lib/sqlalchemy/ext/asyncio/base.py
+++ b/lib/sqlalchemy/ext/asyncio/base.py
@@ -8,6 +8,7 @@ from . import exc as async_exc
class ReversibleProxy:
# weakref.ref(async proxy object) -> weakref.ref(sync proxied object)
_proxy_objects = {}
+ __slots__ = ("__weakref__",)
def _assign_proxied(self, target):
if target is not None:
@@ -46,6 +47,8 @@ class ReversibleProxy:
class StartableContext(abc.ABC):
+ __slots__ = ()
+
@abc.abstractmethod
async def start(self, is_ctxmanager=False):
pass
@@ -68,6 +71,8 @@ class StartableContext(abc.ABC):
class ProxyComparable(ReversibleProxy):
+ __slots__ = ()
+
def __hash__(self):
return id(self)
diff --git a/lib/sqlalchemy/ext/asyncio/engine.py b/lib/sqlalchemy/ext/asyncio/engine.py
index f5c3bdca4..5a692ffb1 100644
--- a/lib/sqlalchemy/ext/asyncio/engine.py
+++ b/lib/sqlalchemy/ext/asyncio/engine.py
@@ -9,6 +9,7 @@ from .base import ProxyComparable
from .base import StartableContext
from .result import AsyncResult
from ... import exc
+from ... import inspection
from ... import util
from ...engine import create_engine as _create_engine
from ...engine.base import NestedTransaction
@@ -80,6 +81,7 @@ class AsyncConnection(ProxyComparable, StartableContext, AsyncConnectable):
# create a new AsyncConnection that matches this one given only the
# "sync" elements.
__slots__ = (
+ "engine",
"sync_engine",
"sync_connection",
)
@@ -709,3 +711,24 @@ def _get_sync_engine_or_connection(async_engine):
raise exc.ArgumentError(
"AsyncEngine expected, got %r" % async_engine
) from e
+
+
+@inspection._inspects(AsyncConnection)
+def _no_insp_for_async_conn_yet(subject):
+ raise exc.NoInspectionAvailable(
+ "Inspection on an AsyncConnection is currently not supported. "
+ "Please use ``run_sync`` to pass a callable where it's possible "
+ "to call ``inspect`` on the passed connection.",
+ code="xd3s",
+ )
+
+
+@inspection._inspects(AsyncEngine)
+def _no_insp_for_async_engine_xyet(subject):
+ raise exc.NoInspectionAvailable(
+ "Inspection on an AsyncEngine is currently not supported. "
+ "Please obtain a connection then use ``conn.run_sync`` to pass a "
+ "callable where it's possible to call ``inspect`` on the "
+ "passed connection.",
+ code="xd3s",
+ )
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 742a9a1f8..e6e5db774 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -1227,6 +1227,10 @@ class SuiteRequirements(Requirements):
return self.python36
@property
+ def insert_order_dicts(self):
+ return self.python37
+
+ @property
def python36(self):
return exclusions.skip_if(
lambda: sys.version_info < (3, 6),
diff --git a/test/dialect/test_sqlite.py b/test/dialect/test_sqlite.py
index b5ce291eb..ed0f11907 100644
--- a/test/dialect/test_sqlite.py
+++ b/test/dialect/test_sqlite.py
@@ -54,6 +54,7 @@ from sqlalchemy.testing import expect_warnings
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import is_
from sqlalchemy.testing import mock
+from sqlalchemy.testing.assertions import expect_raises_message
from sqlalchemy.types import Boolean
from sqlalchemy.types import Date
from sqlalchemy.types import DateTime
@@ -596,6 +597,22 @@ class DialectTest(
)
)
+ @testing.requires.insert_order_dicts
+ @testing.only_on("sqlite+pysqlite")
+ def test_isolation_level_message(self):
+ # needs to test that all three words are present and we also
+ # dont want to default all isolation level messages to use
+ # sorted(), so rely on python 3.7 for ordering of keywords
+ # in the message
+ with expect_raises_message(
+ exc.ArgumentError,
+ "Invalid value 'invalid' for "
+ "isolation_level. Valid isolation levels for "
+ "sqlite are READ UNCOMMITTED, SERIALIZABLE, AUTOCOMMIT",
+ ):
+ with testing.db.connect() as conn:
+ conn.execution_options(isolation_level="invalid")
+
@testing.only_on("sqlite+pysqlcipher")
def test_pysqlcipher_connects(self):
"""test #6586"""
diff --git a/test/ext/asyncio/test_engine_py3k.py b/test/ext/asyncio/test_engine_py3k.py
index fec8bc6da..c75dd8665 100644
--- a/test/ext/asyncio/test_engine_py3k.py
+++ b/test/ext/asyncio/test_engine_py3k.py
@@ -6,6 +6,7 @@ from sqlalchemy import delete
from sqlalchemy import event
from sqlalchemy import exc
from sqlalchemy import func
+from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy import select
from sqlalchemy import String
@@ -653,6 +654,39 @@ class AsyncEventTest(EngineFixture):
[mock.call(sync_conn, mock.ANY, "select 1", (), mock.ANY, False)],
)
+ @async_test
+ async def test_event_on_sync_connection(self, async_engine):
+ canary = mock.Mock()
+
+ async with async_engine.connect() as conn:
+ event.listen(conn.sync_connection, "begin", canary)
+ async with conn.begin():
+ eq_(
+ canary.mock_calls,
+ [mock.call(conn.sync_connection)],
+ )
+
+
+class AsyncInspection(EngineFixture):
+ __backend__ = True
+
+ @async_test
+ async def test_inspect_engine(self, async_engine):
+ with testing.expect_raises_message(
+ exc.NoInspectionAvailable,
+ "Inspection on an AsyncEngine is currently not supported.",
+ ):
+ inspect(async_engine)
+
+ @async_test
+ async def test_inspect_connection(self, async_engine):
+ async with async_engine.connect() as conn:
+ with testing.expect_raises_message(
+ exc.NoInspectionAvailable,
+ "Inspection on an AsyncConnection is currently not supported.",
+ ):
+ inspect(conn)
+
class AsyncResultTest(EngineFixture):
@testing.combinations(
@@ -945,6 +979,7 @@ class AsyncProxyTest(EngineFixture, fixtures.TestBase):
is_not(async_connection.engine, None)
@testing.requires.predictable_gc
+ @async_test
async def test_gc_engine(self, testing_engine):
ReversibleProxy._proxy_objects.clear()
diff --git a/test/ext/asyncio/test_session_py3k.py b/test/ext/asyncio/test_session_py3k.py
index 48faa1ca1..459d95ea6 100644
--- a/test/ext/asyncio/test_session_py3k.py
+++ b/test/ext/asyncio/test_session_py3k.py
@@ -582,12 +582,10 @@ class AsyncEventTest(AsyncFixture):
@async_test
async def test_no_async_listeners(self, async_session):
- with testing.expect_raises(
+ with testing.expect_raises_message(
NotImplementedError,
- "NotImplementedError: asynchronous events are not implemented "
- "at this time. Apply synchronous listeners to the "
- "AsyncEngine.sync_engine or "
- "AsyncConnection.sync_connection attributes.",
+ "asynchronous events are not implemented at this time. "
+ "Apply synchronous listeners to the AsyncSession.sync_session.",
):
event.listen(async_session, "before_flush", mock.Mock())