summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-01-09 11:49:02 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-01-14 16:30:41 -0500
commit4999784664b9e73204474dd3dd91ee60fd174e3e (patch)
tree18f612f9960d5abee702b1bc1e0769ca26728793 /lib/sqlalchemy/sql
parent43f6ae639ca0186f4802255861acdc20f19e702f (diff)
downloadsqlalchemy-4999784664b9e73204474dd3dd91ee60fd174e3e.tar.gz
Initial ORM typing layout
introduces: 1. new mapped_column() helper 2. DeclarativeBase helper 3. declared_attr has been re-typed 4. rework of Mapped[] to return InstrumentedAtribute for class get, so works without Mapped itself having expression methods 5. ORM constructs now generic on [_T] also includes some early typing work, most of which will be in later commits: 1. URL and History become typing.NamedTuple 2. come up with type-checking friendly way of type checking cy extensions, where type checking will be applied to the py versions, just needed to come up with a succinct conditional pattern for the imports References: #6810 References: #7535 References: #7562 Change-Id: Ie5d9a44631626c021d130ca4ce395aba623c71fb
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/base.py7
-rw-r--r--lib/sqlalchemy/sql/coercions.py94
-rw-r--r--lib/sqlalchemy/sql/elements.py559
-rw-r--r--lib/sqlalchemy/sql/functions.py7
-rw-r--r--lib/sqlalchemy/sql/selectable.py26
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py14
-rw-r--r--lib/sqlalchemy/sql/type_api.py5
7 files changed, 331 insertions, 381 deletions
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index 6ab9a75f6..7841ce88a 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -31,11 +31,12 @@ from .. import util
from ..util import HasMemoized
from ..util import hybridmethod
from ..util import typing as compat_typing
+from ..util._has_cy import HAS_CYEXTENSION
-try:
- from sqlalchemy.cyextension.util import prefix_anon_map # noqa
-except ImportError:
+if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
from ._py_util import prefix_anon_map # noqa
+else:
+ from sqlalchemy.cyextension.util import prefix_anon_map # noqa
coercions = None
elements = None
diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py
index 3bec73f7d..d5a75a165 100644
--- a/lib/sqlalchemy/sql/coercions.py
+++ b/lib/sqlalchemy/sql/coercions.py
@@ -10,12 +10,10 @@ import numbers
import re
import typing
from typing import Any
-from typing import Callable
+from typing import Any as TODO_Any
from typing import Optional
-from typing import overload
from typing import Type
from typing import TypeVar
-from typing import Union
from . import operators
from . import roles
@@ -42,7 +40,6 @@ if typing.TYPE_CHECKING:
from . import selectable
from . import traversals
from .elements import ClauseElement
- from .elements import ColumnElement
_SR = TypeVar("_SR", bound=roles.SQLRole)
_StringOnlyR = TypeVar("_StringOnlyR", bound=roles.StringRole)
@@ -129,59 +126,10 @@ def _expression_collection_was_a_list(attrname, fnname, args):
return args
-@overload
-def expect(
- role: Type[roles.InElementRole],
- element: Any,
- *,
- apply_propagate_attrs: Optional["ClauseElement"] = None,
- argname: Optional[str] = None,
- post_inspect: bool = False,
- **kw: Any,
-) -> Union["elements.ColumnElement", "selectable.Select"]:
- ...
-
-
-@overload
-def expect(
- role: Type[roles.HasCTERole],
- element: Any,
- *,
- apply_propagate_attrs: Optional["ClauseElement"] = None,
- argname: Optional[str] = None,
- post_inspect: bool = False,
- **kw: Any,
-) -> "selectable.HasCTE":
- ...
+# TODO; would like to have overloads here, however mypy is being extremely
+# pedantic about them. not sure why pylance is OK with them.
-@overload
-def expect(
- role: Type[roles.ExpressionElementRole],
- element: Any,
- *,
- apply_propagate_attrs: Optional["ClauseElement"] = None,
- argname: Optional[str] = None,
- post_inspect: bool = False,
- **kw: Any,
-) -> "ColumnElement":
- ...
-
-
-@overload
-def expect(
- role: "Type[_StringOnlyR]",
- element: Any,
- *,
- apply_propagate_attrs: Optional["ClauseElement"] = None,
- argname: Optional[str] = None,
- post_inspect: bool = False,
- **kw: Any,
-) -> str:
- ...
-
-
-@overload
def expect(
role: Type[_SR],
element: Any,
@@ -190,32 +138,7 @@ def expect(
argname: Optional[str] = None,
post_inspect: bool = False,
**kw: Any,
-) -> _SR:
- ...
-
-
-@overload
-def expect(
- role: Type[_SR],
- element: Callable[..., Any],
- *,
- apply_propagate_attrs: Optional["ClauseElement"] = None,
- argname: Optional[str] = None,
- post_inspect: bool = False,
- **kw: Any,
-) -> "lambdas.LambdaElement":
- ...
-
-
-def expect(
- role: Type[_SR],
- element: Any,
- *,
- apply_propagate_attrs: Optional["ClauseElement"] = None,
- argname: Optional[str] = None,
- post_inspect: bool = False,
- **kw: Any,
-) -> Union[str, _SR, "lambdas.LambdaElement"]:
+) -> TODO_Any:
if (
role.allows_lambda
# note callable() will not invoke a __getattr__() method, whereas
@@ -350,7 +273,9 @@ class RoleImpl:
self.name = role_class._role_name
self._use_inspection = issubclass(role_class, roles.UsesInspection)
- def _implicit_coercions(self, element, resolved, argname=None, **kw):
+ def _implicit_coercions(
+ self, element, resolved, argname=None, **kw
+ ) -> Any:
self._raise_for_expected(element, argname, resolved)
def _raise_for_expected(
@@ -422,9 +347,8 @@ class _ColumnCoercions:
"subquery.",
)
- def _implicit_coercions(
- self, original_element, resolved, argname=None, **kw
- ):
+ def _implicit_coercions(self, element, resolved, argname=None, **kw):
+ original_element = element
if not getattr(resolved, "is_clause_element", False):
self._raise_for_expected(original_element, argname, resolved)
elif resolved._is_select_statement:
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 705a89889..65f345fb3 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -50,6 +50,7 @@ from .visitors import Traversible
from .. import exc
from .. import inspection
from .. import util
+from ..util.langhelpers import TypingOnly
if typing.TYPE_CHECKING:
from decimal import Decimal
@@ -572,262 +573,8 @@ class CompilerColumnElement(
__slots__ = ()
-class ColumnElement(
- roles.ColumnArgumentOrKeyRole,
- roles.StatementOptionRole,
- roles.WhereHavingRole,
- roles.BinaryElementRole,
- roles.OrderByRole,
- roles.ColumnsClauseRole,
- roles.LimitOffsetRole,
- roles.DMLColumnRole,
- roles.DDLConstraintColumnRole,
- roles.DDLExpressionRole,
- operators.ColumnOperators["ColumnElement"],
- ClauseElement,
- Generic[_T],
-):
- """Represent a column-oriented SQL expression suitable for usage in the
- "columns" clause, WHERE clause etc. of a statement.
-
- While the most familiar kind of :class:`_expression.ColumnElement` is the
- :class:`_schema.Column` object, :class:`_expression.ColumnElement`
- serves as the basis
- for any unit that may be present in a SQL expression, including
- the expressions themselves, SQL functions, bound parameters,
- literal expressions, keywords such as ``NULL``, etc.
- :class:`_expression.ColumnElement`
- is the ultimate base class for all such elements.
-
- A wide variety of SQLAlchemy Core functions work at the SQL expression
- level, and are intended to accept instances of
- :class:`_expression.ColumnElement` as
- arguments. These functions will typically document that they accept a
- "SQL expression" as an argument. What this means in terms of SQLAlchemy
- usually refers to an input which is either already in the form of a
- :class:`_expression.ColumnElement` object,
- or a value which can be **coerced** into
- one. The coercion rules followed by most, but not all, SQLAlchemy Core
- functions with regards to SQL expressions are as follows:
-
- * a literal Python value, such as a string, integer or floating
- point value, boolean, datetime, ``Decimal`` object, or virtually
- any other Python object, will be coerced into a "literal bound
- value". This generally means that a :func:`.bindparam` will be
- produced featuring the given value embedded into the construct; the
- resulting :class:`.BindParameter` object is an instance of
- :class:`_expression.ColumnElement`.
- The Python value will ultimately be sent
- to the DBAPI at execution time as a parameterized argument to the
- ``execute()`` or ``executemany()`` methods, after SQLAlchemy
- type-specific converters (e.g. those provided by any associated
- :class:`.TypeEngine` objects) are applied to the value.
-
- * any special object value, typically ORM-level constructs, which
- feature an accessor called ``__clause_element__()``. The Core
- expression system looks for this method when an object of otherwise
- unknown type is passed to a function that is looking to coerce the
- argument into a :class:`_expression.ColumnElement` and sometimes a
- :class:`_expression.SelectBase` expression.
- It is used within the ORM to
- convert from ORM-specific objects like mapped classes and
- mapped attributes into Core expression objects.
-
- * The Python ``None`` value is typically interpreted as ``NULL``,
- which in SQLAlchemy Core produces an instance of :func:`.null`.
-
- A :class:`_expression.ColumnElement` provides the ability to generate new
- :class:`_expression.ColumnElement`
- objects using Python expressions. This means that Python operators
- such as ``==``, ``!=`` and ``<`` are overloaded to mimic SQL operations,
- and allow the instantiation of further :class:`_expression.ColumnElement`
- instances
- which are composed from other, more fundamental
- :class:`_expression.ColumnElement`
- objects. For example, two :class:`.ColumnClause` objects can be added
- together with the addition operator ``+`` to produce
- a :class:`.BinaryExpression`.
- Both :class:`.ColumnClause` and :class:`.BinaryExpression` are subclasses
- of :class:`_expression.ColumnElement`::
-
- >>> from sqlalchemy.sql import column
- >>> column('a') + column('b')
- <sqlalchemy.sql.expression.BinaryExpression object at 0x101029dd0>
- >>> print(column('a') + column('b'))
- a + b
-
- .. seealso::
-
- :class:`_schema.Column`
-
- :func:`_expression.column`
-
- """
-
- __visit_name__ = "column_element"
-
- primary_key = False
- foreign_keys = []
- _proxies = ()
-
- _tq_label = None
- """The named label that can be used to target
- this column in a result set in a "table qualified" context.
-
- This label is almost always the label used when
- rendering <expr> AS <label> in a SELECT statement when using
- the LABEL_STYLE_TABLENAME_PLUS_COL label style, which is what the legacy
- ORM ``Query`` object uses as well.
-
- For a regular Column bound to a Table, this is typically the label
- <tablename>_<columnname>. For other constructs, different rules
- may apply, such as anonymized labels and others.
-
- .. versionchanged:: 1.4.21 renamed from ``._label``
-
- """
-
- key = None
- """The 'key' that in some circumstances refers to this object in a
- Python namespace.
-
- This typically refers to the "key" of the column as present in the
- ``.c`` collection of a selectable, e.g. ``sometable.c["somekey"]`` would
- return a :class:`_schema.Column` with a ``.key`` of "somekey".
-
- """
-
- @HasMemoized.memoized_attribute
- def _tq_key_label(self):
- """A label-based version of 'key' that in some circumstances refers
- to this object in a Python namespace.
-
-
- _tq_key_label comes into play when a select() statement is constructed
- with apply_labels(); in this case, all Column objects in the ``.c``
- collection are rendered as <tablename>_<columnname> in SQL; this is
- essentially the value of ._label. But to locate those columns in the
- ``.c`` collection, the name is along the lines of <tablename>_<key>;
- that's the typical value of .key_label.
-
- .. versionchanged:: 1.4.21 renamed from ``._key_label``
-
- """
- return self._proxy_key
-
- @property
- def _key_label(self):
- """legacy; renamed to _tq_key_label"""
- return self._tq_key_label
-
- @property
- def _label(self):
- """legacy; renamed to _tq_label"""
- return self._tq_label
-
- @property
- def _non_anon_label(self):
- """the 'name' that naturally applies this element when rendered in
- SQL.
-
- Concretely, this is the "name" of a column or a label in a
- SELECT statement; ``<columnname>`` and ``<labelname>`` below::
-
- SELECT <columnmame> FROM table
-
- SELECT column AS <labelname> FROM table
-
- Above, the two names noted will be what's present in the DBAPI
- ``cursor.description`` as the names.
-
- If this attribute returns ``None``, it means that the SQL element as
- written does not have a 100% fully predictable "name" that would appear
- in the ``cursor.description``. Examples include SQL functions, CAST
- functions, etc. While such things do return names in
- ``cursor.description``, they are only predictable on a
- database-specific basis; e.g. an expression like ``MAX(table.col)`` may
- appear as the string ``max`` on one database (like PostgreSQL) or may
- appear as the whole expression ``max(table.col)`` on SQLite.
-
- The default implementation looks for a ``.name`` attribute on the
- object, as has been the precedent established in SQLAlchemy for many
- years. An exception is made on the ``FunctionElement`` subclass
- so that the return value is always ``None``.
-
- .. versionadded:: 1.4.21
-
-
-
- """
- return getattr(self, "name", None)
-
- _render_label_in_columns_clause = True
- """A flag used by select._columns_plus_names that helps to determine
- we are actually going to render in terms of "SELECT <col> AS <label>".
- This flag can be returned as False for some Column objects that want
- to be rendered as simple "SELECT <col>"; typically columns that don't have
- any parent table and are named the same as what the label would be
- in any case.
-
- """
-
- _allow_label_resolve = True
- """A flag that can be flipped to prevent a column from being resolvable
- by string label name.
-
- The joined eager loader strategy in the ORM uses this, for example.
-
- """
-
- _is_implicitly_boolean = False
-
- _alt_names = ()
-
- def self_group(self, against=None):
- if (
- against in (operators.and_, operators.or_, operators._asbool)
- and self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity
- ):
- return AsBoolean(self, operators.is_true, operators.is_false)
- elif against in (operators.any_op, operators.all_op):
- return Grouping(self)
- else:
- return self
-
- def _negate(self):
- if self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity:
- return AsBoolean(self, operators.is_false, operators.is_true)
- else:
- return super(ColumnElement, self)._negate()
-
- @util.memoized_property
- def type(self) -> "TypeEngine[_T]":
- return type_api.NULLTYPE
-
- @HasMemoized.memoized_attribute
- def comparator(self) -> "TypeEngine.Comparator[_T]":
- try:
- comparator_factory = self.type.comparator_factory
- except AttributeError as err:
- raise TypeError(
- "Object %r associated with '.type' attribute "
- "is not a TypeEngine class or object" % self.type
- ) from err
- else:
- return comparator_factory(self)
-
- def __getattr__(self, key):
- try:
- return getattr(self.comparator, key)
- except AttributeError as err:
- raise AttributeError(
- "Neither %r object nor %r object has an attribute %r"
- % (
- type(self).__name__,
- type(self.comparator).__name__,
- key,
- )
- ) from err
+class SQLCoreOperations(Generic[_T], TypingOnly):
+ __slots__ = ()
# annotations for comparison methods
# these are from operators->Operators / ColumnOperators,
@@ -894,7 +641,9 @@ class ColumnElement(
...
@overload
- def concat(self, other: Any) -> "BinaryExpression[_ST]":
+ def concat(
+ self: "SQLCoreOperations[_ST]", other: Any
+ ) -> "BinaryExpression[_ST]":
...
@overload
@@ -986,7 +735,7 @@ class ColumnElement(
) -> "BinaryExpression[bool]":
...
- def distinct(self: "ColumnElement[_T]") -> "UnaryExpression[_T]":
+ def distinct(self: "SQLCoreOperations[_T]") -> "UnaryExpression[_T]":
...
def any_(self) -> "CollectionAggregate":
@@ -996,22 +745,28 @@ class ColumnElement(
...
# numeric overloads. These need more tweaking
+ # in particular they all need to have a variant for Optiona[_T]
+ # because Optional only applies to the data side, not the expression
+ # side
@overload
def __add__(
- self: "ColumnElement[_NT]", other: "Union[ColumnElement[_NT], _NT]"
+ self: "Union[_SQO[_NT], _SQO[Optional[_NT]]]",
+ other: "Union[_SQO[Optional[_NT]], _SQO[_NT], _NT]",
) -> "BinaryExpression[_NT]":
...
@overload
def __add__(
- self: "ColumnElement[_NT]", other: Any
+ self: "Union[_SQO[_NT], _SQO[Optional[_NT]]]",
+ other: Any,
) -> "BinaryExpression[_NUMERIC]":
...
@overload
def __add__(
- self: "ColumnElement[_ST]", other: Any
+ self: "Union[_SQO[_ST], _SQO[Optional[_ST]]]",
+ other: Any,
) -> "BinaryExpression[_ST]":
...
@@ -1031,7 +786,8 @@ class ColumnElement(
@overload
def __sub__(
- self: "ColumnElement[_NT]", other: "Union[ColumnElement[_NT], _NT]"
+ self: "SQLCoreOperations[_NT]",
+ other: "Union[SQLCoreOperations[_NT], _NT]",
) -> "BinaryExpression[_NT]":
...
@@ -1044,7 +800,7 @@ class ColumnElement(
@overload
def __rsub__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1057,7 +813,7 @@ class ColumnElement(
@overload
def __mul__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1070,7 +826,7 @@ class ColumnElement(
@overload
def __rmul__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1083,7 +839,7 @@ class ColumnElement(
@overload
def __mod__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1096,7 +852,7 @@ class ColumnElement(
@overload
def __rmod__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1109,7 +865,7 @@ class ColumnElement(
@overload
def __truediv__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1122,7 +878,7 @@ class ColumnElement(
@overload
def __rtruediv__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1135,7 +891,7 @@ class ColumnElement(
@overload
def __floordiv__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1148,7 +904,7 @@ class ColumnElement(
@overload
def __rfloordiv__(
- self: "ColumnElement[_NT]", other: Any
+ self: "SQLCoreOperations[_NT]", other: Any
) -> "BinaryExpression[_NUMERIC]":
...
@@ -1159,6 +915,267 @@ class ColumnElement(
def __rfloordiv__(self, other: Any) -> "BinaryExpression":
...
+
+_SQO = SQLCoreOperations
+
+
+class ColumnElement(
+ roles.ColumnArgumentOrKeyRole,
+ roles.StatementOptionRole,
+ roles.WhereHavingRole,
+ roles.BinaryElementRole,
+ roles.OrderByRole,
+ roles.ColumnsClauseRole,
+ roles.LimitOffsetRole,
+ roles.DMLColumnRole,
+ roles.DDLConstraintColumnRole,
+ roles.DDLExpressionRole,
+ SQLCoreOperations[_T],
+ operators.ColumnOperators[SQLCoreOperations],
+ ClauseElement,
+):
+ """Represent a column-oriented SQL expression suitable for usage in the
+ "columns" clause, WHERE clause etc. of a statement.
+
+ While the most familiar kind of :class:`_expression.ColumnElement` is the
+ :class:`_schema.Column` object, :class:`_expression.ColumnElement`
+ serves as the basis
+ for any unit that may be present in a SQL expression, including
+ the expressions themselves, SQL functions, bound parameters,
+ literal expressions, keywords such as ``NULL``, etc.
+ :class:`_expression.ColumnElement`
+ is the ultimate base class for all such elements.
+
+ A wide variety of SQLAlchemy Core functions work at the SQL expression
+ level, and are intended to accept instances of
+ :class:`_expression.ColumnElement` as
+ arguments. These functions will typically document that they accept a
+ "SQL expression" as an argument. What this means in terms of SQLAlchemy
+ usually refers to an input which is either already in the form of a
+ :class:`_expression.ColumnElement` object,
+ or a value which can be **coerced** into
+ one. The coercion rules followed by most, but not all, SQLAlchemy Core
+ functions with regards to SQL expressions are as follows:
+
+ * a literal Python value, such as a string, integer or floating
+ point value, boolean, datetime, ``Decimal`` object, or virtually
+ any other Python object, will be coerced into a "literal bound
+ value". This generally means that a :func:`.bindparam` will be
+ produced featuring the given value embedded into the construct; the
+ resulting :class:`.BindParameter` object is an instance of
+ :class:`_expression.ColumnElement`.
+ The Python value will ultimately be sent
+ to the DBAPI at execution time as a parameterized argument to the
+ ``execute()`` or ``executemany()`` methods, after SQLAlchemy
+ type-specific converters (e.g. those provided by any associated
+ :class:`.TypeEngine` objects) are applied to the value.
+
+ * any special object value, typically ORM-level constructs, which
+ feature an accessor called ``__clause_element__()``. The Core
+ expression system looks for this method when an object of otherwise
+ unknown type is passed to a function that is looking to coerce the
+ argument into a :class:`_expression.ColumnElement` and sometimes a
+ :class:`_expression.SelectBase` expression.
+ It is used within the ORM to
+ convert from ORM-specific objects like mapped classes and
+ mapped attributes into Core expression objects.
+
+ * The Python ``None`` value is typically interpreted as ``NULL``,
+ which in SQLAlchemy Core produces an instance of :func:`.null`.
+
+ A :class:`_expression.ColumnElement` provides the ability to generate new
+ :class:`_expression.ColumnElement`
+ objects using Python expressions. This means that Python operators
+ such as ``==``, ``!=`` and ``<`` are overloaded to mimic SQL operations,
+ and allow the instantiation of further :class:`_expression.ColumnElement`
+ instances
+ which are composed from other, more fundamental
+ :class:`_expression.ColumnElement`
+ objects. For example, two :class:`.ColumnClause` objects can be added
+ together with the addition operator ``+`` to produce
+ a :class:`.BinaryExpression`.
+ Both :class:`.ColumnClause` and :class:`.BinaryExpression` are subclasses
+ of :class:`_expression.ColumnElement`::
+
+ >>> from sqlalchemy.sql import column
+ >>> column('a') + column('b')
+ <sqlalchemy.sql.expression.BinaryExpression object at 0x101029dd0>
+ >>> print(column('a') + column('b'))
+ a + b
+
+ .. seealso::
+
+ :class:`_schema.Column`
+
+ :func:`_expression.column`
+
+ """
+
+ __visit_name__ = "column_element"
+
+ primary_key = False
+ foreign_keys = []
+ _proxies = ()
+
+ _tq_label = None
+ """The named label that can be used to target
+ this column in a result set in a "table qualified" context.
+
+ This label is almost always the label used when
+ rendering <expr> AS <label> in a SELECT statement when using
+ the LABEL_STYLE_TABLENAME_PLUS_COL label style, which is what the legacy
+ ORM ``Query`` object uses as well.
+
+ For a regular Column bound to a Table, this is typically the label
+ <tablename>_<columnname>. For other constructs, different rules
+ may apply, such as anonymized labels and others.
+
+ .. versionchanged:: 1.4.21 renamed from ``._label``
+
+ """
+
+ key = None
+ """The 'key' that in some circumstances refers to this object in a
+ Python namespace.
+
+ This typically refers to the "key" of the column as present in the
+ ``.c`` collection of a selectable, e.g. ``sometable.c["somekey"]`` would
+ return a :class:`_schema.Column` with a ``.key`` of "somekey".
+
+ """
+
+ @HasMemoized.memoized_attribute
+ def _tq_key_label(self):
+ """A label-based version of 'key' that in some circumstances refers
+ to this object in a Python namespace.
+
+
+ _tq_key_label comes into play when a select() statement is constructed
+ with apply_labels(); in this case, all Column objects in the ``.c``
+ collection are rendered as <tablename>_<columnname> in SQL; this is
+ essentially the value of ._label. But to locate those columns in the
+ ``.c`` collection, the name is along the lines of <tablename>_<key>;
+ that's the typical value of .key_label.
+
+ .. versionchanged:: 1.4.21 renamed from ``._key_label``
+
+ """
+ return self._proxy_key
+
+ @property
+ def _key_label(self):
+ """legacy; renamed to _tq_key_label"""
+ return self._tq_key_label
+
+ @property
+ def _label(self):
+ """legacy; renamed to _tq_label"""
+ return self._tq_label
+
+ @property
+ def _non_anon_label(self):
+ """the 'name' that naturally applies this element when rendered in
+ SQL.
+
+ Concretely, this is the "name" of a column or a label in a
+ SELECT statement; ``<columnname>`` and ``<labelname>`` below::
+
+ SELECT <columnmame> FROM table
+
+ SELECT column AS <labelname> FROM table
+
+ Above, the two names noted will be what's present in the DBAPI
+ ``cursor.description`` as the names.
+
+ If this attribute returns ``None``, it means that the SQL element as
+ written does not have a 100% fully predictable "name" that would appear
+ in the ``cursor.description``. Examples include SQL functions, CAST
+ functions, etc. While such things do return names in
+ ``cursor.description``, they are only predictable on a
+ database-specific basis; e.g. an expression like ``MAX(table.col)`` may
+ appear as the string ``max`` on one database (like PostgreSQL) or may
+ appear as the whole expression ``max(table.col)`` on SQLite.
+
+ The default implementation looks for a ``.name`` attribute on the
+ object, as has been the precedent established in SQLAlchemy for many
+ years. An exception is made on the ``FunctionElement`` subclass
+ so that the return value is always ``None``.
+
+ .. versionadded:: 1.4.21
+
+
+
+ """
+ return getattr(self, "name", None)
+
+ _render_label_in_columns_clause = True
+ """A flag used by select._columns_plus_names that helps to determine
+ we are actually going to render in terms of "SELECT <col> AS <label>".
+ This flag can be returned as False for some Column objects that want
+ to be rendered as simple "SELECT <col>"; typically columns that don't have
+ any parent table and are named the same as what the label would be
+ in any case.
+
+ """
+
+ _allow_label_resolve = True
+ """A flag that can be flipped to prevent a column from being resolvable
+ by string label name.
+
+ The joined eager loader strategy in the ORM uses this, for example.
+
+ """
+
+ _is_implicitly_boolean = False
+
+ _alt_names = ()
+
+ def self_group(self, against=None):
+ if (
+ against in (operators.and_, operators.or_, operators._asbool)
+ and self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity
+ ):
+ return AsBoolean(self, operators.is_true, operators.is_false)
+ elif against in (operators.any_op, operators.all_op):
+ return Grouping(self)
+ else:
+ return self
+
+ def _negate(self):
+ if self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity:
+ return AsBoolean(self, operators.is_false, operators.is_true)
+ else:
+ return super(ColumnElement, self)._negate()
+
+ @util.memoized_property
+ def type(self) -> "TypeEngine[_T]":
+ return type_api.NULLTYPE
+
+ @HasMemoized.memoized_attribute
+ def comparator(self) -> "TypeEngine.Comparator[_T]":
+ try:
+ comparator_factory = self.type.comparator_factory
+ except AttributeError as err:
+ raise TypeError(
+ "Object %r associated with '.type' attribute "
+ "is not a TypeEngine class or object" % self.type
+ ) from err
+ else:
+ return comparator_factory(self)
+
+ def __getattr__(self, key):
+ try:
+ return getattr(self.comparator, key)
+ except AttributeError as err:
+ raise AttributeError(
+ "Neither %r object nor %r object has an attribute %r"
+ % (
+ type(self).__name__,
+ type(self.comparator).__name__,
+ key,
+ )
+ ) from err
+
def operate(
self,
op: operators.OperatorType,
diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py
index 7a1e80889..2e6d64c55 100644
--- a/lib/sqlalchemy/sql/functions.py
+++ b/lib/sqlalchemy/sql/functions.py
@@ -9,6 +9,9 @@
"""
+from typing import Any
+from typing import TypeVar
+
from . import annotation
from . import coercions
from . import operators
@@ -42,6 +45,8 @@ from .visitors import InternalTraversal
from .. import util
+_T = TypeVar("_T", bound=Any)
+
_registry = util.defaultdict(dict)
@@ -67,7 +72,7 @@ def register_function(identifier, fn, package="_default"):
reg[identifier] = fn
-class FunctionElement(Executable, ColumnElement, FromClause, Generative):
+class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative):
"""Base for SQL function-oriented constructs.
.. seealso::
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index fd1abd71b..00e20e3fb 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -15,6 +15,9 @@ import collections
import itertools
from operator import attrgetter
import typing
+from typing import Any as TODO_Any
+from typing import Optional
+from typing import Tuple
from typing import Type
from typing import Union
@@ -53,6 +56,7 @@ from .elements import BooleanClauseList
from .elements import ClauseElement
from .elements import ClauseList
from .elements import ColumnClause
+from .elements import ColumnElement
from .elements import GroupedElement
from .elements import Grouping
from .elements import literal_column
@@ -2648,8 +2652,7 @@ class SelectBase(
"""
return self.selected_columns
- @property
- @util.deprecated(
+ @util.deprecated_property(
"1.4",
"The :attr:`_expression.SelectBase.c` and "
":attr:`_expression.SelectBase.columns` attributes "
@@ -4039,16 +4042,16 @@ class Select(
__visit_name__ = "select"
- _setup_joins = ()
- _memoized_select_entities = ()
+ _setup_joins: Tuple[TODO_Any, ...] = ()
+ _memoized_select_entities: Tuple[TODO_Any, ...] = ()
_distinct = False
- _distinct_on = ()
- _correlate = ()
- _correlate_except = None
- _where_criteria = ()
- _having_criteria = ()
- _from_obj = ()
+ _distinct_on: Tuple[ColumnElement, ...] = ()
+ _correlate: Tuple[FromClause, ...] = ()
+ _correlate_except: Optional[Tuple[FromClause, ...]] = None
+ _where_criteria: Tuple[ColumnElement, ...] = ()
+ _having_criteria: Tuple[ColumnElement, ...] = ()
+ _from_obj: Tuple[FromClause, ...] = ()
_auto_correlate = True
_compile_options = SelectState.default_select_compile_options
@@ -4417,8 +4420,7 @@ class Select(
"""
return self._compile_state_factory(self, None)._get_display_froms()
- @property
- @util.deprecated(
+ @util.deprecated_property(
"1.4.23",
"The :attr:`_expression.Select.froms` attribute is moved to "
"the :meth:`_expression.Select.get_final_froms` method.",
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index 81434fbb9..42fad5e04 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -65,7 +65,7 @@ class _LookupExpressionAdapter:
def _expression_adaptations(self):
raise NotImplementedError()
- class Comparator(TypeEngine.Comparator):
+ class Comparator(TypeEngine.Comparator[_T]):
_blank_dict = util.immutabledict()
def _adapt_expression(self, op, other_comparator):
@@ -88,7 +88,7 @@ class Concatenable:
"""A mixin that marks a type as supporting 'concatenation',
typically strings."""
- class Comparator(TypeEngine.Comparator):
+ class Comparator(TypeEngine.Comparator[_T]):
def _adapt_expression(self, op, other_comparator):
if op is operators.add and isinstance(
other_comparator,
@@ -113,7 +113,7 @@ class Indexable:
"""
- class Comparator(TypeEngine.Comparator):
+ class Comparator(TypeEngine.Comparator[_T]):
def _setup_getitem(self, index):
raise NotImplementedError()
@@ -1377,7 +1377,7 @@ class Enum(Emulated, String, TypeEngine[Union[str, enum.Enum]], SchemaType):
)
) from err
- class Comparator(String.Comparator):
+ class Comparator(String.Comparator[_T]):
def _adapt_expression(self, op, other_comparator):
op, typ = super(Enum.Comparator, self)._adapt_expression(
op, other_comparator
@@ -2204,7 +2204,7 @@ class JSON(Indexable, TypeEngine[Any]):
"""
- class Comparator(Indexable.Comparator, Concatenable.Comparator):
+ class Comparator(Indexable.Comparator[_T], Concatenable.Comparator[_T]):
"""Define comparison operations for :class:`_types.JSON`."""
def _setup_getitem(self, index):
@@ -2523,7 +2523,7 @@ class ARRAY(
"""If True, Python zero-based indexes should be interpreted as one-based
on the SQL expression side."""
- class Comparator(Indexable.Comparator, Concatenable.Comparator):
+ class Comparator(Indexable.Comparator[_T], Concatenable.Comparator[_T]):
"""Define comparison operations for :class:`_types.ARRAY`.
@@ -2967,7 +2967,7 @@ class NullType(TypeEngine):
return process
- class Comparator(TypeEngine.Comparator):
+ class Comparator(TypeEngine.Comparator[_T]):
def _adapt_expression(self, op, other_comparator):
if isinstance(
other_comparator, NullType.Comparator
diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py
index 75eb1b8c5..dd29b2c3a 100644
--- a/lib/sqlalchemy/sql/type_api.py
+++ b/lib/sqlalchemy/sql/type_api.py
@@ -11,6 +11,7 @@
import typing
from typing import Any
+from typing import Callable
from typing import Generic
from typing import Tuple
from typing import Type
@@ -1400,7 +1401,7 @@ class TypeDecorator(ExternalType, SchemaEventTarget, TypeEngine[_T]):
"""
- class Comparator(TypeEngine.Comparator):
+ class Comparator(TypeEngine.Comparator[_CT]):
"""A :class:`.TypeEngine.Comparator` that is specific to
:class:`.TypeDecorator`.
@@ -1425,7 +1426,7 @@ class TypeDecorator(ExternalType, SchemaEventTarget, TypeEngine[_T]):
)
@property
- def comparator_factory(self):
+ def comparator_factory(self) -> Callable[..., TypeEngine.Comparator[_T]]:
if TypeDecorator.Comparator in self.impl.comparator_factory.__mro__:
return self.impl.comparator_factory
else: