summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorFederico Caselli <cfederico87@gmail.com>2021-12-22 21:45:45 +0100
committerMike Bayer <mike_mp@zzzcomputing.com>2021-12-30 18:07:26 -0500
commite913ec8155b64e055f3a88ca9c1bb7f112202c76 (patch)
treeb0847baac43de628cd74bc2ff70cc352bbcb0270 /lib/sqlalchemy/sql
parent54875c21601eaca01e3217d5b22fab6f6cf50992 (diff)
downloadsqlalchemy-e913ec8155b64e055f3a88ca9c1bb7f112202c76.tar.gz
Properly type _generative, decorator, public_factory
Good new is that pylance likes it and copies over the singature and everything. Bad news is that mypy does not support this yet https://github.com/python/mypy/issues/8645 Other minor bad news is that non_generative is not typed. I've tried using a protocol like the one in the comment but the signature is not ported over by pylance, so it's probably best to just live without it to have the correct signature. notes from mike: these three decorators are at the core of getting the library to be typed, more good news is that pylance will do all the things we like re: public_factory, see https://github.com/microsoft/pyright/issues/2758#issuecomment-1002788656 . For @_generative, we will likely move to using pep 673 once mypy supports it which may be soon. but overall having the explicit "return self" in the methods, while a little inconvenient, makes the typing more straightforward and locally present in the files rather than being decided at a distance. having "return self" present, or not, both have problems, so maybe we will be able to change it again if things change as far as decorator support. As it is, I feel like we are barely squeaking by with our decorators, the typing is already pretty out there. Change-Id: Ic77e13fc861def76a5925331df85c0aa48d77807 References: #6810
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/base.py49
-rw-r--r--lib/sqlalchemy/sql/ddl.py12
-rw-r--r--lib/sqlalchemy/sql/dml.py61
-rw-r--r--lib/sqlalchemy/sql/elements.py9
-rw-r--r--lib/sqlalchemy/sql/expression.py8
-rw-r--r--lib/sqlalchemy/sql/selectable.py142
6 files changed, 218 insertions, 63 deletions
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index 187651435..30d589258 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -16,6 +16,7 @@ import itertools
from itertools import zip_longest
import operator
import re
+import typing
from . import roles
from . import visitors
@@ -29,6 +30,7 @@ from .. import exc
from .. import util
from ..util import HasMemoized
from ..util import hybridmethod
+from ..util import typing as compat_typing
try:
from sqlalchemy.cyextension.util import prefix_anon_map # noqa
@@ -42,6 +44,10 @@ type_api = None
NO_ARG = util.symbol("NO_ARG")
+# if I use sqlalchemy.util.typing, which has the exact same
+# symbols, mypy reports: "error: _Fn? not callable"
+_Fn = typing.TypeVar("_Fn", bound=typing.Callable)
+
class Immutable:
"""mark a ClauseElement as 'immutable' when expressions are cloned."""
@@ -101,7 +107,16 @@ def _select_iterables(elements):
)
-def _generative(fn):
+_Self = typing.TypeVar("_Self", bound="_GenerativeType")
+_Args = compat_typing.ParamSpec("_Args")
+
+
+class _GenerativeType(compat_typing.Protocol):
+ def _generate(self: "_Self") -> "_Self":
+ ...
+
+
+def _generative(fn: _Fn) -> _Fn:
"""non-caching _generative() decorator.
This is basically the legacy decorator that copies the object and
@@ -110,14 +125,14 @@ def _generative(fn):
"""
@util.decorator
- def _generative(fn, self, *args, **kw):
+ def _generative(
+ fn: _Fn, self: _Self, *args: _Args.args, **kw: _Args.kwargs
+ ) -> _Self:
"""Mark a method as generative."""
self = self._generate()
x = fn(self, *args, **kw)
- assert (
- x is None or x is self
- ), "generative methods must return None or self"
+ assert x is self, "generative methods must return self"
return self
decorated = _generative(fn)
@@ -788,6 +803,9 @@ class ExecutableOption(HasCopyInternals):
return c
+SelfExecutable = typing.TypeVar("SelfExecutable", bound="Executable")
+
+
class Executable(roles.StatementRole, Generative):
"""Mark a :class:`_expression.ClauseElement` as supporting execution.
@@ -824,7 +842,7 @@ class Executable(roles.StatementRole, Generative):
return self.__visit_name__
@_generative
- def options(self, *options):
+ def options(self: SelfExecutable, *options) -> SelfExecutable:
"""Apply options to this statement.
In the general sense, options are any kind of Python object
@@ -857,9 +875,12 @@ class Executable(roles.StatementRole, Generative):
coercions.expect(roles.ExecutableOptionRole, opt)
for opt in options
)
+ return self
@_generative
- def _set_compile_options(self, compile_options):
+ def _set_compile_options(
+ self: SelfExecutable, compile_options
+ ) -> SelfExecutable:
"""Assign the compile options to a new value.
:param compile_options: appropriate CacheableOptions structure
@@ -867,15 +888,21 @@ class Executable(roles.StatementRole, Generative):
"""
self._compile_options = compile_options
+ return self
@_generative
- def _update_compile_options(self, options):
+ def _update_compile_options(
+ self: SelfExecutable, options
+ ) -> SelfExecutable:
"""update the _compile_options with new keys."""
self._compile_options += options
+ return self
@_generative
- def _add_context_option(self, callable_, cache_args):
+ def _add_context_option(
+ self: SelfExecutable, callable_, cache_args
+ ) -> SelfExecutable:
"""Add a context option to this statement.
These are callable functions that will
@@ -887,9 +914,10 @@ class Executable(roles.StatementRole, Generative):
"""
self._with_context_options += ((callable_, cache_args),)
+ return self
@_generative
- def execution_options(self, **kw):
+ def execution_options(self: SelfExecutable, **kw) -> SelfExecutable:
"""Set non-SQL options for the statement which take effect during
execution.
@@ -1004,6 +1032,7 @@ class Executable(roles.StatementRole, Generative):
"on Connection.execution_options(), not per statement."
)
self._execution_options = self._execution_options.union(kw)
+ return self
def get_execution_options(self):
"""Get the non-SQL options which will take effect during execution.
diff --git a/lib/sqlalchemy/sql/ddl.py b/lib/sqlalchemy/sql/ddl.py
index f415aeaff..ad22fa6da 100644
--- a/lib/sqlalchemy/sql/ddl.py
+++ b/lib/sqlalchemy/sql/ddl.py
@@ -9,6 +9,7 @@ Provides the hierarchy of DDL-defining schema items as well as routines
to invoke them for a create/drop call.
"""
+import typing
from . import roles
from .base import _generative
@@ -34,6 +35,9 @@ class _DDLCompiles(ClauseElement):
raise NotImplementedError()
+SelfDDLElement = typing.TypeVar("SelfDDLElement", bound="DDLElement")
+
+
class DDLElement(roles.DDLRole, Executable, _DDLCompiles):
"""Base class for DDL expression constructs.
@@ -77,7 +81,7 @@ class DDLElement(roles.DDLRole, Executable, _DDLCompiles):
)
@_generative
- def against(self, target):
+ def against(self: SelfDDLElement, target) -> SelfDDLElement:
"""Return a copy of this :class:`_schema.DDLElement` which will include
the given target.
@@ -111,9 +115,12 @@ class DDLElement(roles.DDLRole, Executable, _DDLCompiles):
"""
self.target = target
+ return self
@_generative
- def execute_if(self, dialect=None, callable_=None, state=None):
+ def execute_if(
+ self: SelfDDLElement, dialect=None, callable_=None, state=None
+ ) -> SelfDDLElement:
r"""Return a callable that will execute this
:class:`_ddl.DDLElement` conditionally within an event handler.
@@ -181,6 +188,7 @@ class DDLElement(roles.DDLRole, Executable, _DDLCompiles):
self.dialect = dialect
self.callable_ = callable_
self.state = state
+ return self
def _should_execute(self, target, bind, **kw):
if isinstance(self.dialect, str):
diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py
index 7b3716a68..ab0a05651 100644
--- a/lib/sqlalchemy/sql/dml.py
+++ b/lib/sqlalchemy/sql/dml.py
@@ -10,8 +10,8 @@ Provide :class:`_expression.Insert`, :class:`_expression.Update` and
"""
import collections.abc as collections_abc
+import typing
-from sqlalchemy.types import NullType
from . import coercions
from . import roles
from . import util as sql_util
@@ -30,6 +30,7 @@ from .elements import Null
from .selectable import HasCTE
from .selectable import HasPrefixes
from .selectable import ReturnsRows
+from .sqltypes import NullType
from .visitors import InternalTraversal
from .. import exc
from .. import util
@@ -210,6 +211,9 @@ class DeleteDMLState(DMLState):
self._extra_froms = self._make_extra_froms(statement)
+SelfUpdateBase = typing.TypeVar("SelfUpdateBase", bound="UpdateBase")
+
+
class UpdateBase(
roles.DMLRole,
HasCTE,
@@ -313,7 +317,7 @@ class UpdateBase(
)
@_generative
- def with_dialect_options(self, **opt):
+ def with_dialect_options(self: SelfUpdateBase, **opt) -> SelfUpdateBase:
"""Add dialect options to this INSERT/UPDATE/DELETE object.
e.g.::
@@ -326,6 +330,7 @@ class UpdateBase(
"""
self._validate_dialect_kwargs(opt)
+ return self
def _validate_dialect_kwargs_deprecated(self, dialect_kw):
util.warn_deprecated_20(
@@ -337,7 +342,7 @@ class UpdateBase(
self._validate_dialect_kwargs(dialect_kw)
@_generative
- def returning(self, *cols):
+ def returning(self: SelfUpdateBase, *cols) -> SelfUpdateBase:
r"""Add a :term:`RETURNING` or equivalent clause to this statement.
e.g.:
@@ -414,6 +419,7 @@ class UpdateBase(
self._returning += tuple(
coercions.expect(roles.ColumnsClauseRole, c) for c in cols
)
+ return self
@property
def _all_selected_columns(self):
@@ -433,7 +439,9 @@ class UpdateBase(
).as_immutable()
@_generative
- def with_hint(self, text, selectable=None, dialect_name="*"):
+ def with_hint(
+ self: SelfUpdateBase, text, selectable=None, dialect_name="*"
+ ) -> SelfUpdateBase:
"""Add a table hint for a single table to this
INSERT/UPDATE/DELETE statement.
@@ -467,6 +475,10 @@ class UpdateBase(
selectable = self.table
self._hints = self._hints.union({(selectable, dialect_name): text})
+ return self
+
+
+SelfValuesBase = typing.TypeVar("SelfValuesBase", bound="ValuesBase")
class ValuesBase(UpdateBase):
@@ -506,7 +518,7 @@ class ValuesBase(UpdateBase):
"values present",
},
)
- def values(self, *args, **kwargs):
+ def values(self: SelfValuesBase, *args, **kwargs) -> SelfValuesBase:
r"""Specify a fixed VALUES clause for an INSERT statement, or the SET
clause for an UPDATE.
@@ -643,7 +655,7 @@ class ValuesBase(UpdateBase):
if arg and isinstance(arg[0], (list, dict, tuple)):
self._multi_values += (arg,)
- return
+ return self
# tuple values
arg = {c.key: value for c, value in zip(self.table.c, arg)}
@@ -681,6 +693,7 @@ class ValuesBase(UpdateBase):
self._values = self._values.union(arg)
else:
self._values = util.immutabledict(arg)
+ return self
@_generative
@_exclusive_against(
@@ -690,7 +703,7 @@ class ValuesBase(UpdateBase):
},
defaults={"_returning": _returning},
)
- def return_defaults(self, *cols):
+ def return_defaults(self: SelfValuesBase, *cols) -> SelfValuesBase:
"""Make use of a :term:`RETURNING` clause for the purpose
of fetching server-side expressions and defaults.
@@ -776,6 +789,10 @@ class ValuesBase(UpdateBase):
"""
self._return_defaults = True
self._return_defaults_columns = cols
+ return self
+
+
+SelfInsert = typing.TypeVar("SelfInsert", bound="Insert")
class Insert(ValuesBase):
@@ -918,7 +935,7 @@ class Insert(ValuesBase):
self._return_defaults_columns = return_defaults
@_generative
- def inline(self):
+ def inline(self: SelfInsert) -> SelfInsert:
"""Make this :class:`_expression.Insert` construct "inline" .
When set, no attempt will be made to retrieve the
@@ -936,9 +953,12 @@ class Insert(ValuesBase):
"""
self._inline = True
+ return self
@_generative
- def from_select(self, names, select, include_defaults=True):
+ def from_select(
+ self: SelfInsert, names, select, include_defaults=True
+ ) -> SelfInsert:
"""Return a new :class:`_expression.Insert` construct which represents
an ``INSERT...FROM SELECT`` statement.
@@ -997,13 +1017,17 @@ class Insert(ValuesBase):
self._inline = True
self.include_insert_from_select_defaults = include_defaults
self.select = coercions.expect(roles.DMLSelectRole, select)
+ return self
+
+
+SelfDMLWhereBase = typing.TypeVar("SelfDMLWhereBase", bound="DMLWhereBase")
class DMLWhereBase:
_where_criteria = ()
@_generative
- def where(self, *whereclause):
+ def where(self: SelfDMLWhereBase, *whereclause) -> SelfDMLWhereBase:
"""Return a new construct with the given expression(s) added to
its WHERE clause, joined to the existing clause via AND, if any.
@@ -1037,8 +1061,9 @@ class DMLWhereBase:
for criterion in whereclause:
where_criteria = coercions.expect(roles.WhereHavingRole, criterion)
self._where_criteria += (where_criteria,)
+ return self
- def filter(self, *criteria):
+ def filter(self: SelfDMLWhereBase, *criteria) -> SelfDMLWhereBase:
"""A synonym for the :meth:`_dml.DMLWhereBase.where` method.
.. versionadded:: 1.4
@@ -1050,7 +1075,7 @@ class DMLWhereBase:
def _filter_by_zero(self):
return self.table
- def filter_by(self, **kwargs):
+ def filter_by(self: SelfDMLWhereBase, **kwargs) -> SelfDMLWhereBase:
r"""apply the given filtering criterion as a WHERE clause
to this select.
@@ -1081,6 +1106,9 @@ class DMLWhereBase:
)
+SelfUpdate = typing.TypeVar("SelfUpdate", bound="Update")
+
+
class Update(DMLWhereBase, ValuesBase):
"""Represent an Update construct.
@@ -1261,7 +1289,7 @@ class Update(DMLWhereBase, ValuesBase):
self._return_defaults = return_defaults
@_generative
- def ordered_values(self, *args):
+ def ordered_values(self: SelfUpdate, *args) -> SelfUpdate:
"""Specify the VALUES clause of this UPDATE statement with an explicit
parameter ordering that will be maintained in the SET clause of the
resulting UPDATE statement.
@@ -1295,9 +1323,10 @@ class Update(DMLWhereBase, ValuesBase):
kv_generator = DMLState.get_plugin_class(self)._get_crud_kv_pairs
self._ordered_values = kv_generator(self, args)
+ return self
@_generative
- def inline(self):
+ def inline(self: SelfUpdate) -> SelfUpdate:
"""Make this :class:`_expression.Update` construct "inline" .
When set, SQL defaults present on :class:`_schema.Column`
@@ -1313,6 +1342,10 @@ class Update(DMLWhereBase, ValuesBase):
"""
self._inline = True
+ return self
+
+
+SelfDelete = typing.TypeVar("SelfDelete", bound="Delete")
class Delete(DMLWhereBase, UpdateBase):
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 37425345b..f6606e01d 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -13,6 +13,7 @@
import itertools
import operator
import re
+import typing
from . import coercions
from . import operators
@@ -1719,6 +1720,9 @@ class TypeClause(ClauseElement):
self.type = type_
+SelfTextClause = typing.TypeVar("SelfTextClause", bound="TextClause")
+
+
class TextClause(
roles.DDLConstraintColumnRole,
roles.DDLExpressionRole,
@@ -1875,7 +1879,9 @@ class TextClause(
return TextClause(text)
@_generative
- def bindparams(self, *binds, **names_to_values):
+ def bindparams(
+ self: SelfTextClause, *binds, **names_to_values
+ ) -> SelfTextClause:
"""Establish the values and/or types of bound parameters within
this :class:`_expression.TextClause` construct.
@@ -2000,6 +2006,7 @@ class TextClause(
) from err
else:
new_params[key] = existing._with_value(value, required=False)
+ return self
@util.preload_module("sqlalchemy.sql.selectable")
def columns(self, *cols, **types):
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 407f1dd33..89b7c6596 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -88,6 +88,8 @@ __all__ = [
]
+from typing import Callable
+
from .base import _from_objects
from .base import _select_iterables
from .base import ColumnCollection
@@ -175,10 +177,8 @@ from .traversals import CacheKey
from .visitors import Visitable
from ..util.langhelpers import public_factory
-# factory functions - these pull class-bound constructors and classmethods
-# from SQL elements and selectables into public functions. This allows
-# the functions to be available in the sqlalchemy.sql.* namespace and
-# to be auto-cross-documenting from the function to the class itself.
+# TODO: proposal is to remove public_factory and replace with traditional
+# functions exported here.
all_ = public_factory(CollectionAggregate._create_all, ".sql.expression.all_")
any_ = public_factory(CollectionAggregate._create_any, ".sql.expression.any_")
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 802576b89..8b35dc6ac 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -14,6 +14,9 @@ SQL tables and derived rowsets.
import collections
import itertools
from operator import attrgetter
+import typing
+from typing import Type
+from typing import Union
from . import coercions
from . import operators
@@ -209,6 +212,9 @@ class Selectable(ReturnsRows):
)
+SelfHasPrefixes = typing.TypeVar("SelfHasPrefixes", bound="HasPrefixes")
+
+
class HasPrefixes:
_prefixes = ()
@@ -222,7 +228,7 @@ class HasPrefixes:
":meth:`_expression.HasPrefixes.prefix_with`",
":paramref:`.HasPrefixes.prefix_with.*expr`",
)
- def prefix_with(self, *expr, **kw):
+ def prefix_with(self: SelfHasPrefixes, *expr, **kw) -> SelfHasPrefixes:
r"""Add one or more expressions following the statement keyword, i.e.
SELECT, INSERT, UPDATE, or DELETE. Generative.
@@ -255,6 +261,7 @@ class HasPrefixes:
"Unsupported argument(s): %s" % ",".join(kw)
)
self._setup_prefixes(expr, dialect)
+ return self
def _setup_prefixes(self, prefixes, dialect=None):
self._prefixes = self._prefixes + tuple(
@@ -265,6 +272,9 @@ class HasPrefixes:
)
+SelfHasSuffixes = typing.TypeVar("SelfHasSuffixes", bound="HasSuffixes")
+
+
class HasSuffixes:
_suffixes = ()
@@ -278,7 +288,7 @@ class HasSuffixes:
":meth:`_expression.HasSuffixes.suffix_with`",
":paramref:`.HasSuffixes.suffix_with.*expr`",
)
- def suffix_with(self, *expr, **kw):
+ def suffix_with(self: SelfHasSuffixes, *expr, **kw) -> SelfHasSuffixes:
r"""Add one or more expressions following the statement as a whole.
This is used to support backend-specific suffix keywords on
@@ -306,6 +316,7 @@ class HasSuffixes:
"Unsupported argument(s): %s" % ",".join(kw)
)
self._setup_suffixes(expr, dialect)
+ return self
def _setup_suffixes(self, suffixes, dialect=None):
self._suffixes = self._suffixes + tuple(
@@ -316,6 +327,9 @@ class HasSuffixes:
)
+SelfHasHints = typing.TypeVar("SelfHasHints", bound="HasHints")
+
+
class HasHints:
_hints = util.immutabledict()
_statement_hints = ()
@@ -352,7 +366,9 @@ class HasHints:
return self.with_hint(None, text, dialect_name)
@_generative
- def with_hint(self, selectable, text, dialect_name="*"):
+ def with_hint(
+ self: SelfHasHints, selectable, text, dialect_name="*"
+ ) -> SelfHasHints:
r"""Add an indexing or other executional context hint for the given
selectable to this :class:`_expression.Select` or other selectable
object.
@@ -398,6 +414,7 @@ class HasHints:
): text
}
)
+ return self
class FromClause(roles.AnonymizedFromClauseRole, Selectable):
@@ -2082,6 +2099,9 @@ class CTE(
return self._restates if self._restates is not None else self
+SelfHasCTE = typing.TypeVar("SelfHasCTE", bound="HasCTE")
+
+
class HasCTE(roles.HasCTERole):
"""Mixin that declares a class to include CTE support.
@@ -2096,7 +2116,7 @@ class HasCTE(roles.HasCTERole):
_independent_ctes = ()
@_generative
- def add_cte(self, cte):
+ def add_cte(self: SelfHasCTE, cte) -> SelfHasCTE:
"""Add a :class:`_sql.CTE` to this statement object that will be
independently rendered even if not referenced in the statement
otherwise.
@@ -2161,6 +2181,7 @@ class HasCTE(roles.HasCTERole):
"""
cte = coercions.expect(roles.IsCTERole, cte)
self._independent_ctes += (cte,)
+ return self
def cte(self, name=None, recursive=False, nesting=False):
r"""Return a new :class:`_expression.CTE`,
@@ -2759,6 +2780,9 @@ class ForUpdateArg(ClauseElement):
self.of = None
+SelfValues = typing.TypeVar("SelfValues", bound="Values")
+
+
class Values(Generative, FromClause):
"""Represent a ``VALUES`` construct that can be used as a FROM element
in a statement.
@@ -2829,7 +2853,7 @@ class Values(Generative, FromClause):
return [col.type for col in self._column_args]
@_generative
- def alias(self, name, **kw):
+ def alias(self: SelfValues, name, **kw) -> SelfValues:
"""Return a new :class:`_expression.Values`
construct that is a copy of this
one with the given name.
@@ -2846,9 +2870,10 @@ class Values(Generative, FromClause):
"""
self.name = name
self.named_with_column = self.name is not None
+ return self
@_generative
- def lateral(self, name=None):
+ def lateral(self: SelfValues, name=None) -> SelfValues:
"""Return a new :class:`_expression.Values` with the lateral flag set,
so that
it renders as LATERAL.
@@ -2861,9 +2886,10 @@ class Values(Generative, FromClause):
self._is_lateral = True
if name is not None:
self.name = name
+ return self
@_generative
- def data(self, values):
+ def data(self: SelfValues, values) -> SelfValues:
"""Return a new :class:`_expression.Values` construct,
adding the given data
to the data list.
@@ -2879,6 +2905,7 @@ class Values(Generative, FromClause):
"""
self._data += (values,)
+ return self
def _populate_column_collection(self):
for c in self._column_args:
@@ -3312,6 +3339,11 @@ class DeprecatedSelectBaseGenerations:
self.group_by.non_generative(self, *clauses)
+SelfGenerativeSelect = typing.TypeVar(
+ "SelfGenerativeSelect", bound="GenerativeSelect"
+)
+
+
class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
"""Base class for SELECT statements where additional elements can be
added.
@@ -3371,13 +3403,13 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
@_generative
def with_for_update(
- self,
+ self: SelfGenerativeSelect,
nowait=False,
read=False,
of=None,
skip_locked=False,
key_share=False,
- ):
+ ) -> SelfGenerativeSelect:
"""Specify a ``FOR UPDATE`` clause for this
:class:`_expression.GenerativeSelect`.
@@ -3430,6 +3462,7 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
skip_locked=skip_locked,
key_share=key_share,
)
+ return self
def get_label_style(self):
"""
@@ -3573,7 +3606,7 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
)
@_generative
- def limit(self, limit):
+ def limit(self: SelfGenerativeSelect, limit) -> SelfGenerativeSelect:
"""Return a new selectable with the given LIMIT criterion
applied.
@@ -3603,9 +3636,12 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
self._fetch_clause = self._fetch_clause_options = None
self._limit_clause = self._offset_or_limit_clause(limit)
+ return self
@_generative
- def fetch(self, count, with_ties=False, percent=False):
+ def fetch(
+ self: SelfGenerativeSelect, count, with_ties=False, percent=False
+ ) -> SelfGenerativeSelect:
"""Return a new selectable with the given FETCH FIRST criterion
applied.
@@ -3653,9 +3689,10 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
"with_ties": with_ties,
"percent": percent,
}
+ return self
@_generative
- def offset(self, offset):
+ def offset(self: SelfGenerativeSelect, offset) -> SelfGenerativeSelect:
"""Return a new selectable with the given OFFSET criterion
applied.
@@ -3681,10 +3718,11 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
"""
self._offset_clause = self._offset_or_limit_clause(offset)
+ return self
@_generative
@util.preload_module("sqlalchemy.sql.util")
- def slice(self, start, stop):
+ def slice(self: SelfGenerativeSelect, start, stop) -> SelfGenerativeSelect:
"""Apply LIMIT / OFFSET to this statement based on a slice.
The start and stop indices behave like the argument to Python's
@@ -3728,9 +3766,10 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
self._limit_clause, self._offset_clause = sql_util._make_slice(
self._limit_clause, self._offset_clause, start, stop
)
+ return self
@_generative
- def order_by(self, *clauses):
+ def order_by(self: SelfGenerativeSelect, *clauses) -> SelfGenerativeSelect:
r"""Return a new selectable with the given list of ORDER BY
criteria applied.
@@ -3764,9 +3803,10 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
coercions.expect(roles.OrderByRole, clause)
for clause in clauses
)
+ return self
@_generative
- def group_by(self, *clauses):
+ def group_by(self: SelfGenerativeSelect, *clauses) -> SelfGenerativeSelect:
r"""Return a new selectable with the given list of GROUP BY
criterion applied.
@@ -3797,6 +3837,7 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
coercions.expect(roles.GroupByRole, clause)
for clause in clauses
)
+ return self
@CompileState.plugin_for("default", "compound_select")
@@ -4658,6 +4699,10 @@ class _MemoizedSelectEntities(
) = select_stmt._with_options = ()
+# TODO: use pep-673 when feasible
+SelfSelect = typing.TypeVar("SelfSelect", bound="Select")
+
+
class Select(
HasPrefixes,
HasSuffixes,
@@ -4737,7 +4782,9 @@ class Select(
]
@classmethod
- def _create(cls, *entities) -> "Select":
+ def _create(
+ cls, *entities: Union[roles.ColumnsClauseRole, Type]
+ ) -> "Select":
r"""Construct a new :class:`_expression.Select`.
@@ -4788,7 +4835,7 @@ class Select(
return self
@classmethod
- def _create_raw_select(cls, **kw):
+ def _create_raw_select(cls, **kw) -> "Select":
"""Create a :class:`.Select` using raw ``__new__`` with no coercions.
Used internally to build up :class:`.Select` constructs with
@@ -4873,7 +4920,9 @@ class Select(
return meth(self, statement)
@_generative
- def join(self, target, onclause=None, isouter=False, full=False):
+ def join(
+ self: SelfSelect, target, onclause=None, isouter=False, full=False
+ ) -> SelfSelect:
r"""Create a SQL JOIN against this :class:`_expression.Select`
object's criterion
and apply generatively, returning the newly resulting
@@ -4939,6 +4988,7 @@ class Select(
self._setup_joins += (
(target, onclause, None, {"isouter": isouter, "full": full}),
)
+ return self
def outerjoin_from(self, from_, target, onclause=None, full=False):
r"""Create a SQL LEFT OUTER JOIN against this :class:`_expression.Select`
@@ -4955,8 +5005,13 @@ class Select(
@_generative
def join_from(
- self, from_, target, onclause=None, isouter=False, full=False
- ):
+ self: SelfSelect,
+ from_,
+ target,
+ onclause=None,
+ isouter=False,
+ full=False,
+ ) -> SelfSelect:
r"""Create a SQL JOIN against this :class:`_expression.Select`
object's criterion
and apply generatively, returning the newly resulting
@@ -5014,6 +5069,7 @@ class Select(
self._setup_joins += (
(target, onclause, from_, {"isouter": isouter, "full": full}),
)
+ return self
def outerjoin(self, target, onclause=None, full=False):
"""Create a left outer join.
@@ -5211,7 +5267,7 @@ class Select(
)
@_generative
- def add_columns(self, *columns):
+ def add_columns(self: SelfSelect, *columns) -> SelfSelect:
"""Return a new :func:`_expression.select` construct with
the given column expressions added to its columns clause.
@@ -5233,6 +5289,7 @@ class Select(
)
for column in columns
]
+ return self
def _set_entities(self, entities):
self._raw_columns = [
@@ -5297,7 +5354,7 @@ class Select(
)
@_generative
- def with_only_columns(self, *columns, **kw):
+ def with_only_columns(self: SelfSelect, *columns, **kw) -> SelfSelect:
r"""Return a new :func:`_expression.select` construct with its columns
clause replaced with the given columns.
@@ -5372,6 +5429,7 @@ class Select(
"columns", "Select.with_only_columns", columns
)
]
+ return self
@property
def whereclause(self):
@@ -5393,7 +5451,7 @@ class Select(
_whereclause = whereclause
@_generative
- def where(self, *whereclause):
+ def where(self: SelfSelect, *whereclause) -> SelfSelect:
"""Return a new :func:`_expression.select` construct with
the given expression added to
its WHERE clause, joined to the existing clause via AND, if any.
@@ -5405,9 +5463,10 @@ class Select(
for criterion in whereclause:
where_criteria = coercions.expect(roles.WhereHavingRole, criterion)
self._where_criteria += (where_criteria,)
+ return self
@_generative
- def having(self, having):
+ def having(self: SelfSelect, having) -> SelfSelect:
"""Return a new :func:`_expression.select` construct with
the given expression added to
its HAVING clause, joined to the existing clause via AND, if any.
@@ -5416,9 +5475,10 @@ class Select(
self._having_criteria += (
coercions.expect(roles.WhereHavingRole, having),
)
+ return self
@_generative
- def distinct(self, *expr):
+ def distinct(self: SelfSelect, *expr) -> SelfSelect:
r"""Return a new :func:`_expression.select` construct which
will apply DISTINCT to its columns clause.
@@ -5437,9 +5497,10 @@ class Select(
)
else:
self._distinct = True
+ return self
@_generative
- def select_from(self, *froms):
+ def select_from(self: SelfSelect, *froms) -> SelfSelect:
r"""Return a new :func:`_expression.select` construct with the
given FROM expression(s)
merged into its list of FROM objects.
@@ -5480,9 +5541,10 @@ class Select(
)
for fromclause in froms
)
+ return self
@_generative
- def correlate(self, *fromclauses):
+ def correlate(self: SelfSelect, *fromclauses) -> SelfSelect:
r"""Return a new :class:`_expression.Select`
which will correlate the given FROM
clauses to that of an enclosing :class:`_expression.Select`.
@@ -5541,9 +5603,10 @@ class Select(
self._correlate = self._correlate + tuple(
coercions.expect(roles.FromClauseRole, f) for f in fromclauses
)
+ return self
@_generative
- def correlate_except(self, *fromclauses):
+ def correlate_except(self: SelfSelect, *fromclauses) -> SelfSelect:
r"""Return a new :class:`_expression.Select`
which will omit the given FROM
clauses from the auto-correlation process.
@@ -5579,6 +5642,7 @@ class Select(
self._correlate_except = (self._correlate_except or ()) + tuple(
coercions.expect(roles.FromClauseRole, f) for f in fromclauses
)
+ return self
@HasMemoized.memoized_attribute
def selected_columns(self):
@@ -5959,6 +6023,9 @@ class Select(
return CompoundSelect._create_intersect_all(self, *other, **kwargs)
+SelfScalarSelect = typing.TypeVar("SelfScalarSelect", bound="ScalarSelect")
+
+
class ScalarSelect(roles.InElementRole, Generative, Grouping):
"""Represent a scalar subquery.
@@ -5998,18 +6065,19 @@ class ScalarSelect(roles.InElementRole, Generative, Grouping):
c = columns
@_generative
- def where(self, crit):
+ def where(self: SelfScalarSelect, crit) -> SelfScalarSelect:
"""Apply a WHERE clause to the SELECT statement referred to
by this :class:`_expression.ScalarSelect`.
"""
self.element = self.element.where(crit)
+ return self
def self_group(self, **kwargs):
return self
@_generative
- def correlate(self, *fromclauses):
+ def correlate(self: SelfScalarSelect, *fromclauses) -> SelfScalarSelect:
r"""Return a new :class:`_expression.ScalarSelect`
which will correlate the given FROM
clauses to that of an enclosing :class:`_expression.Select`.
@@ -6039,9 +6107,12 @@ class ScalarSelect(roles.InElementRole, Generative, Grouping):
"""
self.element = self.element.correlate(*fromclauses)
+ return self
@_generative
- def correlate_except(self, *fromclauses):
+ def correlate_except(
+ self: SelfScalarSelect, *fromclauses
+ ) -> SelfScalarSelect:
r"""Return a new :class:`_expression.ScalarSelect`
which will omit the given FROM
clauses from the auto-correlation process.
@@ -6073,6 +6144,7 @@ class ScalarSelect(roles.InElementRole, Generative, Grouping):
"""
self.element = self.element.correlate_except(*fromclauses)
+ return self
class Exists(UnaryExpression):
@@ -6228,6 +6300,9 @@ class Exists(UnaryExpression):
return e
+SelfTextualSelect = typing.TypeVar("SelfTextualSelect", bound="TextualSelect")
+
+
class TextualSelect(SelectBase):
"""Wrap a :class:`_expression.TextClause` construct within a
:class:`_expression.SelectBase`
@@ -6315,8 +6390,11 @@ class TextualSelect(SelectBase):
return self
@_generative
- def bindparams(self, *binds, **bind_as_values):
+ def bindparams(
+ self: SelfTextualSelect, *binds, **bind_as_values
+ ) -> SelfTextualSelect:
self.element = self.element.bindparams(*binds, **bind_as_values)
+ return self
def _generate_fromclause_column_proxies(self, fromclause):
fromclause._columns._populate_separate_keys(