summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/cextension/processors.c188
-rw-r--r--lib/sqlalchemy/connectors/pyodbc.py9
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py12
-rw-r--r--lib/sqlalchemy/dialects/mysql/base.py6
-rw-r--r--lib/sqlalchemy/dialects/mysql/mysqlconnector.py26
-rw-r--r--lib/sqlalchemy/dialects/mysql/pymysql.py6
-rw-r--r--lib/sqlalchemy/dialects/oracle/base.py32
-rw-r--r--lib/sqlalchemy/dialects/oracle/cx_oracle.py76
-rw-r--r--lib/sqlalchemy/dialects/postgresql/asyncpg.py3
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py2
-rw-r--r--lib/sqlalchemy/dialects/postgresql/json.py2
-rw-r--r--lib/sqlalchemy/dialects/postgresql/psycopg2.py40
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py2
-rw-r--r--lib/sqlalchemy/engine/base.py6
-rw-r--r--lib/sqlalchemy/engine/create.py76
-rw-r--r--lib/sqlalchemy/engine/default.py103
-rw-r--r--lib/sqlalchemy/engine/events.py4
-rw-r--r--lib/sqlalchemy/engine/mock.py2
-rw-r--r--lib/sqlalchemy/engine/url.py14
-rw-r--r--lib/sqlalchemy/orm/query.py21
-rw-r--r--lib/sqlalchemy/orm/session.py24
-rw-r--r--lib/sqlalchemy/orm/strategies.py21
-rw-r--r--lib/sqlalchemy/pool/events.py4
-rw-r--r--lib/sqlalchemy/processors.py44
-rw-r--r--lib/sqlalchemy/sql/compiler.py13
-rw-r--r--lib/sqlalchemy/sql/schema.py25
-rw-r--r--lib/sqlalchemy/sql/selectable.py184
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py294
-rw-r--r--lib/sqlalchemy/testing/profiling.py6
-rw-r--r--lib/sqlalchemy/testing/requirements.py6
-rw-r--r--lib/sqlalchemy/testing/suite/test_reflection.py17
-rw-r--r--lib/sqlalchemy/testing/suite/test_select.py2
32 files changed, 320 insertions, 950 deletions
diff --git a/lib/sqlalchemy/cextension/processors.c b/lib/sqlalchemy/cextension/processors.c
index f6f203e74..8c031b70a 100644
--- a/lib/sqlalchemy/cextension/processors.c
+++ b/lib/sqlalchemy/cextension/processors.c
@@ -312,192 +312,12 @@ str_to_date(PyObject *self, PyObject *arg)
typedef struct {
PyObject_HEAD
- PyObject *encoding;
- PyObject *errors;
-} UnicodeResultProcessor;
-
-typedef struct {
- PyObject_HEAD
PyObject *type;
PyObject *format;
} DecimalResultProcessor;
-/**************************
- * UnicodeResultProcessor *
- **************************/
-
-static int
-UnicodeResultProcessor_init(UnicodeResultProcessor *self, PyObject *args,
- PyObject *kwds)
-{
- PyObject *encoding, *errors = NULL;
- static char *kwlist[] = {"encoding", "errors", NULL};
-
-#if PY_MAJOR_VERSION >= 3
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "U|U:__init__", kwlist,
- &encoding, &errors))
- return -1;
-#else
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|S:__init__", kwlist,
- &encoding, &errors))
- return -1;
-#endif
-
-#if PY_MAJOR_VERSION >= 3
- encoding = PyUnicode_AsASCIIString(encoding);
-#else
- Py_INCREF(encoding);
-#endif
- self->encoding = encoding;
-
- if (errors) {
-#if PY_MAJOR_VERSION >= 3
- errors = PyUnicode_AsASCIIString(errors);
-#else
- Py_INCREF(errors);
-#endif
- } else {
-#if PY_MAJOR_VERSION >= 3
- errors = PyBytes_FromString("strict");
-#else
- errors = PyString_FromString("strict");
-#endif
- if (errors == NULL)
- return -1;
- }
- self->errors = errors;
-
- return 0;
-}
-
-static PyObject *
-UnicodeResultProcessor_process(UnicodeResultProcessor *self, PyObject *value)
-{
- const char *encoding, *errors;
- char *str;
- Py_ssize_t len;
-
- if (value == Py_None)
- Py_RETURN_NONE;
-
-#if PY_MAJOR_VERSION >= 3
- if (PyBytes_AsStringAndSize(value, &str, &len))
- return NULL;
-
- encoding = PyBytes_AS_STRING(self->encoding);
- errors = PyBytes_AS_STRING(self->errors);
-#else
- if (PyString_AsStringAndSize(value, &str, &len))
- return NULL;
-
- encoding = PyString_AS_STRING(self->encoding);
- errors = PyString_AS_STRING(self->errors);
-#endif
-
- return PyUnicode_Decode(str, len, encoding, errors);
-}
-
-static PyObject *
-UnicodeResultProcessor_conditional_process(UnicodeResultProcessor *self, PyObject *value)
-{
- const char *encoding, *errors;
- char *str;
- Py_ssize_t len;
-
- if (value == Py_None)
- Py_RETURN_NONE;
-
-#if PY_MAJOR_VERSION >= 3
- if (PyUnicode_Check(value) == 1) {
- Py_INCREF(value);
- return value;
- }
-
- if (PyBytes_AsStringAndSize(value, &str, &len))
- return NULL;
-
- encoding = PyBytes_AS_STRING(self->encoding);
- errors = PyBytes_AS_STRING(self->errors);
-#else
-
- if (PyUnicode_Check(value) == 1) {
- Py_INCREF(value);
- return value;
- }
-
- if (PyString_AsStringAndSize(value, &str, &len))
- return NULL;
-
-
- encoding = PyString_AS_STRING(self->encoding);
- errors = PyString_AS_STRING(self->errors);
-#endif
-
- return PyUnicode_Decode(str, len, encoding, errors);
-}
-
-static void
-UnicodeResultProcessor_dealloc(UnicodeResultProcessor *self)
-{
- Py_XDECREF(self->encoding);
- Py_XDECREF(self->errors);
-#if PY_MAJOR_VERSION >= 3
- Py_TYPE(self)->tp_free((PyObject*)self);
-#else
- self->ob_type->tp_free((PyObject*)self);
-#endif
-}
-
-static PyMethodDef UnicodeResultProcessor_methods[] = {
- {"process", (PyCFunction)UnicodeResultProcessor_process, METH_O,
- "The value processor itself."},
- {"conditional_process", (PyCFunction)UnicodeResultProcessor_conditional_process, METH_O,
- "Conditional version of the value processor."},
- {NULL} /* Sentinel */
-};
-
-static PyTypeObject UnicodeResultProcessorType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "sqlalchemy.cprocessors.UnicodeResultProcessor", /* tp_name */
- sizeof(UnicodeResultProcessor), /* tp_basicsize */
- 0, /* tp_itemsize */
- (destructor)UnicodeResultProcessor_dealloc, /* tp_dealloc */
- 0, /* tp_print */
- 0, /* tp_getattr */
- 0, /* tp_setattr */
- 0, /* tp_compare */
- 0, /* tp_repr */
- 0, /* tp_as_number */
- 0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
- 0, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
- 0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
- "UnicodeResultProcessor objects", /* tp_doc */
- 0, /* tp_traverse */
- 0, /* tp_clear */
- 0, /* tp_richcompare */
- 0, /* tp_weaklistoffset */
- 0, /* tp_iter */
- 0, /* tp_iternext */
- UnicodeResultProcessor_methods, /* tp_methods */
- 0, /* tp_members */
- 0, /* tp_getset */
- 0, /* tp_base */
- 0, /* tp_dict */
- 0, /* tp_descr_get */
- 0, /* tp_descr_set */
- 0, /* tp_dictoffset */
- (initproc)UnicodeResultProcessor_init, /* tp_init */
- 0, /* tp_alloc */
- 0, /* tp_new */
-};
/**************************
* DecimalResultProcessor *
@@ -664,10 +484,6 @@ initcprocessors(void)
{
PyObject *m;
- UnicodeResultProcessorType.tp_new = PyType_GenericNew;
- if (PyType_Ready(&UnicodeResultProcessorType) < 0)
- INITERROR;
-
DecimalResultProcessorType.tp_new = PyType_GenericNew;
if (PyType_Ready(&DecimalResultProcessorType) < 0)
INITERROR;
@@ -682,10 +498,6 @@ initcprocessors(void)
PyDateTime_IMPORT;
- Py_INCREF(&UnicodeResultProcessorType);
- PyModule_AddObject(m, "UnicodeResultProcessor",
- (PyObject *)&UnicodeResultProcessorType);
-
Py_INCREF(&DecimalResultProcessorType);
PyModule_AddObject(m, "DecimalResultProcessor",
(PyObject *)&DecimalResultProcessorType);
diff --git a/lib/sqlalchemy/connectors/pyodbc.py b/lib/sqlalchemy/connectors/pyodbc.py
index c2bbdf7ce..9661015ad 100644
--- a/lib/sqlalchemy/connectors/pyodbc.py
+++ b/lib/sqlalchemy/connectors/pyodbc.py
@@ -18,9 +18,6 @@ class PyODBCConnector(Connector):
supports_sane_rowcount_returning = True
supports_sane_multi_rowcount = False
- supports_unicode_statements = True
- supports_unicode_binds = True
-
supports_native_decimal = True
default_paramstyle = "named"
@@ -30,12 +27,8 @@ class PyODBCConnector(Connector):
# hold the desired driver name
pyodbc_driver_name = None
- def __init__(
- self, supports_unicode_binds=None, use_setinputsizes=False, **kw
- ):
+ def __init__(self, use_setinputsizes=False, **kw):
super(PyODBCConnector, self).__init__(**kw)
- if supports_unicode_binds is not None:
- self.supports_unicode_binds = supports_unicode_binds
self.use_setinputsizes = use_setinputsizes
@classmethod
diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py
index 8c8260f3b..0077f7fa1 100644
--- a/lib/sqlalchemy/dialects/mssql/base.py
+++ b/lib/sqlalchemy/dialects/mssql/base.py
@@ -1603,17 +1603,12 @@ class MSExecutionContext(default.DefaultExecutionContext):
def _opt_encode(self, statement):
- if not self.dialect.supports_unicode_statements:
- encoded = self.dialect._encoder(statement)[0]
- else:
- encoded = statement
-
if self.compiled and self.compiled.schema_translate_map:
rst = self.compiled.preparer._render_schema_translates
- encoded = rst(encoded, self.compiled.schema_translate_map)
+ statement = rst(statement, self.compiled.schema_translate_map)
- return encoded
+ return statement
def pre_exec(self):
"""Activate IDENTITY_INSERT if needed."""
@@ -2615,6 +2610,9 @@ def _schema_elements(schema):
# test/dialect/mssql/test_compiler.py -> test_schema_many_tokens_*
#
+ if schema.startswith("__[SCHEMA_"):
+ return None, schema
+
push = []
symbol = ""
bracket = False
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py
index 63a11fcc7..5382e00db 100644
--- a/lib/sqlalchemy/dialects/mysql/base.py
+++ b/lib/sqlalchemy/dialects/mysql/base.py
@@ -213,7 +213,7 @@ techniques are used.
To set isolation level using :func:`_sa.create_engine`::
engine = create_engine(
- "mysql://scott:tiger@localhost/test",
+ "mysql+mysqldb://scott:tiger@localhost/test",
isolation_level="READ UNCOMMITTED"
)
@@ -423,7 +423,7 @@ the ``first_connect`` and ``connect`` events::
from sqlalchemy import create_engine, event
- eng = create_engine("mysql://scott:tiger@localhost/test", echo='debug')
+ eng = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo='debug')
# `insert=True` will ensure this is the very first listener to run
@event.listens_for(eng, "connect", insert=True)
@@ -950,7 +950,7 @@ SQLAlchemy also emits NOT NULL for TIMESTAMP columns that do specify
from sqlalchemy import create_engine
- e = create_engine("mysql://scott:tiger@localhost/test", echo=True)
+ e = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo=True)
m.create_all(e)
output::
diff --git a/lib/sqlalchemy/dialects/mysql/mysqlconnector.py b/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
index e17da3174..fef4f14ca 100644
--- a/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
+++ b/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
@@ -27,7 +27,6 @@ from .base import BIT
from .base import MySQLCompiler
from .base import MySQLDialect
from .base import MySQLIdentifierPreparer
-from ... import processors
from ... import util
@@ -87,8 +86,6 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
driver = "mysqlconnector"
supports_statement_cache = True
- supports_unicode_binds = True
-
supports_sane_rowcount = True
supports_sane_multi_rowcount = True
@@ -101,29 +98,6 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT})
- def __init__(self, *arg, **kw):
- super(MySQLDialect_mysqlconnector, self).__init__(*arg, **kw)
-
- # hack description encoding since mysqlconnector randomly
- # returns bytes or not
- self._description_decoder = (
- processors.to_conditional_unicode_processor_factory
- )(self.description_encoding)
-
- def _check_unicode_description(self, connection):
- # hack description encoding since mysqlconnector randomly
- # returns bytes or not
- return False
-
- @property
- def description_encoding(self):
- # total guess
- return "latin-1"
-
- @util.memoized_property
- def supports_unicode_statements(self):
- return util.py3k or self._mysqlconnector_version_info > (2, 0)
-
@classmethod
def dbapi(cls):
from mysql import connector
diff --git a/lib/sqlalchemy/dialects/mysql/pymysql.py b/lib/sqlalchemy/dialects/mysql/pymysql.py
index 1d2c3be2d..3c30fb9ea 100644
--- a/lib/sqlalchemy/dialects/mysql/pymysql.py
+++ b/lib/sqlalchemy/dialects/mysql/pymysql.py
@@ -48,12 +48,6 @@ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
description_encoding = None
- # generally, these two values should be both True
- # or both False. PyMySQL unicode tests pass all the way back
- # to 0.4 either way. See [ticket:3337]
- supports_unicode_statements = True
- supports_unicode_binds = True
-
@langhelpers.memoized_property
def supports_server_side_cursors(self):
try:
diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py
index 5d9c7beec..f9f23c963 100644
--- a/lib/sqlalchemy/dialects/oracle/base.py
+++ b/lib/sqlalchemy/dialects/oracle/base.py
@@ -10,7 +10,7 @@ r"""
:name: Oracle
:full_support: 11.2, 18c
:normal_support: 11+
- :best_effort: 8+
+ :best_effort: 9+
Auto Increment Behavior
@@ -306,7 +306,7 @@ an INSERT in order to increment a sequence within an INSERT statement and get
the value back at the same time. To disable this feature across the board,
specify ``implicit_returning=False`` to :func:`_sa.create_engine`::
- engine = create_engine("oracle://scott:tiger@dsn",
+ engine = create_engine("oracle+cx_oracle://scott:tiger@dsn",
implicit_returning=False)
Implicit returning can also be disabled on a table-by-table basis as a table
@@ -341,6 +341,9 @@ and specify "passive_updates=False" on each relationship().
Oracle 8 Compatibility
----------------------
+.. warning:: The status of Oracle 8 compatibility is not known for SQLAlchemy
+ 2.0.
+
When Oracle 8 is detected, the dialect internally configures itself to the
following behaviors:
@@ -349,16 +352,12 @@ following behaviors:
makes use of Oracle's (+) operator.
* the NVARCHAR2 and NCLOB datatypes are no longer generated as DDL when
- the :class:`~sqlalchemy.types.Unicode` is used - VARCHAR2 and CLOB are
- issued instead. This because these types don't seem to work correctly on
- Oracle 8 even though they are available. The
- :class:`~sqlalchemy.types.NVARCHAR` and
+ the :class:`~sqlalchemy.types.Unicode` is used - VARCHAR2 and CLOB are issued
+ instead. This because these types don't seem to work correctly on Oracle 8
+ even though they are available. The :class:`~sqlalchemy.types.NVARCHAR` and
:class:`~sqlalchemy.dialects.oracle.NCLOB` types will always generate
NVARCHAR2 and NCLOB.
-* the "native unicode" mode is disabled when using cx_oracle, i.e. SQLAlchemy
- encodes all Python unicode objects to "string" before passing in as bind
- parameters.
Synonym/DBLINK Reflection
-------------------------
@@ -450,7 +449,7 @@ the ``exclude_tablespaces`` parameter::
# exclude SYSAUX and SOME_TABLESPACE, but not SYSTEM
e = create_engine(
- "oracle://scott:tiger@xe",
+ "oracle+cx_oracle://scott:tiger@xe",
exclude_tablespaces=["SYSAUX", "SOME_TABLESPACE"])
.. versionadded:: 1.1
@@ -1439,8 +1438,6 @@ class OracleDialect(default.DefaultDialect):
name = "oracle"
supports_statement_cache = True
supports_alter = True
- supports_unicode_statements = False
- supports_unicode_binds = False
max_identifier_length = 128
implicit_returning = True
@@ -1580,17 +1577,6 @@ class OracleDialect(default.DefaultDialect):
# use the default
return None
- def _check_unicode_returns(self, connection):
- additional_tests = [
- expression.cast(
- expression.literal_column("'test nvarchar2 returns'"),
- sqltypes.NVARCHAR(60),
- )
- ]
- return super(OracleDialect, self)._check_unicode_returns(
- connection, additional_tests
- )
-
_isolation_lookup = ["READ COMMITTED", "SERIALIZABLE"]
def get_isolation_level(self, connection):
diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
index 38e864898..23f619a12 100644
--- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py
+++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
@@ -119,7 +119,7 @@ itself. These options are always passed directly to :func:`_sa.create_engine`
, such as::
e = create_engine(
- "oracle+cx_oracle://user:pass@dsn", coerce_to_unicode=False)
+ "oracle+cx_oracle://user:pass@dsn", coerce_to_decimal=False)
The parameters accepted by the cx_oracle dialect are as follows:
@@ -130,8 +130,6 @@ The parameters accepted by the cx_oracle dialect are as follows:
* ``auto_convert_lobs`` - defaults to True; See :ref:`cx_oracle_lob`.
-* ``coerce_to_unicode`` - see :ref:`cx_oracle_unicode` for detail.
-
* ``coerce_to_decimal`` - see :ref:`cx_oracle_numeric` for detail.
* ``encoding_errors`` - see :ref:`cx_oracle_unicode_encoding_errors` for detail.
@@ -158,7 +156,7 @@ SQLAlchemy's pooling::
encoding="UTF-8", nencoding="UTF-8"
)
- engine = create_engine("oracle://", creator=pool.acquire, poolclass=NullPool)
+ engine = create_engine("oracle+cx_oracle://", creator=pool.acquire, poolclass=NullPool)
The above engine may then be used normally where cx_Oracle's pool handles
connection pooling::
@@ -196,7 +194,7 @@ This can be achieved by wrapping ``pool.acquire()``::
def creator():
return pool.acquire(cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF)
- engine = create_engine("oracle://", creator=creator, poolclass=NullPool)
+ engine = create_engine("oracle+cx_oracle://", creator=creator, poolclass=NullPool)
The above engine may then be used normally where cx_Oracle handles session
pooling and Oracle Database additionally uses DRCP::
@@ -210,8 +208,7 @@ Unicode
-------
As is the case for all DBAPIs under Python 3, all strings are inherently
-Unicode strings. Under Python 2, cx_Oracle also supports Python Unicode
-objects directly. In all cases however, the driver requires an explicit
+Unicode strings. In all cases however, the driver requires an explicit
encoding configuration.
Ensuring the Correct Client Encoding
@@ -264,25 +261,6 @@ SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
unless the ``use_nchar_for_unicode=True`` is passed to the dialect
when :func:`_sa.create_engine` is called.
-Unicode Coercion of result rows under Python 2
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When result sets are fetched that include strings, under Python 3 the cx_Oracle
-DBAPI returns all strings as Python Unicode objects, since Python 3 only has a
-Unicode string type. This occurs for data fetched from datatypes such as
-VARCHAR2, CHAR, CLOB, NCHAR, NCLOB, etc. In order to provide cross-
-compatibility under Python 2, the SQLAlchemy cx_Oracle dialect will add
-Unicode-conversion to string data under Python 2 as well. Historically, this
-made use of converters that were supplied by cx_Oracle but were found to be
-non-performant; SQLAlchemy's own converters are used for the string to Unicode
-conversion under Python 2. To disable the Python 2 Unicode conversion for
-VARCHAR2, CHAR, and CLOB, the flag ``coerce_to_unicode=False`` can be passed to
-:func:`_sa.create_engine`.
-
-.. versionchanged:: 1.3 Unicode conversion is applied to all string values
- by default under python 2. The ``coerce_to_unicode`` now defaults to True
- and can be set to False to disable the Unicode coercion of strings that are
- delivered as VARCHAR2/CHAR/CLOB data.
.. _cx_oracle_unicode_encoding_errors:
@@ -855,9 +833,6 @@ class OracleDialect_cx_oracle(OracleDialect):
supports_sane_rowcount = True
supports_sane_multi_rowcount = True
- supports_unicode_statements = True
- supports_unicode_binds = True
-
use_setinputsizes = True
driver = "cx_oracle"
@@ -892,6 +867,8 @@ class OracleDialect_cx_oracle(OracleDialect):
_cx_oracle_threaded = None
+ _cursor_var_unicode_kwargs = util.immutabledict()
+
@util.deprecated_params(
threaded=(
"1.3",
@@ -906,7 +883,6 @@ class OracleDialect_cx_oracle(OracleDialect):
def __init__(
self,
auto_convert_lobs=True,
- coerce_to_unicode=True,
coerce_to_decimal=True,
arraysize=50,
encoding_errors=None,
@@ -917,10 +893,13 @@ class OracleDialect_cx_oracle(OracleDialect):
OracleDialect.__init__(self, **kwargs)
self.arraysize = arraysize
self.encoding_errors = encoding_errors
+ if encoding_errors:
+ self._cursor_var_unicode_kwargs = {
+ "encodingErrors": encoding_errors
+ }
if threaded is not None:
self._cx_oracle_threaded = threaded
self.auto_convert_lobs = auto_convert_lobs
- self.coerce_to_unicode = coerce_to_unicode
self.coerce_to_decimal = coerce_to_decimal
if self._use_nchar_for_unicode:
self.colspecs = self.colspecs.copy()
@@ -939,6 +918,13 @@ class OracleDialect_cx_oracle(OracleDialect):
"cx_Oracle version 5.2 and above are supported"
)
+ if encoding_errors and self.cx_oracle_ver < (6, 4):
+ util.warn(
+ "cx_oracle version %r does not support encodingErrors"
+ % (self.cx_oracle_ver,)
+ )
+ self._cursor_var_unicode_kwargs = util.immutabledict()
+
self._include_setinputsizes = {
cx_Oracle.DATETIME,
cx_Oracle.NCLOB,
@@ -974,19 +960,6 @@ class OracleDialect_cx_oracle(OracleDialect):
self._is_cx_oracle_6 = self.cx_oracle_ver >= (6,)
- @property
- def _cursor_var_unicode_kwargs(self):
- if self.encoding_errors:
- if self.cx_oracle_ver >= (6, 4):
- return {"encodingErrors": self.encoding_errors}
- else:
- util.warn(
- "cx_oracle version %r does not support encodingErrors"
- % (self.cx_oracle_ver,)
- )
-
- return {}
-
def _parse_cx_oracle_ver(self, version):
m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", version)
if m:
@@ -1002,9 +975,6 @@ class OracleDialect_cx_oracle(OracleDialect):
def initialize(self, connection):
super(OracleDialect_cx_oracle, self).initialize(connection)
- if self._is_oracle_8:
- self.supports_unicode_binds = False
-
self._detect_decimal_char(connection)
def get_isolation_level(self, connection):
@@ -1141,9 +1111,10 @@ class OracleDialect_cx_oracle(OracleDialect):
cursor, name, default_type, size, precision, scale
)
- # allow all strings to come back natively as Unicode
+ # if unicode options were specified, add a decoder, otherwise
+ # cx_Oracle should return Unicode
elif (
- dialect.coerce_to_unicode
+ dialect._cursor_var_unicode_kwargs
and default_type
in (
cx_Oracle.STRING,
@@ -1338,13 +1309,6 @@ class OracleDialect_cx_oracle(OracleDialect):
if dbtype
)
- if not self.supports_unicode_binds:
- # oracle 8 only
- collection = (
- (self.dialect._encoder(key)[0], dbtype)
- for key, dbtype in collection
- )
-
cursor.setinputsizes(**{key: dbtype for key, dbtype in collection})
def do_recover_twophase(self, connection):
diff --git a/lib/sqlalchemy/dialects/postgresql/asyncpg.py b/lib/sqlalchemy/dialects/postgresql/asyncpg.py
index fedc0b495..28374ed60 100644
--- a/lib/sqlalchemy/dialects/postgresql/asyncpg.py
+++ b/lib/sqlalchemy/dialects/postgresql/asyncpg.py
@@ -863,11 +863,8 @@ class PGDialect_asyncpg(PGDialect):
driver = "asyncpg"
supports_statement_cache = True
- supports_unicode_statements = True
supports_server_side_cursors = True
- supports_unicode_binds = True
-
default_paramstyle = "format"
supports_sane_multi_rowcount = False
execution_ctx_cls = PGExecutionContext_asyncpg
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index 87afa0293..a00c26e87 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -346,7 +346,7 @@ were set to include ``test_schema``, and we invoked a table
reflection process as follows::
>>> from sqlalchemy import Table, MetaData, create_engine, text
- >>> engine = create_engine("postgresql://scott:tiger@localhost/test")
+ >>> engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
>>> with engine.connect() as conn:
... conn.execute(text("SET search_path TO test_schema, public"))
... meta = MetaData()
diff --git a/lib/sqlalchemy/dialects/postgresql/json.py b/lib/sqlalchemy/dialects/postgresql/json.py
index 2acf177f5..ef046e3ae 100644
--- a/lib/sqlalchemy/dialects/postgresql/json.py
+++ b/lib/sqlalchemy/dialects/postgresql/json.py
@@ -159,7 +159,7 @@ class JSON(sqltypes.JSON):
using psycopg2, the DBAPI only allows serializers at the per-cursor
or per-connection level. E.g.::
- engine = create_engine("postgresql://scott:tiger@localhost/test",
+ engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test",
json_serializer=my_serialize_fn,
json_deserializer=my_deserialize_fn
)
diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
index 162ddde94..aadd11059 100644
--- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py
+++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
@@ -40,13 +40,6 @@ may be passed to :func:`_sa.create_engine()`, and include the following:
:ref:`psycopg2_unicode`
-* ``use_native_unicode``: Under Python 2 only, this can be set to False to
- disable the use of psycopg2's native Unicode support.
-
- .. seealso::
-
- :ref:`psycopg2_disable_native_unicode`
-
* ``executemany_mode``, ``executemany_batch_page_size``,
``executemany_values_page_size``: Allows use of psycopg2
@@ -295,10 +288,7 @@ size defaults to 100. These can be affected by passing new values to
Unicode with Psycopg2
----------------------
-The psycopg2 DBAPI driver supports Unicode data transparently. Under Python 2
-only, the SQLAlchemy psycopg2 dialect will enable the
-``psycopg2.extensions.UNICODE`` extension by default to ensure Unicode is
-handled properly; under Python 3, this is psycopg2's default behavior.
+The psycopg2 DBAPI driver supports Unicode data transparently.
The client character encoding can be controlled for the psycopg2 dialect
in the following ways:
@@ -347,21 +337,6 @@ in the following ways:
# encoding
client_encoding = utf8
-.. _psycopg2_disable_native_unicode:
-
-Disabling Native Unicode
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-Under Python 2 only, SQLAlchemy can also be instructed to skip the usage of the
-psycopg2 ``UNICODE`` extension and to instead utilize its own unicode
-encode/decode services, which are normally reserved only for those DBAPIs that
-don't fully support unicode directly. Passing ``use_native_unicode=False`` to
-:func:`_sa.create_engine` will disable usage of ``psycopg2.extensions.
-UNICODE``. SQLAlchemy will instead encode data itself into Python bytestrings
-on the way in and coerce from bytes on the way back, using the value of the
-:func:`_sa.create_engine` ``encoding`` parameter, which defaults to ``utf-8``.
-SQLAlchemy's own unicode encode/decode functionality is steadily becoming
-obsolete as most DBAPIs now support unicode fully.
Transactions
@@ -659,10 +634,6 @@ class PGDialect_psycopg2(PGDialect):
_has_native_hstore = True
- engine_config_types = PGDialect.engine_config_types.union(
- {"use_native_unicode": util.asbool}
- )
-
colspecs = util.update_copy(
PGDialect.colspecs,
{
@@ -678,7 +649,6 @@ class PGDialect_psycopg2(PGDialect):
def __init__(
self,
- use_native_unicode=True,
client_encoding=None,
use_native_hstore=True,
use_native_uuid=True,
@@ -688,16 +658,10 @@ class PGDialect_psycopg2(PGDialect):
**kwargs
):
PGDialect.__init__(self, **kwargs)
- self.use_native_unicode = use_native_unicode
- if not use_native_unicode:
- raise exc.ArgumentError(
- "psycopg2 native_unicode mode is required under Python 3"
- )
if not use_native_hstore:
self._has_native_hstore = False
self.use_native_hstore = use_native_hstore
self.use_native_uuid = use_native_uuid
- self.supports_unicode_binds = use_native_unicode
self.client_encoding = client_encoding
# Parse executemany_mode argument, allowing it to be only one of the
@@ -892,8 +856,6 @@ class PGDialect_psycopg2(PGDialect):
executemany_values = (
"(%s)" % context.compiled.insert_single_values_expr
)
- if not self.supports_unicode_statements:
- executemany_values = executemany_values.encode(self.encoding)
# guard for statement that was altered via event hook or similar
if executemany_values not in statement:
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index e936c9080..dc8425859 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -1795,8 +1795,6 @@ class SQLiteExecutionContext(default.DefaultExecutionContext):
class SQLiteDialect(default.DefaultDialect):
name = "sqlite"
supports_alter = False
- supports_unicode_statements = True
- supports_unicode_binds = True
# SQlite supports "DEFAULT VALUES" but *does not* support
# "VALUES (DEFAULT)"
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 41c5f4753..ef6282525 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1887,7 +1887,7 @@ class Transaction(TransactionalContext):
:class:`_engine.Connection`::
from sqlalchemy import create_engine
- engine = create_engine("postgresql://scott:tiger@localhost/test")
+ engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
connection = engine.connect()
trans = connection.begin()
connection.execute(text("insert into x (a, b) values (1, 2)"))
@@ -1914,7 +1914,7 @@ class Transaction(TransactionalContext):
.. index::
single: thread safety; Transaction
- """
+ """ # noqa
__slots__ = ()
@@ -2413,7 +2413,7 @@ class Engine(ConnectionEventsTarget, log.Identified):
from sqlalchemy import event
from sqlalchemy.engine import Engine
- primary_engine = create_engine("mysql://")
+ primary_engine = create_engine("mysql+mysqldb://")
shard1 = primary_engine.execution_options(shard_id="shard1")
shard2 = primary_engine.execution_options(shard_id="shard2")
diff --git a/lib/sqlalchemy/engine/create.py b/lib/sqlalchemy/engine/create.py
index b10e72601..efcd2b530 100644
--- a/lib/sqlalchemy/engine/create.py
+++ b/lib/sqlalchemy/engine/create.py
@@ -47,7 +47,7 @@ def create_engine(url, **kwargs):
first positional argument, usually a string
that indicates database dialect and connection arguments::
- engine = create_engine("postgresql://scott:tiger@localhost/test")
+ engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
.. note::
@@ -60,7 +60,7 @@ def create_engine(url, **kwargs):
and its underlying :class:`.Dialect` and :class:`_pool.Pool`
constructs::
- engine = create_engine("mysql://scott:tiger@hostname/dbname",
+ engine = create_engine("mysql+mysqldb://scott:tiger@hostname/dbname",
encoding='latin1', echo=True)
The string form of the URL is
@@ -100,21 +100,6 @@ def create_engine(url, **kwargs):
additional keyword arguments. See the example
at :ref:`custom_dbapi_args`.
- :param convert_unicode=False: if set to True, causes
- all :class:`.String` datatypes to act as though the
- :paramref:`.String.convert_unicode` flag has been set to ``True``,
- regardless of a setting of ``False`` on an individual :class:`.String`
- type. This has the effect of causing all :class:`.String` -based
- columns to accommodate Python Unicode objects directly as though the
- datatype were the :class:`.Unicode` type.
-
- .. deprecated:: 1.3
-
- The :paramref:`_sa.create_engine.convert_unicode` parameter
- is deprecated and will be removed in a future release.
- All modern DBAPIs now support Python Unicode directly and this
- parameter is unnecessary.
-
:param creator: a callable which returns a DBAPI connection.
This creation function will be passed to the underlying
connection pool and will be used to create all new database
@@ -174,63 +159,6 @@ def create_engine(url, **kwargs):
:ref:`change_4737`
- :param encoding: **legacy Python 2 value only, where it only applies to
- specific DBAPIs, not used in Python 3 for any modern DBAPI driver.
- Please refer to individual dialect documentation for client encoding
- behaviors.** Defaults to the string value ``utf-8``. This value
- refers **only** to the character encoding that is used when SQLAlchemy
- sends or receives data from a :term:`DBAPI` that does not support
- Python Unicode and **is only used under Python 2**, only for certain
- DBAPI drivers, and only in certain circumstances. **Python 3 users
- please DISREGARD this parameter and refer to the documentation for the
- specific dialect in use in order to configure character encoding
- behavior.**
-
- .. note:: The ``encoding`` parameter deals only with in-Python
- encoding issues that were prevalent with **some DBAPIS only**
- under **Python 2 only**. Under Python 3 it is not used by
- any modern dialect. For DBAPIs that require
- client encoding configurations, which are most of those outside
- of SQLite, please consult specific :ref:`dialect documentation
- <dialect_toplevel>` for details.
-
- All modern DBAPIs that work in Python 3 necessarily feature direct
- support for Python unicode strings. Under Python 2, this was not
- always the case. For those scenarios where the DBAPI is detected as
- not supporting a Python ``unicode`` object under Python 2, this
- encoding is used to determine the source/destination encoding. It is
- **not used** for those cases where the DBAPI handles unicode directly.
-
- To properly configure a system to accommodate Python ``unicode``
- objects, the DBAPI should be configured to handle unicode to the
- greatest degree as is appropriate - see the notes on unicode pertaining
- to the specific target database in use at :ref:`dialect_toplevel`.
-
- Areas where string encoding may need to be accommodated
- outside of the DBAPI, nearly always under **Python 2 only**,
- include zero or more of:
-
- * the values passed to bound parameters, corresponding to
- the :class:`.Unicode` type or the :class:`.String` type
- when ``convert_unicode`` is ``True``;
- * the values returned in result set columns corresponding
- to the :class:`.Unicode` type or the :class:`.String`
- type when ``convert_unicode`` is ``True``;
- * the string SQL statement passed to the DBAPI's
- ``cursor.execute()`` method;
- * the string names of the keys in the bound parameter
- dictionary passed to the DBAPI's ``cursor.execute()``
- as well as ``cursor.setinputsizes()`` methods;
- * the string column names retrieved from the DBAPI's
- ``cursor.description`` attribute.
-
- When using Python 3, the DBAPI is required to support all of the above
- values as Python ``unicode`` objects, which in Python 3 are just known
- as ``str``. In Python 2, the DBAPI does not specify unicode behavior
- at all, so SQLAlchemy must make decisions for each of the above values
- on a per-DBAPI basis - implementations are completely inconsistent in
- their behavior.
-
:param execution_options: Dictionary execution options which will
be applied to all connections. See
:meth:`~sqlalchemy.engine.Connection.execution_options`
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index 9a59250e9..d670cf231 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -13,7 +13,6 @@ as the base class for their own corresponding classes.
"""
-import codecs
import functools
import random
import re
@@ -26,7 +25,6 @@ from .base import Connection
from .. import event
from .. import exc
from .. import pool
-from .. import processors
from .. import types as sqltypes
from .. import util
from ..sql import compiler
@@ -92,7 +90,6 @@ class DefaultDialect(interfaces.Dialect):
engine_config_types = util.immutabledict(
[
- ("convert_unicode", util.bool_or_str("force")),
("pool_timeout", util.asint),
("echo", util.bool_or_str("debug")),
("echo_pool", util.bool_or_str("debug")),
@@ -108,9 +105,6 @@ class DefaultDialect(interfaces.Dialect):
# *not* the FLOAT type however.
supports_native_decimal = False
- supports_unicode_statements = True
- supports_unicode_binds = True
- returns_unicode_strings = sqltypes.String.RETURNS_UNICODE
description_encoding = None
name = "default"
@@ -223,13 +217,6 @@ class DefaultDialect(interfaces.Dialect):
NO_DIALECT_SUPPORT = NO_DIALECT_SUPPORT
@util.deprecated_params(
- convert_unicode=(
- "1.3",
- "The :paramref:`_sa.create_engine.convert_unicode` parameter "
- "and corresponding dialect-level parameters are deprecated, "
- "and will be removed in a future release. Modern DBAPIs support "
- "Python Unicode natively and this parameter is unnecessary.",
- ),
empty_in_strategy=(
"1.4",
"The :paramref:`_sa.create_engine.empty_in_strategy` keyword is "
@@ -250,7 +237,6 @@ class DefaultDialect(interfaces.Dialect):
)
def __init__(
self,
- convert_unicode=False,
encoding="utf-8",
paramstyle=None,
dbapi=None,
@@ -279,7 +265,6 @@ class DefaultDialect(interfaces.Dialect):
else:
self.server_side_cursors = True
- self.convert_unicode = convert_unicode
self.encoding = encoding
self.positional = False
self._ischema = None
@@ -305,16 +290,6 @@ class DefaultDialect(interfaces.Dialect):
)
self.label_length = label_length
self.compiler_linting = compiler_linting
- if self.description_encoding == "use_encoding":
- self._description_decoder = (
- processors.to_unicode_processor_factory
- )(encoding)
- elif self.description_encoding is not None:
- self._description_decoder = (
- processors.to_unicode_processor_factory
- )(self.description_encoding)
- self._encoder = codecs.getencoder(self.encoding)
- self._decoder = processors.to_unicode_processor_factory(self.encoding)
def _ensure_has_table_connection(self, arg):
@@ -391,12 +366,6 @@ class DefaultDialect(interfaces.Dialect):
except NotImplementedError:
self.default_isolation_level = None
- if (
- self.description_encoding is not None
- and self._check_unicode_description(connection)
- ):
- self._description_decoder = self.description_encoding = None
-
if not self._user_defined_max_identifier_length:
max_ident_length = self._check_max_identifier_length(connection)
if max_ident_length:
@@ -444,22 +413,6 @@ class DefaultDialect(interfaces.Dialect):
"""
return self.get_isolation_level(dbapi_conn)
- def _check_unicode_description(self, connection):
- cast_to = util.text_type
-
- cursor = connection.connection.cursor()
- try:
- cursor.execute(
- cast_to(
- expression.select(
- expression.literal_column("'x'").label("some_label")
- ).compile(dialect=self)
- )
- )
- return isinstance(cursor.description[0][0], util.text_type)
- finally:
- cursor.close()
-
def type_descriptor(self, typeobj):
"""Provide a database-specific :class:`.TypeEngine` object, given
the generic object which comes from the types module.
@@ -790,10 +743,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
self.unicode_statement, schema_translate_map
)
- if not dialect.supports_unicode_statements:
- self.statement = dialect._encoder(self.unicode_statement)[0]
- else:
- self.statement = self.unicode_statement
+ self.statement = self.unicode_statement
self.cursor = self.create_cursor()
self.compiled_parameters = []
@@ -913,12 +863,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
# final self.unicode_statement is now assigned, encode if needed
# by dialect
- if not dialect.supports_unicode_statements:
- self.statement = self.unicode_statement.encode(
- self.dialect.encoding
- )
- else:
- self.statement = self.unicode_statement
+ self.statement = self.unicode_statement
# Convert the dictionary of bind parameter values
# into a dict or list to be sent to the DBAPI's
@@ -934,25 +879,14 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
]
parameters.append(dialect.execute_sequence_format(param))
else:
- encode = not dialect.supports_unicode_statements
- if encode:
- encoder = dialect._encoder
for compiled_params in self.compiled_parameters:
- if encode:
- param = {
- encoder(key)[0]: processors[key](compiled_params[key])
- if key in processors
- else compiled_params[key]
- for key in compiled_params
- }
- else:
- param = {
- key: processors[key](compiled_params[key])
- if key in processors
- else compiled_params[key]
- for key in compiled_params
- }
+ param = {
+ key: processors[key](compiled_params[key])
+ if key in processors
+ else compiled_params[key]
+ for key in compiled_params
+ }
parameters.append(param)
@@ -988,13 +922,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
elif isinstance(parameters[0], dialect.execute_sequence_format):
self.parameters = parameters
elif isinstance(parameters[0], dict):
- if dialect.supports_unicode_statements:
- self.parameters = parameters
- else:
- self.parameters = [
- {dialect._encoder(k)[0]: d[k] for k in d}
- for d in parameters
- ] or [{}]
+ self.parameters = parameters
else:
self.parameters = [
dialect.execute_sequence_format(p) for p in parameters
@@ -1002,13 +930,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
self.executemany = len(parameters) > 1
- if not dialect.supports_unicode_statements and isinstance(
- statement, util.text_type
- ):
- self.unicode_statement = statement
- self.statement = dialect._encoder(statement)[0]
- else:
- self.statement = self.unicode_statement = statement
+ self.statement = self.unicode_statement = statement
self.cursor = self.create_cursor()
return self
@@ -1101,11 +1023,6 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
"""
conn = self.root_connection
- if (
- isinstance(stmt, util.text_type)
- and not self.dialect.supports_unicode_statements
- ):
- stmt = self.dialect._encoder(stmt)[0]
if "schema_translate_map" in self.execution_options:
schema_translate_map = self.execution_options.get(
diff --git a/lib/sqlalchemy/engine/events.py b/lib/sqlalchemy/engine/events.py
index cfb616aff..57628066d 100644
--- a/lib/sqlalchemy/engine/events.py
+++ b/lib/sqlalchemy/engine/events.py
@@ -30,7 +30,7 @@ class ConnectionEvents(event.Events):
executemany):
log.info("Received statement: %s", statement)
- engine = create_engine('postgresql://scott:tiger@localhost/test')
+ engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/test')
event.listen(engine, "before_cursor_execute", before_cursor_execute)
or with a specific :class:`_engine.Connection`::
@@ -88,7 +88,7 @@ class ConnectionEvents(event.Events):
and parameters. See those methods for a description of
specific return arguments.
- """
+ """ # noqa
_target_class_doc = "SomeEngine"
_dispatch_target = ConnectionEventsTarget
diff --git a/lib/sqlalchemy/engine/mock.py b/lib/sqlalchemy/engine/mock.py
index 5da716b6b..731dacc33 100644
--- a/lib/sqlalchemy/engine/mock.py
+++ b/lib/sqlalchemy/engine/mock.py
@@ -62,7 +62,7 @@ def create_mock_engine(url, executor, **kw):
def dump(sql, *multiparams, **params):
print(sql.compile(dialect=engine.dialect))
- engine = create_mock_engine('postgresql://', dump)
+ engine = create_mock_engine('postgresql+psycopg2://', dump)
metadata.create_all(engine, checkfirst=False)
:param url: A string URL which typically needs to contain only the
diff --git a/lib/sqlalchemy/engine/url.py b/lib/sqlalchemy/engine/url.py
index be330eb6c..7f09b1eac 100644
--- a/lib/sqlalchemy/engine/url.py
+++ b/lib/sqlalchemy/engine/url.py
@@ -286,10 +286,10 @@ class URL(
E.g.::
>>> from sqlalchemy.engine import make_url
- >>> url = make_url("postgresql://user:pass@host/dbname")
+ >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname")
>>> url = url.update_query_string("alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt")
>>> str(url)
- 'postgresql://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'
+ 'postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'
:param query_string: a URL escaped query string, not including the
question mark.
@@ -320,10 +320,10 @@ class URL(
E.g.::
>>> from sqlalchemy.engine import make_url
- >>> url = make_url("postgresql://user:pass@host/dbname")
+ >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname")
>>> url = url.update_query_pairs([("alt_host", "host1"), ("alt_host", "host2"), ("ssl_cipher", "/path/to/crt")])
>>> str(url)
- 'postgresql://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'
+ 'postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'
:param key_value_pairs: A sequence of tuples containing two strings
each.
@@ -389,10 +389,10 @@ class URL(
>>> from sqlalchemy.engine import make_url
- >>> url = make_url("postgresql://user:pass@host/dbname")
+ >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname")
>>> url = url.update_query_dict({"alt_host": ["host1", "host2"], "ssl_cipher": "/path/to/crt"})
>>> str(url)
- 'postgresql://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'
+ 'postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'
:param query_parameters: A dictionary with string keys and values
@@ -485,7 +485,7 @@ class URL(
>>> from sqlalchemy.engine import make_url
- >>> url = make_url("postgresql://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt")
+ >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt")
>>> url.query
immutabledict({'alt_host': ('host1', 'host2'), 'ssl_cipher': '/path/to/crt'})
>>> url.normalized_query
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index bd897211c..c987f6b16 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -101,9 +101,6 @@ class Query(
:meth:`_query.Query.with_session`
method.
- For a full walk through of :class:`_query.Query` usage, see the
- :ref:`ormtutorial_toplevel`.
-
"""
# elements that are in Core and can be cached in the same way
@@ -2290,17 +2287,6 @@ class Query(
.. note:: This flag is considered legacy.
- .. seealso::
-
- :ref:`ormtutorial_joins` in the ORM tutorial.
-
- :ref:`inheritance_toplevel` for details on how
- :meth:`_query.Query.join` is used for inheritance relationships.
-
- :func:`_orm.join` - a standalone ORM-level join function,
- used internally by :meth:`_query.Query.join`, which in previous
- SQLAlchemy versions was the primary ORM-level joining interface.
-
"""
aliased, from_joinpoint, isouter, full = (
@@ -2772,11 +2758,6 @@ class Query(
appropriate to the entity class represented by this
:class:`_query.Query`.
- .. seealso::
-
- :ref:`orm_tutorial_literal_sql` - usage examples in the
- ORM tutorial
-
"""
statement = coercions.expect(
roles.SelectStatementRole, statement, apply_propagate_attrs=self
@@ -3117,8 +3098,6 @@ class Query(
:ref:`faq_query_deduplicating`
- :ref:`orm_tutorial_query_returning`
-
For fine grained control over specific columns to count, to skip the
usage of a subquery or otherwise control of the FROM clause, or to use
other aggregate functions, use :attr:`~sqlalchemy.sql.expression.func`
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index c76ece5de..8be5fbee7 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -1001,10 +1001,10 @@ class Session(_SessionClassMethods):
described at :meth:`.Session.get_bind`. Usage looks like::
Session = sessionmaker(binds={
- SomeMappedClass: create_engine('postgresql://engine1'),
- SomeDeclarativeBase: create_engine('postgresql://engine2'),
- some_mapper: create_engine('postgresql://engine3'),
- some_table: create_engine('postgresql://engine4'),
+ SomeMappedClass: create_engine('postgresql+psycopg2://engine1'),
+ SomeDeclarativeBase: create_engine('postgresql+psycopg2://engine2'),
+ some_mapper: create_engine('postgresql+psycopg2://engine3'),
+ some_table: create_engine('postgresql+psycopg2://engine4'),
})
.. seealso::
@@ -1086,7 +1086,7 @@ class Session(_SessionClassMethods):
:param autocommit: the "autocommit" keyword is present for backwards
compatibility but must remain at its default value of ``False``.
- """
+ """ # noqa
# considering allowing the "autocommit" keyword to still be accepted
# as long as it's False, so that external test suites, oslo.db etc
@@ -2042,6 +2042,18 @@ class Session(_SessionClassMethods):
"""Return a new :class:`_query.Query` object corresponding to this
:class:`_orm.Session`.
+ Note that the :class:`_query.Query` object is legacy as of
+ SQLAlchemy 2.0; the :func:`_sql.select` construct is now used
+ to construct ORM queries.
+
+ .. seealso::
+
+ :ref:`unified_tutorial`
+
+ :ref:`queryguide_toplevel`
+
+ :ref:`query_api_toplevel` - legacy API doc
+
"""
return self._query_cls(entities, self, **kwargs)
@@ -3906,7 +3918,7 @@ class sessionmaker(_SessionClassMethods):
# an Engine, which the Session will use for connection
# resources
- engine = create_engine('postgresql://scott:tiger@localhost/')
+ engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/')
Session = sessionmaker(engine)
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 2a283caad..71c4a6976 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -382,7 +382,26 @@ class DeferredColumnLoader(LoaderStrategy):
# dictionary. Normally, the DeferredColumnLoader.setup_query()
# sets up that data in the "memoized_populators" dictionary
# and "create_row_processor()" here is never invoked.
- if not self.is_class_level:
+
+ if (
+ context.refresh_state
+ and context.query._compile_options._only_load_props
+ and self.key in context.query._compile_options._only_load_props
+ ):
+ self.parent_property._get_strategy(
+ (("deferred", False), ("instrument", True))
+ ).create_row_processor(
+ context,
+ query_entity,
+ path,
+ loadopt,
+ mapper,
+ result,
+ adapter,
+ populators,
+ )
+
+ elif not self.is_class_level:
if self.raiseload:
set_deferred_for_local_state = (
self.parent_property._raise_column_loader
diff --git a/lib/sqlalchemy/pool/events.py b/lib/sqlalchemy/pool/events.py
index 7c2cae7c5..57e3893b0 100644
--- a/lib/sqlalchemy/pool/events.py
+++ b/lib/sqlalchemy/pool/events.py
@@ -32,12 +32,12 @@ class PoolEvents(event.Events):
targets, which will be resolved to the ``.pool`` attribute of the
given engine or the :class:`_pool.Pool` class::
- engine = create_engine("postgresql://scott:tiger@localhost/test")
+ engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
# will associate with engine.pool
event.listen(engine, 'checkout', my_on_checkout)
- """
+ """ # noqa
_target_class_doc = "SomeEngineOrPool"
_dispatch_target = Pool
diff --git a/lib/sqlalchemy/processors.py b/lib/sqlalchemy/processors.py
index 0c0aa1bd6..156005c6a 100644
--- a/lib/sqlalchemy/processors.py
+++ b/lib/sqlalchemy/processors.py
@@ -13,7 +13,6 @@ They all share one common characteristic: None is passed through unchanged.
"""
-import codecs
import datetime
import re
@@ -64,36 +63,6 @@ def str_to_datetime_processor_factory(regexp, type_):
def py_fallback():
- def to_unicode_processor_factory(encoding, errors=None):
- decoder = codecs.getdecoder(encoding)
-
- def process(value):
- if value is None:
- return None
- else:
- # decoder returns a tuple: (value, len). Simply dropping the
- # len part is safe: it is done that way in the normal
- # 'xx'.decode(encoding) code path.
- return decoder(value, errors)[0]
-
- return process
-
- def to_conditional_unicode_processor_factory(encoding, errors=None):
- decoder = codecs.getdecoder(encoding)
-
- def process(value):
- if value is None:
- return None
- elif isinstance(value, util.text_type):
- return value
- else:
- # decoder returns a tuple: (value, len). Simply dropping the
- # len part is safe: it is done that way in the normal
- # 'xx'.decode(encoding) code path.
- return decoder(value, errors)[0]
-
- return process
-
def to_decimal_processor_factory(target_class, scale):
fstring = "%%.%df" % scale
@@ -149,19 +118,6 @@ try:
from sqlalchemy.cprocessors import str_to_time # noqa
from sqlalchemy.cprocessors import to_float # noqa
from sqlalchemy.cprocessors import to_str # noqa
- from sqlalchemy.cprocessors import UnicodeResultProcessor # noqa
-
- def to_unicode_processor_factory(encoding, errors=None):
- if errors is not None:
- return UnicodeResultProcessor(encoding, errors).process
- else:
- return UnicodeResultProcessor(encoding).process
-
- def to_conditional_unicode_processor_factory(encoding, errors=None):
- if errors is not None:
- return UnicodeResultProcessor(encoding, errors).conditional_process
- else:
- return UnicodeResultProcessor(encoding).conditional_process
def to_decimal_processor_factory(target_class, scale):
# Note that the scale argument is not taken into account for integer
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 4611c5e13..191a07299 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -1247,7 +1247,7 @@ class SQLCompiler(Compiled):
return expr
statement = re.sub(
- r"\[POSTCOMPILE_(\S+?)(~~.+?~~)?\]",
+ r"__\[POSTCOMPILE_(\S+?)(~~.+?~~)?\]",
process_expanding,
self.string,
)
@@ -2374,9 +2374,9 @@ class SQLCompiler(Compiled):
# for postcompile w/ expanding, move the "wrapped" part
# of this into the inside
m = re.match(
- r"^(.*)\(\[POSTCOMPILE_(\S+?)\]\)(.*)$", wrapped
+ r"^(.*)\(__\[POSTCOMPILE_(\S+?)\]\)(.*)$", wrapped
)
- wrapped = "([POSTCOMPILE_%s~~%s~~REPL~~%s~~])" % (
+ wrapped = "(__[POSTCOMPILE_%s~~%s~~REPL~~%s~~])" % (
m.group(2),
m.group(1),
m.group(3),
@@ -2582,7 +2582,7 @@ class SQLCompiler(Compiled):
self.escaped_bind_names = {}
self.escaped_bind_names[escaped_from] = name
if post_compile:
- return "[POSTCOMPILE_%s]" % name
+ return "__[POSTCOMPILE_%s]" % name
else:
return self.bindtemplate % {"name": name}
@@ -3216,6 +3216,7 @@ class SQLCompiler(Compiled):
# passed in. for ORM use this will convert from an ORM-state
# SELECT to a regular "Core" SELECT. other composed operations
# such as computation of joins will be performed.
+
kwargs["within_columns_clause"] = False
compile_state = select_stmt._compile_state_factory(
@@ -5039,7 +5040,7 @@ class IdentifierPreparer(object):
"in schema translate name '%s'" % name
)
return quoted_name(
- "[SCHEMA_%s]" % (name or "_none"), quote=False
+ "__[SCHEMA_%s]" % (name or "_none"), quote=False
)
else:
return obj.schema
@@ -5065,7 +5066,7 @@ class IdentifierPreparer(object):
)
return self.quote_schema(effective_schema)
- return re.sub(r"(\[SCHEMA_([^\]]+)\])", replace, statement)
+ return re.sub(r"(__\[SCHEMA_([^\]]+)\])", replace, statement)
def _escape_identifier(self, value):
"""Escape an identifier.
diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py
index dbd50d5d8..641e62be3 100644
--- a/lib/sqlalchemy/sql/schema.py
+++ b/lib/sqlalchemy/sql/schema.py
@@ -763,25 +763,25 @@ class Table(DialectKWArgs, SchemaItem, TableClause):
)
include_columns = kwargs.pop("include_columns", None)
-
- resolve_fks = kwargs.pop("resolve_fks", True)
-
if include_columns is not None:
for c in self.c:
if c.name not in include_columns:
self._columns.remove(c)
+ resolve_fks = kwargs.pop("resolve_fks", True)
+
for key in ("quote", "quote_schema"):
if key in kwargs:
raise exc.ArgumentError(
"Can't redefine 'quote' or 'quote_schema' arguments"
)
- if "comment" in kwargs:
- self.comment = kwargs.pop("comment", None)
-
- if "info" in kwargs:
- self.info = kwargs.pop("info")
+ # update `self` with these kwargs, if provided
+ self.comment = kwargs.pop("comment", self.comment)
+ self.implicit_returning = kwargs.pop(
+ "implicit_returning", self.implicit_returning
+ )
+ self.info = kwargs.pop("info", self.info)
if autoload:
if not autoload_replace:
@@ -1431,7 +1431,7 @@ class Column(DialectKWArgs, SchemaItem, ColumnClause):
from sqlalchemy.dialects.postgresql import array
engine = create_engine(
- 'postgresql://scott:tiger@localhost/mydatabase'
+ 'postgresql+psycopg2://scott:tiger@localhost/mydatabase'
)
metadata_obj = MetaData()
tbl = Table(
@@ -1639,13 +1639,6 @@ class Column(DialectKWArgs, SchemaItem, ColumnClause):
if isinstance(self.default, (ColumnDefault, Sequence)):
args.append(self.default)
else:
- if getattr(self.type, "_warn_on_bytestring", False):
- if isinstance(self.default, util.binary_type):
- util.warn(
- "Unicode column '%s' has non-unicode "
- "default value %r specified."
- % (self.key, self.default)
- )
args.append(ColumnDefault(self.default))
if self.server_default is not None:
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 0e5ae89e4..350e55c49 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -2121,9 +2121,23 @@ class CTE(
_suffixes=self._suffixes,
)
- def union(self, other):
+ def union(self, *other):
+ r"""Return a new :class:`_expression.CTE` with a SQL ``UNION``
+ of the original CTE against the given selectables provided
+ as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28 multiple elements are now accepted.
+
+ .. seealso::
+
+ :meth:`_sql.HasCTE.cte` - examples of calling styles
+
+ """
return CTE._construct(
- self.element.union(other),
+ self.element.union(*other),
name=self.name,
recursive=self.recursive,
nesting=self.nesting,
@@ -2132,9 +2146,23 @@ class CTE(
_suffixes=self._suffixes,
)
- def union_all(self, other):
+ def union_all(self, *other):
+ r"""Return a new :class:`_expression.CTE` with a SQL ``UNION ALL``
+ of the original CTE against the given selectables provided
+ as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28 multiple elements are now accepted.
+
+ .. seealso::
+
+ :meth:`_sql.HasCTE.cte` - examples of calling styles
+
+ """
return CTE._construct(
- self.element.union_all(other),
+ self.element.union_all(*other),
name=self.name,
recursive=self.recursive,
nesting=self.nesting,
@@ -2396,7 +2424,7 @@ class HasCTE(roles.HasCTERole):
connection.execute(upsert)
- Example 4, Nesting CTE::
+ Example 4, Nesting CTE (SQLAlchemy 1.4.24 and above)::
value_a = select(
literal("root").label("n")
@@ -2426,6 +2454,44 @@ class HasCTE(roles.HasCTERole):
SELECT value_a.n AS a, value_b.n AS b
FROM value_a, value_b
+ Example 5, Non-Linear CTE (SQLAlchemy 1.4.28 and above)::
+
+ edge = Table(
+ "edge",
+ metadata,
+ Column("id", Integer, primary_key=True),
+ Column("left", Integer),
+ Column("right", Integer),
+ )
+
+ root_node = select(literal(1).label("node")).cte(
+ "nodes", recursive=True
+ )
+
+ left_edge = select(edge.c.left).join(
+ root_node, edge.c.right == root_node.c.node
+ )
+ right_edge = select(edge.c.right).join(
+ root_node, edge.c.left == root_node.c.node
+ )
+
+ subgraph_cte = root_node.union(left_edge, right_edge)
+
+ subgraph = select(subgraph_cte)
+
+ The above query will render 2 UNIONs inside the recursive CTE::
+
+ WITH RECURSIVE nodes(node) AS (
+ SELECT 1 AS node
+ UNION
+ SELECT edge."left" AS "left"
+ FROM edge JOIN nodes ON edge."right" = nodes.node
+ UNION
+ SELECT edge."right" AS "right"
+ FROM edge JOIN nodes ON edge."left" = nodes.node
+ )
+ SELECT nodes.node FROM nodes
+
.. seealso::
:meth:`_orm.Query.cte` - ORM version of
@@ -6269,47 +6335,107 @@ class Select(
else:
return SelectStatementGrouping(self)
- def union(self, other, **kwargs):
- """Return a SQL ``UNION`` of this select() construct against
- the given selectable.
+ def union(self, *other, **kwargs):
+ r"""Return a SQL ``UNION`` of this select() construct against
+ the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
"""
- return CompoundSelect._create_union(self, other, **kwargs)
+ return CompoundSelect._create_union(self, *other, **kwargs)
+
+ def union_all(self, *other, **kwargs):
+ r"""Return a SQL ``UNION ALL`` of this select() construct against
+ the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
- def union_all(self, other, **kwargs):
- """Return a SQL ``UNION ALL`` of this select() construct against
- the given selectable.
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
"""
- return CompoundSelect._create_union_all(self, other, **kwargs)
+ return CompoundSelect._create_union_all(self, *other, **kwargs)
+
+ def except_(self, *other, **kwargs):
+ r"""Return a SQL ``EXCEPT`` of this select() construct against
+ the given selectable provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
- def except_(self, other, **kwargs):
- """Return a SQL ``EXCEPT`` of this select() construct against
- the given selectable.
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
"""
- return CompoundSelect._create_except(self, other, **kwargs)
+ return CompoundSelect._create_except(self, *other, **kwargs)
- def except_all(self, other, **kwargs):
- """Return a SQL ``EXCEPT ALL`` of this select() construct against
- the given selectable.
+ def except_all(self, *other, **kwargs):
+ r"""Return a SQL ``EXCEPT ALL`` of this select() construct against
+ the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
"""
- return CompoundSelect._create_except_all(self, other, **kwargs)
+ return CompoundSelect._create_except_all(self, *other, **kwargs)
+
+ def intersect(self, *other, **kwargs):
+ r"""Return a SQL ``INTERSECT`` of this select() construct against
+ the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
- def intersect(self, other, **kwargs):
- """Return a SQL ``INTERSECT`` of this select() construct against
- the given selectable.
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
"""
- return CompoundSelect._create_intersect(self, other, **kwargs)
+ return CompoundSelect._create_intersect(self, *other, **kwargs)
+
+ def intersect_all(self, *other, **kwargs):
+ r"""Return a SQL ``INTERSECT ALL`` of this select() construct
+ against the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
- def intersect_all(self, other, **kwargs):
- """Return a SQL ``INTERSECT ALL`` of this select() construct
- against the given selectable.
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
"""
- return CompoundSelect._create_intersect_all(self, other, **kwargs)
+ return CompoundSelect._create_intersect_all(self, *other, **kwargs)
@property
@util.deprecated_20(
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index 0d7a06e31..559946072 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -9,7 +9,6 @@
"""
-import codecs
import datetime as dt
import decimal
import json
@@ -127,9 +126,7 @@ class String(Concatenable, TypeEngine):
"""The base for all string and character types.
- In SQL, corresponds to VARCHAR. Can also take Python unicode objects
- and encode to the database's encoding in bind params (and the reverse for
- result sets.)
+ In SQL, corresponds to VARCHAR.
The `length` field is usually required when the `String` type is
used within a CREATE TABLE statement, as VARCHAR requires a length
@@ -139,91 +136,10 @@ class String(Concatenable, TypeEngine):
__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",
- "The :paramref:`.String.convert_unicode` parameter is deprecated "
- "and will be removed in a future release. All modern DBAPIs "
- "now support Python Unicode directly and this parameter is "
- "unnecessary.",
- ),
- unicode_error=(
- "1.3",
- "The :paramref:`.String.unicode_errors` parameter is deprecated "
- "and will be removed in a future release. This parameter is "
- "unnecessary for modern Python DBAPIs and degrades performance "
- "significantly.",
- ),
- )
def __init__(
self,
length=None,
collation=None,
- convert_unicode=False,
- unicode_error=None,
- _warn_on_bytestring=False,
- _expect_unicode=False,
):
"""
Create a string-holding type.
@@ -245,65 +161,17 @@ class String(Concatenable, TypeEngine):
>>> print(select(cast('some string', String(collation='utf8'))))
SELECT CAST(:param_1 AS VARCHAR COLLATE utf8) AS anon_1
- :param convert_unicode: When set to ``True``, the
- :class:`.String` type will assume that
- input is to be passed as Python Unicode objects under Python 2,
- and results returned as Python Unicode objects.
- In the rare circumstance that the DBAPI does not support
- Python unicode under Python 2, SQLAlchemy will use its own
- encoder/decoder functionality on strings, referring to the
- value of the :paramref:`_sa.create_engine.encoding` parameter
- parameter passed to :func:`_sa.create_engine` as the encoding.
-
- For the extremely rare case that Python Unicode
- is to be encoded/decoded by SQLAlchemy on a backend
- that *does* natively support Python Unicode,
- the string value ``"force"`` can be passed here which will
- cause SQLAlchemy's encode/decode services to be
- used unconditionally.
-
- .. note::
-
- SQLAlchemy's unicode-conversion flags and features only apply
- to Python 2; in Python 3, all string objects are Unicode objects.
- For this reason, as well as the fact that virtually all modern
- DBAPIs now support Unicode natively even under Python 2,
- the :paramref:`.String.convert_unicode` flag is inherently a
- legacy feature.
-
.. note::
- In the vast majority of cases, the :class:`.Unicode` or
- :class:`.UnicodeText` datatypes should be used for a
- :class:`_schema.Column` that expects to store non-ascii data.
- These
- datatypes will ensure that the correct types are used on the
- database side as well as set up the correct Unicode behaviors
- under Python 2.
-
- .. seealso::
-
- :paramref:`_sa.create_engine.convert_unicode` -
- :class:`_engine.Engine`-wide parameter
-
- :param unicode_error: Optional, a method to use to handle Unicode
- conversion errors. Behaves like the ``errors`` keyword argument to
- the standard library's ``string.decode()`` functions, requires
- that :paramref:`.String.convert_unicode` is set to
- ``"force"``
+ In most cases, the :class:`.Unicode` or :class:`.UnicodeText`
+ datatypes should be used for a :class:`_schema.Column` that expects
+ to store non-ascii data. These datatypes will ensure that the
+ correct types are used on the database.
"""
- if unicode_error is not None and convert_unicode != "force":
- raise exc.ArgumentError(
- "convert_unicode must be 'force' " "when unicode_error is set."
- )
self.length = length
self.collation = collation
- self._expect_unicode = convert_unicode or _expect_unicode
- self._expect_unicode_error = unicode_error
-
- self._warn_on_bytestring = _warn_on_bytestring
def literal_processor(self, dialect):
def process(value):
@@ -317,100 +185,24 @@ class String(Concatenable, TypeEngine):
return process
def bind_processor(self, dialect):
- if self._expect_unicode or dialect.convert_unicode:
- if (
- dialect.supports_unicode_binds
- and self._expect_unicode != "force"
- ):
- if self._warn_on_bytestring:
-
- def process(value):
- if isinstance(value, util.binary_type):
- util.warn_limited(
- "Unicode type received non-unicode "
- "bind param value %r.",
- (util.ellipses_string(value),),
- )
- return value
-
- return process
- else:
- return None
- else:
- encoder = codecs.getencoder(dialect.encoding)
- warn_on_bytestring = self._warn_on_bytestring
-
- def process(value):
- if isinstance(value, util.text_type):
- return encoder(value, self._expect_unicode_error)[0]
- elif warn_on_bytestring and value is not None:
- util.warn_limited(
- "Unicode type received non-unicode bind "
- "param value %r.",
- (util.ellipses_string(value),),
- )
- return value
-
- return process
- else:
- return None
+ return None
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 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:
- if needs_isinstance:
- return processors.to_conditional_unicode_processor_factory(
- dialect.encoding, self._expect_unicode_error
- )
- else:
- return processors.to_unicode_processor_factory(
- dialect.encoding, self._expect_unicode_error
- )
- else:
- return None
+ return None
@property
def python_type(self):
- if self._expect_unicode:
- return util.text_type
- else:
- return str
+ return util.text_type
def get_dbapi_type(self, dbapi):
return dbapi.STRING
- @classmethod
- def _warn_deprecated_unicode(cls):
- util.warn_deprecated(
- "The convert_unicode on Engine and String as well as the "
- "unicode_error flag on String are deprecated. All modern "
- "DBAPIs now support Python Unicode natively under Python 2, and "
- "under Python 3 all strings are inherently Unicode. These flags "
- "will be removed in a future release.",
- version="1.3",
- )
-
class Text(String):
"""A variably sized string type.
- In SQL, usually corresponds to CLOB or TEXT. Can also take Python
- unicode objects and encode to the database's encoding in bind
- params (and the reverse for result sets.) In general, TEXT objects
+ In SQL, usually corresponds to CLOB or TEXT. In general, TEXT objects
do not have a length; while some databases will accept a length
argument here, it will be rejected by others.
@@ -428,9 +220,7 @@ class Unicode(String):
some backends implies an underlying column type that is explicitly
supporting of non-ASCII data, such as ``NVARCHAR`` on Oracle and SQL
Server. This will impact the output of ``CREATE TABLE`` statements and
- ``CAST`` functions at the dialect level, and also in some cases will
- indicate different behavior in the DBAPI itself in how it handles bound
- parameters.
+ ``CAST`` functions at the dialect level.
The character encoding used by the :class:`.Unicode` type that is used to
transmit and receive data to the database is usually determined by the
@@ -440,18 +230,10 @@ class Unicode(String):
in the :ref:`dialect_toplevel` section.
In modern SQLAlchemy, use of the :class:`.Unicode` datatype does not
- typically imply any encoding/decoding behavior within SQLAlchemy itself.
- Historically, when DBAPIs did not support Python ``unicode`` objects under
- Python 2, SQLAlchemy handled unicode encoding/decoding services itself
- which would be controlled by the flag :paramref:`.String.convert_unicode`;
- this flag is deprecated as it is no longer needed for Python 3.
-
- When using Python 2, data that is passed to columns that use the
- :class:`.Unicode` datatype must be of type ``unicode``, and not ``str``
- which in Python 2 is equivalent to ``bytes``. In Python 3, all data
- passed to columns that use the :class:`.Unicode` datatype should be
- of type ``str``. See the flag :paramref:`.String.convert_unicode` for
- more discussion of unicode encode/decode behavior under Python 2.
+ imply any encoding/decoding behavior within SQLAlchemy itself. In Python
+ 3, all string objects are inherently Unicode capable, and SQLAlchemy
+ does not produce bytestring objects nor does it accommodate a DBAPI that
+ does not return Python Unicode objects in result sets for string values.
.. warning:: Some database backends, particularly SQL Server with pyodbc,
are known to have undesirable behaviors regarding data that is noted
@@ -466,8 +248,6 @@ class Unicode(String):
:class:`.UnicodeText` - unlengthed textual counterpart
to :class:`.Unicode`.
- :paramref:`.String.convert_unicode`
-
:meth:`.DialectEvents.do_setinputsizes`
@@ -479,13 +259,9 @@ class Unicode(String):
"""
Create a :class:`.Unicode` object.
- Parameters are the same as that of :class:`.String`,
- with the exception that ``convert_unicode``
- defaults to ``True``.
+ Parameters are the same as that of :class:`.String`.
"""
- kwargs.setdefault("_expect_unicode", True)
- kwargs.setdefault("_warn_on_bytestring", True)
super(Unicode, self).__init__(length=length, **kwargs)
@@ -508,18 +284,11 @@ class UnicodeText(Text):
"""
Create a Unicode-converting Text type.
- Parameters are the same as that of :class:`_expression.TextClause`,
- with the exception that ``convert_unicode``
- defaults to ``True``.
+ Parameters are the same as that of :class:`_expression.TextClause`.
"""
- kwargs.setdefault("_expect_unicode", True)
- kwargs.setdefault("_warn_on_bytestring", True)
super(UnicodeText, self).__init__(length=length, **kwargs)
- def _warn_deprecated_unicode(self):
- pass
-
class Integer(_LookupExpressionAdapter, TypeEngine):
@@ -1306,15 +1075,6 @@ class Enum(Emulated, String, SchemaType):
__visit_name__ = "enum"
- @util.deprecated_params(
- convert_unicode=(
- "1.3",
- "The :paramref:`.Enum.convert_unicode` parameter is deprecated "
- "and will be removed in a future release. All modern DBAPIs "
- "now support Python Unicode directly and this parameter is "
- "unnecessary.",
- )
- )
def __init__(self, *enums, **kw):
r"""Construct an enum.
@@ -1327,11 +1087,6 @@ class Enum(Emulated, String, SchemaType):
.. versionadded:: 1.1 a PEP-435 style enumerated class may be
passed.
- :param convert_unicode: Enable unicode-aware bind parameter and
- result-set processing for this Enum's data under Python 2 only.
- Under Python 2, this is set automatically based on the presence of
- unicode label strings. This flag will be removed in SQLAlchemy 2.0.
-
:param create_constraint: defaults to False. When creating a
non-native enumerated type, also build a CHECK constraint on the
database against the valid values.
@@ -1481,14 +1236,8 @@ class Enum(Emulated, String, SchemaType):
values, objects = self._parse_into_values(enums, kw)
self._setup_for_values(values, objects, kw)
- convert_unicode = kw.pop("convert_unicode", None)
self.validate_strings = kw.pop("validate_strings", False)
- if convert_unicode is None:
- _expect_unicode = True
- else:
- _expect_unicode = convert_unicode
-
if self.enums:
length = max(len(x) for x in self.enums)
else:
@@ -1504,9 +1253,7 @@ class Enum(Emulated, String, SchemaType):
self._valid_lookup[None] = self._object_lookup[None] = None
- super(Enum, self).__init__(
- length=length, _expect_unicode=_expect_unicode
- )
+ super(Enum, self).__init__(length=length)
if self.enum_class:
kw.setdefault("name", self.enum_class.__name__.lower())
@@ -1615,9 +1362,7 @@ class Enum(Emulated, String, SchemaType):
op, other_comparator
)
if op is operators.concat_op:
- typ = String(
- self.type.length, _expect_unicode=self.type._expect_unicode
- )
+ typ = String(self.type.length)
return op, typ
comparator_factory = Comparator
@@ -1659,7 +1404,6 @@ class Enum(Emulated, String, SchemaType):
return util.constructor_copy(self, self._generic_type_affinity, *args)
def adapt_to_emulated(self, impltype, **kw):
- kw.setdefault("_expect_unicode", self._expect_unicode)
kw.setdefault("validate_strings", self.validate_strings)
kw.setdefault("name", self.name)
kw.setdefault("schema", self.schema)
@@ -2605,7 +2349,7 @@ class JSON(Indexable, TypeEngine):
@util.memoized_property
def _str_impl(self):
- return String(_expect_unicode=True)
+ return String()
def bind_processor(self, dialect):
string_process = self._str_impl.bind_processor(dialect)
diff --git a/lib/sqlalchemy/testing/profiling.py b/lib/sqlalchemy/testing/profiling.py
index dd5040205..10344c8d6 100644
--- a/lib/sqlalchemy/testing/profiling.py
+++ b/lib/sqlalchemy/testing/profiling.py
@@ -105,11 +105,7 @@ class ProfileStatsFile(object):
dbapi_key,
]
- platform_tokens.append(
- "nativeunicode"
- if config.db.dialect.convert_unicode
- else "dbapiunicode"
- )
+ platform_tokens.append("dbapiunicode")
_has_cext = has_compiled_ext()
platform_tokens.append(_has_cext and "cextensions" or "nocextensions")
return "_".join(platform_tokens)
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 08acbd2d2..4cc431bb7 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -1224,6 +1224,12 @@ class SuiteRequirements(Requirements):
return exclusions.only_if(check)
@property
+ def python38(self):
+ return exclusions.only_if(
+ lambda: util.py38, "Python 3.8 or above required"
+ )
+
+ @property
def cpython(self):
return exclusions.only_if(
lambda: util.cpython, "cPython interpreter needed"
diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py
index 6fbd74689..ba176bcd9 100644
--- a/lib/sqlalchemy/testing/suite/test_reflection.py
+++ b/lib/sqlalchemy/testing/suite/test_reflection.py
@@ -2,7 +2,6 @@ import operator
import re
import sqlalchemy as sa
-from sqlalchemy import func
from .. import config
from .. import engines
from .. import eq_
@@ -15,6 +14,7 @@ from ..schema import Column
from ..schema import Table
from ... import event
from ... import ForeignKey
+from ... import func
from ... import Identity
from ... import inspect
from ... import Integer
@@ -25,6 +25,7 @@ from ... import types as sql_types
from ...schema import DDL
from ...schema import Index
from ...sql.elements import quoted_name
+from ...sql.schema import BLANK_SCHEMA
from ...testing import is_false
from ...testing import is_true
@@ -539,6 +540,20 @@ class ComponentReflectionTest(fixtures.TablesTest):
self.assert_(testing.config.test_schema in insp.get_schema_names())
@testing.requires.schema_reflection
+ def test_get_schema_names_w_translate_map(self, connection):
+ """test #7300"""
+
+ connection = connection.execution_options(
+ schema_translate_map={
+ "foo": "bar",
+ BLANK_SCHEMA: testing.config.test_schema,
+ }
+ )
+ insp = inspect(connection)
+
+ self.assert_(testing.config.test_schema in insp.get_schema_names())
+
+ @testing.requires.schema_reflection
def test_dialect_initialize(self):
engine = engines.testing_engine()
inspect(engine)
diff --git a/lib/sqlalchemy/testing/suite/test_select.py b/lib/sqlalchemy/testing/suite/test_select.py
index bea8a6075..b5a3dca3a 100644
--- a/lib/sqlalchemy/testing/suite/test_select.py
+++ b/lib/sqlalchemy/testing/suite/test_select.py
@@ -884,7 +884,7 @@ class PostCompileParamsTest(
self.assert_compile(
stmt,
"SELECT some_table.id FROM some_table "
- "WHERE some_table.x = [POSTCOMPILE_q]",
+ "WHERE some_table.x = __[POSTCOMPILE_q]",
{},
)