--- /dev/null
+.. change::
+ :tags: change, performance, engine, py3k
+ :tickets: 5315
+
+ Disabled the "unicode returns" check that runs on dialect startup when
+ running under Python 3, which for many years has occurred in order to test
+ the current DBAPI's behavior for whether or not it returns Python Unicode
+ or Py2K strings for the VARCHAR and NVARCHAR datatypes. The check still
+ occurs by default under Python 2, however the mechanism to test the
+ behavior will be removed in SQLAlchemy 2.0 when Python 2 support is also
+ removed.
+
+ This logic was very effective when it was needed, however now that Python 3
+ is standard, all DBAPIs are expected to return Python 3 strings for
+ character datatypes. In the unlikely case that a third party DBAPI does
+ not support this, the conversion logic within :class:`.String` is still
+ available and the third party dialect may specify this in its upfront
+ dialect flags by setting the dialect level flag ``returns_unicode_strings``
+ to one of :attr:`.String.RETURNS_CONDITIONAL` or
+ :attr:`.String.RETURNS_BYTES`, both of which will enable Unicode conversion
+ even under Python 3.
.. sourcecode:: pycon+sql
{sql}>>> metadata.create_all(engine)
- SE...
+ PRAGMA...
CREATE TABLE users (
id INTEGER NOT NULL,
name VARCHAR,
.. sourcecode:: python+sql
>>> Base.metadata.create_all(engine)
- SELECT ...
PRAGMA main.table_info("users")
()
PRAGMA temp.table_info("users")
if util.py3k:
supports_unicode_statements = True
supports_unicode_binds = True
- returns_unicode_strings = True
+ returns_unicode_strings = sqltypes.String.RETURNS_UNICODE
description_encoding = None
else:
supports_unicode_statements = False
supports_unicode_binds = False
- returns_unicode_strings = False
+ returns_unicode_strings = sqltypes.String.RETURNS_UNKNOWN
description_encoding = "use_encoding"
name = "default"
except NotImplementedError:
self.default_isolation_level = None
- self.returns_unicode_strings = self._check_unicode_returns(connection)
+ if self.returns_unicode_strings is sqltypes.String.RETURNS_UNKNOWN:
+ if util.py3k:
+ raise exc.InvalidRequestError(
+ "RETURNS_UNKNOWN is unsupported in Python 3"
+ )
+ self.returns_unicode_strings = self._check_unicode_returns(
+ connection
+ )
if (
self.description_encoding is not None
results = {check_unicode(test) for test in tests}
if results.issuperset([True, False]):
- return "conditional"
+ return sqltypes.String.RETURNS_CONDITIONAL
else:
- return results == {True}
+ return (
+ sqltypes.String.RETURNS_UNICODE
+ if results == {True}
+ else sqltypes.String.RETURNS_BYTES
+ )
def _check_unicode_description(self, connection):
# all DBAPIs on Py2K return cursor.description as encoded
__visit_name__ = "string"
+ RETURNS_UNICODE = util.symbol(
+ "RETURNS_UNICODE",
+ """Indicates that the DBAPI returns Python Unicode for VARCHAR,
+ NVARCHAR, and other character-based datatypes in all cases.
+
+ This is the default value for
+ :attr:`.DefaultDialect.returns_unicode_strings` under Python 3.
+
+ .. versionadded:: 1.4
+
+ """,
+ )
+
+ RETURNS_BYTES = util.symbol(
+ "RETURNS_BYTES",
+ """Indicates that the DBAPI returns byte objects under Python 3
+ or non-Unicode string objects under Python 2 for VARCHAR, NVARCHAR,
+ and other character-based datatypes in all cases.
+
+ This may be applied to the
+ :attr:`.DefaultDialect.returns_unicode_strings` attribute.
+
+ .. versionadded:: 1.4
+
+ """,
+ )
+
+ RETURNS_CONDITIONAL = util.symbol(
+ "RETURNS_CONDITIONAL",
+ """Indicates that the DBAPI may return Unicode or bytestrings for
+ VARCHAR, NVARCHAR, and other character-based datatypes, and that
+ SQLAlchemy's default String datatype will need to test on a per-row
+ basis for Unicode or bytes.
+
+ This may be applied to the
+ :attr:`.DefaultDialect.returns_unicode_strings` attribute.
+
+ .. versionadded:: 1.4
+
+ """,
+ )
+
+ RETURNS_UNKNOWN = util.symbol(
+ "RETURNS_UNKNOWN",
+ """Indicates that the dialect should test on first connect what the
+ string-returning behavior of character-based datatypes is.
+
+ This is the default value for DefaultDialect.unicode_returns under
+ Python 2.
+
+ This may be applied to the
+ :attr:`.DefaultDialect.returns_unicode_strings` attribute under
+ Python 2 only. The value is disallowed under Python 3.
+
+ .. versionadded:: 1.4
+
+ .. deprecated:: 1.4 This value will be removed in SQLAlchemy 2.0.
+
+ """,
+ )
+
@util.deprecated_params(
convert_unicode=(
"1.3",
def result_processor(self, dialect, coltype):
wants_unicode = self._expect_unicode or dialect.convert_unicode
needs_convert = wants_unicode and (
- dialect.returns_unicode_strings is not True
+ dialect.returns_unicode_strings is not String.RETURNS_UNICODE
or self._expect_unicode in ("force", "force_nocheck")
)
needs_isinstance = (
needs_convert
and dialect.returns_unicode_strings
+ in (String.RETURNS_CONDITIONAL, String.RETURNS_UNICODE,)
and self._expect_unicode != "force_nocheck"
)
if needs_convert:
class DocTest(fixtures.TestBase):
+ __requires__ = ("python3",)
+
def _setup_logger(self):
rootlogger = logging.getLogger("sqlalchemy.engine.Engine")
eng2 = eng.execution_options(foo="bar")
assert eng2._has_events
+ def test_works_after_dispose(self):
+ eng = create_engine(testing.db.url)
+ for i in range(3):
+ eq_(eng.scalar(select([1])), 1)
+ eng.dispose()
+
+ def test_works_after_dispose_testing_engine(self):
+ eng = engines.testing_engine()
+ for i in range(3):
+ eq_(eng.scalar(select([1])), 1)
+ eng.dispose()
+
+
+class UnicodeReturnsTest(fixtures.TestBase):
+ @testing.requires.python3
+ def test_unicode_test_not_in_python3(self):
+ eng = engines.testing_engine()
+ eng.dialect.returns_unicode_strings = String.RETURNS_UNKNOWN
+
+ assert_raises_message(
+ tsa.exc.InvalidRequestError,
+ "RETURNS_UNKNOWN is unsupported in Python 3",
+ eng.connect,
+ )
+
+ @testing.requires.python2
def test_unicode_test_fails_warning(self):
class MockCursor(engines.DBAPIProxyCursor):
def execute(self, stmt, params=None, **kw):
eng.connect()
# because plain varchar passed, we don't know the correct answer
- eq_(eng.dialect.returns_unicode_strings, "conditional")
+ eq_(eng.dialect.returns_unicode_strings, String.RETURNS_CONDITIONAL)
eng.dispose()
- def test_works_after_dispose(self):
- eng = create_engine(testing.db.url)
- for i in range(3):
- eq_(eng.scalar(select([1])), 1)
- eng.dispose()
-
- def test_works_after_dispose_testing_engine(self):
- eng = engines.testing_engine()
- for i in range(3):
- eq_(eng.scalar(select([1])), 1)
- eng.dispose()
-
class ConvenienceExecuteTest(fixtures.TablesTest):
__backend__ = True
url, options=dict(module=dbapi, _initialize=initialize)
)
eng.pool.logger = Mock()
+
+ def get_default_schema_name(connection):
+ try:
+ cursor = connection.connection.cursor()
+ connection._cursor_execute(cursor, "statement", {})
+ cursor.close()
+ except exc.DBAPIError:
+ util.warn("Exception attempting to detect")
+
+ eng.dialect._get_default_schema_name = get_default_schema_name
return eng
def test_cursor_explode(self):
from sqlalchemy.testing.util import picklers
from sqlalchemy.testing.util import round_decimal
from sqlalchemy.util import OrderedDict
+from sqlalchemy.util import u
def _all_dialect_modules():
eq_(a.dialect_specific_args["bar"], "bar")
+class StringConvertUnicodeTest(fixtures.TestBase):
+ @testing.combinations((Unicode,), (String,), argnames="datatype")
+ @testing.combinations((True,), (False,), argnames="convert_unicode")
+ @testing.combinations(
+ (String.RETURNS_CONDITIONAL,),
+ (String.RETURNS_BYTES,),
+ (String.RETURNS_UNICODE),
+ argnames="returns_unicode_strings",
+ )
+ def test_convert_unicode(
+ self, datatype, convert_unicode, returns_unicode_strings
+ ):
+ s1 = datatype()
+ dialect = mock.Mock(
+ returns_unicode_strings=returns_unicode_strings,
+ encoding="utf-8",
+ convert_unicode=convert_unicode,
+ )
+
+ proc = s1.result_processor(dialect, None)
+
+ string = u("méil")
+ bytestring = string.encode("utf-8")
+
+ if (
+ datatype is Unicode or convert_unicode
+ ) and returns_unicode_strings in (
+ String.RETURNS_CONDITIONAL,
+ String.RETURNS_BYTES,
+ ):
+ eq_(proc(bytestring), string)
+
+ if returns_unicode_strings is String.RETURNS_CONDITIONAL:
+ eq_(proc(string), string)
+ else:
+ if util.py3k:
+ # trying to decode a unicode
+ assert_raises(TypeError, proc, string)
+ else:
+ assert_raises(UnicodeEncodeError, proc, string)
+ else:
+ is_(proc, None)
+
+
class TypeCoerceCastTest(fixtures.TablesTest):
__backend__ = True