summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/selectable.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql/selectable.py')
-rw-r--r--lib/sqlalchemy/sql/selectable.py238
1 files changed, 142 insertions, 96 deletions
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 2f37317f2..24edc1cae 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -16,13 +16,14 @@ from __future__ import annotations
import collections
from enum import Enum
import itertools
-from operator import attrgetter
import typing
from typing import Any as TODO_Any
from typing import Any
from typing import Iterable
+from typing import List
from typing import NamedTuple
from typing import Optional
+from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
@@ -34,13 +35,15 @@ from . import roles
from . import traversals
from . import type_api
from . import visitors
-from ._typing import _ColumnsClauseElement
+from ._typing import _ColumnsClauseArgument
+from ._typing import is_column_element
from .annotation import Annotated
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 _EntityNamespace
from .base import _expand_cloned
from .base import _from_objects
from .base import _generative
@@ -78,6 +81,13 @@ and_ = BooleanClauseList.and_
_T = TypeVar("_T", bound=Any)
+if TYPE_CHECKING:
+ from ._typing import _SelectIterable
+ from .base import ReadOnlyColumnCollection
+ from .elements import NamedColumn
+ from .schema import ForeignKey
+ from .schema import PrimaryKeyConstraint
+
class _OffsetLimitParam(BindParameter):
inherit_cache = True
@@ -111,8 +121,8 @@ class ReturnsRows(roles.ReturnsRowsRole, DQLDMLClauseElement):
def selectable(self):
return self
- @property
- def _all_selected_columns(self):
+ @util.non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
"""A sequence of column expression objects that represents the
"selected" columns of this :class:`_expression.ReturnsRows`.
@@ -457,7 +467,7 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
__visit_name__ = "fromclause"
named_with_column = False
- @property
+ @util.ro_non_memoized_property
def _hide_froms(self) -> Iterable[FromClause]:
return ()
@@ -707,10 +717,10 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
"""
- return self.columns
+ return self.c
- @util.memoized_property
- def columns(self) -> ColumnCollection[Any]:
+ @util.ro_non_memoized_property
+ def columns(self) -> ReadOnlyColumnCollection[str, Any]:
"""A named-based collection of :class:`_expression.ColumnElement`
objects maintained by this :class:`_expression.FromClause`.
@@ -723,14 +733,23 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
:return: a :class:`.ColumnCollection` object.
"""
+ return self.c
+
+ @util.ro_memoized_property
+ def c(self) -> ReadOnlyColumnCollection[str, Any]:
+ """
+ A synonym for :attr:`.FromClause.columns`
+
+ :return: a :class:`.ColumnCollection`
+ """
if "_columns" not in self.__dict__:
self._init_collections()
self._populate_column_collection()
- return self._columns.as_immutable()
+ return self._columns.as_readonly()
- @property
- def entity_namespace(self):
+ @util.ro_non_memoized_property
+ def entity_namespace(self) -> _EntityNamespace:
"""Return a namespace used for name-based access in SQL expressions.
This is the namespace that is used to resolve "filter_by()" type
@@ -743,10 +762,10 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
alternative results.
"""
- return self.columns
+ return self.c
- @util.memoized_property
- def primary_key(self):
+ @util.ro_memoized_property
+ def primary_key(self) -> Iterable[NamedColumn[Any]]:
"""Return the iterable collection of :class:`_schema.Column` objects
which comprise the primary key of this :class:`_selectable.FromClause`.
@@ -759,8 +778,8 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
self._populate_column_collection()
return self.primary_key
- @util.memoized_property
- def foreign_keys(self):
+ @util.ro_memoized_property
+ def foreign_keys(self) -> Iterable[ForeignKey]:
"""Return the collection of :class:`_schema.ForeignKey` marker objects
which this FromClause references.
@@ -791,28 +810,12 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
"""
- for key in ["_columns", "columns", "primary_key", "foreign_keys"]:
+ for key in ["_columns", "columns", "c", "primary_key", "foreign_keys"]:
self.__dict__.pop(key, None)
- # this is awkward. maybe there's a better way
- if TYPE_CHECKING:
- c: ColumnCollection[Any]
- else:
- c = property(
- attrgetter("columns"),
- doc="""
- A named-based collection of :class:`_expression.ColumnElement`
- objects maintained by this :class:`_expression.FromClause`.
-
- The :attr:`_sql.FromClause.c` attribute is an alias for the
- :attr:`_sql.FromClause.columns` attribute.
-
- :return: a :class:`.ColumnCollection`
-
- """,
- )
-
- _select_iterable = property(attrgetter("columns"))
+ @util.ro_non_memoized_property
+ def _select_iterable(self) -> _SelectIterable:
+ return self.c
def _init_collections(self):
assert "_columns" not in self.__dict__
@@ -820,8 +823,8 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
assert "foreign_keys" not in self.__dict__
self._columns = ColumnCollection()
- self.primary_key = ColumnSet()
- self.foreign_keys = set()
+ self.primary_key = ColumnSet() # type: ignore
+ self.foreign_keys = set() # type: ignore
@property
def _cols_populated(self):
@@ -1050,9 +1053,7 @@ class Join(roles.DMLTableRole, FromClause):
@util.preload_module("sqlalchemy.sql.util")
def _populate_column_collection(self):
sqlutil = util.preloaded.sql_util
- columns = [c for c in self.left.columns] + [
- c for c in self.right.columns
- ]
+ columns = [c for c in self.left.c] + [c for c in self.right.c]
self.primary_key.extend(
sqlutil.reduce_columns(
@@ -1300,14 +1301,14 @@ class Join(roles.DMLTableRole, FromClause):
.alias(name)
)
- @property
+ @util.ro_non_memoized_property
def _hide_froms(self) -> Iterable[FromClause]:
return itertools.chain(
*[_from_objects(x.left, x.right) for x in self._cloned_set]
)
- @property
- def _from_objects(self):
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
return [self] + self.left._from_objects + self.right._from_objects
@@ -1415,7 +1416,7 @@ class AliasedReturnsRows(NoInit, NamedFromClause):
self._reset_column_collection()
@property
- def _from_objects(self):
+ def _from_objects(self) -> List[FromClause]:
return [self]
@@ -2329,10 +2330,14 @@ class FromGrouping(GroupedElement, FromClause):
def _init_collections(self):
pass
- @property
+ @util.ro_non_memoized_property
def columns(self):
return self.element.columns
+ @util.ro_non_memoized_property
+ def c(self):
+ return self.element.columns
+
@property
def primary_key(self):
return self.element.primary_key
@@ -2350,12 +2355,12 @@ class FromGrouping(GroupedElement, FromClause):
def _anonymous_fromclause(self, **kw):
return FromGrouping(self.element._anonymous_fromclause(**kw))
- @property
+ @util.ro_non_memoized_property
def _hide_froms(self) -> Iterable[FromClause]:
return self.element._hide_froms
- @property
- def _from_objects(self):
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
return self.element._from_objects
def __getstate__(self):
@@ -2436,6 +2441,16 @@ class TableClause(roles.DMLTableRole, Immutable, NamedFromClause):
if kw:
raise exc.ArgumentError("Unsupported argument(s): %s" % list(kw))
+ if TYPE_CHECKING:
+
+ @util.ro_non_memoized_property
+ def columns(self) -> ReadOnlyColumnCollection[str, ColumnClause[Any]]:
+ ...
+
+ @util.ro_non_memoized_property
+ def c(self) -> ReadOnlyColumnCollection[str, ColumnClause[Any]]:
+ ...
+
def __str__(self):
if self.schema is not None:
return self.schema + "." + self.name
@@ -2507,8 +2522,8 @@ class TableClause(roles.DMLTableRole, Immutable, NamedFromClause):
"""
return util.preloaded.sql_dml.Delete(self)
- @property
- def _from_objects(self):
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
return [self]
@@ -2669,11 +2684,14 @@ class Values(Generative, NamedFromClause):
self._columns.add(c)
c.table = self
- @property
- def _from_objects(self):
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
return [self]
+SelfSelectBase = TypeVar("SelfSelectBase", bound=Any)
+
+
class SelectBase(
roles.SelectStatementRole,
roles.DMLSelectRole,
@@ -2697,12 +2715,27 @@ class SelectBase(
_is_select_statement = True
is_select = True
- def _generate_fromclause_column_proxies(self, fromclause):
+ def _generate_fromclause_column_proxies(
+ self, fromclause: FromClause
+ ) -> None:
raise NotImplementedError()
- def _refresh_for_new_column(self, column):
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
self._reset_memoizations()
+ def _generate_columns_plus_names(
+ self, anon_for_dupe_key: bool
+ ) -> List[Tuple[str, str, str, ColumnElement[Any], bool]]:
+ raise NotImplementedError()
+
+ def set_label_style(
+ self: SelfSelectBase, label_style: SelectLabelStyle
+ ) -> SelfSelectBase:
+ raise NotImplementedError()
+
+ def get_label_style(self) -> SelectLabelStyle:
+ raise NotImplementedError()
+
@property
def selected_columns(self):
"""A :class:`_expression.ColumnCollection`
@@ -2733,8 +2766,8 @@ class SelectBase(
"""
raise NotImplementedError()
- @property
- def _all_selected_columns(self):
+ @util.non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
"""A sequence of expressions that correspond to what is rendered
in the columns clause, including :class:`_sql.TextClause`
constructs.
@@ -2893,8 +2926,8 @@ class SelectBase(
"""
return Lateral._factory(self, name)
- @property
- def _from_objects(self):
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
return [self]
def subquery(self, name=None):
@@ -2979,6 +3012,8 @@ class SelectStatementGrouping(GroupedElement, SelectBase):
_is_select_container = True
+ element: SelectBase
+
def __init__(self, element):
self.element = coercions.expect(roles.SelectStatementRole, element)
@@ -2990,37 +3025,34 @@ class SelectStatementGrouping(GroupedElement, SelectBase):
return self
def get_label_style(self) -> SelectLabelStyle:
- return self._label_style
+ return self.element.get_label_style()
def set_label_style(
self, label_style: SelectLabelStyle
- ) -> "SelectStatementGrouping":
+ ) -> SelectStatementGrouping:
return SelectStatementGrouping(
self.element.set_label_style(label_style)
)
@property
- def _label_style(self):
- return self.element._label_style
-
- @property
def select_statement(self):
return self.element
def self_group(self, against=None):
return self
- def _generate_columns_plus_names(self, anon_for_dupe_key):
+ def _generate_columns_plus_names(
+ self, anon_for_dupe_key: bool
+ ) -> List[Tuple[str, str, str, ColumnElement[Any], bool]]:
return self.element._generate_columns_plus_names(anon_for_dupe_key)
- def _generate_fromclause_column_proxies(self, subquery):
+ def _generate_fromclause_column_proxies(
+ self, subquery: FromClause
+ ) -> None:
self.element._generate_fromclause_column_proxies(subquery)
- def _generate_proxy_for_new_column(self, column, subquery):
- return self.element._generate_proxy_for_new_column(subquery)
-
- @property
- def _all_selected_columns(self):
+ @util.non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
return self.element._all_selected_columns
@property
@@ -3039,8 +3071,8 @@ class SelectStatementGrouping(GroupedElement, SelectBase):
"""
return self.element.selected_columns
- @property
- def _from_objects(self):
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
return self.element._from_objects
@@ -3612,10 +3644,10 @@ class CompoundSelect(HasCompileState, GenerativeSelect):
return True
return False
- def _set_label_style(self, style):
+ def set_label_style(self, style):
if self._label_style is not style:
self = self._generate()
- select_0 = self.selects[0]._set_label_style(style)
+ select_0 = self.selects[0].set_label_style(style)
self.selects = [select_0] + self.selects[1:]
return self
@@ -3665,8 +3697,8 @@ class CompoundSelect(HasCompileState, GenerativeSelect):
for select in self.selects:
select._refresh_for_new_column(column)
- @property
- def _all_selected_columns(self):
+ @util.non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
return self.selects[0]._all_selected_columns
@property
@@ -3701,8 +3733,18 @@ class SelectState(util.MemoizedSlots, CompileState):
"_label_resolve_dict",
)
- class default_select_compile_options(CacheableOptions):
- _cache_key_traversal = []
+ if TYPE_CHECKING:
+ default_select_compile_options: CacheableOptions
+ else:
+
+ class default_select_compile_options(CacheableOptions):
+ _cache_key_traversal = []
+
+ if TYPE_CHECKING:
+
+ @classmethod
+ def get_plugin_class(cls, statement: Select) -> SelectState:
+ ...
def __init__(self, statement, compiler, **kw):
self.statement = statement
@@ -3966,7 +4008,7 @@ class SelectState(util.MemoizedSlots, CompileState):
return None
@classmethod
- def all_selected_columns(cls, statement):
+ def all_selected_columns(cls, statement: Select) -> _SelectIterable:
return [c for c in _select_iterables(statement._raw_columns)]
def _setup_joins(self, args, raw_columns):
@@ -4205,15 +4247,17 @@ class Select(
_memoized_select_entities: Tuple[TODO_Any, ...] = ()
_distinct = False
- _distinct_on: Tuple[ColumnElement, ...] = ()
+ _distinct_on: Tuple[ColumnElement[Any], ...] = ()
_correlate: Tuple[FromClause, ...] = ()
_correlate_except: Optional[Tuple[FromClause, ...]] = None
- _where_criteria: Tuple[ColumnElement, ...] = ()
- _having_criteria: Tuple[ColumnElement, ...] = ()
+ _where_criteria: Tuple[ColumnElement[Any], ...] = ()
+ _having_criteria: Tuple[ColumnElement[Any], ...] = ()
_from_obj: Tuple[FromClause, ...] = ()
_auto_correlate = True
- _compile_options = SelectState.default_select_compile_options
+ _compile_options: CacheableOptions = (
+ SelectState.default_select_compile_options
+ )
_traverse_internals = (
[
@@ -4264,7 +4308,7 @@ class Select(
stmt.__dict__.update(kw)
return stmt
- def __init__(self, *entities: _ColumnsClauseElement):
+ def __init__(self, *entities: _ColumnsClauseArgument):
r"""Construct a new :class:`_expression.Select`.
The public constructor for :class:`_expression.Select` is the
@@ -4286,7 +4330,7 @@ class Select(
cols = list(elem._select_iterable)
return cols[0].type
- def filter(self, *criteria):
+ def filter(self: SelfSelect, *criteria: ColumnElement[Any]) -> SelfSelect:
"""A synonym for the :meth:`_future.Select.where` method."""
return self.where(*criteria)
@@ -4896,7 +4940,7 @@ class Select(
return self
@property
- def whereclause(self):
+ def whereclause(self) -> Optional[ColumnElement[Any]]:
"""Return the completed WHERE clause for this
:class:`_expression.Select` statement.
@@ -5161,12 +5205,12 @@ class Select(
[
(conv(c), c)
for c in self._all_selected_columns
- if not c._is_text_clause
+ if is_column_element(c)
]
- ).as_immutable()
+ ).as_readonly()
@HasMemoized.memoized_attribute
- def _all_selected_columns(self):
+ def _all_selected_columns(self) -> Sequence[ColumnElement[Any]]:
meth = SelectState.get_plugin_class(self).all_selected_columns
return list(meth(self))
@@ -5175,7 +5219,9 @@ class Select(
self = self.set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY)
return self
- def _generate_columns_plus_names(self, anon_for_dupe_key):
+ def _generate_columns_plus_names(
+ self, anon_for_dupe_key: bool
+ ) -> List[Tuple[str, str, str, ColumnElement[Any], bool]]:
"""Generate column names as rendered in a SELECT statement by
the compiler.
@@ -5805,13 +5851,13 @@ class TextualSelect(SelectBase):
"""
return ColumnCollection(
(c.key, c) for c in self.column_args
- ).as_immutable()
+ ).as_readonly()
- @property
- def _all_selected_columns(self):
+ @util.non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
return self.column_args
- def _set_label_style(self, style):
+ def set_label_style(self, style):
return self
def _ensure_disambiguated_names(self):