summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2020-07-08 15:07:44 +0000
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>2020-07-08 15:07:44 +0000
commitb330ffbc13ddb4274a004eab6a13ce40d641e555 (patch)
tree88605188d6adac26c77defa544fc5cde208952f5 /lib/sqlalchemy
parenta6d8b674e92ef1cabdb2ab85490397f3ed12a42c (diff)
parent91f376692d472a5bf0c4b4033816250ec1ce3ab6 (diff)
downloadsqlalchemy-b330ffbc13ddb4274a004eab6a13ce40d641e555.tar.gz
Merge "Add future=True to create_engine/Session; unify select()"
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/cextension/resultproxy.c3
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py66
-rw-r--r--lib/sqlalchemy/engine/base.py1
-rw-r--r--lib/sqlalchemy/engine/create.py18
-rw-r--r--lib/sqlalchemy/engine/default.py6
-rw-r--r--lib/sqlalchemy/ext/baked.py2
-rw-r--r--lib/sqlalchemy/future/__init__.py2
-rw-r--r--lib/sqlalchemy/future/selectable.py165
-rw-r--r--lib/sqlalchemy/orm/context.py28
-rw-r--r--lib/sqlalchemy/orm/loading.py1
-rw-r--r--lib/sqlalchemy/orm/persistence.py15
-rw-r--r--lib/sqlalchemy/orm/query.py9
-rw-r--r--lib/sqlalchemy/orm/relationships.py16
-rw-r--r--lib/sqlalchemy/orm/session.py43
-rw-r--r--lib/sqlalchemy/sql/base.py22
-rw-r--r--lib/sqlalchemy/sql/expression.py2
-rw-r--r--lib/sqlalchemy/sql/selectable.py517
-rw-r--r--lib/sqlalchemy/sql/util.py22
-rw-r--r--lib/sqlalchemy/testing/assertions.py6
-rw-r--r--lib/sqlalchemy/testing/suite/test_types.py78
-rw-r--r--lib/sqlalchemy/testing/warnings.py3
-rw-r--r--lib/sqlalchemy/util/__init__.py4
-rw-r--r--lib/sqlalchemy/util/deprecations.py49
-rw-r--r--lib/sqlalchemy/util/langhelpers.py4
24 files changed, 661 insertions, 421 deletions
diff --git a/lib/sqlalchemy/cextension/resultproxy.c b/lib/sqlalchemy/cextension/resultproxy.c
index ed6f57470..f99236e1e 100644
--- a/lib/sqlalchemy/cextension/resultproxy.c
+++ b/lib/sqlalchemy/cextension/resultproxy.c
@@ -505,7 +505,8 @@ BaseRow_getattro(BaseRow *self, PyObject *name)
else
return tmp;
- tmp = BaseRow_subscript_mapping(self, name);
+ tmp = BaseRow_subscript_impl(self, name, 1);
+
if (tmp == NULL && PyErr_ExceptionMatches(PyExc_KeyError)) {
#if PY_MAJOR_VERSION >= 3
diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py
index 4b211bde7..06ea80b9e 100644
--- a/lib/sqlalchemy/dialects/mssql/base.py
+++ b/lib/sqlalchemy/dialects/mssql/base.py
@@ -2612,7 +2612,7 @@ class MSDialect(default.DefaultDialect):
def has_table(self, connection, tablename, dbname, owner, schema):
tables = ischema.tables
- s = sql.select([tables.c.table_name]).where(
+ s = sql.select(tables.c.table_name).where(
sql.and_(
tables.c.table_type == "BASE TABLE",
tables.c.table_name == tablename,
@@ -2630,7 +2630,7 @@ class MSDialect(default.DefaultDialect):
def has_sequence(self, connection, sequencename, dbname, owner, schema):
sequences = ischema.sequences
- s = sql.select([sequences.c.sequence_name]).where(
+ s = sql.select(sequences.c.sequence_name).where(
sequences.c.sequence_name == sequencename
)
@@ -2646,7 +2646,7 @@ class MSDialect(default.DefaultDialect):
def get_sequence_names(self, connection, dbname, owner, schema, **kw):
sequences = ischema.sequences
- s = sql.select([sequences.c.sequence_name])
+ s = sql.select(sequences.c.sequence_name)
if owner:
s = s.where(sequences.c.sequence_schema == owner)
@@ -2668,7 +2668,7 @@ class MSDialect(default.DefaultDialect):
def get_table_names(self, connection, dbname, owner, schema, **kw):
tables = ischema.tables
s = (
- sql.select([tables.c.table_name])
+ sql.select(tables.c.table_name)
.where(
sql.and_(
tables.c.table_schema == owner,
@@ -2684,12 +2684,15 @@ class MSDialect(default.DefaultDialect):
@_db_plus_owner_listing
def get_view_names(self, connection, dbname, owner, schema, **kw):
tables = ischema.tables
- s = sql.select(
- [tables.c.table_name],
- sql.and_(
- tables.c.table_schema == owner, tables.c.table_type == "VIEW"
- ),
- order_by=[tables.c.table_name],
+ s = (
+ sql.select(tables.c.table_name)
+ .where(
+ sql.and_(
+ tables.c.table_schema == owner,
+ tables.c.table_type == "VIEW",
+ )
+ )
+ .order_by(tables.c.table_name)
)
view_names = [r[0] for r in connection.execute(s)]
return view_names
@@ -2807,11 +2810,13 @@ class MSDialect(default.DefaultDialect):
computed_cols.c.definition, NVARCHAR(4000)
)
- s = sql.select(
- [columns, computed_definition, computed_cols.c.is_persisted],
- whereclause,
- from_obj=join,
- order_by=[columns.c.ordinal_position],
+ s = (
+ sql.select(
+ columns, computed_definition, computed_cols.c.is_persisted
+ )
+ .where(whereclause)
+ .select_from(join)
+ .order_by(columns.c.ordinal_position)
)
c = connection.execution_options(future_result=True).execute(s)
@@ -2930,7 +2935,8 @@ class MSDialect(default.DefaultDialect):
# Primary key constraints
s = sql.select(
- [C.c.column_name, TC.c.constraint_type, C.c.constraint_name],
+ C.c.column_name, TC.c.constraint_type, C.c.constraint_name
+ ).where(
sql.and_(
TC.c.constraint_name == C.c.constraint_name,
TC.c.table_schema == C.c.table_schema,
@@ -2957,8 +2963,8 @@ class MSDialect(default.DefaultDialect):
R = ischema.key_constraints.alias("R")
# Foreign key constraints
- s = sql.select(
- [
+ s = (
+ sql.select(
C.c.column_name,
R.c.table_schema,
R.c.table_name,
@@ -2967,17 +2973,19 @@ class MSDialect(default.DefaultDialect):
RR.c.match_option,
RR.c.update_rule,
RR.c.delete_rule,
- ],
- sql.and_(
- C.c.table_name == tablename,
- C.c.table_schema == owner,
- RR.c.constraint_schema == C.c.table_schema,
- C.c.constraint_name == RR.c.constraint_name,
- R.c.constraint_name == RR.c.unique_constraint_name,
- R.c.constraint_schema == RR.c.unique_constraint_schema,
- C.c.ordinal_position == R.c.ordinal_position,
- ),
- order_by=[RR.c.constraint_name, R.c.ordinal_position],
+ )
+ .where(
+ sql.and_(
+ C.c.table_name == tablename,
+ C.c.table_schema == owner,
+ RR.c.constraint_schema == C.c.table_schema,
+ C.c.constraint_name == RR.c.constraint_name,
+ R.c.constraint_name == RR.c.unique_constraint_name,
+ R.c.constraint_schema == RR.c.unique_constraint_schema,
+ C.c.ordinal_position == R.c.ordinal_position,
+ )
+ )
+ .order_by(RR.c.constraint_name, R.c.ordinal_position)
)
# group rows by constraint ID, to handle multi-column FKs
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 9ac61fe12..6bc9588ad 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1511,7 +1511,6 @@ class Connection(Connectable):
# legacy stuff.
if should_close_with_result and context._soft_closed:
assert not self._is_future
- assert not context._is_future_result
# CursorResult already exhausted rows / has no rows.
# close us now
diff --git a/lib/sqlalchemy/engine/create.py b/lib/sqlalchemy/engine/create.py
index c199c21e0..8b0377a58 100644
--- a/lib/sqlalchemy/engine/create.py
+++ b/lib/sqlalchemy/engine/create.py
@@ -227,6 +227,15 @@ def create_engine(url, **kwargs):
be applied to all connections. See
:meth:`~sqlalchemy.engine.Connection.execution_options`
+ :param future: Use the 2.0 style :class:`_future.Engine` and
+ :class:`_future.Connection` API.
+
+ ..versionadded:: 1.4
+
+ .. seealso::
+
+ :ref:`migration_20_toplevel`
+
:param hide_parameters: Boolean, when set to True, SQL statement parameters
will not be displayed in INFO logging nor will they be formatted into
the string representation of :class:`.StatementError` objects.
@@ -575,7 +584,14 @@ def create_engine(url, **kwargs):
pool._dialect = dialect
# create engine.
- engineclass = kwargs.pop("_future_engine_class", base.Engine)
+ if kwargs.pop("future", False):
+ from sqlalchemy import future
+
+ default_engine_class = future.Engine
+ else:
+ default_engine_class = base.Engine
+
+ engineclass = kwargs.pop("_future_engine_class", default_engine_class)
engine_args = {}
for k in util.get_cls_kwargs(engineclass):
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index f1fc505ac..e567e11e7 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -393,9 +393,7 @@ class DefaultDialect(interfaces.Dialect):
parameters = {}
def check_unicode(test):
- statement = cast_to(
- expression.select([test]).compile(dialect=self)
- )
+ statement = cast_to(expression.select(test).compile(dialect=self))
try:
cursor = connection.connection.cursor()
connection._cursor_execute(cursor, statement, parameters)
@@ -453,7 +451,7 @@ class DefaultDialect(interfaces.Dialect):
cursor.execute(
cast_to(
expression.select(
- [expression.literal_column("'x'").label("some_label")]
+ expression.literal_column("'x'").label("some_label")
).compile(dialect=self)
)
)
diff --git a/lib/sqlalchemy/ext/baked.py b/lib/sqlalchemy/ext/baked.py
index ecbf871e2..fc6623609 100644
--- a/lib/sqlalchemy/ext/baked.py
+++ b/lib/sqlalchemy/ext/baked.py
@@ -405,7 +405,7 @@ class Result(object):
)
result = self.session.execute(
- statement, params, execution_options=execution_options
+ statement, params, execution_options=execution_options, future=True
)
if result._attributes.get("is_single_entity", False):
result = result.scalars()
diff --git a/lib/sqlalchemy/future/__init__.py b/lib/sqlalchemy/future/__init__.py
index 6a3581599..37ce46e47 100644
--- a/lib/sqlalchemy/future/__init__.py
+++ b/lib/sqlalchemy/future/__init__.py
@@ -11,7 +11,7 @@
from .engine import Connection # noqa
from .engine import create_engine # noqa
from .engine import Engine # noqa
-from .selectable import Select # noqa
+from ..sql.selectable import Select # noqa
from ..util.langhelpers import public_factory
select = public_factory(Select._create_future_select, ".future.select")
diff --git a/lib/sqlalchemy/future/selectable.py b/lib/sqlalchemy/future/selectable.py
deleted file mode 100644
index 9d0ae7c89..000000000
--- a/lib/sqlalchemy/future/selectable.py
+++ /dev/null
@@ -1,165 +0,0 @@
-from ..sql import coercions
-from ..sql import roles
-from ..sql.base import _generative
-from ..sql.selectable import GenerativeSelect
-from ..sql.selectable import Select as _LegacySelect
-from ..sql.selectable import SelectState
-from ..sql.util import _entity_namespace_key
-
-
-class Select(_LegacySelect):
- _is_future = True
- _setup_joins = ()
- _legacy_setup_joins = ()
- inherit_cache = True
-
- @classmethod
- def _create_select(cls, *entities):
- raise NotImplementedError("use _create_future_select")
-
- @classmethod
- def _create_future_select(cls, *entities):
- r"""Construct a new :class:`_expression.Select` using the 2.
- x style API.
-
- .. versionadded:: 2.0 - the :func:`_future.select` construct is
- the same construct as the one returned by
- :func:`_expression.select`, except that the function only
- accepts the "columns clause" entities up front; the rest of the
- state of the SELECT should be built up using generative methods.
-
- Similar functionality is also available via the
- :meth:`_expression.FromClause.select` method on any
- :class:`_expression.FromClause`.
-
- .. seealso::
-
- :ref:`coretutorial_selecting` - Core Tutorial description of
- :func:`_expression.select`.
-
- :param \*entities:
- Entities to SELECT from. For Core usage, this is typically a series
- of :class:`_expression.ColumnElement` and / or
- :class:`_expression.FromClause`
- objects which will form the columns clause of the resulting
- statement. For those objects that are instances of
- :class:`_expression.FromClause` (typically :class:`_schema.Table`
- or :class:`_expression.Alias`
- objects), the :attr:`_expression.FromClause.c`
- collection is extracted
- to form a collection of :class:`_expression.ColumnElement` objects.
-
- This parameter will also accept :class:`_expression.TextClause`
- constructs as
- given, as well as ORM-mapped classes.
-
- """
-
- self = cls.__new__(cls)
- self._raw_columns = [
- coercions.expect(
- roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
- )
- for ent in entities
- ]
-
- GenerativeSelect.__init__(self)
-
- return self
-
- def filter(self, *criteria):
- """A synonym for the :meth:`_future.Select.where` method."""
-
- return self.where(*criteria)
-
- def _exported_columns_iterator(self):
- meth = SelectState.get_plugin_class(self).exported_columns_iterator
- return meth(self)
-
- def _filter_by_zero(self):
- if self._setup_joins:
- meth = SelectState.get_plugin_class(
- self
- ).determine_last_joined_entity
- _last_joined_entity = meth(self)
- if _last_joined_entity is not None:
- return _last_joined_entity
-
- if self._from_obj:
- return self._from_obj[0]
-
- return self._raw_columns[0]
-
- def filter_by(self, **kwargs):
- r"""Apply the given filtering criterion as a WHERE clause
- to this select.
-
- """
- from_entity = self._filter_by_zero()
-
- clauses = [
- _entity_namespace_key(from_entity, key) == value
- for key, value in kwargs.items()
- ]
- return self.filter(*clauses)
-
- @property
- def column_descriptions(self):
- """Return a 'column descriptions' structure which may be
- plugin-specific.
-
- """
- meth = SelectState.get_plugin_class(self).get_column_descriptions
- return meth(self)
-
- @_generative
- def join(self, target, onclause=None, isouter=False, full=False):
- r"""Create a SQL JOIN against this :class:`_expression.Select`
- object's criterion
- and apply generatively, returning the newly resulting
- :class:`_expression.Select`.
-
-
- """
- target = coercions.expect(
- roles.JoinTargetRole, target, apply_propagate_attrs=self
- )
- if onclause is not None:
- onclause = coercions.expect(roles.OnClauseRole, onclause)
- self._setup_joins += (
- (target, onclause, None, {"isouter": isouter, "full": full}),
- )
-
- @_generative
- def join_from(
- self, from_, target, onclause=None, isouter=False, full=False
- ):
- r"""Create a SQL JOIN against this :class:`_expression.Select`
- object's criterion
- and apply generatively, returning the newly resulting
- :class:`_expression.Select`.
-
-
- """
- # note the order of parsing from vs. target is important here, as we
- # are also deriving the source of the plugin (i.e. the subject mapper
- # in an ORM query) which should favor the "from_" over the "target"
-
- from_ = coercions.expect(
- roles.FromClauseRole, from_, apply_propagate_attrs=self
- )
- target = coercions.expect(
- roles.JoinTargetRole, target, apply_propagate_attrs=self
- )
-
- self._setup_joins += (
- (target, onclause, from_, {"isouter": isouter, "full": full}),
- )
-
- def outerjoin(self, target, onclause=None, full=False):
- """Create a left outer join.
-
-
-
- """
- return self.join(target, onclause=onclause, isouter=True, full=full,)
diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py
index 09163d4e9..d5f001db1 100644
--- a/lib/sqlalchemy/orm/context.py
+++ b/lib/sqlalchemy/orm/context.py
@@ -25,6 +25,7 @@ from ..sql import expression
from ..sql import roles
from ..sql import util as sql_util
from ..sql import visitors
+from ..sql.base import _entity_namespace_key
from ..sql.base import _select_iterables
from ..sql.base import CacheableOptions
from ..sql.base import CompileState
@@ -241,8 +242,6 @@ class ORMCompileState(CompileState):
# were passed to session.execute:
# session.execute(legacy_select([User.id, User.name]))
# see test_query->test_legacy_tuple_old_select
- if not statement._is_future:
- return result
load_options = execution_options.get(
"_sa_orm_load_options", QueryContext.default_load_options
@@ -399,6 +398,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
compound_eager_adapter = None
correlate = None
+ correlate_except = None
_where_criteria = ()
_having_criteria = ()
@@ -406,9 +406,6 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
def create_for_statement(cls, statement, compiler, **kw):
"""compiler hook, we arrive here from compiler.visit_select() only."""
- if not statement._is_future:
- return SelectState(statement, compiler, **kw)
-
if compiler is not None:
toplevel = not compiler.stack
compiler._rewrites_selected_columns = True
@@ -592,6 +589,13 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
for s in query._correlate
)
)
+ elif query._correlate_except:
+ self.correlate_except = tuple(
+ util.flatten_iterator(
+ sql_util.surface_selectables(s) if s is not None else None
+ for s in query._correlate_except
+ )
+ )
elif not query._auto_correlate:
self.correlate = (None,)
@@ -827,6 +831,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
hints=self.select_statement._hints,
statement_hints=self.select_statement._statement_hints,
correlate=self.correlate,
+ correlate_except=self.correlate_except,
**self._select_args
)
@@ -902,6 +907,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
hints=self.select_statement._hints,
statement_hints=self.select_statement._statement_hints,
correlate=self.correlate,
+ correlate_except=self.correlate_except,
**self._select_args
)
@@ -921,6 +927,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
hints,
statement_hints,
correlate,
+ correlate_except,
limit_clause,
offset_clause,
distinct,
@@ -972,6 +979,11 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
if correlate:
statement.correlate.non_generative(statement, *correlate)
+ if correlate_except:
+ statement.correlate_except.non_generative(
+ statement, *correlate_except
+ )
+
return statement
def _adapt_polymorphic_element(self, element):
@@ -1222,7 +1234,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
# string given, e.g. query(Foo).join("bar").
# we look to the left entity or what we last joined
# towards
- onclause = sql.util._entity_namespace_key(
+ onclause = _entity_namespace_key(
inspect(self._joinpoint_zero()), onclause
)
@@ -1243,9 +1255,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
info = inspect(jp0)
if getattr(info, "mapper", None) is onclause._parententity:
- onclause = sql.util._entity_namespace_key(
- info, onclause.key
- )
+ onclause = _entity_namespace_key(info, onclause.key)
# legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^
if isinstance(onclause, interfaces.PropComparator):
diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py
index abb8ce32d..55c2b79f5 100644
--- a/lib/sqlalchemy/orm/loading.py
+++ b/lib/sqlalchemy/orm/loading.py
@@ -431,6 +431,7 @@ def load_on_pk_identity(
params=load_options._params,
execution_options={"_sa_orm_load_options": load_options},
bind_arguments=bind_arguments,
+ future=True,
)
.unique()
.scalars()
diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py
index 45ac2442a..1b2779c00 100644
--- a/lib/sqlalchemy/orm/persistence.py
+++ b/lib/sqlalchemy/orm/persistence.py
@@ -29,17 +29,17 @@ from .. import future
from .. import sql
from .. import util
from ..engine import result as _result
-from ..future import select as future_select
from ..sql import coercions
from ..sql import expression
from ..sql import operators
from ..sql import roles
+from ..sql import select
+from ..sql.base import _entity_namespace_key
from ..sql.base import CompileState
from ..sql.base import Options
from ..sql.dml import DeleteDMLState
from ..sql.dml import UpdateDMLState
from ..sql.elements import BooleanClauseList
-from ..sql.util import _entity_namespace_key
def _bulk_insert(
@@ -887,7 +887,7 @@ def _emit_update_statements(
)
)
- stmt = table.update(clauses)
+ stmt = table.update().where(clauses)
return stmt
cached_stmt = base_mapper._memo(("update", table), update_stmt)
@@ -1280,7 +1280,7 @@ def _emit_post_update_statements(
)
)
- stmt = table.update(clauses)
+ stmt = table.update().where(clauses)
if mapper.version_id_col is not None:
stmt = stmt.return_defaults(mapper.version_id_col)
@@ -1394,7 +1394,7 @@ def _emit_delete_statements(
)
)
- return table.delete(clauses)
+ return table.delete().where(clauses)
statement = base_mapper._memo(("delete", table), delete_stmt)
for connection, recs in groupby(delete, lambda rec: rec[1]): # connection
@@ -1950,7 +1950,7 @@ class BulkUDCompileState(CompileState):
for k, v in iterator:
if mapper:
if isinstance(k, util.string_types):
- desc = sql.util._entity_namespace_key(mapper, k)
+ desc = _entity_namespace_key(mapper, k)
values.extend(desc._bulk_update_tuples(v))
elif "entity_namespace" in k._annotations:
k_anno = k._annotations
@@ -1999,7 +1999,7 @@ class BulkUDCompileState(CompileState):
):
mapper = update_options._subject_mapper
- select_stmt = future_select(
+ select_stmt = select(
*(mapper.primary_key + (mapper.select_identity_token,))
)
select_stmt._where_criteria = statement._where_criteria
@@ -2017,6 +2017,7 @@ class BulkUDCompileState(CompileState):
execution_options,
bind_arguments,
_add_event=skip_for_full_returning,
+ future=True,
)
matched_rows = result.fetchall()
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 1ca65c733..acc76094b 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -45,12 +45,13 @@ from .. import inspection
from .. import log
from .. import sql
from .. import util
-from ..future.selectable import Select as FutureSelect
from ..sql import coercions
from ..sql import expression
from ..sql import roles
+from ..sql import Select
from ..sql import util as sql_util
from ..sql.annotation import SupportsCloneAnnotations
+from ..sql.base import _entity_namespace_key
from ..sql.base import _generative
from ..sql.base import Executable
from ..sql.selectable import _SelectFromElements
@@ -61,7 +62,6 @@ from ..sql.selectable import HasSuffixes
from ..sql.selectable import LABEL_STYLE_NONE
from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
from ..sql.selectable import SelectStatementGrouping
-from ..sql.util import _entity_namespace_key
from ..sql.visitors import InternalTraversal
from ..util import collections_abc
@@ -419,7 +419,7 @@ class Query(
stmt._propagate_attrs = self._propagate_attrs
else:
# Query / select() internal attributes are 99% cross-compatible
- stmt = FutureSelect.__new__(FutureSelect)
+ stmt = Select.__new__(Select)
stmt.__dict__.update(self.__dict__)
stmt.__dict__.update(
_label_style=self._label_style,
@@ -2836,6 +2836,7 @@ class Query(
statement,
params,
execution_options={"_sa_orm_load_options": self.load_options},
+ future=True,
)
# legacy: automatically set scalars, unique
@@ -3209,6 +3210,7 @@ class Query(
delete_,
self.load_options._params,
execution_options={"synchronize_session": synchronize_session},
+ future=True,
)
bulk_del.result = result
self.session.dispatch.after_bulk_delete(bulk_del)
@@ -3363,6 +3365,7 @@ class Query(
upd,
self.load_options._params,
execution_options={"synchronize_session": synchronize_session},
+ future=True,
)
bulk_ud.result = result
self.session.dispatch.after_bulk_update(bulk_ud)
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index bedc54153..0be15260e 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -1352,12 +1352,18 @@ class RelationshipProperty(StrategizedProperty):
crit = j & sql.True_._ifnone(criterion)
if secondary is not None:
- ex = sql.exists(
- [1], crit, from_obj=[dest, secondary]
- ).correlate_except(dest, secondary)
+ ex = (
+ sql.exists(1)
+ .where(crit)
+ .select_from(dest, secondary)
+ .correlate_except(dest, secondary)
+ )
else:
- ex = sql.exists([1], crit, from_obj=dest).correlate_except(
- dest
+ ex = (
+ sql.exists(1)
+ .where(crit)
+ .select_from(dest)
+ .correlate_except(dest)
)
return ex
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index abc990f7b..f4f7374e4 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -118,6 +118,7 @@ class ORMExecuteState(util.MemoizedSlots):
"_compile_state_cls",
"_starting_event_idx",
"_events_todo",
+ "_future",
)
def __init__(
@@ -129,6 +130,7 @@ class ORMExecuteState(util.MemoizedSlots):
bind_arguments,
compile_state_cls,
events_todo,
+ future,
):
self.session = session
self.statement = statement
@@ -137,6 +139,7 @@ class ORMExecuteState(util.MemoizedSlots):
self.bind_arguments = bind_arguments
self._compile_state_cls = compile_state_cls
self._events_todo = list(events_todo)
+ self._future = future
def _remaining_events(self):
return self._events_todo[self._starting_event_idx + 1 :]
@@ -212,6 +215,7 @@ class ORMExecuteState(util.MemoizedSlots):
_execution_options,
_bind_arguments,
_parent_execute_state=self,
+ future=self._future,
)
@property
@@ -924,6 +928,7 @@ class Session(_SessionClassMethods):
self,
bind=None,
autoflush=True,
+ future=False,
expire_on_commit=True,
autocommit=False,
twophase=False,
@@ -1039,6 +1044,26 @@ class Session(_SessionClassMethods):
so that all attribute/object access subsequent to a completed
transaction will load from the most recent database state.
+ :param future: if True, use 2.0 style behavior for the
+ :meth:`_orm.Session.execute` method. This includes that the
+ :class:`_engine.Result` object returned will return new-style
+ tuple rows, as well as that Core constructs such as
+ :class:`_sql.Select`,
+ :class:`_sql.Update` and :class:`_sql.Delete` will be interpreted
+ in an ORM context if they are made against ORM entities rather than
+ plain :class:`.Table` metadata objects.
+
+ The "future" flag is also available on a per-execution basis
+ using the :paramref:`_orm.Session.execute.future` flag.
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :ref:`migration_20_toplevel`
+
+ :ref:`migration_20_result_rows`
+
:param info: optional dictionary of arbitrary data to be associated
with this :class:`.Session`. Is available via the
:attr:`.Session.info` attribute. Note the dictionary is copied at
@@ -1071,6 +1096,7 @@ class Session(_SessionClassMethods):
self._flushing = False
self._warn_on_events = False
self._transaction = None
+ self.future = future
self.hash_key = _new_sessionid()
self.autoflush = autoflush
self.autocommit = autocommit
@@ -1387,6 +1413,7 @@ class Session(_SessionClassMethods):
params=None,
execution_options=util.immutabledict(),
bind_arguments=None,
+ future=False,
_parent_execute_state=None,
_add_event=None,
**kw
@@ -1493,6 +1520,14 @@ class Session(_SessionClassMethods):
Contents of this dictionary are passed to the
:meth:`.Session.get_bind` method.
+ :param future:
+ Use future style execution for this statement. This is
+ the same effect as the :paramref:`_orm.Session.future` flag,
+ except at the level of this single statement execution. See
+ that flag for details.
+
+ .. versionadded:: 1.4
+
:param mapper:
deprecated; use the bind_arguments dictionary
@@ -1518,15 +1553,18 @@ class Session(_SessionClassMethods):
"""
statement = coercions.expect(roles.CoerceTextStatementRole, statement)
+ future = future or self.future
+
if not bind_arguments:
bind_arguments = kw
elif kw:
bind_arguments.update(kw)
- if (
+ if future and (
statement._propagate_attrs.get("compile_state_plugin", None)
== "orm"
):
+ # note that even without "future" mode, we need
compile_state_cls = CompileState._get_plugin_class_for_plugin(
statement, "orm"
)
@@ -1547,7 +1585,7 @@ class Session(_SessionClassMethods):
)
else:
bind_arguments.setdefault("clause", statement)
- if statement._is_future:
+ if future:
execution_options = util.immutabledict().merge_with(
execution_options, {"future_result": True}
)
@@ -1568,6 +1606,7 @@ class Session(_SessionClassMethods):
bind_arguments,
compile_state_cls,
events_todo,
+ future,
)
for idx, fn in enumerate(events_todo):
orm_exec_state._starting_event_idx = idx
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index 6cdab8eac..4bc6d8280 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -1388,3 +1388,25 @@ def _bind_or_error(schemaitem, msg=None):
)
raise exc.UnboundExecutionError(msg)
return bind
+
+
+def _entity_namespace_key(entity, key):
+ """Return an entry from an entity_namespace.
+
+
+ Raises :class:`_exc.InvalidRequestError` rather than attribute error
+ on not found.
+
+ """
+
+ ns = entity.entity_namespace
+ try:
+ return getattr(ns, key)
+ except AttributeError as err:
+ util.raise_(
+ exc.InvalidRequestError(
+ 'Entity namespace for "%s" has no property "%s"'
+ % (entity, key)
+ ),
+ replace_context=err,
+ )
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 37441a125..d60c63363 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -185,7 +185,7 @@ tablesample = public_factory(
lateral = public_factory(Lateral._factory, ".sql.expression.lateral")
or_ = public_factory(BooleanClauseList.or_, ".sql.expression.or_")
bindparam = public_factory(BindParameter, ".sql.expression.bindparam")
-select = public_factory(Select, ".sql.expression.select")
+select = public_factory(Select._create, ".sql.expression.select")
text = public_factory(TextClause._create_text, ".sql.expression.text")
table = public_factory(TableClause, ".sql.expression.table")
column = public_factory(ColumnClause, ".sql.expression.column")
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 832da1a57..12fcc00c3 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -24,6 +24,7 @@ from .annotation import SupportsCloneAnnotations
from .base import _clone
from .base import _cloned_difference
from .base import _cloned_intersection
+from .base import _entity_namespace_key
from .base import _expand_cloned
from .base import _from_objects
from .base import _generative
@@ -83,7 +84,7 @@ def subquery(alias, *args, **kwargs):
:func:`_expression.select` function.
"""
- return Select(*args, **kwargs).subquery(alias)
+ return Select.create_legacy_select(*args, **kwargs).subquery(alias)
class ReturnsRows(roles.ReturnsRowsRole, ClauseElement):
@@ -468,8 +469,38 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
_use_schema_map = False
- def select(self, whereclause=None, **params):
- """Return a SELECT of this :class:`_expression.FromClause`.
+ @util.deprecated_params(
+ whereclause=(
+ "2.0",
+ "The :paramref:`_sql.FromClause.select().whereclause` parameter "
+ "is deprecated and will be removed in version 2.0. "
+ "Please make use of "
+ "the :meth:`.Select.where` "
+ "method to add WHERE criteria to the SELECT statement.",
+ ),
+ kwargs=(
+ "2.0",
+ "The :meth:`_sql.FromClause.select` method will no longer accept "
+ "keyword arguments in version 2.0. Please use generative methods "
+ "from the "
+ ":class:`_sql.Select` construct in order to apply additional "
+ "modifications.",
+ ),
+ )
+ def select(self, whereclause=None, **kwargs):
+ r"""Return a SELECT of this :class:`_expression.FromClause`.
+
+
+ e.g.::
+
+ stmt = some_table.select().where(some_table.c.id == 5)
+
+ :param whereclause: a WHERE clause, equivalent to calling the
+ :meth:`_sql.Select.where` method.
+
+ :param \**kwargs: additional keyword arguments are passed to the
+ legacy constructor for :class:`_sql.Select` described at
+ :meth:`_sql.Select.create_legacy_select`.
.. seealso::
@@ -477,8 +508,9 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
method which allows for arbitrary column lists.
"""
-
- return Select([self], whereclause, **params)
+ if whereclause is not None:
+ kwargs["whereclause"] = whereclause
+ return Select._create_select_from_fromclause(self, [self], **kwargs)
def join(self, right, onclause=None, isouter=False, full=False):
"""Return a :class:`_expression.Join` from this
@@ -1138,24 +1170,45 @@ class Join(roles.DMLTableRole, FromClause):
"join explicitly." % (a.description, b.description)
)
+ @util.deprecated_params(
+ whereclause=(
+ "2.0",
+ "The :paramref:`_sql.Join.select().whereclause` parameter "
+ "is deprecated and will be removed in version 2.0. "
+ "Please make use of "
+ "the :meth:`.Select.where` "
+ "method to add WHERE criteria to the SELECT statement.",
+ ),
+ kwargs=(
+ "2.0",
+ "The :meth:`_sql.Join.select` method will no longer accept "
+ "keyword arguments in version 2.0. Please use generative "
+ "methods from the "
+ ":class:`_sql.Select` construct in order to apply additional "
+ "modifications.",
+ ),
+ )
def select(self, whereclause=None, **kwargs):
r"""Create a :class:`_expression.Select` from this
:class:`_expression.Join`.
- The equivalent long-hand form, given a :class:`_expression.Join`
- object
- ``j``, is::
+ E.g.::
+
+ stmt = table_a.join(table_b, table_a.c.id == table_b.c.a_id)
- from sqlalchemy import select
- j = select([j.left, j.right], **kw).\
- where(whereclause).\
- select_from(j)
+ stmt = stmt.select()
- :param whereclause: the WHERE criterion that will be sent to
- the :func:`select()` function
+ The above will produce a SQL string resembling::
- :param \**kwargs: all other kwargs are sent to the
- underlying :func:`select()` function.
+ SELECT table_a.id, table_a.col, table_b.id, table_b.a_id
+ FROM table_a JOIN table_b ON table_a.id = table_b.a_id
+
+ :param whereclause: WHERE criteria, same as calling
+ :meth:`_sql.Select.where` on the resulting statement
+
+ :param \**kwargs: additional keyword arguments are passed to the
+ legacy constructor for :class:`_sql.Select` described at
+ :meth:`_sql.Select.create_legacy_select`.
"""
collist = [self.left, self.right]
@@ -2444,30 +2497,6 @@ class SelectBase(
def select(self, *arg, **kw):
return self._implicit_subquery.select(*arg, **kw)
- @util.deprecated(
- "1.4",
- "The :meth:`_expression.SelectBase.join` method is deprecated "
- "and will be removed in a future release; this method implicitly "
- "creates a subquery that should be explicit. "
- "Please call :meth:`_expression.SelectBase.subquery` "
- "first in order to create "
- "a subquery, which then can be selected.",
- )
- def join(self, *arg, **kw):
- return self._implicit_subquery.join(*arg, **kw)
-
- @util.deprecated(
- "1.4",
- "The :meth:`_expression.SelectBase.outerjoin` method is deprecated "
- "and will be removed in a future release; this method implicitly "
- "creates a subquery that should be explicit. "
- "Please call :meth:`_expression.SelectBase.subquery` "
- "first in order to create "
- "a subquery, which then can be selected.",
- )
- def outerjoin(self, *arg, **kw):
- return self._implicit_subquery.outerjoin(*arg, **kw)
-
@HasMemoized.memoized_attribute
def _implicit_subquery(self):
return self.subquery()
@@ -3103,6 +3132,16 @@ class CompoundSelect(HasCompileState, GenerativeSelect):
for s in selects
]
+ if kwargs and util.SQLALCHEMY_WARN_20:
+ util.warn_deprecated_20(
+ "Set functions such as union(), union_all(), extract(), etc. "
+ "in SQLAlchemy 2.0 will accept a "
+ "series of SELECT statements only. "
+ "Please use generative methods such as order_by() for "
+ "additional modifications to this CompoundSelect.",
+ stacklevel=4,
+ )
+
GenerativeSelect.__init__(self, **kwargs)
@classmethod
@@ -3770,7 +3809,6 @@ class Select(
__visit_name__ = "select"
- _is_future = False
_setup_joins = ()
_legacy_setup_joins = ()
@@ -3817,38 +3855,21 @@ class Select(
]
@classmethod
- def _create_select(cls, *entities):
- r"""Construct an old style :class:`_expression.Select` using the
- the 2.x style constructor.
-
- """
-
- self = cls.__new__(cls)
- self._raw_columns = [
- coercions.expect(roles.ColumnsClauseRole, ent) for ent in entities
- ]
-
- GenerativeSelect.__init__(self)
-
- return self
-
- @classmethod
def _create_select_from_fromclause(cls, target, entities, *arg, **kw):
if arg or kw:
- if util.SQLALCHEMY_WARN_20:
- util.warn_deprecated_20(
- "Passing arguments to %s.select() is deprecated and "
- "will be removed in SQLAlchemy 2.0. "
- "Please use generative "
- "methods such as select().where(), etc."
- % (target.__class__.__name__,)
- )
- return Select(entities, *arg, **kw)
+ return Select.create_legacy_select(entities, *arg, **kw)
else:
return Select._create_select(*entities)
- def __init__(
- self,
+ @classmethod
+ @util.deprecated(
+ "2.0",
+ "The legacy calling style of :func:`_sql.select` is deprecated and "
+ "will be removed in SQLAlchemy 2.0. Please use the new calling "
+ "style described at :func:`_sql.select`.",
+ )
+ def create_legacy_select(
+ cls,
columns=None,
whereclause=None,
from_obj=None,
@@ -3859,18 +3880,25 @@ class Select(
suffixes=None,
**kwargs
):
- """Construct a new :class:`_expression.Select` using the 1.x style
- API.
+ """Construct a new :class:`_expression.Select` using the 1.x style API.
+
+ This method is called implicitly when the :func:`_expression.select`
+ construct is used and the first argument is a Python list or other
+ plain sequence object, which is taken to refer to the columns
+ collection.
+
+ .. versionchanged:: 1.4 Added the :meth:`.Select.create_legacy_select`
+ constructor which documents the calling style in use when the
+ :func:`.select` construct is invoked using 1.x-style arguments.
Similar functionality is also available via the
:meth:`_expression.FromClause.select` method on any
:class:`_expression.FromClause`.
- All arguments which accept :class:`_expression.ClauseElement`
- arguments also
- accept string arguments, which will be converted as appropriate into
- either :func:`_expression.text` or
- :func:`_expression.literal_column` constructs.
+ All arguments which accept :class:`_expression.ClauseElement` arguments
+ also accept string arguments, which will be converted as appropriate
+ into either :func:`_expression.text()` or
+ :func:`_expression.literal_column()` constructs.
.. seealso::
@@ -4054,14 +4082,7 @@ class Select(
:meth:`_expression.Select.apply_labels`
"""
- if util.SQLALCHEMY_WARN_20:
- util.warn_deprecated_20(
- "The select() function in SQLAlchemy 2.0 will accept a "
- "series of columns / tables and other entities only, "
- "passed positionally. For forwards compatibility, use the "
- "sqlalchemy.future.select() construct.",
- stacklevel=4,
- )
+ self = cls.__new__(cls)
self._auto_correlate = correlate
@@ -4079,8 +4100,10 @@ class Select(
except TypeError as err:
util.raise_(
exc.ArgumentError(
- "columns argument to select() must "
- "be a Python list or other iterable"
+ "select() construct created in legacy mode, i.e. with "
+ "keyword arguments, must provide the columns argument as "
+ "a Python list or other iterable.",
+ code="c9ae",
),
from_=err,
)
@@ -4108,12 +4131,247 @@ class Select(
self._setup_suffixes(suffixes)
GenerativeSelect.__init__(self, **kwargs)
+ return self
+
+ @classmethod
+ def _create_future_select(cls, *entities):
+ r"""Construct a new :class:`_expression.Select` using the 2.
+ x style API.
+
+ .. versionadded:: 1.4 - The :func:`_sql.select` function now accepts
+ column arguments positionally. The top-level :func:`_sql.select`
+ function will automatically use the 1.x or 2.x style API based on
+ the incoming argumnents; using :func:`_future.select` from the
+ ``sqlalchemy.future`` module will enforce that only the 2.x style
+ constructor is used.
+
+ Similar functionality is also available via the
+ :meth:`_expression.FromClause.select` method on any
+ :class:`_expression.FromClause`.
+
+ .. seealso::
+
+ :ref:`coretutorial_selecting` - Core Tutorial description of
+ :func:`_expression.select`.
+
+ :param \*entities:
+ Entities to SELECT from. For Core usage, this is typically a series
+ of :class:`_expression.ColumnElement` and / or
+ :class:`_expression.FromClause`
+ objects which will form the columns clause of the resulting
+ statement. For those objects that are instances of
+ :class:`_expression.FromClause` (typically :class:`_schema.Table`
+ or :class:`_expression.Alias`
+ objects), the :attr:`_expression.FromClause.c`
+ collection is extracted
+ to form a collection of :class:`_expression.ColumnElement` objects.
+
+ This parameter will also accept :class:`_expression.TextClause`
+ constructs as
+ given, as well as ORM-mapped classes.
+
+ """
+
+ self = cls.__new__(cls)
+ self._raw_columns = [
+ coercions.expect(
+ roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
+ )
+ for ent in entities
+ ]
+
+ GenerativeSelect.__init__(self)
+
+ return self
+
+ _create_select = _create_future_select
+
+ @classmethod
+ def _create(cls, *args, **kw):
+ r"""Create a :class:`.Select` using either the 1.x or 2.0 constructor
+ style.
+
+ For the legacy calling style, see :meth:`.Select.create_legacy_select`.
+ If the first argument passed is a Python sequence or if keyword
+ arguments are present, this style is used.
+
+ .. versionadded:: 2.0 - the :func:`_future.select` construct is
+ the same construct as the one returned by
+ :func:`_expression.select`, except that the function only
+ accepts the "columns clause" entities up front; the rest of the
+ state of the SELECT should be built up using generative methods.
+
+ Similar functionality is also available via the
+ :meth:`_expression.FromClause.select` method on any
+ :class:`_expression.FromClause`.
+
+ .. seealso::
+
+ :ref:`coretutorial_selecting` - Core Tutorial description of
+ :func:`_expression.select`.
+
+ :param \*entities:
+ Entities to SELECT from. For Core usage, this is typically a series
+ of :class:`_expression.ColumnElement` and / or
+ :class:`_expression.FromClause`
+ objects which will form the columns clause of the resulting
+ statement. For those objects that are instances of
+ :class:`_expression.FromClause` (typically :class:`_schema.Table`
+ or :class:`_expression.Alias`
+ objects), the :attr:`_expression.FromClause.c`
+ collection is extracted
+ to form a collection of :class:`_expression.ColumnElement` objects.
+
+ This parameter will also accept :class:`_expression.TextClause`
+ constructs as given, as well as ORM-mapped classes.
+
+ """
+ if (args and isinstance(args[0], list)) or kw:
+ return cls.create_legacy_select(*args, **kw)
+ else:
+ return cls._create_future_select(*args)
+
+ def __init__(self,):
+ raise NotImplementedError()
def _scalar_type(self):
elem = self._raw_columns[0]
cols = list(elem._select_iterable)
return cols[0].type
+ def filter(self, *criteria):
+ """A synonym for the :meth:`_future.Select.where` method."""
+
+ return self.where(*criteria)
+
+ def _filter_by_zero(self):
+ if self._setup_joins:
+ meth = SelectState.get_plugin_class(
+ self
+ ).determine_last_joined_entity
+ _last_joined_entity = meth(self)
+ if _last_joined_entity is not None:
+ return _last_joined_entity
+
+ if self._from_obj:
+ return self._from_obj[0]
+
+ return self._raw_columns[0]
+
+ def filter_by(self, **kwargs):
+ r"""apply the given filtering criterion as a WHERE clause
+ to this select.
+
+ """
+ from_entity = self._filter_by_zero()
+
+ clauses = [
+ _entity_namespace_key(from_entity, key) == value
+ for key, value in kwargs.items()
+ ]
+ return self.filter(*clauses)
+
+ @property
+ def column_descriptions(self):
+ """Return a 'column descriptions' structure which may be
+ plugin-specific.
+
+ """
+ meth = SelectState.get_plugin_class(self).get_column_descriptions
+ return meth(self)
+
+ @_generative
+ def join(self, target, onclause=None, isouter=False, full=False):
+ r"""Create a SQL JOIN against this :class:`_expresson.Select`
+ object's criterion
+ and apply generatively, returning the newly resulting
+ :class:`_expression.Select`.
+
+ .. versionchanged:: 1.4 :meth:`_expression.Select.join` now modifies
+ the FROM list of the :class:`.Select` object in place, rather than
+ implicitly producing a subquery.
+
+ :param target: target table to join towards
+
+ :param onclause: ON clause of the join.
+
+ :param isouter: if True, generate LEFT OUTER join. Same as
+ :meth:`_expression.Select.outerjoin`.
+
+ :param full: if True, generate FULL OUTER join.
+
+ .. seealso::
+
+ :meth:`_expression.Select.join_from`
+
+ """
+ target = coercions.expect(
+ roles.JoinTargetRole, target, apply_propagate_attrs=self
+ )
+ if onclause is not None:
+ onclause = coercions.expect(roles.OnClauseRole, onclause)
+ self._setup_joins += (
+ (target, onclause, None, {"isouter": isouter, "full": full}),
+ )
+
+ @_generative
+ def join_from(
+ self, from_, target, onclause=None, isouter=False, full=False
+ ):
+ r"""Create a SQL JOIN against this :class:`_expresson.Select`
+ object's criterion
+ and apply generatively, returning the newly resulting
+ :class:`_expression.Select`.
+
+ .. versionadded:: 1.4
+
+ :param from\_: the left side of the join, will be rendered in the
+ FROM clause and is roughly equivalent to using the
+ :meth:`.Select.select_from` method.
+
+ :param target: target table to join towards
+
+ :param onclause: ON clause of the join.
+
+ :param isouter: if True, generate LEFT OUTER join. Same as
+ :meth:`_expression.Select.outerjoin`.
+
+ :param full: if True, generate FULL OUTER join.
+
+ .. seealso::
+
+ :meth:`_expression.Select.join`
+
+ """
+ # note the order of parsing from vs. target is important here, as we
+ # are also deriving the source of the plugin (i.e. the subject mapper
+ # in an ORM query) which should favor the "from_" over the "target"
+
+ from_ = coercions.expect(
+ roles.FromClauseRole, from_, apply_propagate_attrs=self
+ )
+ target = coercions.expect(
+ roles.JoinTargetRole, target, apply_propagate_attrs=self
+ )
+ if onclause is not None:
+ onclause = coercions.expect(roles.OnClauseRole, onclause)
+
+ self._setup_joins += (
+ (target, onclause, from_, {"isouter": isouter, "full": full}),
+ )
+
+ def outerjoin(self, target, onclause=None, full=False):
+ """Create a left outer join.
+
+ Parameters are the same as that of :meth:`_expression.Select.join`.
+
+ .. versionchanged:: 1.4 :meth:`_expression.Select.outerjoin` now
+ modifies the FROM list of the :class:`.Select` object in place,
+ rather than implicitly producing a subquery.
+
+ """
+ return self.join(target, onclause=onclause, isouter=True, full=full,)
+
@property
def froms(self):
"""Return the displayed list of :class:`_expression.FromClause`
@@ -4642,8 +4900,12 @@ class Select(
return ColumnCollection(collection).as_immutable()
+ # def _exported_columns_iterator(self):
+ # return _select_iterables(self._raw_columns)
+
def _exported_columns_iterator(self):
- return _select_iterables(self._raw_columns)
+ meth = SelectState.get_plugin_class(self).exported_columns_iterator
+ return meth(self)
def _ensure_disambiguated_names(self):
if self._label_style is LABEL_STYLE_NONE:
@@ -4922,37 +5184,30 @@ class Exists(UnaryExpression):
inherit_cache = True
def __init__(self, *args, **kwargs):
- """Construct a new :class:`_expression.Exists` against an existing
- :class:`_expression.Select` object.
+ """Construct a new :class:`_expression.Exists` construct.
- Calling styles are of the following forms::
+ The modern form of :func:`.exists` is to invoke with no arguments,
+ which will produce an ``"EXISTS *"`` construct. A WHERE clause
+ is then added using the :meth:`.Exists.where` method::
- # use on an existing select()
- s = select([table.c.col1]).where(table.c.col2==5)
- s_e = exists(s)
+ exists_criteria = exists().where(table1.c.col1 == table2.c.col2)
- # an exists is usually used in a where of another select
- # to produce a WHERE EXISTS (SELECT ... )
- select([table.c.col1]).where(s_e)
+ The EXISTS criteria is then used inside of an enclosing SELECT::
- # but can also be used in a select to produce a
- # SELECT EXISTS (SELECT ... ) query
- select([s_e])
+ stmt = select(table1.c.col1).where(exists_criteria)
- # construct a select() at once
- exists(['*'], **select_arguments).where(criterion)
+ The above statement will then be of the form::
- # columns argument is optional, generates "EXISTS (SELECT *)"
- # by default.
- exists().where(table.c.col2==5)
+ SELECT col1 FROM table1 WHERE EXISTS
+ (SELECT * FROM table2 WHERE table2.col2 = table1.col1)
"""
if args and isinstance(args[0], (SelectBase, ScalarSelect)):
s = args[0]
else:
if not args:
- args = ([literal_column("*")],)
- s = Select(*args, **kwargs).scalar_subquery()
+ args = (literal_column("*"),)
+ s = Select._create(*args, **kwargs).scalar_subquery()
UnaryExpression.__init__(
self,
@@ -4967,10 +5222,52 @@ class Exists(UnaryExpression):
element = fn(element)
return element.self_group(against=operators.exists)
- def select(self, whereclause=None, **params):
+ @util.deprecated_params(
+ whereclause=(
+ "2.0",
+ "The :paramref:`_sql.Exists.select().whereclause` parameter "
+ "is deprecated and will be removed in version 2.0. "
+ "Please make use "
+ "of the :meth:`.Select.where` "
+ "method to add WHERE criteria to the SELECT statement.",
+ ),
+ kwargs=(
+ "2.0",
+ "The :meth:`_sql.Exists.select` method will no longer accept "
+ "keyword arguments in version 2.0. "
+ "Please use generative methods from the "
+ ":class:`_sql.Select` construct in order to apply additional "
+ "modifications.",
+ ),
+ )
+ def select(self, whereclause=None, **kwargs):
+ r"""Return a SELECT of this :class:`_expression.Exists`.
+
+ e.g.::
+
+ stmt = exists(some_table.c.id).where(some_table.c.id == 5).select()
+
+ This will produce a statement resembling::
+
+ SELECT EXISTS (SELECT id FROM some_table WHERE some_table = :param) AS anon_1
+
+ :param whereclause: a WHERE clause, equivalent to calling the
+ :meth:`_sql.Select.where` method.
+
+ :param **kwargs: additional keyword arguments are passed to the
+ legacy constructor for :class:`_sql.Select` described at
+ :meth:`_sql.Select.create_legacy_select`.
+
+ .. seealso::
+
+ :func:`_expression.select` - general purpose
+ method which allows for arbitrary column lists.
+
+ """ # noqa
+
if whereclause is not None:
- params["whereclause"] = whereclause
- return Select._create_select_from_fromclause(self, [self], **params)
+ kwargs["whereclause"] = whereclause
+ return Select._create_select_from_fromclause(self, [self], **kwargs)
def correlate(self, *fromclause):
e = self._clone()
@@ -4986,7 +5283,7 @@ class Exists(UnaryExpression):
)
return e
- def select_from(self, clause):
+ def select_from(self, *froms):
"""Return a new :class:`_expression.Exists` construct,
applying the given
expression to the :meth:`_expression.Select.select_from`
@@ -4995,7 +5292,7 @@ class Exists(UnaryExpression):
"""
e = self._clone()
- e.element = self._regroup(lambda element: element.select_from(clause))
+ e.element = self._regroup(lambda element: element.select_from(*froms))
return e
def where(self, clause):
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index b803ef912..814253266 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -977,25 +977,3 @@ class ColumnAdapter(ClauseAdapter):
def __setstate__(self, state):
self.__dict__.update(state)
self.columns = util.WeakPopulateDict(self._locate_col)
-
-
-def _entity_namespace_key(entity, key):
- """Return an entry from an entity_namespace.
-
-
- Raises :class:`_exc.InvalidRequestError` rather than attribute error
- on not found.
-
- """
-
- ns = entity.entity_namespace
- try:
- return getattr(ns, key)
- except AttributeError as err:
- util.raise_(
- exc.InvalidRequestError(
- 'Entity namespace for "%s" has no property "%s"'
- % (entity, key)
- ),
- replace_context=err,
- )
diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py
index 998dde66b..1ce59431e 100644
--- a/lib/sqlalchemy/testing/assertions.py
+++ b/lib/sqlalchemy/testing/assertions.py
@@ -170,7 +170,11 @@ def _expect_warnings(
with mock.patch("warnings.warn", our_warn), mock.patch(
"sqlalchemy.util.SQLALCHEMY_WARN_20", True
- ), mock.patch("sqlalchemy.engine.row.LegacyRow._default_key_style", 2):
+ ), mock.patch(
+ "sqlalchemy.util.deprecations.SQLALCHEMY_WARN_20", True
+ ), mock.patch(
+ "sqlalchemy.engine.row.LegacyRow._default_key_style", 2
+ ):
yield
if assert_ and (not py2konly or not compat.py3k):
diff --git a/lib/sqlalchemy/testing/suite/test_types.py b/lib/sqlalchemy/testing/suite/test_types.py
index 00b5fab27..48144f885 100644
--- a/lib/sqlalchemy/testing/suite/test_types.py
+++ b/lib/sqlalchemy/testing/suite/test_types.py
@@ -114,9 +114,7 @@ class _UnicodeFixture(_LiteralRoundTripFixture, fixtures.TestBase):
connection.execute(unicode_table.insert(), {"unicode_data": self.data})
- row = connection.execute(
- select([unicode_table.c.unicode_data])
- ).first()
+ row = connection.execute(select(unicode_table.c.unicode_data)).first()
eq_(row, (self.data,))
assert isinstance(row[0], util.text_type)
@@ -130,7 +128,7 @@ class _UnicodeFixture(_LiteralRoundTripFixture, fixtures.TestBase):
)
rows = connection.execute(
- select([unicode_table.c.unicode_data])
+ select(unicode_table.c.unicode_data)
).fetchall()
eq_(rows, [(self.data,) for i in range(3)])
for row in rows:
@@ -140,18 +138,14 @@ class _UnicodeFixture(_LiteralRoundTripFixture, fixtures.TestBase):
unicode_table = self.tables.unicode_table
connection.execute(unicode_table.insert(), {"unicode_data": None})
- row = connection.execute(
- select([unicode_table.c.unicode_data])
- ).first()
+ row = connection.execute(select(unicode_table.c.unicode_data)).first()
eq_(row, (None,))
def _test_empty_strings(self, connection):
unicode_table = self.tables.unicode_table
connection.execute(unicode_table.insert(), {"unicode_data": u("")})
- row = connection.execute(
- select([unicode_table.c.unicode_data])
- ).first()
+ row = connection.execute(select(unicode_table.c.unicode_data)).first()
eq_(row, (u(""),))
def test_literal(self):
@@ -214,7 +208,7 @@ class TextTest(_LiteralRoundTripFixture, fixtures.TablesTest):
text_table = self.tables.text_table
connection.execute(text_table.insert(), {"text_data": "some text"})
- row = connection.execute(select([text_table.c.text_data])).first()
+ row = connection.execute(select(text_table.c.text_data)).first()
eq_(row, ("some text",))
@testing.requires.empty_strings_text
@@ -222,14 +216,14 @@ class TextTest(_LiteralRoundTripFixture, fixtures.TablesTest):
text_table = self.tables.text_table
connection.execute(text_table.insert(), {"text_data": ""})
- row = connection.execute(select([text_table.c.text_data])).first()
+ row = connection.execute(select(text_table.c.text_data)).first()
eq_(row, ("",))
def test_text_null_strings(self, connection):
text_table = self.tables.text_table
connection.execute(text_table.insert(), {"text_data": None})
- row = connection.execute(select([text_table.c.text_data])).first()
+ row = connection.execute(select(text_table.c.text_data)).first()
eq_(row, (None,))
def test_literal(self):
@@ -302,7 +296,7 @@ class _DateFixture(_LiteralRoundTripFixture, fixtures.TestBase):
connection.execute(date_table.insert(), {"date_data": self.data})
- row = connection.execute(select([date_table.c.date_data])).first()
+ row = connection.execute(select(date_table.c.date_data)).first()
compare = self.compare or self.data
eq_(row, (compare,))
@@ -313,7 +307,7 @@ class _DateFixture(_LiteralRoundTripFixture, fixtures.TestBase):
connection.execute(date_table.insert(), {"date_data": None})
- row = connection.execute(select([date_table.c.date_data])).first()
+ row = connection.execute(select(date_table.c.date_data)).first()
eq_(row, (None,))
@testing.requires.datetime_literals
@@ -332,7 +326,7 @@ class _DateFixture(_LiteralRoundTripFixture, fixtures.TestBase):
date_table.insert(), {"date_data": self.data}
)
id_ = result.inserted_primary_key[0]
- stmt = select([date_table.c.id]).where(
+ stmt = select(date_table.c.id).where(
case(
[
(
@@ -438,7 +432,7 @@ class IntegerTest(_LiteralRoundTripFixture, fixtures.TestBase):
connection.execute(int_table.insert(), {"integer_data": data})
- row = connection.execute(select([int_table.c.integer_data])).first()
+ row = connection.execute(select(int_table.c.integer_data)).first()
eq_(row, (data,))
@@ -545,7 +539,7 @@ class NumericTest(_LiteralRoundTripFixture, fixtures.TestBase):
def test_float_coerce_round_trip(self, connection):
expr = 15.7563
- val = connection.scalar(select([literal(expr)]))
+ val = connection.scalar(select(literal(expr)))
eq_(val, expr)
# this does not work in MySQL, see #4036, however we choose not
@@ -556,14 +550,14 @@ class NumericTest(_LiteralRoundTripFixture, fixtures.TestBase):
def test_decimal_coerce_round_trip(self, connection):
expr = decimal.Decimal("15.7563")
- val = connection.scalar(select([literal(expr)]))
+ val = connection.scalar(select(literal(expr)))
eq_(val, expr)
@testing.emits_warning(r".*does \*not\* support Decimal objects natively")
def test_decimal_coerce_round_trip_w_cast(self, connection):
expr = decimal.Decimal("15.7563")
- val = connection.scalar(select([cast(expr, Numeric(10, 4))]))
+ val = connection.scalar(select(cast(expr, Numeric(10, 4))))
eq_(val, expr)
@testing.requires.precision_numerics_general
@@ -665,9 +659,7 @@ class BooleanTest(_LiteralRoundTripFixture, fixtures.TablesTest):
)
row = connection.execute(
- select(
- [boolean_table.c.value, boolean_table.c.unconstrained_value]
- )
+ select(boolean_table.c.value, boolean_table.c.unconstrained_value)
).first()
eq_(row, (True, False))
@@ -683,9 +675,7 @@ class BooleanTest(_LiteralRoundTripFixture, fixtures.TablesTest):
)
row = connection.execute(
- select(
- [boolean_table.c.value, boolean_table.c.unconstrained_value]
- )
+ select(boolean_table.c.value, boolean_table.c.unconstrained_value)
).first()
eq_(row, (None, None))
@@ -705,13 +695,13 @@ class BooleanTest(_LiteralRoundTripFixture, fixtures.TablesTest):
eq_(
conn.scalar(
- select([boolean_table.c.id]).where(boolean_table.c.value)
+ select(boolean_table.c.id).where(boolean_table.c.value)
),
1,
)
eq_(
conn.scalar(
- select([boolean_table.c.id]).where(
+ select(boolean_table.c.id).where(
boolean_table.c.unconstrained_value
)
),
@@ -719,13 +709,13 @@ class BooleanTest(_LiteralRoundTripFixture, fixtures.TablesTest):
)
eq_(
conn.scalar(
- select([boolean_table.c.id]).where(~boolean_table.c.value)
+ select(boolean_table.c.id).where(~boolean_table.c.value)
),
2,
)
eq_(
conn.scalar(
- select([boolean_table.c.id]).where(
+ select(boolean_table.c.id).where(
~boolean_table.c.unconstrained_value
)
),
@@ -760,7 +750,7 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
data_table.insert(), {"name": "row1", "data": data_element}
)
- row = connection.execute(select([data_table.c.data])).first()
+ row = connection.execute(select(data_table.c.data)).first()
eq_(row, (data_element,))
@@ -806,7 +796,7 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
expr = data_table.c.data["key1"]
expr = getattr(expr, "as_%s" % datatype)()
- roundtrip = conn.scalar(select([expr]))
+ roundtrip = conn.scalar(select(expr))
eq_(roundtrip, value)
if util.py3k: # skip py2k to avoid comparing unicode to str etc.
is_(type(roundtrip), type(value))
@@ -828,7 +818,7 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
expr = data_table.c.data["key1"]
expr = getattr(expr, "as_%s" % datatype)()
- row = conn.execute(select([expr]).where(expr == value)).first()
+ row = conn.execute(select(expr).where(expr == value)).first()
# make sure we get a row even if value is None
eq_(row, (value,))
@@ -850,7 +840,7 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
expr = data_table.c.data[("key1", "subkey1")]
expr = getattr(expr, "as_%s" % datatype)()
- row = conn.execute(select([expr]).where(expr == value)).first()
+ row = conn.execute(select(expr).where(expr == value)).first()
# make sure we get a row even if value is None
eq_(row, (value,))
@@ -882,7 +872,7 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
)
row = conn.execute(
- select([data_table.c.data, data_table.c.nulldata])
+ select(data_table.c.data, data_table.c.nulldata)
).first()
eq_(row, (data_element, data_element))
@@ -903,7 +893,7 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
conn.execute(
data_table.insert(), {"name": "row1", "data": data_element}
)
- row = conn.execute(select([data_table.c.data])).first()
+ row = conn.execute(select(data_table.c.data)).first()
eq_(row, (data_element,))
eq_(js.mock_calls, [mock.call(data_element)])
@@ -919,12 +909,12 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
eq_(
conn.scalar(
- select([self.tables.data_table.c.name]).where(col.is_(null()))
+ select(self.tables.data_table.c.name).where(col.is_(null()))
),
"r1",
)
- eq_(conn.scalar(select([col])), None)
+ eq_(conn.scalar(select(col)), None)
def test_round_trip_json_null_as_json_null(self, connection):
col = self.tables.data_table.c["data"]
@@ -936,14 +926,14 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
eq_(
conn.scalar(
- select([self.tables.data_table.c.name]).where(
+ select(self.tables.data_table.c.name).where(
cast(col, String) == "null"
)
),
"r1",
)
- eq_(conn.scalar(select([col])), None)
+ eq_(conn.scalar(select(col)), None)
def test_round_trip_none_as_json_null(self):
col = self.tables.data_table.c["data"]
@@ -955,14 +945,14 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
eq_(
conn.scalar(
- select([self.tables.data_table.c.name]).where(
+ select(self.tables.data_table.c.name).where(
cast(col, String) == "null"
)
),
"r1",
)
- eq_(conn.scalar(select([col])), None)
+ eq_(conn.scalar(select(col)), None)
def test_unicode_round_trip(self):
# note we include Unicode supplementary characters as well
@@ -979,7 +969,7 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
)
eq_(
- conn.scalar(select([self.tables.data_table.c.data])),
+ conn.scalar(select(self.tables.data_table.c.data)),
{
util.u("réve🐍 illé"): util.u("réve🐍 illé"),
"data": {"k1": util.u("drôl🐍e")},
@@ -1087,7 +1077,7 @@ class JSONStringCastIndexTest(_LiteralRoundTripFixture, fixtures.TablesTest):
def _test_index_criteria(self, crit, expected, test_literal=True):
self._criteria_fixture()
with config.db.connect() as conn:
- stmt = select([self.tables.data_table.c.name]).where(crit)
+ stmt = select(self.tables.data_table.c.name).where(crit)
eq_(conn.scalar(stmt), expected)
diff --git a/lib/sqlalchemy/testing/warnings.py b/lib/sqlalchemy/testing/warnings.py
index 3850f65c8..298b20c11 100644
--- a/lib/sqlalchemy/testing/warnings.py
+++ b/lib/sqlalchemy/testing/warnings.py
@@ -31,9 +31,6 @@ def setup_filters():
"ignore", category=DeprecationWarning, message=".*inspect.get.*argspec"
)
- # ignore 2.0 warnings unless we are explicitly testing for them
- warnings.filterwarnings("ignore", category=sa_exc.RemovedIn20Warning)
-
# ignore things that are deprecated *as of* 2.0 :)
warnings.filterwarnings(
"ignore",
diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py
index fb90975a1..b2407ea18 100644
--- a/lib/sqlalchemy/util/__init__.py
+++ b/lib/sqlalchemy/util/__init__.py
@@ -92,6 +92,7 @@ from .deprecations import deprecated_20_cls # noqa
from .deprecations import deprecated_cls # noqa
from .deprecations import deprecated_params # noqa
from .deprecations import inject_docstring_text # noqa
+from .deprecations import SQLALCHEMY_WARN_20 # noqa
from .deprecations import warn_deprecated # noqa
from .deprecations import warn_deprecated_20 # noqa
from .langhelpers import add_parameter_text # noqa
@@ -149,6 +150,3 @@ from .langhelpers import warn # noqa
from .langhelpers import warn_exception # noqa
from .langhelpers import warn_limited # noqa
from .langhelpers import wrap_callable # noqa
-
-
-SQLALCHEMY_WARN_20 = False
diff --git a/lib/sqlalchemy/util/deprecations.py b/lib/sqlalchemy/util/deprecations.py
index e0669c4e8..0a79344c5 100644
--- a/lib/sqlalchemy/util/deprecations.py
+++ b/lib/sqlalchemy/util/deprecations.py
@@ -8,6 +8,7 @@
"""Helpers related to deprecation of functions, methods, classes, other
functionality."""
+import os
import re
import warnings
@@ -19,7 +20,19 @@ from .langhelpers import inject_param_text
from .. import exc
+SQLALCHEMY_WARN_20 = False
+
+if os.getenv("SQLALCHEMY_WARN_20", "false").lower() in ("true", "yes", "1"):
+ SQLALCHEMY_WARN_20 = True
+
+
def _warn_with_version(msg, version, type_, stacklevel):
+ if type_ is exc.RemovedIn20Warning and not SQLALCHEMY_WARN_20:
+ return
+
+ if type_ is exc.RemovedIn20Warning:
+ msg += " (Background on SQLAlchemy 2.0 at: http://sqlalche.me/e/b8d9)"
+
warn = type_(msg)
warn.deprecated_since = version
@@ -41,7 +54,6 @@ def warn_deprecated_limited(msg, args, version, stacklevel=3):
def warn_deprecated_20(msg, stacklevel=3):
- msg += " (Background on SQLAlchemy 2.0 at: http://sqlalche.me/e/b8d9)"
_warn_with_version(
msg,
@@ -69,7 +81,7 @@ def deprecated_cls(version, message, constructor="__init__"):
def deprecated_20_cls(clsname, alternative=None, constructor="__init__"):
message = (
- ".. deprecated:: 2.0 The %s class is considered legacy as of the "
+ ".. deprecated:: 1.4 The %s class is considered legacy as of the "
"1.x series of SQLAlchemy and will be removed in 2.0." % clsname
)
@@ -108,8 +120,16 @@ def deprecated(
"""
+ # nothing is deprecated "since" 2.0 at this time. All "removed in 2.0"
+ # should emit the RemovedIn20Warning, but messaging should be expressed
+ # in terms of "deprecated since 1.4".
+
+ if version == "2.0":
+ if warning is None:
+ warning = exc.RemovedIn20Warning
+ version = "1.4"
if add_deprecation_to_docstring:
- header = ".. deprecated:: %s %s" % (version, (message or ""))
+ header = ".. deprecated:: %s %s" % (version, (message or ""),)
else:
header = None
@@ -119,7 +139,8 @@ def deprecated(
if warning is None:
warning = exc.SADeprecationWarning
- message += " (deprecated since: %s)" % version
+ if warning is not exc.RemovedIn20Warning:
+ message += " (deprecated since: %s)" % version
def decorate(fn):
return _decorate_with_warning(
@@ -162,6 +183,7 @@ def deprecated_params(**specs):
messages = {}
versions = {}
version_warnings = {}
+
for param, (version, message) in specs.items():
versions[param] = version
messages[param] = _sanitize_restructured_text(message)
@@ -173,6 +195,7 @@ def deprecated_params(**specs):
def decorate(fn):
spec = compat.inspect_getfullargspec(fn)
+
if spec.defaults is not None:
defaults = dict(
zip(
@@ -186,6 +209,8 @@ def deprecated_params(**specs):
check_defaults = ()
check_kw = set(messages)
+ check_any_kw = spec.varkw
+
@decorator
def warned(fn, *args, **kwargs):
for m in check_defaults:
@@ -198,6 +223,18 @@ def deprecated_params(**specs):
version_warnings[m],
stacklevel=3,
)
+
+ if check_any_kw in messages and set(kwargs).difference(
+ check_defaults
+ ):
+
+ _warn_with_version(
+ messages[check_any_kw],
+ versions[check_any_kw],
+ version_warnings[check_any_kw],
+ stacklevel=3,
+ )
+
for m in check_kw:
if m in kwargs:
_warn_with_version(
@@ -206,7 +243,6 @@ def deprecated_params(**specs):
version_warnings[m],
stacklevel=3,
)
-
return fn(*args, **kwargs)
doc = fn.__doc__ is not None and fn.__doc__ or ""
@@ -214,7 +250,8 @@ def deprecated_params(**specs):
doc = inject_param_text(
doc,
{
- param: ".. deprecated:: %s %s" % (version, (message or ""))
+ param: ".. deprecated:: %s %s"
+ % ("1.4" if version == "2.0" else version, (message or ""))
for param, (version, message) in specs.items()
},
)
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py
index 57d3be83b..28b7aa4cc 100644
--- a/lib/sqlalchemy/util/langhelpers.py
+++ b/lib/sqlalchemy/util/langhelpers.py
@@ -1701,9 +1701,9 @@ def inject_param_text(doctext, inject_params):
while doclines:
line = doclines.pop(0)
if to_inject is None:
- m = re.match(r"(\s+):param (?:\\\*\*?)?(.+?):", line)
+ m = re.match(r"(\s+):param (.+?):", line)
if m:
- param = m.group(2)
+ param = m.group(2).lstrip("*")
if param in inject_params:
# default indent to that of :param: plus one
indent = " " * len(m.group(1)) + " "